Skip to content

Commit 20337b4

Browse files
authored
Merge pull request #2714 from pypa/feature/distutils-sync
Update distutils
2 parents 0aa3576 + d4f066a commit 20337b4

12 files changed

+257
-95
lines changed

changelog.d/2714.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update to distutils at pypa/distutils@e2627b7.

setuptools/_distutils/command/build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def finalize_options(self):
102102
# particular module distribution -- if user didn't supply it, pick
103103
# one of 'build_purelib' or 'build_platlib'.
104104
if self.build_lib is None:
105-
if self.distribution.ext_modules:
105+
if self.distribution.has_ext_modules():
106106
self.build_lib = self.build_platlib
107107
else:
108108
self.build_lib = self.build_purelib

setuptools/_distutils/command/build_ext.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -690,13 +690,15 @@ def get_export_symbols(self, ext):
690690
provided, "PyInit_" + module_name. Only relevant on Windows, where
691691
the .pyd file (DLL) must export the module "PyInit_" function.
692692
"""
693-
suffix = '_' + ext.name.split('.')[-1]
693+
name = ext.name.split('.')[-1]
694694
try:
695695
# Unicode module name support as defined in PEP-489
696696
# https://www.python.org/dev/peps/pep-0489/#export-hook-name
697-
suffix.encode('ascii')
697+
name.encode('ascii')
698698
except UnicodeEncodeError:
699-
suffix = 'U' + suffix.encode('punycode').replace(b'-', b'_').decode('ascii')
699+
suffix = 'U_' + name.encode('punycode').replace(b'-', b'_').decode('ascii')
700+
else:
701+
suffix = "_" + name
700702

701703
initfunc_name = "PyInit" + suffix
702704
if initfunc_name not in ext.export_symbols:

setuptools/_distutils/command/install.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def finalize_options(self):
348348
# module distribution is pure or not. Of course, if the user
349349
# already specified install_lib, use their selection.
350350
if self.install_lib is None:
351-
if self.distribution.ext_modules: # has extensions: non-pure
351+
if self.distribution.has_ext_modules(): # has extensions: non-pure
352352
self.install_lib = self.install_platlib
353353
else:
354354
self.install_lib = self.install_purelib

setuptools/_distutils/cygwinccompiler.py

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
# (ld supports -shared)
4545
# * mingw gcc 3.2/ld 2.13 works
4646
# (ld supports -shared)
47+
# * llvm-mingw with Clang 11 works
48+
# (lld supports -shared)
4749

4850
import os
4951
import sys
@@ -109,41 +111,46 @@ def __init__(self, verbose=0, dry_run=0, force=0):
109111
"Compiling may fail because of undefined preprocessor macros."
110112
% details)
111113

112-
self.gcc_version, self.ld_version, self.dllwrap_version = \
113-
get_versions()
114-
self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" %
115-
(self.gcc_version,
116-
self.ld_version,
117-
self.dllwrap_version) )
118-
119-
# ld_version >= "2.10.90" and < "2.13" should also be able to use
120-
# gcc -mdll instead of dllwrap
121-
# Older dllwraps had own version numbers, newer ones use the
122-
# same as the rest of binutils ( also ld )
123-
# dllwrap 2.10.90 is buggy
124-
if self.ld_version >= "2.10.90":
125-
self.linker_dll = "gcc"
126-
else:
127-
self.linker_dll = "dllwrap"
114+
self.cc = os.environ.get('CC', 'gcc')
115+
self.cxx = os.environ.get('CXX', 'g++')
116+
117+
if ('gcc' in self.cc): # Start gcc workaround
118+
self.gcc_version, self.ld_version, self.dllwrap_version = \
119+
get_versions()
120+
self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" %
121+
(self.gcc_version,
122+
self.ld_version,
123+
self.dllwrap_version) )
124+
125+
# ld_version >= "2.10.90" and < "2.13" should also be able to use
126+
# gcc -mdll instead of dllwrap
127+
# Older dllwraps had own version numbers, newer ones use the
128+
# same as the rest of binutils ( also ld )
129+
# dllwrap 2.10.90 is buggy
130+
if self.ld_version >= "2.10.90":
131+
self.linker_dll = self.cc
132+
else:
133+
self.linker_dll = "dllwrap"
128134

129-
# ld_version >= "2.13" support -shared so use it instead of
130-
# -mdll -static
131-
if self.ld_version >= "2.13":
135+
# ld_version >= "2.13" support -shared so use it instead of
136+
# -mdll -static
137+
if self.ld_version >= "2.13":
138+
shared_option = "-shared"
139+
else:
140+
shared_option = "-mdll -static"
141+
else: # Assume linker is up to date
142+
self.linker_dll = self.cc
132143
shared_option = "-shared"
133-
else:
134-
shared_option = "-mdll -static"
135144

136-
# Hard-code GCC because that's what this is all about.
137-
# XXX optimization, warnings etc. should be customizable.
138-
self.set_executables(compiler='gcc -mcygwin -O -Wall',
139-
compiler_so='gcc -mcygwin -mdll -O -Wall',
140-
compiler_cxx='g++ -mcygwin -O -Wall',
141-
linker_exe='gcc -mcygwin',
145+
self.set_executables(compiler='%s -mcygwin -O -Wall' % self.cc,
146+
compiler_so='%s -mcygwin -mdll -O -Wall' % self.cc,
147+
compiler_cxx='%s -mcygwin -O -Wall' % self.cxx,
148+
linker_exe='%s -mcygwin' % self.cc,
142149
linker_so=('%s -mcygwin %s' %
143150
(self.linker_dll, shared_option)))
144151

145152
# cygwin and mingw32 need different sets of libraries
146-
if self.gcc_version == "2.91.57":
153+
if ('gcc' in self.cc and self.gcc_version == "2.91.57"):
147154
# cygwin shouldn't need msvcrt, but without the dlls will crash
148155
# (gcc version 2.91.57) -- perhaps something about initialization
149156
self.dll_libraries=["msvcrt"]
@@ -281,26 +288,26 @@ def __init__(self, verbose=0, dry_run=0, force=0):
281288

282289
# ld_version >= "2.13" support -shared so use it instead of
283290
# -mdll -static
284-
if self.ld_version >= "2.13":
285-
shared_option = "-shared"
286-
else:
291+
if ('gcc' in self.cc and self.ld_version < "2.13"):
287292
shared_option = "-mdll -static"
293+
else:
294+
shared_option = "-shared"
288295

289296
# A real mingw32 doesn't need to specify a different entry point,
290297
# but cygwin 2.91.57 in no-cygwin-mode needs it.
291-
if self.gcc_version <= "2.91.57":
298+
if ('gcc' in self.cc and self.gcc_version <= "2.91.57"):
292299
entry_point = '--entry _DllMain@12'
293300
else:
294301
entry_point = ''
295302

296-
if is_cygwingcc():
303+
if is_cygwincc(self.cc):
297304
raise CCompilerError(
298305
'Cygwin gcc cannot be used with --compiler=mingw32')
299306

300-
self.set_executables(compiler='gcc -O -Wall',
301-
compiler_so='gcc -mdll -O -Wall',
302-
compiler_cxx='g++ -O -Wall',
303-
linker_exe='gcc',
307+
self.set_executables(compiler='%s -O -Wall' % self.cc,
308+
compiler_so='%s -mdll -O -Wall' % self.cc,
309+
compiler_cxx='%s -O -Wall' % self.cxx,
310+
linker_exe='%s' % self.cc,
304311
linker_so='%s %s %s'
305312
% (self.linker_dll, shared_option,
306313
entry_point))
@@ -351,6 +358,10 @@ def check_config_h():
351358
if "GCC" in sys.version:
352359
return CONFIG_H_OK, "sys.version mentions 'GCC'"
353360

361+
# Clang would also work
362+
if "Clang" in sys.version:
363+
return CONFIG_H_OK, "sys.version mentions 'Clang'"
364+
354365
# let's see if __GNUC__ is mentioned in python.h
355366
fn = sysconfig.get_config_h_filename()
356367
try:
@@ -397,7 +408,7 @@ def get_versions():
397408
commands = ['gcc -dumpversion', 'ld -v', 'dllwrap --version']
398409
return tuple([_find_exe_version(cmd) for cmd in commands])
399410

400-
def is_cygwingcc():
401-
'''Try to determine if the gcc that would be used is from cygwin.'''
402-
out_string = check_output(['gcc', '-dumpmachine'])
411+
def is_cygwincc(cc):
412+
'''Try to determine if the compiler that would be used is from cygwin.'''
413+
out_string = check_output([cc, '-dumpmachine'])
403414
return out_string.strip().endswith(b'cygwin')

setuptools/_distutils/filelist.py

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
and building lists of files.
55
"""
66

7-
import os, re
7+
import os
8+
import re
89
import fnmatch
910
import functools
11+
1012
from distutils.util import convert_path
1113
from distutils.errors import DistutilsTemplateError, DistutilsInternalError
1214
from distutils import log
1315

16+
1417
class FileList:
1518
"""A list of files built by on exploring the filesystem and filtered by
1619
applying various patterns to what we find there.
@@ -46,7 +49,7 @@ def debug_print(self, msg):
4649
if DEBUG:
4750
print(msg)
4851

49-
# -- List-like methods ---------------------------------------------
52+
# Collection methods
5053

5154
def append(self, item):
5255
self.files.append(item)
@@ -61,17 +64,15 @@ def sort(self):
6164
for sort_tuple in sortable_files:
6265
self.files.append(os.path.join(*sort_tuple))
6366

64-
65-
# -- Other miscellaneous utility methods ---------------------------
67+
# Other miscellaneous utility methods
6668

6769
def remove_duplicates(self):
6870
# Assumes list has been sorted!
6971
for i in range(len(self.files) - 1, 0, -1):
7072
if self.files[i] == self.files[i - 1]:
7173
del self.files[i]
7274

73-
74-
# -- "File template" methods ---------------------------------------
75+
# "File template" methods
7576

7677
def _parse_template_line(self, line):
7778
words = line.split()
@@ -146,9 +147,11 @@ def process_template_line(self, line):
146147
(dir, ' '.join(patterns)))
147148
for pattern in patterns:
148149
if not self.include_pattern(pattern, prefix=dir):
149-
log.warn(("warning: no files found matching '%s' "
150-
"under directory '%s'"),
151-
pattern, dir)
150+
msg = (
151+
"warning: no files found matching '%s' "
152+
"under directory '%s'"
153+
)
154+
log.warn(msg, pattern, dir)
152155

153156
elif action == 'recursive-exclude':
154157
self.debug_print("recursive-exclude %s %s" %
@@ -174,8 +177,7 @@ def process_template_line(self, line):
174177
raise DistutilsInternalError(
175178
"this cannot happen: invalid action '%s'" % action)
176179

177-
178-
# -- Filtering/selection methods -----------------------------------
180+
# Filtering/selection methods
179181

180182
def include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
181183
"""Select strings (presumably filenames) from 'self.files' that
@@ -219,9 +221,8 @@ def include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
219221
files_found = True
220222
return files_found
221223

222-
223-
def exclude_pattern (self, pattern,
224-
anchor=1, prefix=None, is_regex=0):
224+
def exclude_pattern(
225+
self, pattern, anchor=1, prefix=None, is_regex=0):
225226
"""Remove strings (presumably filenames) from 'files' that match
226227
'pattern'. Other parameters are the same as for
227228
'include_pattern()', above.
@@ -240,21 +241,47 @@ def exclude_pattern (self, pattern,
240241
return files_found
241242

242243

243-
# ----------------------------------------------------------------------
244244
# Utility functions
245245

246246
def _find_all_simple(path):
247247
"""
248248
Find all files under 'path'
249249
"""
250+
all_unique = _UniqueDirs.filter(os.walk(path, followlinks=True))
250251
results = (
251252
os.path.join(base, file)
252-
for base, dirs, files in os.walk(path, followlinks=True)
253+
for base, dirs, files in all_unique
253254
for file in files
254255
)
255256
return filter(os.path.isfile, results)
256257

257258

259+
class _UniqueDirs(set):
260+
"""
261+
Exclude previously-seen dirs from walk results,
262+
avoiding infinite recursion.
263+
Ref https://bugs.python.org/issue44497.
264+
"""
265+
def __call__(self, walk_item):
266+
"""
267+
Given an item from an os.walk result, determine
268+
if the item represents a unique dir for this instance
269+
and if not, prevent further traversal.
270+
"""
271+
base, dirs, files = walk_item
272+
stat = os.stat(base)
273+
candidate = stat.st_dev, stat.st_ino
274+
found = candidate in self
275+
if found:
276+
del dirs[:]
277+
self.add(candidate)
278+
return not found
279+
280+
@classmethod
281+
def filter(cls, items):
282+
return filter(cls(), items)
283+
284+
258285
def findall(dir=os.curdir):
259286
"""
260287
Find all files under 'dir' and return the list of full filenames.
@@ -319,7 +346,8 @@ def translate_pattern(pattern, anchor=1, prefix=None, is_regex=0):
319346
if os.sep == '\\':
320347
sep = r'\\'
321348
pattern_re = pattern_re[len(start): len(pattern_re) - len(end)]
322-
pattern_re = r'%s\A%s%s.*%s%s' % (start, prefix_re, sep, pattern_re, end)
349+
pattern_re = r'%s\A%s%s.*%s%s' % (
350+
start, prefix_re, sep, pattern_re, end)
323351
else: # no prefix -- respect anchor flag
324352
if anchor:
325353
pattern_re = r'%s\A%s' % (start, pattern_re[len(start):])

setuptools/_distutils/spawn.py

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515
from distutils import log
1616

1717

18-
if sys.platform == 'darwin':
19-
_cfg_target = None
20-
_cfg_target_split = None
21-
22-
2318
def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
2419
"""Run another program, specified as a command list 'cmd', in a new process.
2520
@@ -52,28 +47,10 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
5247
env = env if env is not None else dict(os.environ)
5348

5449
if sys.platform == 'darwin':
55-
global _cfg_target, _cfg_target_split
56-
if _cfg_target is None:
57-
from distutils import sysconfig
58-
_cfg_target = sysconfig.get_config_var(
59-
'MACOSX_DEPLOYMENT_TARGET') or ''
60-
if _cfg_target:
61-
_cfg_target_split = [int(x) for x in _cfg_target.split('.')]
62-
if _cfg_target:
63-
# Ensure that the deployment target of the build process is not
64-
# less than 10.3 if the interpreter was built for 10.3 or later.
65-
# This ensures extension modules are built with correct
66-
# compatibility values, specifically LDSHARED which can use
67-
# '-undefined dynamic_lookup' which only works on >= 10.3.
68-
cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target)
69-
cur_target_split = [int(x) for x in cur_target.split('.')]
70-
if _cfg_target_split[:2] >= [10, 3] and cur_target_split[:2] < [10, 3]:
71-
my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
72-
'now "%s" but "%s" during configure;'
73-
'must use 10.3 or later'
74-
% (cur_target, _cfg_target))
75-
raise DistutilsPlatformError(my_msg)
76-
env.update(MACOSX_DEPLOYMENT_TARGET=cur_target)
50+
from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver
51+
macosx_target_ver = get_macosx_target_ver()
52+
if macosx_target_ver:
53+
env[MACOSX_VERSION_VAR] = macosx_target_ver
7754

7855
try:
7956
proc = subprocess.Popen(cmd, env=env)

setuptools/_distutils/tests/test_build_ext.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def test_unicode_module_names(self):
316316
self.assertRegex(cmd.get_ext_filename(modules[0].name), r'foo(_d)?\..*')
317317
self.assertRegex(cmd.get_ext_filename(modules[1].name), r'föö(_d)?\..*')
318318
self.assertEqual(cmd.get_export_symbols(modules[0]), ['PyInit_foo'])
319-
self.assertEqual(cmd.get_export_symbols(modules[1]), ['PyInitU_f_gkaa'])
319+
self.assertEqual(cmd.get_export_symbols(modules[1]), ['PyInitU_f_1gaa'])
320320

321321
def test_compiler_option(self):
322322
# cmd.compiler is an option and

setuptools/_distutils/tests/test_filelist.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,16 @@ def test_non_local_discovery(self):
331331
expected = [file1]
332332
self.assertEqual(filelist.findall(temp_dir), expected)
333333

334+
@os_helper.skip_unless_symlink
335+
def test_symlink_loop(self):
336+
with os_helper.temp_dir() as temp_dir:
337+
link = os.path.join(temp_dir, 'link-to-parent')
338+
content = os.path.join(temp_dir, 'somefile')
339+
os_helper.create_empty_file(content)
340+
os.symlink('.', link)
341+
files = filelist.findall(temp_dir)
342+
assert len(files) == 1
343+
334344

335345
def test_suite():
336346
return unittest.TestSuite([

0 commit comments

Comments
 (0)