diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2021-11-06 13:30:21 -0400 |
---|---|---|
committer | Jason R. Coombs <jaraco@jaraco.com> | 2021-11-06 13:31:30 -0400 |
commit | 10035d4a24de28537eb8eb2849175e334bb0393b (patch) | |
tree | a38c6f644609c64f00e23ec07984cd382b987426 | |
parent | af44971ccc28f5151c896eec66b801944cf8545b (diff) | |
parent | 5c836a504b7f040e2ebb09172802e7364b070ddc (diff) | |
download | python-setuptools-git-10035d4a24de28537eb8eb2849175e334bb0393b.tar.gz |
Merge branch 'main' into feature/local-schemes
-rw-r--r-- | .github/workflows/main.yml | 42 | ||||
-rw-r--r-- | README.rst | 37 | ||||
-rw-r--r-- | distutils/_msvccompiler.py | 2 | ||||
-rw-r--r-- | distutils/ccompiler.py | 7 | ||||
-rw-r--r-- | distutils/command/build.py | 2 | ||||
-rw-r--r-- | distutils/command/build_ext.py | 10 | ||||
-rw-r--r-- | distutils/command/build_py.py | 26 | ||||
-rw-r--r-- | distutils/command/build_scripts.py | 10 | ||||
-rw-r--r-- | distutils/command/install.py | 29 | ||||
-rw-r--r-- | distutils/cygwinccompiler.py | 91 | ||||
-rw-r--r-- | distutils/filelist.py | 62 | ||||
-rw-r--r-- | distutils/msvc9compiler.py | 4 | ||||
-rw-r--r-- | distutils/msvccompiler.py | 4 | ||||
-rw-r--r-- | distutils/spawn.py | 27 | ||||
-rw-r--r-- | distutils/sysconfig.py | 23 | ||||
-rw-r--r-- | distutils/tests/test_build_ext.py | 2 | ||||
-rw-r--r-- | distutils/tests/test_filelist.py | 10 | ||||
-rw-r--r-- | distutils/tests/test_sysconfig.py | 17 | ||||
-rw-r--r-- | distutils/tests/test_unixccompiler.py | 101 | ||||
-rw-r--r-- | distutils/unixccompiler.py | 37 | ||||
-rw-r--r-- | distutils/util.py | 136 | ||||
-rw-r--r-- | docs/distutils/apiref.rst | 18 |
22 files changed, 388 insertions, 309 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..6a8ff006 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,42 @@ +name: tests + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + python: [3.6, 3.8, 3.9] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install tox + run: | + python -m pip install tox + - name: Run tests + run: tox + + release: + needs: test + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install tox + run: | + python -m pip install tox + - name: Release + run: tox -e release + env: + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -3,42 +3,7 @@ Python Module Distribution Utilities extracted from the Python Standard Library Synchronizing ============= -This project is kept in sync with the code still in stdlib. - -From CPython ------------- - -The original history and attribution of all changes contributed to CPython can be found in the `cpython branch <https://github.com/pypa/distutils/tree/cpython>`_. If new commits are added to CPython, they should be synchronized back to this project. - -First, create a clone of CPython that only includes the distutils changes. Due to the large size of the CPython repository, this operation is fairly expensive. - -:: - - git clone https://github.com/python/cpython python-distutils - cd python-distutils - git filter-branch --prune-empty --subdirectory-filter Lib/distutils master - -Then, pull those changes into the repository at the cpython branch. - -:: - - cd $distutils - git checkout cpython - git fetch $python_distutils - git merge FETCH_HEAD - -Finally, merge the changes from cpython into master (possibly as a pull request). - -To CPython ----------- - -Merging changes from this repository is easier. - -From the CPython repo, cherry-pick the changes from this project. - -:: - - git -C $distutils format-patch HEAD~2 --stdout | git am --directory Lib +This project is no longer kept in sync with the code still in stdlib, which is deprecated and scheduled for removal. To Setuptools ------------- diff --git a/distutils/_msvccompiler.py b/distutils/_msvccompiler.py index e9af4cf5..b7a06082 100644 --- a/distutils/_msvccompiler.py +++ b/distutils/_msvccompiler.py @@ -248,7 +248,7 @@ class MSVCCompiler(CCompiler) : # Future releases of Python 3.x will include all past # versions of vcruntime*.dll for compatibility. self.compile_options = [ - '/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG', '/MD' + '/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD' ] self.compile_options_debug = [ diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index 57bb94e8..b38cf261 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -792,6 +792,8 @@ int main (int argc, char **argv) { objects = self.compile([fname], include_dirs=include_dirs) except CompileError: return False + finally: + os.remove(fname) try: self.link_executable(objects, "a.out", @@ -799,6 +801,11 @@ int main (int argc, char **argv) { library_dirs=library_dirs) except (LinkError, TypeError): return False + else: + os.remove("a.out") + finally: + for fn in objects: + os.remove(fn) return True def find_library_file (self, dirs, lib, debug=0): diff --git a/distutils/command/build.py b/distutils/command/build.py index a86df0bc..4355a632 100644 --- a/distutils/command/build.py +++ b/distutils/command/build.py @@ -102,7 +102,7 @@ class build(Command): # particular module distribution -- if user didn't supply it, pick # one of 'build_purelib' or 'build_platlib'. if self.build_lib is None: - if self.distribution.ext_modules: + if self.distribution.has_ext_modules(): self.build_lib = self.build_platlib else: self.build_lib = self.build_purelib diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index bbb34833..22628baf 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -220,7 +220,7 @@ class build_ext(Command): # For extensions under Cygwin, Python's library directory must be # appended to library_dirs if sys.platform[:6] == 'cygwin': - if sys.executable.startswith(os.path.join(sys.exec_prefix, "bin")): + if not sysconfig.python_build: # building third party extensions self.library_dirs.append(os.path.join(sys.prefix, "lib", "python" + get_python_version(), @@ -690,13 +690,15 @@ class build_ext(Command): provided, "PyInit_" + module_name. Only relevant on Windows, where the .pyd file (DLL) must export the module "PyInit_" function. """ - suffix = '_' + ext.name.split('.')[-1] + name = ext.name.split('.')[-1] try: # Unicode module name support as defined in PEP-489 # https://www.python.org/dev/peps/pep-0489/#export-hook-name - suffix.encode('ascii') + name.encode('ascii') except UnicodeEncodeError: - suffix = 'U' + suffix.encode('punycode').replace(b'-', b'_').decode('ascii') + suffix = 'U_' + name.encode('punycode').replace(b'-', b'_').decode('ascii') + else: + suffix = "_" + name initfunc_name = "PyInit" + suffix if initfunc_name not in ext.export_symbols: diff --git a/distutils/command/build_py.py b/distutils/command/build_py.py index edc2171c..7ef9bcef 100644 --- a/distutils/command/build_py.py +++ b/distutils/command/build_py.py @@ -9,7 +9,7 @@ import glob from distutils.core import Command from distutils.errors import * -from distutils.util import convert_path, Mixin2to3 +from distutils.util import convert_path from distutils import log class build_py (Command): @@ -390,27 +390,3 @@ class build_py (Command): if self.optimize > 0: byte_compile(files, optimize=self.optimize, force=self.force, prefix=prefix, dry_run=self.dry_run) - -class build_py_2to3(build_py, Mixin2to3): - def run(self): - self.updated_files = [] - - # Base class code - if self.py_modules: - self.build_modules() - if self.packages: - self.build_packages() - self.build_package_data() - - # 2to3 - self.run_2to3(self.updated_files) - - # Remaining base class code - self.byte_compile(self.get_outputs(include_bytecode=0)) - - def build_module(self, module, module_file, package): - res = build_py.build_module(self, module, module_file, package) - if res[1]: - # file was copied - self.updated_files.append(res[0]) - return res diff --git a/distutils/command/build_scripts.py b/distutils/command/build_scripts.py index ccc70e64..e3312cf0 100644 --- a/distutils/command/build_scripts.py +++ b/distutils/command/build_scripts.py @@ -7,7 +7,7 @@ from stat import ST_MODE from distutils import sysconfig from distutils.core import Command from distutils.dep_util import newer -from distutils.util import convert_path, Mixin2to3 +from distutils.util import convert_path from distutils import log import tokenize @@ -150,11 +150,3 @@ class build_scripts(Command): os.chmod(file, newmode) # XXX should we modify self.outfiles? return outfiles, updated_files - -class build_scripts_2to3(build_scripts, Mixin2to3): - - def copy_scripts(self): - outfiles, updated_files = build_scripts.copy_scripts(self) - if not self.dry_run: - self.run_2to3(updated_files) - return outfiles, updated_files diff --git a/distutils/command/install.py b/distutils/command/install.py index 0f4ff70c..8161aa9e 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -30,16 +30,16 @@ WINDOWS_SCHEME = { INSTALL_SCHEMES = { 'unix_prefix': { - 'purelib': '{base}/lib/python{py_version_short}/site-packages', - 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', - 'include': '{base}/include/python{py_version_short}{abiflags}/{dist_name}', + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', + 'include': '{base}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', 'scripts': '{base}/bin', 'data' : '{base}', }, 'unix_home': { - 'purelib': '{base}/lib/python', - 'platlib': '{base}/{platlibdir}/python', - 'include': '{base}/include/python/{dist_name}', + 'purelib': '{base}/lib/{implementation_lower}', + 'platlib': '{base}/{platlibdir}/{implementation_lower}', + 'include': '{base}/include/{implementation_lower}/{dist_name}', 'scripts': '{base}/bin', 'data' : '{base}', }, @@ -65,8 +65,8 @@ if HAS_USER_SITE: INSTALL_SCHEMES['nt_user'] = { 'purelib': '{usersite}', 'platlib': '{usersite}', - 'include': '{userbase}/Python{py_version_nodot}/Include/{dist_name}', - 'scripts': '{userbase}/Python{py_version_nodot}/Scripts', + 'include': '{userbase}/{implementation}{py_version_nodot}/Include/{dist_name}', + 'scripts': '{userbase}/{implementation}{py_version_nodot}/Scripts', 'data' : '{userbase}', } @@ -74,7 +74,7 @@ if HAS_USER_SITE: 'purelib': '{usersite}', 'platlib': '{usersite}', 'include': - '{userbase}/include/python{py_version_short}{abiflags}/{dist_name}', + '{userbase}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', 'scripts': '{userbase}/bin', 'data' : '{userbase}', } @@ -102,6 +102,12 @@ def _load_schemes(): return schemes +def _get_implementation(): + if hasattr(sys, 'pypy_version_info'): + return 'PyPy' + else: + return 'Python' + class install(Command): @@ -336,6 +342,8 @@ class install(Command): 'exec_prefix': exec_prefix, 'abiflags': abiflags, 'platlibdir': getattr(sys, 'platlibdir', 'lib'), + 'implementation_lower': _get_implementation().lower(), + 'implementation': _get_implementation(), } if HAS_USER_SITE: @@ -371,7 +379,7 @@ class install(Command): # module distribution is pure or not. Of course, if the user # already specified install_lib, use their selection. if self.install_lib is None: - if self.distribution.ext_modules: # has extensions: non-pure + if self.distribution.has_ext_modules(): # has extensions: non-pure self.install_lib = self.install_platlib else: self.install_lib = self.install_purelib @@ -493,6 +501,7 @@ class install(Command): """Sets the install directories by applying the install schemes.""" # it's the caller's problem if they supply a bad name! if (hasattr(sys, 'pypy_version_info') and + sys.version_info < (3, 8) and not name.endswith(('_user', '_home'))): if os.name == 'nt': name = 'pypy_nt' diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 66c12dd3..f1c38e39 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -44,6 +44,8 @@ cygwin in no-cygwin mode). # (ld supports -shared) # * mingw gcc 3.2/ld 2.13 works # (ld supports -shared) +# * llvm-mingw with Clang 11 works +# (lld supports -shared) import os import sys @@ -109,41 +111,46 @@ class CygwinCCompiler(UnixCCompiler): "Compiling may fail because of undefined preprocessor macros." % details) - self.gcc_version, self.ld_version, self.dllwrap_version = \ - get_versions() - self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" % - (self.gcc_version, - self.ld_version, - self.dllwrap_version) ) - - # ld_version >= "2.10.90" and < "2.13" should also be able to use - # gcc -mdll instead of dllwrap - # Older dllwraps had own version numbers, newer ones use the - # same as the rest of binutils ( also ld ) - # dllwrap 2.10.90 is buggy - if self.ld_version >= "2.10.90": - self.linker_dll = "gcc" - else: - self.linker_dll = "dllwrap" + self.cc = os.environ.get('CC', 'gcc') + self.cxx = os.environ.get('CXX', 'g++') + + if ('gcc' in self.cc): # Start gcc workaround + self.gcc_version, self.ld_version, self.dllwrap_version = \ + get_versions() + self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" % + (self.gcc_version, + self.ld_version, + self.dllwrap_version) ) + + # ld_version >= "2.10.90" and < "2.13" should also be able to use + # gcc -mdll instead of dllwrap + # Older dllwraps had own version numbers, newer ones use the + # same as the rest of binutils ( also ld ) + # dllwrap 2.10.90 is buggy + if self.ld_version >= "2.10.90": + self.linker_dll = self.cc + else: + self.linker_dll = "dllwrap" - # ld_version >= "2.13" support -shared so use it instead of - # -mdll -static - if self.ld_version >= "2.13": + # ld_version >= "2.13" support -shared so use it instead of + # -mdll -static + if self.ld_version >= "2.13": + shared_option = "-shared" + else: + shared_option = "-mdll -static" + else: # Assume linker is up to date + self.linker_dll = self.cc shared_option = "-shared" - else: - shared_option = "-mdll -static" - # Hard-code GCC because that's what this is all about. - # XXX optimization, warnings etc. should be customizable. - self.set_executables(compiler='gcc -mcygwin -O -Wall', - compiler_so='gcc -mcygwin -mdll -O -Wall', - compiler_cxx='g++ -mcygwin -O -Wall', - linker_exe='gcc -mcygwin', + self.set_executables(compiler='%s -mcygwin -O -Wall' % self.cc, + compiler_so='%s -mcygwin -mdll -O -Wall' % self.cc, + compiler_cxx='%s -mcygwin -O -Wall' % self.cxx, + linker_exe='%s -mcygwin' % self.cc, linker_so=('%s -mcygwin %s' % (self.linker_dll, shared_option))) # cygwin and mingw32 need different sets of libraries - if self.gcc_version == "2.91.57": + if ('gcc' in self.cc and self.gcc_version == "2.91.57"): # cygwin shouldn't need msvcrt, but without the dlls will crash # (gcc version 2.91.57) -- perhaps something about initialization self.dll_libraries=["msvcrt"] @@ -281,26 +288,26 @@ class Mingw32CCompiler(CygwinCCompiler): # ld_version >= "2.13" support -shared so use it instead of # -mdll -static - if self.ld_version >= "2.13": - shared_option = "-shared" - else: + if ('gcc' in self.cc and self.ld_version < "2.13"): shared_option = "-mdll -static" + else: + shared_option = "-shared" # A real mingw32 doesn't need to specify a different entry point, # but cygwin 2.91.57 in no-cygwin-mode needs it. - if self.gcc_version <= "2.91.57": + if ('gcc' in self.cc and self.gcc_version <= "2.91.57"): entry_point = '--entry _DllMain@12' else: entry_point = '' - if is_cygwingcc(): + if is_cygwincc(self.cc): raise CCompilerError( 'Cygwin gcc cannot be used with --compiler=mingw32') - self.set_executables(compiler='gcc -O -Wall', - compiler_so='gcc -mdll -O -Wall', - compiler_cxx='g++ -O -Wall', - linker_exe='gcc', + self.set_executables(compiler='%s -O -Wall' % self.cc, + compiler_so='%s -mdll -O -Wall' % self.cc, + compiler_cxx='%s -O -Wall' % self.cxx, + linker_exe='%s' % self.cc, linker_so='%s %s %s' % (self.linker_dll, shared_option, entry_point)) @@ -351,6 +358,10 @@ def check_config_h(): if "GCC" in sys.version: return CONFIG_H_OK, "sys.version mentions 'GCC'" + # Clang would also work + if "Clang" in sys.version: + return CONFIG_H_OK, "sys.version mentions 'Clang'" + # let's see if __GNUC__ is mentioned in python.h fn = sysconfig.get_config_h_filename() try: @@ -397,7 +408,7 @@ def get_versions(): commands = ['gcc -dumpversion', 'ld -v', 'dllwrap --version'] return tuple([_find_exe_version(cmd) for cmd in commands]) -def is_cygwingcc(): - '''Try to determine if the gcc that would be used is from cygwin.''' - out_string = check_output(['gcc', '-dumpmachine']) +def is_cygwincc(cc): + '''Try to determine if the compiler that would be used is from cygwin.''' + out_string = check_output([cc, '-dumpmachine']) return out_string.strip().endswith(b'cygwin') diff --git a/distutils/filelist.py b/distutils/filelist.py index c92d5fdb..82a77384 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -4,13 +4,16 @@ Provides the FileList class, used for poking about the filesystem and building lists of files. """ -import os, re +import os +import re import fnmatch import functools + from distutils.util import convert_path from distutils.errors import DistutilsTemplateError, DistutilsInternalError from distutils import log + class FileList: """A list of files built by on exploring the filesystem and filtered by applying various patterns to what we find there. @@ -46,7 +49,7 @@ class FileList: if DEBUG: print(msg) - # -- List-like methods --------------------------------------------- + # Collection methods def append(self, item): self.files.append(item) @@ -61,8 +64,7 @@ class FileList: for sort_tuple in sortable_files: self.files.append(os.path.join(*sort_tuple)) - - # -- Other miscellaneous utility methods --------------------------- + # Other miscellaneous utility methods def remove_duplicates(self): # Assumes list has been sorted! @@ -70,8 +72,7 @@ class FileList: if self.files[i] == self.files[i - 1]: del self.files[i] - - # -- "File template" methods --------------------------------------- + # "File template" methods def _parse_template_line(self, line): words = line.split() @@ -146,9 +147,11 @@ class FileList: (dir, ' '.join(patterns))) for pattern in patterns: if not self.include_pattern(pattern, prefix=dir): - log.warn(("warning: no files found matching '%s' " - "under directory '%s'"), - pattern, dir) + msg = ( + "warning: no files found matching '%s' " + "under directory '%s'" + ) + log.warn(msg, pattern, dir) elif action == 'recursive-exclude': self.debug_print("recursive-exclude %s %s" % @@ -174,8 +177,7 @@ class FileList: raise DistutilsInternalError( "this cannot happen: invalid action '%s'" % action) - - # -- Filtering/selection methods ----------------------------------- + # Filtering/selection methods def include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0): """Select strings (presumably filenames) from 'self.files' that @@ -219,9 +221,8 @@ class FileList: files_found = True return files_found - - def exclude_pattern (self, pattern, - anchor=1, prefix=None, is_regex=0): + def exclude_pattern( + self, pattern, anchor=1, prefix=None, is_regex=0): """Remove strings (presumably filenames) from 'files' that match 'pattern'. Other parameters are the same as for 'include_pattern()', above. @@ -240,21 +241,47 @@ class FileList: return files_found -# ---------------------------------------------------------------------- # Utility functions def _find_all_simple(path): """ Find all files under 'path' """ + all_unique = _UniqueDirs.filter(os.walk(path, followlinks=True)) results = ( os.path.join(base, file) - for base, dirs, files in os.walk(path, followlinks=True) + for base, dirs, files in all_unique for file in files ) return filter(os.path.isfile, results) +class _UniqueDirs(set): + """ + Exclude previously-seen dirs from walk results, + avoiding infinite recursion. + Ref https://bugs.python.org/issue44497. + """ + def __call__(self, walk_item): + """ + Given an item from an os.walk result, determine + if the item represents a unique dir for this instance + and if not, prevent further traversal. + """ + base, dirs, files = walk_item + stat = os.stat(base) + candidate = stat.st_dev, stat.st_ino + found = candidate in self + if found: + del dirs[:] + self.add(candidate) + return not found + + @classmethod + def filter(cls, items): + return filter(cls(), items) + + def findall(dir=os.curdir): """ 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): if os.sep == '\\': sep = r'\\' pattern_re = pattern_re[len(start): len(pattern_re) - len(end)] - pattern_re = r'%s\A%s%s.*%s%s' % (start, prefix_re, sep, pattern_re, end) + pattern_re = r'%s\A%s%s.*%s%s' % ( + start, prefix_re, sep, pattern_re, end) else: # no prefix -- respect anchor flag if anchor: pattern_re = r'%s\A%s' % (start, pattern_re[len(start):]) diff --git a/distutils/msvc9compiler.py b/distutils/msvc9compiler.py index 6934e964..a1b3b02f 100644 --- a/distutils/msvc9compiler.py +++ b/distutils/msvc9compiler.py @@ -399,13 +399,13 @@ class MSVCCompiler(CCompiler) : self.preprocess_options = None if self.__arch == "x86": - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/DNDEBUG'] self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/Z7', '/D_DEBUG'] else: # Win64 - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' , + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GS-' , '/DNDEBUG'] self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', '/Z7', '/D_DEBUG'] diff --git a/distutils/msvccompiler.py b/distutils/msvccompiler.py index d5857cb1..2d447b85 100644 --- a/distutils/msvccompiler.py +++ b/distutils/msvccompiler.py @@ -283,13 +283,13 @@ class MSVCCompiler(CCompiler) : self.preprocess_options = None if self.__arch == "Intel": - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GX' , + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GX' , '/DNDEBUG'] self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX', '/Z7', '/D_DEBUG'] else: # Win64 - self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' , + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GS-' , '/DNDEBUG'] self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', '/Z7', '/D_DEBUG'] diff --git a/distutils/spawn.py b/distutils/spawn.py index a73b8b9b..6e1c89f1 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -15,11 +15,6 @@ from distutils.debug import DEBUG from distutils import log -if sys.platform == 'darwin': - _cfg_target = None - _cfg_target_split = None - - def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): """Run another program, specified as a command list 'cmd', in a new process. @@ -52,24 +47,10 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): env = env if env is not None else dict(os.environ) if sys.platform == 'darwin': - global _cfg_target, _cfg_target_split - if _cfg_target is None: - from distutils import sysconfig - _cfg_target = sysconfig.get_config_var( - 'MACOSX_DEPLOYMENT_TARGET') or '' - if _cfg_target: - _cfg_target_split = [int(x) for x in _cfg_target.split('.')] - if _cfg_target: - # ensure that the deployment target of build process is not less - # than that used when the interpreter was built. This ensures - # extension modules are built with correct compatibility values - cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target) - if _cfg_target_split > [int(x) for x in cur_target.split('.')]: - my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: ' - 'now "%s" but "%s" during configure' - % (cur_target, _cfg_target)) - raise DistutilsPlatformError(my_msg) - env.update(MACOSX_DEPLOYMENT_TARGET=cur_target) + from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver + macosx_target_ver = get_macosx_target_ver() + if macosx_target_ver: + env[MACOSX_VERSION_VAR] = macosx_target_ver try: proc = subprocess.Popen(cmd, env=env) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 879b6981..8832b3ec 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -99,9 +99,9 @@ def get_python_inc(plat_specific=0, prefix=None): """ if prefix is None: prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX - if IS_PYPY: - return os.path.join(prefix, 'include') - elif os.name == "posix": + if os.name == "posix": + if IS_PYPY and sys.version_info < (3, 8): + return os.path.join(prefix, 'include') if python_build: # Assume the executable is in the build directory. The # pyconfig.h file should be in the same directory. Since @@ -113,7 +113,8 @@ def get_python_inc(plat_specific=0, prefix=None): else: incdir = os.path.join(get_config_var('srcdir'), 'Include') return os.path.normpath(incdir) - python_dir = 'python' + get_python_version() + build_flags + implementation = 'pypy' if IS_PYPY else 'python' + python_dir = implementation + get_python_version() + build_flags return os.path.join(prefix, "include", python_dir) elif os.name == "nt": if python_build: @@ -142,7 +143,8 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): If 'prefix' is supplied, use it instead of sys.base_prefix or sys.base_exec_prefix -- i.e., ignore 'plat_specific'. """ - if IS_PYPY: + + if IS_PYPY and sys.version_info < (3, 8): # PyPy-specific schema if prefix is None: prefix = PREFIX @@ -164,8 +166,9 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): else: # Pure Python libdir = "lib" + implementation = 'pypy' if IS_PYPY else 'python' libpython = os.path.join(prefix, libdir, - "python" + get_python_version()) + implementation + get_python_version()) if standard_lib: return libpython else: @@ -211,10 +214,9 @@ def customize_compiler(compiler): if 'CC' in os.environ: newcc = os.environ['CC'] - if (sys.platform == 'darwin' - and 'LDSHARED' not in os.environ + if('LDSHARED' not in os.environ and ldshared.startswith(cc)): - # On OS X, if CC is overridden, use that as the default + # If CC is overridden, use that as the default # command for LDSHARED as well ldshared = newcc + ldshared[len(cc):] cc = newcc @@ -252,6 +254,9 @@ def customize_compiler(compiler): linker_exe=cc, archiver=archiver) + if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None): + compiler.set_executables(ranlib=os.environ['RANLIB']) + compiler.shared_lib_extension = shlib_suffix diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index 5a72458c..85ecf4b7 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -316,7 +316,7 @@ class BuildExtTestCase(TempdirManager, self.assertRegex(cmd.get_ext_filename(modules[0].name), r'foo(_d)?\..*') self.assertRegex(cmd.get_ext_filename(modules[1].name), r'föö(_d)?\..*') self.assertEqual(cmd.get_export_symbols(modules[0]), ['PyInit_foo']) - self.assertEqual(cmd.get_export_symbols(modules[1]), ['PyInitU_f_gkaa']) + self.assertEqual(cmd.get_export_symbols(modules[1]), ['PyInitU_f_1gaa']) def test_compiler_option(self): # cmd.compiler is an option and diff --git a/distutils/tests/test_filelist.py b/distutils/tests/test_filelist.py index d8e4b39f..9ec507b5 100644 --- a/distutils/tests/test_filelist.py +++ b/distutils/tests/test_filelist.py @@ -331,6 +331,16 @@ class FindAllTestCase(unittest.TestCase): expected = [file1] self.assertEqual(filelist.findall(temp_dir), expected) + @os_helper.skip_unless_symlink + def test_symlink_loop(self): + with os_helper.temp_dir() as temp_dir: + link = os.path.join(temp_dir, 'link-to-parent') + content = os.path.join(temp_dir, 'somefile') + os_helper.create_empty_file(content) + os.symlink('.', link) + files = filelist.findall(temp_dir) + assert len(files) == 1 + def test_suite(): return unittest.TestSuite([ diff --git a/distutils/tests/test_sysconfig.py b/distutils/tests/test_sysconfig.py index c7571942..80cd1599 100644 --- a/distutils/tests/test_sysconfig.py +++ b/distutils/tests/test_sysconfig.py @@ -9,6 +9,7 @@ import unittest from distutils import sysconfig from distutils.ccompiler import get_default_compiler +from distutils.unixccompiler import UnixCCompiler from distutils.tests import support from test.support import run_unittest, swap_item @@ -84,9 +85,14 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): # make sure AR gets caught class compiler: compiler_type = 'unix' + executables = UnixCCompiler.executables + + def __init__(self): + self.exes = {} def set_executables(self, **kw): - self.exes = kw + for k, v in kw.items(): + self.exes[k] = v sysconfig_vars = { 'AR': 'sc_ar', @@ -125,6 +131,7 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): os.environ['ARFLAGS'] = '--env-arflags' os.environ['CFLAGS'] = '--env-cflags' os.environ['CPPFLAGS'] = '--env-cppflags' + os.environ['RANLIB'] = 'env_ranlib' comp = self.customize_compiler() self.assertEqual(comp.exes['archiver'], @@ -145,6 +152,12 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): ' --env-cppflags')) self.assertEqual(comp.shared_lib_extension, 'sc_shutil_suffix') + if sys.platform == "darwin": + self.assertEqual(comp.exes['ranlib'], + 'env_ranlib') + else: + self.assertTrue('ranlib' not in comp.exes) + del os.environ['AR'] del os.environ['CC'] del os.environ['CPP'] @@ -154,6 +167,7 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): del os.environ['ARFLAGS'] del os.environ['CFLAGS'] del os.environ['CPPFLAGS'] + del os.environ['RANLIB'] comp = self.customize_compiler() self.assertEqual(comp.exes['archiver'], @@ -171,6 +185,7 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase): self.assertEqual(comp.exes['linker_so'], 'sc_ldshared') self.assertEqual(comp.shared_lib_extension, 'sc_shutil_suffix') + self.assertTrue('ranlib' not in comp.exes) def test_parse_makefile_base(self): self.makefile = TESTFN diff --git a/distutils/tests/test_unixccompiler.py b/distutils/tests/test_unixccompiler.py index 1828ba1a..ee2fe99c 100644 --- a/distutils/tests/test_unixccompiler.py +++ b/distutils/tests/test_unixccompiler.py @@ -1,4 +1,5 @@ """Tests for distutils.unixccompiler.""" +import os import sys import unittest from test.support import run_unittest @@ -6,7 +7,9 @@ from test.support import run_unittest from .py38compat import EnvironmentVarGuard from distutils import sysconfig +from distutils.errors import DistutilsPlatformError from distutils.unixccompiler import UnixCCompiler +from distutils.util import _clear_cached_macosx_ver class UnixCCompilerTestCase(unittest.TestCase): @@ -26,18 +29,90 @@ class UnixCCompilerTestCase(unittest.TestCase): @unittest.skipIf(sys.platform == 'win32', "can't test on Windows") def test_runtime_libdir_option(self): - # Issue#5900 + # Issue #5900; GitHub Issue #37 # # Ensure RUNPATH is added to extension modules with RPATH if # GNU ld is used # darwin sys.platform = 'darwin' - self.assertEqual(self.cc.rpath_foo(), '-L/foo') + darwin_ver_var = 'MACOSX_DEPLOYMENT_TARGET' + darwin_rpath_flag = '-Wl,-rpath,/foo' + darwin_lib_flag = '-L/foo' + + # (macOS version from syscfg, macOS version from env var) -> flag + # Version value of None generates two tests: as None and as empty string + # Expected flag value of None means an mismatch exception is expected + darwin_test_cases = [ + ((None , None ), darwin_lib_flag), + ((None , '11' ), darwin_rpath_flag), + (('10' , None ), darwin_lib_flag), + (('10.3' , None ), darwin_lib_flag), + (('10.3.1', None ), darwin_lib_flag), + (('10.5' , None ), darwin_rpath_flag), + (('10.5.1', None ), darwin_rpath_flag), + (('10.3' , '10.3' ), darwin_lib_flag), + (('10.3' , '10.5' ), darwin_rpath_flag), + (('10.5' , '10.3' ), darwin_lib_flag), + (('10.5' , '11' ), darwin_rpath_flag), + (('10.4' , '10' ), None), + ] + + def make_darwin_gcv(syscfg_macosx_ver): + def gcv(var): + if var == darwin_ver_var: + return syscfg_macosx_ver + return "xxx" + return gcv + + def do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag): + env = os.environ + msg = "macOS version = (sysconfig=%r, env=%r)" % \ + (syscfg_macosx_ver, env_macosx_ver) + + # Save + old_gcv = sysconfig.get_config_var + old_env_macosx_ver = env.get(darwin_ver_var) + + # Setup environment + _clear_cached_macosx_ver() + sysconfig.get_config_var = make_darwin_gcv(syscfg_macosx_ver) + if env_macosx_ver is not None: + env[darwin_ver_var] = env_macosx_ver + elif darwin_ver_var in env: + env.pop(darwin_ver_var) + + # Run the test + if expected_flag is not None: + self.assertEqual(self.cc.rpath_foo(), expected_flag, msg=msg) + else: + with self.assertRaisesRegex(DistutilsPlatformError, + darwin_ver_var + r' mismatch', msg=msg): + self.cc.rpath_foo() + + # Restore + if old_env_macosx_ver is not None: + env[darwin_ver_var] = old_env_macosx_ver + elif darwin_ver_var in env: + env.pop(darwin_ver_var) + sysconfig.get_config_var = old_gcv + _clear_cached_macosx_ver() + + for macosx_vers, expected_flag in darwin_test_cases: + syscfg_macosx_ver, env_macosx_ver = macosx_vers + do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag) + # Bonus test cases with None interpreted as empty string + if syscfg_macosx_ver is None: + do_darwin_test("", env_macosx_ver, expected_flag) + if env_macosx_ver is None: + do_darwin_test(syscfg_macosx_ver, "", expected_flag) + if syscfg_macosx_ver is None and env_macosx_ver is None: + do_darwin_test("", "", expected_flag) + + old_gcv = sysconfig.get_config_var # hp-ux sys.platform = 'hp-ux' - old_gcv = sysconfig.get_config_var def gcv(v): return 'xxx' sysconfig.get_config_var = gcv @@ -65,6 +140,14 @@ class UnixCCompilerTestCase(unittest.TestCase): sysconfig.get_config_var = gcv self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + def gcv(v): + if v == 'CC': + return 'gcc -pthread -B /bar' + elif v == 'GNULD': + return 'yes' + sysconfig.get_config_var = gcv + self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + # GCC non-GNULD sys.platform = 'bar' def gcv(v): @@ -94,7 +177,7 @@ class UnixCCompilerTestCase(unittest.TestCase): elif v == 'GNULD': return 'yes' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-R/foo') + self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') # non-GCC non-GNULD sys.platform = 'bar' @@ -104,10 +187,10 @@ class UnixCCompilerTestCase(unittest.TestCase): elif v == 'GNULD': return 'no' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-R/foo') + self.assertEqual(self.cc.rpath_foo(), '-Wl,-R/foo') - @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for OS X') - def test_osx_cc_overrides_ldshared(self): + @unittest.skipIf(sys.platform == 'win32', "can't test on Windows") + def test_cc_overrides_ldshared(self): # Issue #18080: # ensure that setting CC env variable also changes default linker def gcv(v): @@ -127,8 +210,8 @@ class UnixCCompilerTestCase(unittest.TestCase): sysconfig.customize_compiler(self.cc) self.assertEqual(self.cc.linker_so[0], 'my_cc') - @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for OS X') - def test_osx_explicit_ldshared(self): + @unittest.skipIf(sys.platform == 'win32', "can't test on Windows") + def test_explicit_ldshared(self): # Issue #18080: # ensure that setting CC env variable does not change # explicit LDSHARED setting for linker diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 4d7a6de7..a07e5988 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -13,7 +13,7 @@ the "typical" Unix-style command-line C compiler: * link shared library handled by 'cc -shared' """ -import os, sys, re +import os, sys, re, shlex from distutils import sysconfig from distutils.dep_util import newer @@ -231,33 +231,30 @@ class UnixCCompiler(CCompiler): # this time, there's no way to determine this information from # the configuration data stored in the Python installation, so # we use this hack. - compiler = os.path.basename(sysconfig.get_config_var("CC")) + compiler = os.path.basename(shlex.split(sysconfig.get_config_var("CC"))[0]) if sys.platform[:6] == "darwin": - # MacOSX's linker doesn't understand the -R flag at all - return "-L" + dir + from distutils.util import get_macosx_target_ver, split_version + macosx_target_ver = get_macosx_target_ver() + if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]: + return "-Wl,-rpath," + dir + else: # no support for -rpath on earlier macOS versions + return "-L" + dir elif sys.platform[:7] == "freebsd": return "-Wl,-rpath=" + dir elif sys.platform[:5] == "hp-ux": if self._is_gcc(compiler): return ["-Wl,+s", "-L" + dir] return ["+s", "-L" + dir] + + # For all compilers, `-Wl` is the presumed way to + # pass a compiler option to the linker and `-R` is + # the way to pass an RPATH. + if sysconfig.get_config_var("GNULD") == "yes": + # GNU ld needs an extra option to get a RUNPATH + # instead of just an RPATH. + return "-Wl,--enable-new-dtags,-R" + dir else: - if self._is_gcc(compiler): - # gcc on non-GNU systems does not need -Wl, but can - # use it anyway. Since distutils has always passed in - # -Wl whenever gcc was used in the past it is probably - # safest to keep doing so. - if sysconfig.get_config_var("GNULD") == "yes": - # GNU ld needs an extra option to get a RUNPATH - # instead of just an RPATH. - return "-Wl,--enable-new-dtags,-R" + dir - else: - return "-Wl,-R" + dir - else: - # No idea how --enable-new-dtags would be passed on to - # ld if this system was using GNU ld. Don't know if a - # system like this even exists. - return "-R" + dir + return "-Wl,-R" + dir def library_option(self, lib): return "-l" + lib diff --git a/distutils/util.py b/distutils/util.py index 81d6869f..d9916a0a 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -103,11 +103,66 @@ def get_platform(): 'x86' : 'win32', 'x64' : 'win-amd64', 'arm' : 'win-arm32', + 'arm64': 'win-arm64', } return TARGET_TO_PLAT.get(os.environ.get('VSCMD_ARG_TGT_ARCH')) or get_host_platform() else: return get_host_platform() + +if sys.platform == 'darwin': + _syscfg_macosx_ver = None # cache the version pulled from sysconfig +MACOSX_VERSION_VAR = 'MACOSX_DEPLOYMENT_TARGET' + +def _clear_cached_macosx_ver(): + """For testing only. Do not call.""" + global _syscfg_macosx_ver + _syscfg_macosx_ver = None + +def get_macosx_target_ver_from_syscfg(): + """Get the version of macOS latched in the Python interpreter configuration. + Returns the version as a string or None if can't obtain one. Cached.""" + global _syscfg_macosx_ver + if _syscfg_macosx_ver is None: + from distutils import sysconfig + ver = sysconfig.get_config_var(MACOSX_VERSION_VAR) or '' + if ver: + _syscfg_macosx_ver = ver + return _syscfg_macosx_ver + +def get_macosx_target_ver(): + """Return the version of macOS for which we are building. + + The target version defaults to the version in sysconfig latched at time + the Python interpreter was built, unless overridden by an environment + variable. If neither source has a value, then None is returned""" + + syscfg_ver = get_macosx_target_ver_from_syscfg() + env_ver = os.environ.get(MACOSX_VERSION_VAR) + + if env_ver: + # Validate overridden version against sysconfig version, if have both. + # Ensure that the deployment target of the build process is not less + # than 10.3 if the interpreter was built for 10.3 or later. This + # ensures extension modules are built with correct compatibility + # values, specifically LDSHARED which can use + # '-undefined dynamic_lookup' which only works on >= 10.3. + if syscfg_ver and split_version(syscfg_ver) >= [10, 3] and \ + split_version(env_ver) < [10, 3]: + my_msg = ('$' + MACOSX_VERSION_VAR + ' mismatch: ' + 'now "%s" but "%s" during configure; ' + 'must use 10.3 or later' + % (env_ver, syscfg_ver)) + raise DistutilsPlatformError(my_msg) + return env_ver + return syscfg_ver + + +def split_version(s): + """Convert a dot-separated string into a list of numbers for comparisons""" + return [int(n) for n in s.split('.')] + + def convert_path (pathname): """Return 'pathname' as a name that will work on the native filesystem, i.e. split it on '/' and put it back together again using the current @@ -491,84 +546,3 @@ def rfc822_escape (header): lines = header.split('\n') sep = '\n' + 8 * ' ' return sep.join(lines) - -# 2to3 support - -def run_2to3(files, fixer_names=None, options=None, explicit=None): - """Invoke 2to3 on a list of Python files. - The files should all come from the build area, as the - modification is done in-place. To reduce the build time, - only files modified since the last invocation of this - function should be passed in the files argument.""" - - if not files: - return - - # Make this class local, to delay import of 2to3 - from lib2to3.refactor import RefactoringTool, get_fixers_from_package - class DistutilsRefactoringTool(RefactoringTool): - def log_error(self, msg, *args, **kw): - log.error(msg, *args) - - def log_message(self, msg, *args): - log.info(msg, *args) - - def log_debug(self, msg, *args): - log.debug(msg, *args) - - if fixer_names is None: - fixer_names = get_fixers_from_package('lib2to3.fixes') - r = DistutilsRefactoringTool(fixer_names, options=options) - r.refactor(files, write=True) - -def copydir_run_2to3(src, dest, template=None, fixer_names=None, - options=None, explicit=None): - """Recursively copy a directory, only copying new and changed files, - running run_2to3 over all newly copied Python modules afterward. - - If you give a template string, it's parsed like a MANIFEST.in. - """ - from distutils.dir_util import mkpath - from distutils.file_util import copy_file - from distutils.filelist import FileList - filelist = FileList() - curdir = os.getcwd() - os.chdir(src) - try: - filelist.findall() - finally: - os.chdir(curdir) - filelist.files[:] = filelist.allfiles - if template: - for line in template.splitlines(): - line = line.strip() - if not line: continue - filelist.process_template_line(line) - copied = [] - for filename in filelist.files: - outname = os.path.join(dest, filename) - mkpath(os.path.dirname(outname)) - res = copy_file(os.path.join(src, filename), outname, update=1) - if res[1]: copied.append(outname) - run_2to3([fn for fn in copied if fn.lower().endswith('.py')], - fixer_names=fixer_names, options=options, explicit=explicit) - return copied - -class Mixin2to3: - '''Mixin class for commands that run 2to3. - To configure 2to3, setup scripts may either change - the class variables, or inherit from individual commands - to override how 2to3 is invoked.''' - - # provide list of fixers to run; - # defaults to all from lib2to3.fixers - fixer_names = None - - # options dictionary - options = None - - # list of fixers to invoke even though they are marked as explicit - explicit = None - - def run_2to3(self, files): - return run_2to3(files, self.fixer_names, self.options, self.explicit) diff --git a/docs/distutils/apiref.rst b/docs/distutils/apiref.rst index b14197c2..372755a8 100644 --- a/docs/distutils/apiref.rst +++ b/docs/distutils/apiref.rst @@ -1940,24 +1940,6 @@ Subclasses of :class:`Command` must define the following methods. .. class:: build_py -.. class:: build_py_2to3 - - Alternative implementation of build_py which also runs the - 2to3 conversion library on each .py file that is going to be - installed. To use this in a setup.py file for a distribution - that is designed to run with both Python 2.x and 3.x, add:: - - try: - from distutils.command.build_py import build_py_2to3 as build_py - except ImportError: - from distutils.command.build_py import build_py - - to your setup.py, and later:: - - cmdclass = {'build_py': build_py} - - to the invocation of setup(). - :mod:`distutils.command.build_scripts` --- Build the scripts of a package ========================================================================= |