From 2c937116cc0dcd9b26b6070e89a3dc5dcbedc2ae Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Thu, 14 Dec 2023 09:24:14 -0500 Subject: [PATCH 01/48] Distutils C++ support Upstreamed fix from nix, see patch here: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/python-modules/setuptools/setuptools-distutils-C%2B%2B.patch --- distutils/cygwinccompiler.py | 17 ++++++++++++++--- distutils/sysconfig.py | 20 ++++++++++++++++++-- distutils/unixccompiler.py | 15 ++++++++++++--- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index b3dbc3be15..c683e3eeac 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -101,14 +101,19 @@ def __init__(self, verbose=0, dry_run=0, force=0): self.cxx = os.environ.get('CXX', 'g++') self.linker_dll = self.cc + self.linker_dll_cxx = self.cxx shared_option = "-shared" 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, + compiler_so_cxx='%s -mcygwin -mdll -O -Wall' % self.cxx, linker_exe='%s -mcygwin' % self.cc, linker_so=('{} -mcygwin {}'.format(self.linker_dll, shared_option)), + linker_exe_cxx='%s -mcygwin' % self.cxx, + linker_so_cxx=('%s -mcygwin %s' % + (self.linker_dll_cxx, shared_option)), ) # Include the appropriate MSVC runtime library if Python was built @@ -140,9 +145,12 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): raise CompileError(msg) else: # for other files use the C-compiler try: - self.spawn( - self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs - ) + if self.detect_language(src) == 'c++': + self.spawn(self.compiler_so_cxx + cc_args + [src, '-o', obj] + + extra_postargs) + else: + self.spawn( + self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs) except DistutilsExecError as msg: raise CompileError(msg) @@ -278,9 +286,12 @@ def __init__(self, verbose=0, dry_run=0, force=0): self.set_executables( compiler='%s -O -Wall' % self.cc, compiler_so='%s -mdll -O -Wall' % self.cc, + compiler_so_cxx='%s -mdll -O -Wall' % self.cxx, compiler_cxx='%s -O -Wall' % self.cxx, linker_exe='%s' % self.cc, linker_so='{} {}'.format(self.linker_dll, shared_option), + linker_exe_cxx='%s' % self.cxx, + linker_so_cxx='%s %s' % (self.linker_dll_cxx, shared_option) ) def runtime_library_dir_option(self, dir): diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 5fb811c406..bab4ab693a 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -300,6 +300,7 @@ def customize_compiler(compiler): # noqa: C901 cflags, ccshared, ldshared, + ldcxxshared, shlib_suffix, ar, ar_flags, @@ -309,11 +310,14 @@ def customize_compiler(compiler): # noqa: C901 'CFLAGS', 'CCSHARED', 'LDSHARED', + 'LDCXXSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS', ) + cxxflags = cflags + if 'CC' in os.environ: newcc = os.environ['CC'] if 'LDSHARED' not in os.environ and ldshared.startswith(cc): @@ -325,19 +329,27 @@ def customize_compiler(compiler): # noqa: C901 cxx = os.environ['CXX'] if 'LDSHARED' in os.environ: ldshared = os.environ['LDSHARED'] + if 'LDCXXSHARED' in os.environ: + ldcxxshared = os.environ['LDCXXSHARED'] if 'CPP' in os.environ: cpp = os.environ['CPP'] else: cpp = cc + " -E" # not always if 'LDFLAGS' in os.environ: ldshared = ldshared + ' ' + os.environ['LDFLAGS'] + ldcxxshared = ldcxxshared + ' ' + os.environ['LDFLAGS'] if 'CFLAGS' in os.environ: - cflags = cflags + ' ' + os.environ['CFLAGS'] + cflags = os.environ['CFLAGS'] ldshared = ldshared + ' ' + os.environ['CFLAGS'] + if 'CXXFLAGS' in os.environ: + cxxflags = os.environ['CXXFLAGS'] + ldcxxshared = ldcxxshared + ' ' + os.environ['CXXFLAGS'] if 'CPPFLAGS' in os.environ: cpp = cpp + ' ' + os.environ['CPPFLAGS'] cflags = cflags + ' ' + os.environ['CPPFLAGS'] + cxxflags = cxxflags + ' ' + os.environ['CPPFLAGS'] ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] + ldcxxshared = ldcxxshared + ' ' + os.environ['CPPFLAGS'] if 'AR' in os.environ: ar = os.environ['AR'] if 'ARFLAGS' in os.environ: @@ -346,13 +358,17 @@ def customize_compiler(compiler): # noqa: C901 archiver = ar + ' ' + ar_flags cc_cmd = cc + ' ' + cflags + cxx_cmd = cxx + ' ' + cxxflags compiler.set_executables( preprocessor=cpp, compiler=cc_cmd, compiler_so=cc_cmd + ' ' + ccshared, - compiler_cxx=cxx, + compiler_cxx=cxx_cmd, + compiler_so_cxx=cxx_cmd + ' ' + ccshared, linker_so=ldshared, + linker_so_cxx=ldcxxshared, linker_exe=cc, + linker_exe_cxx=cxx, archiver=archiver, ) diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index d749fe2529..0919868af6 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -115,9 +115,12 @@ class UnixCCompiler(CCompiler): 'preprocessor': None, 'compiler': ["cc"], 'compiler_so': ["cc"], - 'compiler_cxx': ["cc"], + 'compiler_cxx': ["c++"], + 'compiler_so_cxx': ["c++"], 'linker_so': ["cc", "-shared"], + 'linker_so_cxx': ["c++", "-shared"], 'linker_exe': ["cc"], + 'linker_exe_cxx': ["c++", "-shared"], 'archiver': ["ar", "-cr"], 'ranlib': None, } @@ -181,8 +184,13 @@ def preprocess( def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs) + compiler_so_cxx = compiler_fixup(self.compiler_so_cxx, cc_args + extra_postargs) try: - self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) + if self.detect_language(src) == 'c++': + self.spawn(compiler_so_cxx + cc_args + [ src, '-o', obj] + + extra_postargs) + else: + self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) except DistutilsExecError as msg: raise CompileError(msg) @@ -250,7 +258,8 @@ def link( # building an executable or linker_so (with shared options) # when building a shared library. building_exe = target_desc == CCompiler.EXECUTABLE - linker = (self.linker_exe if building_exe else self.linker_so)[:] + linker = (self.linker_exe if building_exe else (self.linker_so_cxx if + target_lang == "c++" else self.linker_so))[:] if target_lang == "c++" and self.compiler_cxx: env, linker_ne = _split_env(linker) From 26f4723dbcbc32f44be33e849eca86f15317612a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Feb 2024 13:29:18 -0500 Subject: [PATCH 02/48] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- distutils/cygwinccompiler.py | 16 ++++++++++------ distutils/unixccompiler.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index c683e3eeac..d767c6d305 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -112,8 +112,7 @@ def __init__(self, verbose=0, dry_run=0, force=0): linker_exe='%s -mcygwin' % self.cc, linker_so=('{} -mcygwin {}'.format(self.linker_dll, shared_option)), linker_exe_cxx='%s -mcygwin' % self.cxx, - linker_so_cxx=('%s -mcygwin %s' % - (self.linker_dll_cxx, shared_option)), + linker_so_cxx=('%s -mcygwin %s' % (self.linker_dll_cxx, shared_option)), ) # Include the appropriate MSVC runtime library if Python was built @@ -146,11 +145,16 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): else: # for other files use the C-compiler try: if self.detect_language(src) == 'c++': - self.spawn(self.compiler_so_cxx + cc_args + [src, '-o', obj] + - extra_postargs) + self.spawn( + self.compiler_so_cxx + + cc_args + + [src, '-o', obj] + + extra_postargs + ) else: self.spawn( - self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs) + self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs + ) except DistutilsExecError as msg: raise CompileError(msg) @@ -291,7 +295,7 @@ def __init__(self, verbose=0, dry_run=0, force=0): linker_exe='%s' % self.cc, linker_so='{} {}'.format(self.linker_dll, shared_option), linker_exe_cxx='%s' % self.cxx, - linker_so_cxx='%s %s' % (self.linker_dll_cxx, shared_option) + linker_so_cxx='%s %s' % (self.linker_dll_cxx, shared_option), ) def runtime_library_dir_option(self, dir): diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 0919868af6..2b1bbacef2 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -187,8 +187,9 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): compiler_so_cxx = compiler_fixup(self.compiler_so_cxx, cc_args + extra_postargs) try: if self.detect_language(src) == 'c++': - self.spawn(compiler_so_cxx + cc_args + [ src, '-o', obj] + - extra_postargs) + self.spawn( + compiler_so_cxx + cc_args + [src, '-o', obj] + extra_postargs + ) else: self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) except DistutilsExecError as msg: @@ -258,8 +259,13 @@ def link( # building an executable or linker_so (with shared options) # when building a shared library. building_exe = target_desc == CCompiler.EXECUTABLE - linker = (self.linker_exe if building_exe else (self.linker_so_cxx if - target_lang == "c++" else self.linker_so))[:] + linker = ( + self.linker_exe + if building_exe + else ( + self.linker_so_cxx if target_lang == "c++" else self.linker_so + ) + )[:] if target_lang == "c++" and self.compiler_cxx: env, linker_ne = _split_env(linker) From fbac766736c5a3807968ec4ad9f5a0ca9b96a458 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Tue, 13 Feb 2024 19:33:55 -0500 Subject: [PATCH 03/48] Adjust tests further to accommodate C++ support. --- distutils/sysconfig.py | 2 +- distutils/tests/test_sysconfig.py | 4 ++-- distutils/tests/test_unixccompiler.py | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index bab4ab693a..ab80e8216f 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -339,7 +339,7 @@ def customize_compiler(compiler): # noqa: C901 ldshared = ldshared + ' ' + os.environ['LDFLAGS'] ldcxxshared = ldcxxshared + ' ' + os.environ['LDFLAGS'] if 'CFLAGS' in os.environ: - cflags = os.environ['CFLAGS'] + cflags = cflags + ' ' + os.environ['CFLAGS'] ldshared = ldshared + ' ' + os.environ['CFLAGS'] if 'CXXFLAGS' in os.environ: cxxflags = os.environ['CXXFLAGS'] diff --git a/distutils/tests/test_sysconfig.py b/distutils/tests/test_sysconfig.py index f656be6089..4879d72039 100644 --- a/distutils/tests/test_sysconfig.py +++ b/distutils/tests/test_sysconfig.py @@ -132,7 +132,7 @@ def test_customize_compiler(self): assert comp.exes['compiler_so'] == ( 'env_cc --sc-cflags ' '--env-cflags ' '--env-cppflags --sc-ccshared' ) - assert comp.exes['compiler_cxx'] == 'env_cxx --env-cxx-flags' + assert comp.exes['compiler_cxx'] == 'env_cxx --env-cxx-flags --sc-cflags --env-cppflags' assert comp.exes['linker_exe'] == 'env_cc' assert comp.exes['linker_so'] == ( 'env_ldshared --env-ldflags --env-cflags' ' --env-cppflags' @@ -160,7 +160,7 @@ def test_customize_compiler(self): assert comp.exes['preprocessor'] == 'sc_cc -E' assert comp.exes['compiler'] == 'sc_cc --sc-cflags' assert comp.exes['compiler_so'] == 'sc_cc --sc-cflags --sc-ccshared' - assert comp.exes['compiler_cxx'] == 'sc_cxx' + assert comp.exes['compiler_cxx'] == 'sc_cxx --sc-cflags' assert comp.exes['linker_exe'] == 'sc_cc' assert comp.exes['linker_so'] == 'sc_ldshared' assert comp.shared_lib_extension == 'sc_shutil_suffix' diff --git a/distutils/tests/test_unixccompiler.py b/distutils/tests/test_unixccompiler.py index 2763db9c02..ad08a173e2 100644 --- a/distutils/tests/test_unixccompiler.py +++ b/distutils/tests/test_unixccompiler.py @@ -260,9 +260,13 @@ def test_cc_overrides_ldshared_for_cxx_correctly(self): def gcv(v): if v == 'LDSHARED': return 'gcc-4.2 -bundle -undefined dynamic_lookup ' + elif v == 'LDCXXSHARED': + return 'g++-4.2 -bundle -undefined dynamic_lookup ' elif v == 'CXX': return 'g++-4.2' - return 'gcc-4.2' + elif v == 'CC': + return 'gcc-4.2' + return '' def gcvs(*args, _orig=sysconfig.get_config_vars): if args: From 9f176ac9ea2688e05aa4cb50a75da9bebf7e174a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Feb 2024 19:39:55 -0500 Subject: [PATCH 04/48] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- distutils/tests/test_sysconfig.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/distutils/tests/test_sysconfig.py b/distutils/tests/test_sysconfig.py index 4879d72039..60a4ba3099 100644 --- a/distutils/tests/test_sysconfig.py +++ b/distutils/tests/test_sysconfig.py @@ -132,7 +132,10 @@ def test_customize_compiler(self): assert comp.exes['compiler_so'] == ( 'env_cc --sc-cflags ' '--env-cflags ' '--env-cppflags --sc-ccshared' ) - assert comp.exes['compiler_cxx'] == 'env_cxx --env-cxx-flags --sc-cflags --env-cppflags' + assert ( + comp.exes['compiler_cxx'] + == 'env_cxx --env-cxx-flags --sc-cflags --env-cppflags' + ) assert comp.exes['linker_exe'] == 'env_cc' assert comp.exes['linker_so'] == ( 'env_ldshared --env-ldflags --env-cflags' ' --env-cppflags' From 51749270038f117f8f3103523bb37fd73bb4fe29 Mon Sep 17 00:00:00 2001 From: Samuel Cormier-Iijima Date: Fri, 1 Mar 2024 22:16:58 -0500 Subject: [PATCH 05/48] Ignore sysconfig variables for LDCXXSHARED as it appears not to exist on PyPy. --- distutils/sysconfig.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index ab80e8216f..3c72989347 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -300,7 +300,6 @@ def customize_compiler(compiler): # noqa: C901 cflags, ccshared, ldshared, - ldcxxshared, shlib_suffix, ar, ar_flags, @@ -310,13 +309,13 @@ def customize_compiler(compiler): # noqa: C901 'CFLAGS', 'CCSHARED', 'LDSHARED', - 'LDCXXSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS', ) cxxflags = cflags + ldcxxshared = "" if 'CC' in os.environ: newcc = os.environ['CC'] From fa6e2183cdc2d09db9b18f1cb9619116f104dd0e Mon Sep 17 00:00:00 2001 From: Mridul Seth Date: Thu, 29 Feb 2024 13:07:44 +0530 Subject: [PATCH 06/48] ENH: Extension should be able to accept PathLike sources objects --- distutils/extension.py | 11 ++++++++--- distutils/tests/test_extension.py | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/distutils/extension.py b/distutils/extension.py index 8f186b72ff..2d6950fe53 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -26,7 +26,7 @@ class Extension: name : string the full name of the extension, including any packages -- ie. *not* a filename or pathname, but Python dotted name - sources : [string] + sources : [string | os.PathLike] list of source filenames, relative to the distribution root (where the setup script lives), in Unix form (slash-separated) for portability. Source files may be C, C++, SWIG (.i), @@ -106,8 +106,13 @@ def __init__( ): if not isinstance(name, str): raise AssertionError("'name' must be a string") - if not (isinstance(sources, list) and all(isinstance(v, str) for v in sources)): - raise AssertionError("'sources' must be a list of strings") + if not ( + isinstance(sources, list) + and all(isinstance(v, (str, os.PathLike)) for v in sources) + ): + raise AssertionError( + "'sources' must be a list of strings or PathLike objects." + ) self.name = name self.sources = sources diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 297ae44bfe..3fbea21e28 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -2,6 +2,7 @@ import os import warnings +from pathlib import Path from distutils.extension import read_setup_file, Extension @@ -68,13 +69,15 @@ def test_extension_init(self): assert ext.name == 'name' # the second argument, which is the list of files, must - # be a list of strings + # be a list of strings or PathLike objects with pytest.raises(AssertionError): Extension('name', 'file') with pytest.raises(AssertionError): Extension('name', ['file', 1]) ext = Extension('name', ['file1', 'file2']) assert ext.sources == ['file1', 'file2'] + ext = Extension('name', [Path('file1'), Path('file2')]) + assert ext.sources == ['file1', 'file2'] # others arguments have defaults for attr in ( From 45a232ab88198c488bcb0abf0acb9bfd1ecb518c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Mar 2024 16:42:03 -0500 Subject: [PATCH 07/48] Adjust expectation to match behavior. --- distutils/tests/test_extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 3fbea21e28..9a53787333 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -77,7 +77,7 @@ def test_extension_init(self): ext = Extension('name', ['file1', 'file2']) assert ext.sources == ['file1', 'file2'] ext = Extension('name', [Path('file1'), Path('file2')]) - assert ext.sources == ['file1', 'file2'] + assert ext.sources == [Path('file1'), Path('file2')] # others arguments have defaults for attr in ( From 9cc0c93cf19dfbb737a3ae96c08034203aee0a88 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Mar 2024 16:45:05 -0500 Subject: [PATCH 08/48] For consistency, ensure Extension.sources is always a pathlib.Path object and adjust expectations in tests. --- distutils/extension.py | 3 ++- distutils/tests/test_build_ext.py | 3 ++- distutils/tests/test_extension.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/distutils/extension.py b/distutils/extension.py index 2d6950fe53..3f950d5a73 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -4,6 +4,7 @@ modules in setup scripts.""" import os +import pathlib import warnings # This class is really only used by the "build_ext" command, so it might @@ -115,7 +116,7 @@ def __init__( ) self.name = name - self.sources = sources + self.sources = list(map(pathlib.Path, sources)) self.include_dirs = include_dirs or [] self.define_macros = define_macros or [] self.undef_macros = undef_macros or [] diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index ae66bc4eb8..7e8a4ddb29 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -4,6 +4,7 @@ import textwrap import site import contextlib +import pathlib import platform import tempfile import importlib @@ -335,7 +336,7 @@ def test_get_source_files(self): dist = Distribution({'name': 'xx', 'ext_modules': modules}) cmd = self.build_ext(dist) cmd.ensure_finalized() - assert cmd.get_source_files() == ['xxx'] + assert cmd.get_source_files() == [pathlib.Path('xxx')] def test_unicode_module_names(self): modules = [ diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 9a53787333..023c7f9fc3 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -75,7 +75,7 @@ def test_extension_init(self): with pytest.raises(AssertionError): Extension('name', ['file', 1]) ext = Extension('name', ['file1', 'file2']) - assert ext.sources == ['file1', 'file2'] + assert ext.sources == [Path('file1'), Path('file2')] ext = Extension('name', [Path('file1'), Path('file2')]) assert ext.sources == [Path('file1'), Path('file2')] From c489cdc9ac46aeab6f94e3f510c50ac3983782a5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Mar 2024 20:53:25 -0500 Subject: [PATCH 09/48] When computing input_opt, ensure src is a string (when it could be a pathlib Path). --- distutils/_msvccompiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distutils/_msvccompiler.py b/distutils/_msvccompiler.py index 4f081c7e92..8c6fb5b5e6 100644 --- a/distutils/_msvccompiler.py +++ b/distutils/_msvccompiler.py @@ -367,9 +367,9 @@ def compile( # noqa: C901 src = os.path.abspath(src) if ext in self._c_extensions: - input_opt = "/Tc" + src + input_opt = "/Tc" + str(src) elif ext in self._cpp_extensions: - input_opt = "/Tp" + src + input_opt = "/Tp" + str(src) add_cpp_opts = True elif ext in self._rc_extensions: # compile .RC to .RES file From f2a85c1c290f75415c9df7dba473756ee62f8581 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Mar 2024 21:47:38 -0500 Subject: [PATCH 10/48] In filelist, allow for self.files to be pathlib objects. --- distutils/filelist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/filelist.py b/distutils/filelist.py index 3205762654..d0ad54aba6 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -251,7 +251,7 @@ def exclude_pattern(self, pattern, anchor=1, prefix=None, is_regex=0): pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) self.debug_print("exclude_pattern: applying regex r'%s'" % pattern_re.pattern) for i in range(len(self.files) - 1, -1, -1): - if pattern_re.search(self.files[i]): + if pattern_re.search(str(self.files[i])): self.debug_print(" removing " + self.files[i]) del self.files[i] files_found = True From 1bcd839f162be4c99dd4dd144c85a937548392bb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 10 Jul 2024 15:42:56 -0400 Subject: [PATCH 11/48] Revert "Disable cygwin tests for now. Ref pypa/setuptools#3921" This reverts commit d3e5de05f6afe958d0fde20945ed0f7a2dfef270. --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1f63867d85..70d70bc6f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,8 +92,6 @@ jobs: run: tox -e ${{ matrix.job }} test_cygwin: - # disabled due to lack of Rust support pypa/setuptools#3921 - if: ${{ false }} strategy: matrix: python: From f21f19a1eb5ea1e070e601ff4f05d762469a59ad Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Fri, 12 Jul 2024 21:16:45 +0200 Subject: [PATCH 12/48] find_library_file: port from doctest to normal test and fix for cygwin Starting with b42197ceb9ac8a0cd95b530 UnixCCompiler got some improved support for cygwin which made the doctest fail. The doctest is hard to read as is, and adding more platform specific exceptions wouldn't help, so just convert to a normal test and special case cygwin to make the test pass there again. --- distutils/tests/test_unixccompiler.py | 30 +++++++++++++++++++++++++++ distutils/unixccompiler.py | 16 -------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/distutils/tests/test_unixccompiler.py b/distutils/tests/test_unixccompiler.py index d2c88e9116..afd08bbb87 100644 --- a/distutils/tests/test_unixccompiler.py +++ b/distutils/tests/test_unixccompiler.py @@ -315,3 +315,33 @@ def test_has_function(self): self.cc.output_dir = 'scratch' os.chdir(self.mkdtemp()) self.cc.has_function('abort') + + def test_find_library_file(self, monkeypatch): + compiler = UnixCCompiler() + compiler._library_root = lambda dir: dir + monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d) + + dylibname = 'libabc.dylib' if sys.platform != 'cygwin' else 'cygabc.dll' + dirs = ('/foo/bar/missing', '/foo/bar/existing') + assert ( + compiler.find_library_file(dirs, 'abc').replace('\\', '/') + == '/foo/bar/existing/' + dylibname + ) + assert ( + compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') + == '/foo/bar/existing/' + dylibname + ) + + monkeypatch.setattr( + os.path, + 'exists', + lambda d: 'existing' in d and '.a' in d and '.dll.a' not in d, + ) + assert ( + compiler.find_library_file(dirs, 'abc').replace('\\', '/') + == '/foo/bar/existing/libabc.a' + ) + assert ( + compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') + == '/foo/bar/existing/libabc.a' + ) diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 7e68596b26..6450fff547 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -371,22 +371,6 @@ def find_library_file(self, dirs, lib, debug=False): data to go on: GCC seems to prefer the shared library, so assume that *all* Unix C compilers do, ignoring even GCC's "-static" option. - - >>> compiler = UnixCCompiler() - >>> compiler._library_root = lambda dir: dir - >>> monkeypatch = getfixture('monkeypatch') - >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d) - >>> dirs = ('/foo/bar/missing', '/foo/bar/existing') - >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.dylib' - >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.dylib' - >>> monkeypatch.setattr(os.path, 'exists', - ... lambda d: 'existing' in d and '.a' in d) - >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.a' - >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') - '/foo/bar/existing/libabc.a' """ lib_names = ( self.library_filename(lib, lib_type=type) From 7d78834c7b9e12a5a0b5b084d43383c548adb39d Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Fri, 12 Jul 2024 20:56:07 +0200 Subject: [PATCH 13/48] test_customize_compiler_with_msvc_python: skip for cygwin Mingw32CCompiler() checks that the default compiler isn't cygwin, so it can't be used under cygwin, so skip it there. --- distutils/tests/test_mingwccompiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/distutils/tests/test_mingwccompiler.py b/distutils/tests/test_mingwccompiler.py index fd201cd773..28f4762b2a 100644 --- a/distutils/tests/test_mingwccompiler.py +++ b/distutils/tests/test_mingwccompiler.py @@ -45,6 +45,7 @@ def test_cygwincc_error(self, monkeypatch): with pytest.raises(CCompilerError): distutils.cygwinccompiler.Mingw32CCompiler() + @pytest.mark.skipif('sys.platform == "cygwin"') def test_customize_compiler_with_msvc_python(self): from distutils.cygwinccompiler import Mingw32CCompiler From 3debdcc4bcf9904457c9154694b2fcbd1f773886 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 16:21:30 -0400 Subject: [PATCH 14/48] Simply accept pathlib objects and convert them early for compatibilty with upstream. --- distutils/_msvccompiler.py | 4 ++-- distutils/extension.py | 3 +-- distutils/filelist.py | 2 +- distutils/tests/test_build_ext.py | 3 +-- distutils/tests/test_extension.py | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/distutils/_msvccompiler.py b/distutils/_msvccompiler.py index 8c6fb5b5e6..4f081c7e92 100644 --- a/distutils/_msvccompiler.py +++ b/distutils/_msvccompiler.py @@ -367,9 +367,9 @@ def compile( # noqa: C901 src = os.path.abspath(src) if ext in self._c_extensions: - input_opt = "/Tc" + str(src) + input_opt = "/Tc" + src elif ext in self._cpp_extensions: - input_opt = "/Tp" + str(src) + input_opt = "/Tp" + src add_cpp_opts = True elif ext in self._rc_extensions: # compile .RC to .RES file diff --git a/distutils/extension.py b/distutils/extension.py index 3f950d5a73..6b195598f6 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -4,7 +4,6 @@ modules in setup scripts.""" import os -import pathlib import warnings # This class is really only used by the "build_ext" command, so it might @@ -116,7 +115,7 @@ def __init__( ) self.name = name - self.sources = list(map(pathlib.Path, sources)) + self.sources = list(map(os.fspath, sources)) self.include_dirs = include_dirs or [] self.define_macros = define_macros or [] self.undef_macros = undef_macros or [] diff --git a/distutils/filelist.py b/distutils/filelist.py index d0ad54aba6..3205762654 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -251,7 +251,7 @@ def exclude_pattern(self, pattern, anchor=1, prefix=None, is_regex=0): pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) self.debug_print("exclude_pattern: applying regex r'%s'" % pattern_re.pattern) for i in range(len(self.files) - 1, -1, -1): - if pattern_re.search(str(self.files[i])): + if pattern_re.search(self.files[i]): self.debug_print(" removing " + self.files[i]) del self.files[i] files_found = True diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index 7e8a4ddb29..ae66bc4eb8 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -4,7 +4,6 @@ import textwrap import site import contextlib -import pathlib import platform import tempfile import importlib @@ -336,7 +335,7 @@ def test_get_source_files(self): dist = Distribution({'name': 'xx', 'ext_modules': modules}) cmd = self.build_ext(dist) cmd.ensure_finalized() - assert cmd.get_source_files() == [pathlib.Path('xxx')] + assert cmd.get_source_files() == ['xxx'] def test_unicode_module_names(self): modules = [ diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 023c7f9fc3..3fbea21e28 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -75,9 +75,9 @@ def test_extension_init(self): with pytest.raises(AssertionError): Extension('name', ['file', 1]) ext = Extension('name', ['file1', 'file2']) - assert ext.sources == [Path('file1'), Path('file2')] + assert ext.sources == ['file1', 'file2'] ext = Extension('name', [Path('file1'), Path('file2')]) - assert ext.sources == [Path('file1'), Path('file2')] + assert ext.sources == ['file1', 'file2'] # others arguments have defaults for attr in ( From 4b98bf009287262413a634250921b7f791e7610e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 15 Jul 2024 11:53:38 -0400 Subject: [PATCH 15/48] Prefer f-strings --- distutils/tests/test_unixccompiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distutils/tests/test_unixccompiler.py b/distutils/tests/test_unixccompiler.py index afd08bbb87..42b98dfe08 100644 --- a/distutils/tests/test_unixccompiler.py +++ b/distutils/tests/test_unixccompiler.py @@ -321,15 +321,15 @@ def test_find_library_file(self, monkeypatch): compiler._library_root = lambda dir: dir monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d) - dylibname = 'libabc.dylib' if sys.platform != 'cygwin' else 'cygabc.dll' + libname = 'libabc.dylib' if sys.platform != 'cygwin' else 'cygabc.dll' dirs = ('/foo/bar/missing', '/foo/bar/existing') assert ( compiler.find_library_file(dirs, 'abc').replace('\\', '/') - == '/foo/bar/existing/' + dylibname + == f'/foo/bar/existing/{libname}' ) assert ( compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') - == '/foo/bar/existing/' + dylibname + == f'/foo/bar/existing/{libname}' ) monkeypatch.setattr( From f350a98aaf9f800859f8e83422ff056157beec5d Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 18 Jul 2024 22:58:46 -0400 Subject: [PATCH 16/48] Allow path objects --- distutils/command/install_data.py | 11 +++++++++++ distutils/tests/test_install_data.py | 26 +++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index 624c0b901b..6b7c9aefd9 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -6,6 +6,7 @@ # contributed by Bastian Kleineidam import os +from pathlib import Path from ..core import Command from ..util import change_root, convert_path @@ -56,6 +57,16 @@ def run(self): ) (out, _) = self.copy_file(f, self.install_dir) self.outfiles.append(out) + elif isinstance(f, Path): + # it's a simple file, so copy it + f = convert_path(str(f)) + if self.warn_dir: + self.warn( + "setup script did not provide a directory for " + f"'{f}' -- installing right in '{self.install_dir}'" + ) + (out, _) = self.copy_file(f, self.install_dir) + self.outfiles.append(out) else: # it's a tuple with path to install to and a list of files dir = convert_path(f[0]) diff --git a/distutils/tests/test_install_data.py b/distutils/tests/test_install_data.py index f34070b10b..43fd98ed6e 100644 --- a/distutils/tests/test_install_data.py +++ b/distutils/tests/test_install_data.py @@ -1,6 +1,7 @@ """Tests for distutils.command.install_data.""" import os +from pathlib import Path from distutils.command.install_data import install_data from distutils.tests import support @@ -18,22 +19,27 @@ def test_simple_run(self): # data_files can contain # - simple files + # - a Path object # - a tuple with a path, and a list of file one = os.path.join(pkg_dir, 'one') self.write_file(one, 'xxx') inst2 = os.path.join(pkg_dir, 'inst2') two = os.path.join(pkg_dir, 'two') self.write_file(two, 'xxx') + three = Path(pkg_dir) / 'three' + self.write_file(three, 'xxx') - cmd.data_files = [one, (inst2, [two])] - assert cmd.get_inputs() == [one, (inst2, [two])] + cmd.data_files = [one, (inst2, [two]), three] + assert cmd.get_inputs() == [one, (inst2, [two]), three] # let's run the command cmd.ensure_finalized() cmd.run() # let's check the result - assert len(cmd.get_outputs()) == 2 + assert len(cmd.get_outputs()) == 3 + rthree = os.path.split(one)[-1] + assert os.path.exists(os.path.join(inst, rthree)) rtwo = os.path.split(two)[-1] assert os.path.exists(os.path.join(inst2, rtwo)) rone = os.path.split(one)[-1] @@ -46,21 +52,23 @@ def test_simple_run(self): cmd.run() # let's check the result - assert len(cmd.get_outputs()) == 2 + assert len(cmd.get_outputs()) == 3 + assert os.path.exists(os.path.join(inst, rthree)) assert os.path.exists(os.path.join(inst2, rtwo)) assert os.path.exists(os.path.join(inst, rone)) cmd.outfiles = [] # now using root and empty dir cmd.root = os.path.join(pkg_dir, 'root') - inst4 = os.path.join(pkg_dir, 'inst4') - three = os.path.join(cmd.install_dir, 'three') - self.write_file(three, 'xx') - cmd.data_files = [one, (inst2, [two]), ('inst3', [three]), (inst4, [])] + inst5 = os.path.join(pkg_dir, 'inst5') + four = os.path.join(cmd.install_dir, 'four') + self.write_file(four, 'xx') + cmd.data_files = [one, (inst2, [two]), ('inst5', [four]), (inst5, [])] cmd.ensure_finalized() cmd.run() # let's check the result - assert len(cmd.get_outputs()) == 4 + assert len(cmd.get_outputs()) == 5 + assert os.path.exists(os.path.join(inst, rthree)) assert os.path.exists(os.path.join(inst2, rtwo)) assert os.path.exists(os.path.join(inst, rone)) From 202ff81fb5adcbde9cf990f27a6c9fbdc4ad7602 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 15:17:08 -0400 Subject: [PATCH 17/48] Need to include 'three' in the input. --- distutils/tests/test_install_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/tests/test_install_data.py b/distutils/tests/test_install_data.py index 43fd98ed6e..e340c75800 100644 --- a/distutils/tests/test_install_data.py +++ b/distutils/tests/test_install_data.py @@ -63,7 +63,7 @@ def test_simple_run(self): inst5 = os.path.join(pkg_dir, 'inst5') four = os.path.join(cmd.install_dir, 'four') self.write_file(four, 'xx') - cmd.data_files = [one, (inst2, [two]), ('inst5', [four]), (inst5, [])] + cmd.data_files = [one, (inst2, [two]), three, ('inst5', [four]), (inst5, [])] cmd.ensure_finalized() cmd.run() From c2d9fba3913a4394c9222199eb8518197df2ff06 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 15:25:43 -0400 Subject: [PATCH 18/48] Consolidate str and Path handling. --- distutils/command/install_data.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index 6b7c9aefd9..a53786b9f6 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -47,19 +47,9 @@ def finalize_options(self): def run(self): self.mkpath(self.install_dir) for f in self.data_files: - if isinstance(f, str): + if isinstance(f, (str, Path)): # it's a simple file, so copy it - f = convert_path(f) - if self.warn_dir: - self.warn( - "setup script did not provide a directory for " - f"'{f}' -- installing right in '{self.install_dir}'" - ) - (out, _) = self.copy_file(f, self.install_dir) - self.outfiles.append(out) - elif isinstance(f, Path): - # it's a simple file, so copy it - f = convert_path(str(f)) + f = convert_path(os.fspath(f)) if self.warn_dir: self.warn( "setup script did not provide a directory for " From a6a55cfe05f891aa83253a389083d746e09de4fa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 15 Jul 2024 19:59:32 -0400 Subject: [PATCH 19/48] Remove r string now that docstring is gone. --- distutils/unixccompiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 6450fff547..3254c53f2f 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -366,7 +366,7 @@ def _library_root(dir): return os.path.join(match.group(1), dir[1:]) if apply_root else dir def find_library_file(self, dirs, lib, debug=False): - r""" + """ Second-guess the linker with not much hard data to go on: GCC seems to prefer the shared library, so assume that *all* Unix C compilers do, From 29281a6441f5c95baf451b8c6a05b5ac9dd95796 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 15:37:03 -0400 Subject: [PATCH 20/48] Expand convert_path to also accept pathlib.Path objects. --- distutils/command/install_data.py | 2 +- distutils/tests/test_util.py | 5 +++++ distutils/util.py | 10 +++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index a53786b9f6..735adf2b52 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -49,7 +49,7 @@ def run(self): for f in self.data_files: if isinstance(f, (str, Path)): # it's a simple file, so copy it - f = convert_path(os.fspath(f)) + f = convert_path(f) if self.warn_dir: self.warn( "setup script did not provide a directory for " diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index 0de4e1a59c..a614a1dae5 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -5,6 +5,7 @@ import email.policy import io import os +import pathlib import sys import sysconfig as stdlib_sysconfig import unittest.mock as mock @@ -72,6 +73,7 @@ def _join(path): os.path.join = _join assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' + assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff' # win os.sep = '\\' @@ -85,8 +87,11 @@ def _join(*path): convert_path('/home/to/my/stuff') with pytest.raises(ValueError): convert_path('home/to/my/stuff/') + with pytest.raises(ValueError): + convert_path(pathlib.Path('/home/to/my/stuff')) assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff' + assert convert_path(pathlib.Path('home/to/my/stuff')) == 'home\\to\\my\\stuff' assert convert_path('.') == os.curdir def test_change_root(self): diff --git a/distutils/util.py b/distutils/util.py index 9db89b0979..635d715ad6 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -7,6 +7,7 @@ import functools import importlib.util import os +import pathlib import re import string import subprocess @@ -116,7 +117,14 @@ def split_version(s): return [int(n) for n in s.split('.')] -def convert_path(pathname): +def convert_path(pathname: str | pathlib.Path) -> str: + """ + Allow for pathlib.Path inputs and then make native. + """ + return make_native(os.fspath(pathname)) + + +def make_native(pathname: str) -> str: """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 directory separator. Needed because filenames in the setup script are From 0edebdc35ddbc3c6ef4e723c51825a5a85bf71e2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 14:54:16 -0400 Subject: [PATCH 21/48] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- distutils/compat/py38.py | 1 + 1 file changed, 1 insertion(+) diff --git a/distutils/compat/py38.py b/distutils/compat/py38.py index 2d44211147..03ec73ef0e 100644 --- a/distutils/compat/py38.py +++ b/distutils/compat/py38.py @@ -14,6 +14,7 @@ def removeprefix(self, prefix): return self[len(prefix) :] else: return self[:] + else: def removesuffix(self, suffix): From 2a7122a885ddf34664ed9f6edb88c66a8510ef68 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 15:37:53 -0400 Subject: [PATCH 22/48] Prefer simply 'pathlib' for import. --- distutils/command/install_data.py | 4 ++-- distutils/tests/test_install_data.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index 735adf2b52..cb03d0e5c6 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -6,7 +6,7 @@ # contributed by Bastian Kleineidam import os -from pathlib import Path +import pathlib from ..core import Command from ..util import change_root, convert_path @@ -47,7 +47,7 @@ def finalize_options(self): def run(self): self.mkpath(self.install_dir) for f in self.data_files: - if isinstance(f, (str, Path)): + if isinstance(f, (str, pathlib.Path)): # it's a simple file, so copy it f = convert_path(f) if self.warn_dir: diff --git a/distutils/tests/test_install_data.py b/distutils/tests/test_install_data.py index e340c75800..4b15a26945 100644 --- a/distutils/tests/test_install_data.py +++ b/distutils/tests/test_install_data.py @@ -1,12 +1,13 @@ """Tests for distutils.command.install_data.""" import os -from pathlib import Path -from distutils.command.install_data import install_data -from distutils.tests import support +import pathlib import pytest +from distutils.command.install_data import install_data +from distutils.tests import support + @pytest.mark.usefixtures('save_env') class TestInstallData( @@ -26,7 +27,7 @@ def test_simple_run(self): inst2 = os.path.join(pkg_dir, 'inst2') two = os.path.join(pkg_dir, 'two') self.write_file(two, 'xxx') - three = Path(pkg_dir) / 'three' + three = pathlib.Path(pkg_dir) / 'three' self.write_file(three, 'xxx') cmd.data_files = [one, (inst2, [two]), three] From dcb1bf8308f46d1b8e5235382122f0a4060fb65b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 15:10:31 -0400 Subject: [PATCH 23/48] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix PERF401 errors. --- distutils/bcppcompiler.py | 3 +-- distutils/ccompiler.py | 16 ++++++---------- distutils/command/bdist.py | 7 ++++--- distutils/command/build_ext.py | 5 +---- distutils/command/check.py | 7 +++---- distutils/command/install_lib.py | 4 +--- distutils/command/sdist.py | 8 ++++---- distutils/dist.py | 15 +++------------ distutils/msvc9compiler.py | 4 +--- distutils/msvccompiler.py | 4 +--- 10 files changed, 25 insertions(+), 48 deletions(-) diff --git a/distutils/bcppcompiler.py b/distutils/bcppcompiler.py index e47dca5d09..9157b43328 100644 --- a/distutils/bcppcompiler.py +++ b/distutils/bcppcompiler.py @@ -236,8 +236,7 @@ def link( # noqa: C901 temp_dir = os.path.dirname(objects[0]) # preserve tree structure def_file = os.path.join(temp_dir, f'{modname}.def') contents = ['EXPORTS'] - for sym in export_symbols or []: - contents.append(f' {sym}=_{sym}') + contents.extend(f' {sym}=_{sym}' for sym in export_symbols) self.execute(write_file, (def_file, contents), f"writing {def_file}") # Borland C++ has problems with '/' in paths diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index 9d5297b944..42b0812454 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -1124,10 +1124,10 @@ def show_compilers(): # commands that use it. from distutils.fancy_getopt import FancyGetopt - compilers = [] - for compiler in compiler_class.keys(): - compilers.append(("compiler=" + compiler, None, compiler_class[compiler][2])) - compilers.sort() + compilers = sorted( + ("compiler=" + compiler, None, compiler_class[compiler][2]) + for compiler in compiler_class.keys() + ) pretty_printer = FancyGetopt(compilers) pretty_printer.print_help("List of available compilers:") @@ -1218,8 +1218,7 @@ def gen_preprocess_options(macros, include_dirs): # shell at all costs when we spawn the command! pp_opts.append("-D{}={}".format(*macro)) - for dir in include_dirs: - pp_opts.append(f"-I{dir}") + pp_opts.extend(f"-I{dir}" for dir in include_dirs) return pp_opts @@ -1230,10 +1229,7 @@ def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries): directories. Returns a list of command-line options suitable for use with some compiler (depending on the two format strings passed in). """ - lib_opts = [] - - for dir in library_dirs: - lib_opts.append(compiler.library_dir_option(dir)) + lib_opts = [compiler.library_dir_option(dir) for dir in library_dirs] for dir in runtime_library_dirs: lib_opts.extend(always_iterable(compiler.runtime_library_dir_option(dir))) diff --git a/distutils/command/bdist.py b/distutils/command/bdist.py index 1738f4e56b..f334075159 100644 --- a/distutils/command/bdist.py +++ b/distutils/command/bdist.py @@ -15,9 +15,10 @@ def show_formats(): """Print list of available formats (arguments to "--format" option).""" from ..fancy_getopt import FancyGetopt - formats = [] - for format in bdist.format_commands: - formats.append(("formats=" + format, None, bdist.format_commands[format][1])) + formats = [ + ("formats=" + format, None, bdist.format_commands[format][1]) + for format in bdist.format_commands + ] pretty_printer = FancyGetopt(formats) pretty_printer.print_help("List of available distribution formats:") diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index 18e1601a28..cf475fe824 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -465,10 +465,7 @@ def get_outputs(self): # And build the list of output (built) filenames. Note that this # ignores the 'inplace' flag, and assumes everything goes in the # "build" tree. - outputs = [] - for ext in self.extensions: - outputs.append(self.get_ext_fullpath(ext.name)) - return outputs + return [self.get_ext_fullpath(ext.name) for ext in self.extensions] def build_extensions(self): # First, sanity-check the 'extensions' list diff --git a/distutils/command/check.py b/distutils/command/check.py index 58b3f949f9..93d754e73d 100644 --- a/distutils/command/check.py +++ b/distutils/command/check.py @@ -100,10 +100,9 @@ def check_metadata(self): """ metadata = self.distribution.metadata - missing = [] - for attr in 'name', 'version': - if not getattr(metadata, attr, None): - missing.append(attr) + missing = [ + attr for attr in ('name', 'version') if not getattr(metadata, attr, None) + ] if missing: self.warn("missing required meta-data: {}".format(', '.join(missing))) diff --git a/distutils/command/install_lib.py b/distutils/command/install_lib.py index 54a12d38a8..01579d46b4 100644 --- a/distutils/command/install_lib.py +++ b/distutils/command/install_lib.py @@ -161,9 +161,7 @@ def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): build_dir = getattr(build_cmd, cmd_option) prefix_len = len(build_dir) + len(os.sep) - outputs = [] - for file in build_files: - outputs.append(os.path.join(output_dir, file[prefix_len:])) + outputs = [os.path.join(output_dir, file[prefix_len:]) for file in build_files] return outputs diff --git a/distutils/command/sdist.py b/distutils/command/sdist.py index 04333dd214..e8abb73920 100644 --- a/distutils/command/sdist.py +++ b/distutils/command/sdist.py @@ -24,10 +24,10 @@ def show_formats(): from ..archive_util import ARCHIVE_FORMATS from ..fancy_getopt import FancyGetopt - formats = [] - for format in ARCHIVE_FORMATS.keys(): - formats.append(("formats=" + format, None, ARCHIVE_FORMATS[format][2])) - formats.sort() + formats = sorted( + ("formats=" + format, None, ARCHIVE_FORMATS[format][2]) + for format in ARCHIVE_FORMATS.keys() + ) FancyGetopt(formats).print_help("List of available source distribution formats:") diff --git a/distutils/dist.py b/distutils/dist.py index d7d4ca8fc8..0a57d60be9 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -745,10 +745,7 @@ def print_commands(self): for cmd in std_commands: is_std.add(cmd) - extra_commands = [] - for cmd in self.cmdclass.keys(): - if cmd not in is_std: - extra_commands.append(cmd) + extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std] max_length = 0 for cmd in std_commands + extra_commands: @@ -776,10 +773,7 @@ def get_command_list(self): for cmd in std_commands: is_std.add(cmd) - extra_commands = [] - for cmd in self.cmdclass.keys(): - if cmd not in is_std: - extra_commands.append(cmd) + extra_commands = [cmd for cmd in self.cmdclass.keys() if cmd not in is_std] rv = [] for cmd in std_commands + extra_commands: @@ -1301,7 +1295,4 @@ def fix_help_options(options): """Convert a 4-tuple 'help_options' list as found in various command classes to the 3-tuple form required by FancyGetopt. """ - new_options = [] - for help_tuple in options: - new_options.append(help_tuple[0:3]) - return new_options + return [opt[0:3] for opt in options] diff --git a/distutils/msvc9compiler.py b/distutils/msvc9compiler.py index f860a8d383..4c70848730 100644 --- a/distutils/msvc9compiler.py +++ b/distutils/msvc9compiler.py @@ -640,9 +640,7 @@ def link( # noqa: C901 else: ldflags = self.ldflags_shared - export_opts = [] - for sym in export_symbols or []: - export_opts.append("/EXPORT:" + sym) + export_opts = [f"/EXPORT:{sym}" for sym in export_symbols or []] ld_args = ( ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename] diff --git a/distutils/msvccompiler.py b/distutils/msvccompiler.py index 2bf94e60c9..2a5e61d78d 100644 --- a/distutils/msvccompiler.py +++ b/distutils/msvccompiler.py @@ -534,9 +534,7 @@ def link( # noqa: C901 else: ldflags = self.ldflags_shared - export_opts = [] - for sym in export_symbols or []: - export_opts.append("/EXPORT:" + sym) + export_opts = [f"/EXPORT:{sym}" for sym in export_symbols or []] ld_args = ( ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename] From 6099343a445935b13136da23a4b92d816dd5ee29 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 15:44:54 -0400 Subject: [PATCH 24/48] Extract a singledispatchmethod _copy for handling the copy of each data file. --- distutils/command/install_data.py | 69 ++++++++++++++++++------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index cb03d0e5c6..e13b5ca664 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -5,13 +5,19 @@ # contributed by Bastian Kleineidam +import functools import os import pathlib +from typing import Tuple, Iterable + from ..core import Command from ..util import change_root, convert_path +StrPath = str | pathlib.Path + + class install_data(Command): description = "install data files" @@ -47,36 +53,41 @@ def finalize_options(self): def run(self): self.mkpath(self.install_dir) for f in self.data_files: - if isinstance(f, (str, pathlib.Path)): - # it's a simple file, so copy it - f = convert_path(f) - if self.warn_dir: - self.warn( - "setup script did not provide a directory for " - f"'{f}' -- installing right in '{self.install_dir}'" - ) - (out, _) = self.copy_file(f, self.install_dir) + self._copy(f) + + @functools.singledispatchmethod + def _copy(self, f: StrPath | Tuple[StrPath, Iterable[StrPath]]): + # it's a tuple with path to install to and a list of files + dir = convert_path(f[0]) + if not os.path.isabs(dir): + dir = os.path.join(self.install_dir, dir) + elif self.root: + dir = change_root(self.root, dir) + self.mkpath(dir) + + if f[1] == []: + # If there are no files listed, the user must be + # trying to create an empty directory, so add the + # directory to the list of output files. + self.outfiles.append(dir) + else: + # Copy files, adding them to the list of output files. + for data in f[1]: + data = convert_path(data) + (out, _) = self.copy_file(data, dir) self.outfiles.append(out) - else: - # it's a tuple with path to install to and a list of files - dir = convert_path(f[0]) - if not os.path.isabs(dir): - dir = os.path.join(self.install_dir, dir) - elif self.root: - dir = change_root(self.root, dir) - self.mkpath(dir) - - if f[1] == []: - # If there are no files listed, the user must be - # trying to create an empty directory, so add the - # directory to the list of output files. - self.outfiles.append(dir) - else: - # Copy files, adding them to the list of output files. - for data in f[1]: - data = convert_path(data) - (out, _) = self.copy_file(data, dir) - self.outfiles.append(out) + + @_copy.register + def _(self, f: StrPath): + # it's a simple file, so copy it + f = convert_path(f) + if self.warn_dir: + self.warn( + "setup script did not provide a directory for " + f"'{f}' -- installing right in '{self.install_dir}'" + ) + (out, _) = self.copy_file(f, self.install_dir) + self.outfiles.append(out) def get_inputs(self): return self.data_files or [] From 3a2d7a62e355db2d1e67af38ba2be03c63099480 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 16:12:53 -0400 Subject: [PATCH 25/48] Use explicit registration for compatibility with older Pythons. Prior to 3.11, singledispatch[method] doesn't know about unions. --- distutils/command/install_data.py | 15 +++++++-------- distutils/util.py | 2 ++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index e13b5ca664..bd2932ab0b 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -5,19 +5,17 @@ # contributed by Bastian Kleineidam +from __future__ import annotations + import functools import os -import pathlib -from typing import Tuple, Iterable +from typing import Iterable from ..core import Command from ..util import change_root, convert_path -StrPath = str | pathlib.Path - - class install_data(Command): description = "install data files" @@ -56,7 +54,7 @@ def run(self): self._copy(f) @functools.singledispatchmethod - def _copy(self, f: StrPath | Tuple[StrPath, Iterable[StrPath]]): + def _copy(self, f: tuple[str | os.PathLike, Iterable[str | os.PathLike]]): # it's a tuple with path to install to and a list of files dir = convert_path(f[0]) if not os.path.isabs(dir): @@ -77,8 +75,9 @@ def _copy(self, f: StrPath | Tuple[StrPath, Iterable[StrPath]]): (out, _) = self.copy_file(data, dir) self.outfiles.append(out) - @_copy.register - def _(self, f: StrPath): + @_copy.register(str) + @_copy.register(os.PathLike) + def _(self, f: str | os.PathLike): # it's a simple file, so copy it f = convert_path(f) if self.warn_dir: diff --git a/distutils/util.py b/distutils/util.py index 635d715ad6..95503e957c 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -4,6 +4,8 @@ one of the other *util.py modules. """ +from __future__ import annotations + import functools import importlib.util import os From a5e3e5f47e333b0c3d04a5f35cf8a3a67a4ffb62 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 16:13:35 -0400 Subject: [PATCH 26/48] Prefer os.PathLike in convert_path --- distutils/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/distutils/util.py b/distutils/util.py index 95503e957c..7751eb9465 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -9,7 +9,6 @@ import functools import importlib.util import os -import pathlib import re import string import subprocess @@ -119,7 +118,7 @@ def split_version(s): return [int(n) for n in s.split('.')] -def convert_path(pathname: str | pathlib.Path) -> str: +def convert_path(pathname: str | os.PathLike) -> str: """ Allow for pathlib.Path inputs and then make native. """ From 87f77315d5fb2b00e88db0bd6931a2f94c10cdfd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 16:39:26 -0400 Subject: [PATCH 27/48] Convert needs to accept None for Setuptools' sake. --- distutils/util.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/distutils/util.py b/distutils/util.py index 7751eb9465..68565534ca 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -16,6 +16,7 @@ import sysconfig import tempfile +from ._functools import pass_none from ._log import log from ._modified import newer from .errors import DistutilsByteCompileError, DistutilsPlatformError @@ -118,9 +119,16 @@ def split_version(s): return [int(n) for n in s.split('.')] +@pass_none def convert_path(pathname: str | os.PathLike) -> str: """ Allow for pathlib.Path inputs and then make native. + + Also if None is passed, will just pass it through as + Setuptools relies on this behavior. + + >>> convert_path(None) is None + True """ return make_native(os.fspath(pathname)) From 2e6a69e92aa2eb5a85e068d8aa8e92427855a9fc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 16:50:07 -0400 Subject: [PATCH 28/48] In test_convert_path, utilize posixpath.join and ntpath.join for maximum compatibility with other libraries. --- distutils/tests/test_util.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index a614a1dae5..b24f1fb4cd 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -4,8 +4,10 @@ import email.generator import email.policy import io +import ntpath import os import pathlib +import posixpath import sys import sysconfig as stdlib_sysconfig import unittest.mock as mock @@ -66,22 +68,14 @@ def test_get_platform(self): def test_convert_path(self): # linux/mac os.sep = '/' - - def _join(path): - return '/'.join(path) - - os.path.join = _join + os.path.join = posixpath.join assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff' # win os.sep = '\\' - - def _join(*path): - return '\\'.join(path) - - os.path.join = _join + os.path.join = ntpath.join with pytest.raises(ValueError): convert_path('/home/to/my/stuff') From 90cbfbe72f5ed07d1fd589bfb7c1660f66159756 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 17:06:10 -0400 Subject: [PATCH 29/48] Wrap paths in PurePosixPath to ensure that any WindowsPaths don't get backslashes. --- distutils/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/distutils/util.py b/distutils/util.py index 68565534ca..f8e4fbb778 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -9,6 +9,7 @@ import functools import importlib.util import os +import pathlib import re import string import subprocess @@ -130,7 +131,9 @@ def convert_path(pathname: str | os.PathLike) -> str: >>> convert_path(None) is None True """ - return make_native(os.fspath(pathname)) + # wrap in PurePosixPath to retain forward slashes on Windows + # see https://github.com/pypa/distutils/pull/272#issuecomment-2240100013 + return make_native(os.fspath(pathlib.PurePosixPath(pathname))) def make_native(pathname: str) -> str: From e2cbf97dda5929ae835c52921461c09bda521a06 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 19 Jul 2024 17:48:30 -0400 Subject: [PATCH 30/48] convert_path no longer fails if passed a path with a trailing slash. Instead, trailing slashes are stripped just as they are with pathlib.Path. Ref https://github.com/pypa/distutils/pull/272#issuecomment-2240252653. --- distutils/tests/test_util.py | 2 -- distutils/util.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index b24f1fb4cd..c05df03a4b 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -79,8 +79,6 @@ def test_convert_path(self): with pytest.raises(ValueError): convert_path('/home/to/my/stuff') - with pytest.raises(ValueError): - convert_path('home/to/my/stuff/') with pytest.raises(ValueError): convert_path(pathlib.Path('/home/to/my/stuff')) diff --git a/distutils/util.py b/distutils/util.py index f8e4fbb778..50ebb1c155 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -151,8 +151,6 @@ def make_native(pathname: str) -> str: return pathname if pathname[0] == '/': raise ValueError(f"path '{pathname}' cannot be absolute") - if pathname[-1] == '/': - raise ValueError(f"path '{pathname}' cannot end with '/'") paths = pathname.split('/') while '.' in paths: From 9d6ca5fe777e7bc2ab611814adac989789d7f023 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 14:25:46 -0400 Subject: [PATCH 31/48] convert_path now converts to a platform-native path.Path, but then calls `.as_posix()` on it. This change will have the unintended effect of adding support for backslashes on Windows. Maybe that's fine, or maybe it should be prohibited. --- distutils/util.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/distutils/util.py b/distutils/util.py index 50ebb1c155..3163eb4804 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -123,17 +123,17 @@ def split_version(s): @pass_none def convert_path(pathname: str | os.PathLike) -> str: """ - Allow for pathlib.Path inputs and then make native. + Allow for pathlib.Path inputs, coax to posix, and then make native. - Also if None is passed, will just pass it through as + If None is passed, will just pass it through as Setuptools relies on this behavior. >>> convert_path(None) is None True """ - # wrap in PurePosixPath to retain forward slashes on Windows + # Use .as_posix() to retain forward slashes on Windows # see https://github.com/pypa/distutils/pull/272#issuecomment-2240100013 - return make_native(os.fspath(pathlib.PurePosixPath(pathname))) + return make_native(pathlib.Path(pathname).as_posix()) def make_native(pathname: str) -> str: From 4eaa02f2fff8f8e6d4eac5442988dc586fe56004 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 14:45:06 -0400 Subject: [PATCH 32/48] Separate test_convert_path into two tests to avoid interactions in monkeypatching os.path. --- distutils/tests/test_util.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index c05df03a4b..434c9b8085 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -65,15 +65,14 @@ def test_get_platform(self): with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}): assert get_platform() == 'win-arm64' - def test_convert_path(self): - # linux/mac + def test_convert_path_unix(self): os.sep = '/' os.path.join = posixpath.join assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff' - # win + def test_convert_path_windows(self): os.sep = '\\' os.path.join = ntpath.join From 5469f24a5978acb5c7397a4b2e2b2ffd78c7c9e6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 15:16:56 -0400 Subject: [PATCH 33/48] Remove expectation that a ValueError is raised for data_files being drive-relative absolute on Windows. --- distutils/tests/test_util.py | 5 ----- distutils/util.py | 2 -- 2 files changed, 7 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index 434c9b8085..16fcfe3e8c 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -76,11 +76,6 @@ def test_convert_path_windows(self): os.sep = '\\' os.path.join = ntpath.join - with pytest.raises(ValueError): - convert_path('/home/to/my/stuff') - with pytest.raises(ValueError): - convert_path(pathlib.Path('/home/to/my/stuff')) - assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff' assert convert_path(pathlib.Path('home/to/my/stuff')) == 'home\\to\\my\\stuff' assert convert_path('.') == os.curdir diff --git a/distutils/util.py b/distutils/util.py index 3163eb4804..c7e73011f6 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -149,8 +149,6 @@ def make_native(pathname: str) -> str: return pathname if not pathname: return pathname - if pathname[0] == '/': - raise ValueError(f"path '{pathname}' cannot be absolute") paths = pathname.split('/') while '.' in paths: From 28e7431142f4bc380518f4691076af2bd81222b7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 15:33:26 -0400 Subject: [PATCH 34/48] Simplify convert_path by simply relying on the logic in PurePath. Test for convert_path no longer runs on all operating systems as it's too difficult (impossible) to monkeypatch PurePath reliably. --- distutils/tests/test_util.py | 10 ++-------- distutils/util.py | 36 +++++++----------------------------- 2 files changed, 9 insertions(+), 37 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index 16fcfe3e8c..25e26e017d 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -4,10 +4,8 @@ import email.generator import email.policy import io -import ntpath import os import pathlib -import posixpath import sys import sysconfig as stdlib_sysconfig import unittest.mock as mock @@ -65,17 +63,13 @@ def test_get_platform(self): with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}): assert get_platform() == 'win-arm64' + @pytest.mark.skipif('platform.system() == "Windows"') def test_convert_path_unix(self): - os.sep = '/' - os.path.join = posixpath.join - assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff' + @pytest.mark.skipif('platform.system() != "Windows"') def test_convert_path_windows(self): - os.sep = '\\' - os.path.join = ntpath.join - assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff' assert convert_path(pathlib.Path('home/to/my/stuff')) == 'home\\to\\my\\stuff' assert convert_path('.') == os.curdir diff --git a/distutils/util.py b/distutils/util.py index c7e73011f6..4cc6bd283c 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -122,43 +122,21 @@ def split_version(s): @pass_none def convert_path(pathname: str | os.PathLike) -> str: - """ - Allow for pathlib.Path inputs, coax to posix, and then make native. + r""" + Allow for pathlib.Path inputs, coax to a native path string. If None is passed, will just pass it through as Setuptools relies on this behavior. >>> convert_path(None) is None True - """ - # Use .as_posix() to retain forward slashes on Windows - # see https://github.com/pypa/distutils/pull/272#issuecomment-2240100013 - return make_native(pathlib.Path(pathname).as_posix()) - - -def make_native(pathname: str) -> str: - """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 - directory separator. Needed because filenames in the setup script are - always supplied in Unix style, and have to be converted to the local - convention before we can actually use them in the filesystem. Raises - ValueError on non-Unix-ish systems if 'pathname' either starts or - ends with a slash. - """ - if os.sep == '/': - return pathname - if not pathname: - return pathname - paths = pathname.split('/') - while '.' in paths: - paths.remove('.') - if not paths: - return os.curdir - return os.path.join(*paths) + Removes empty paths. - -# convert_path () + >>> convert_path('foo/./bar').replace('\\', '/') + 'foo/bar' + """ + return os.fspath(pathlib.PurePath(pathname)) def change_root(new_root, pathname): From 8cada19d07e286558e5193841f66483f631170b3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 15:43:45 -0400 Subject: [PATCH 35/48] Harmonize convert_path tests across Unix and Windows. --- distutils/tests/test_util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index 25e26e017d..d1254ca18b 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -67,11 +67,12 @@ def test_get_platform(self): def test_convert_path_unix(self): assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff' + assert convert_path('.') == os.curdir @pytest.mark.skipif('platform.system() != "Windows"') def test_convert_path_windows(self): - assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff' - assert convert_path(pathlib.Path('home/to/my/stuff')) == 'home\\to\\my\\stuff' + assert convert_path('/home/to/my/stuff') == r'\home\to\my\stuff' + assert convert_path(pathlib.Path('/home/to/my/stuff')) == r'\home\to\my\stuff' assert convert_path('.') == os.curdir def test_change_root(self): From 8f2498a1b072f3206b85f786016b9defbb4b796c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 15:52:05 -0400 Subject: [PATCH 36/48] Consolidate convert_path tests and just generate the expected value in a platform-sensitive way. Should fix failures on mingw. --- distutils/tests/test_util.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/distutils/tests/test_util.py b/distutils/tests/test_util.py index d1254ca18b..00c9743ed0 100644 --- a/distutils/tests/test_util.py +++ b/distutils/tests/test_util.py @@ -63,16 +63,10 @@ def test_get_platform(self): with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}): assert get_platform() == 'win-arm64' - @pytest.mark.skipif('platform.system() == "Windows"') - def test_convert_path_unix(self): - assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' - assert convert_path(pathlib.Path('/home/to/my/stuff')) == '/home/to/my/stuff' - assert convert_path('.') == os.curdir - - @pytest.mark.skipif('platform.system() != "Windows"') - def test_convert_path_windows(self): - assert convert_path('/home/to/my/stuff') == r'\home\to\my\stuff' - assert convert_path(pathlib.Path('/home/to/my/stuff')) == r'\home\to\my\stuff' + def test_convert_path(self): + expected = os.sep.join(('', 'home', 'to', 'my', 'stuff')) + assert convert_path('/home/to/my/stuff') == expected + assert convert_path(pathlib.Path('/home/to/my/stuff')) == expected assert convert_path('.') == os.curdir def test_change_root(self): From 65675e5f102f5887827c9edd0372a9cbbd924784 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Aug 2024 21:07:41 -0400 Subject: [PATCH 37/48] Harmonize '-shared' parameter for C and C++ in Mingw32CCompiler --- distutils/cygwinccompiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index f0704ee7e4..10a0f7a872 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -288,7 +288,7 @@ def __init__(self, verbose=False, dry_run=False, force=False): self.set_executables( compiler=f'{self.cc} -O -Wall', compiler_so=f'{self.cc} -shared -O -Wall', - compiler_so_cxx=f'{self.cxx} -mdll -O -Wall', + compiler_so_cxx=f'{self.cxx} -shared -O -Wall', compiler_cxx=f'{self.cxx} -O -Wall', linker_exe=f'{self.cc}', linker_so=f'{self.linker_dll} {shared_option}', From 52cd70b8dd7f5c3405ab0d4a9719b4cb0493fe9e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Aug 2024 21:37:36 -0400 Subject: [PATCH 38/48] In sysconfig.customize_compiler, initialize ldcxxshared from config vars. --- distutils/sysconfig.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 31bdbec1bb..847a26ba49 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -304,6 +304,7 @@ def customize_compiler(compiler): # noqa: C901 cflags, ccshared, ldshared, + ldcxxshared, shlib_suffix, ar, ar_flags, @@ -313,13 +314,13 @@ def customize_compiler(compiler): # noqa: C901 'CFLAGS', 'CCSHARED', 'LDSHARED', + 'LDCXXSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS', ) cxxflags = cflags - ldcxxshared = "" if 'CC' in os.environ: newcc = os.environ['CC'] From 1f3188ca5a69f3aff440a13dcfcdef76f53ecab4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Aug 2024 10:23:15 -0400 Subject: [PATCH 39/48] Refactored customize_compiler to reduce logical branches and extract _add_flags logic. Reduces cyclomatic complexity so it passes QA checks. --- distutils/sysconfig.py | 66 ++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 847a26ba49..8929deab5a 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -287,7 +287,7 @@ def _customize_macos(): ) -def customize_compiler(compiler): # noqa: C901 +def customize_compiler(compiler): """Do any platform-specific customization of a CCompiler instance. Mainly needed on Unix, so we can plug in the information that @@ -329,40 +329,32 @@ def customize_compiler(compiler): # noqa: C901 # command for LDSHARED as well ldshared = newcc + ldshared[len(cc) :] cc = newcc - if 'CXX' in os.environ: - cxx = os.environ['CXX'] - if 'LDSHARED' in os.environ: - ldshared = os.environ['LDSHARED'] - if 'LDCXXSHARED' in os.environ: - ldcxxshared = os.environ['LDCXXSHARED'] - if 'CPP' in os.environ: - cpp = os.environ['CPP'] - else: - cpp = cc + " -E" # not always - if 'LDFLAGS' in os.environ: - ldshared = ldshared + ' ' + os.environ['LDFLAGS'] - ldcxxshared = ldcxxshared + ' ' + os.environ['LDFLAGS'] - if 'CFLAGS' in os.environ: - cflags = cflags + ' ' + os.environ['CFLAGS'] - ldshared = ldshared + ' ' + os.environ['CFLAGS'] - if 'CXXFLAGS' in os.environ: - cxxflags = os.environ['CXXFLAGS'] - ldcxxshared = ldcxxshared + ' ' + os.environ['CXXFLAGS'] - if 'CPPFLAGS' in os.environ: - cpp = cpp + ' ' + os.environ['CPPFLAGS'] - cflags = cflags + ' ' + os.environ['CPPFLAGS'] - cxxflags = cxxflags + ' ' + os.environ['CPPFLAGS'] - ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] - ldcxxshared = ldcxxshared + ' ' + os.environ['CPPFLAGS'] - if 'AR' in os.environ: - ar = os.environ['AR'] - if 'ARFLAGS' in os.environ: - archiver = ar + ' ' + os.environ['ARFLAGS'] - else: - archiver = ar + ' ' + ar_flags + cxx = os.environ.get('CXX', cxx) + ldshared = os.environ.get('LDSHARED', ldshared) + ldcxxshared = os.environ.get('LDCXXSHARED', ldcxxshared) + cpp = os.environ.get( + 'CPP', + cc + " -E", # not always + ) + ldshared = _add_flags(ldshared, 'LD') + ldcxxshared = _add_flags(ldcxxshared, 'LD') + cflags = _add_flags(cflags, 'C') + ldshared = _add_flags(ldshared, 'C') + cxxflags = os.environ.get('CXXFLAGS', cxxflags) + ldcxxshared = _add_flags(ldcxxshared, 'CXX') + cpp = _add_flags(cpp, 'CPP') + cflags = _add_flags(cflags, 'CPP') + cxxflags = _add_flags(cxxflags, 'CPP') + ldshared = _add_flags(ldshared, 'CPP') + ldcxxshared = _add_flags(ldcxxshared, 'CPP') + + ar = os.environ.get('AR', ar) + + archiver = ar + ' ' + os.environ.get('ARFLAGS', ar_flags) cc_cmd = cc + ' ' + cflags cxx_cmd = cxx + ' ' + cxxflags + compiler.set_executables( preprocessor=cpp, compiler=cc_cmd, @@ -577,3 +569,13 @@ def get_config_var(name): warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2) return get_config_vars().get(name) + + +def _add_flags(value: str, type: str) -> str: + """ + Add any flags from the environment for the given type. + + type is the prefix to FLAGS in the environment key (e.g. "C" for "CFLAGS"). + """ + flags = os.environ.get(f'{type}FLAGS') + return f'{value} {flags}' if flags else value From 3b86d4babdd5ffba14e10194c5b97b6001f532f8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Jul 2024 18:40:07 -0400 Subject: [PATCH 40/48] Use simple import --- distutils/tests/test_extension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index c5219d7bd7..733709966c 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -1,8 +1,8 @@ """Tests for distutils.extension.""" import os +import pathlib import warnings -from pathlib import Path from distutils.extension import Extension, read_setup_file @@ -77,7 +77,7 @@ def test_extension_init(self): Extension('name', ['file', 1]) ext = Extension('name', ['file1', 'file2']) assert ext.sources == ['file1', 'file2'] - ext = Extension('name', [Path('file1'), Path('file2')]) + ext = Extension('name', [pathlib.Path('file1'), pathlib.Path('file2')]) assert ext.sources == ['file1', 'file2'] # others arguments have defaults From d13da588f6b303da509638c341f4dd37ee49da47 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sun, 21 Jul 2024 17:23:15 +0200 Subject: [PATCH 41/48] CI: add a job for running tests under MSVC CPython with GCC as the default compiler The tests currently assume everywhere that there is only one compiler per platform, and while it would be possible to parametrize all the tests it would make things more complex and we'd also have to decide which compiler is required for running the tests and which one is optional etc. To avoid all this introduce a DISTUTILS_TEST_DEFAULT_COMPILER env var which can be used to override the default compiler type for the whole test run. This keeps the tests as is and makes sure all tests run against the alternative compiler. Also add it to pass_env for tox, so it gets passed to pytest, if set. The added CI job installs an ucrt targeting GCC via MSYS2, and forces the MSVC CPython to use it via DISTUTILS_TEST_DEFAULT_COMPILER=mingw32. --- .github/workflows/main.yml | 22 ++++++++++++++++++++++ conftest.py | 23 +++++++++++++++++++++++ tox.ini | 2 ++ 3 files changed, 47 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 70d70bc6f1..2f4ec6b478 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -163,6 +163,28 @@ jobs: source /tmp/venv/bin/activate pytest + test_msvc_python_mingw: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + - name: Install tox + run: python -m pip install tox + - name: Install GCC + uses: msys2/setup-msys2@v2 + with: + msystem: ucrt64 + install: mingw-w64-ucrt-x86_64-cc + - name: Run + run: | + $env:MSYS2_ROOT = msys2 -c 'cygpath -m /' + $env:PATH = "$env:MSYS2_ROOT/ucrt64/bin;$env:PATH" + $env:DISTUTILS_TEST_DEFAULT_COMPILER = "mingw32" + tox + ci_setuptools: # Integration testing with setuptools strategy: diff --git a/conftest.py b/conftest.py index 6639aa65bd..fd6cb6d61b 100644 --- a/conftest.py +++ b/conftest.py @@ -162,3 +162,26 @@ def disable_macos_customization(monkeypatch): from distutils import sysconfig monkeypatch.setattr(sysconfig, '_customize_macos', lambda: None) + + +@pytest.fixture(autouse=True, scope="session") +def monkey_patch_get_default_compiler(): + """ + Monkey patch distutils get_default_compiler to allow overriding the + default compiler. Mainly to test mingw32 with a MSVC Python. + """ + from distutils import ccompiler + + default_compiler = os.environ.get("DISTUTILS_TEST_DEFAULT_COMPILER") + + if default_compiler is not None: + + def patched_get_default_compiler(*args, **kwargs): + return default_compiler + + original = ccompiler.get_default_compiler + ccompiler.get_default_compiler = patched_get_default_compiler + yield + ccompiler.get_default_compiler = original + else: + yield diff --git a/tox.ini b/tox.ini index d4bcc4165d..5483587625 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,8 @@ setenv = PYTHONWARNDEFAULTENCODING = 1 # pypa/distutils#99 VIRTUALENV_NO_SETUPTOOLS = 1 +pass_env = + DISTUTILS_TEST_DEFAULT_COMPILER commands = pytest {posargs} usedevelop = True From 8307b56117acf27a3b15413c0d488d35717b6d0b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Aug 2024 10:27:14 -0400 Subject: [PATCH 42/48] Don't add flags when the value is None, such as when on PyPy, LDCXXSHARED is unset. --- distutils/sysconfig.py | 1 + 1 file changed, 1 insertion(+) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 8929deab5a..fbdd5d73ae 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -571,6 +571,7 @@ def get_config_var(name): return get_config_vars().get(name) +@pass_none def _add_flags(value: str, type: str) -> str: """ Add any flags from the environment for the given type. From d4a685ac7ba5b43505f91c814f5508819b1087c8 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sun, 21 Jul 2024 18:13:03 +0200 Subject: [PATCH 43/48] mingw: make get_msvcr() a noop This was added back in the day to make mingw use the same CRT as CPython (https://bugs.python.org/issue870382), but at least with newer mingw-w64 and ucrt switching the CRT at "runtime" isn't supported anymore. To build a compatible extension you have to use a ucrt mingw-w64 build, so things match up and link against the same CRT. CPython 3.5+ uses ucrt (see https://wiki.python.org/moin/WindowsCompilers), so anything besides that is no longer relevant, which only leaves vcruntime140. Since it's not clear what linking against vcruntime140 solves, and there have been reports of it needing to be patched out: * https://github.com/pypa/setuptools/issues/4101 * https://github.com/pypa/distutils/issues/204#issuecomment-1420892028 let's just make it return nothing. Keep get_msvcr() around for now to avoid breaking code which patched it. Fixes #204 --- distutils/cygwinccompiler.py | 40 ++--------------------- distutils/tests/test_cygwinccompiler.py | 42 ------------------------- 2 files changed, 2 insertions(+), 80 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 7b812fd055..f3e593a40e 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -9,13 +9,11 @@ import copy import os import pathlib -import re import shlex import sys import warnings from subprocess import check_output -from ._collections import RangeMap from .errors import ( CCompilerError, CompileError, @@ -26,42 +24,10 @@ from .unixccompiler import UnixCCompiler from .version import LooseVersion, suppress_known_deprecation -_msvcr_lookup = RangeMap.left( - { - # MSVC 7.0 - 1300: ['msvcr70'], - # MSVC 7.1 - 1310: ['msvcr71'], - # VS2005 / MSVC 8.0 - 1400: ['msvcr80'], - # VS2008 / MSVC 9.0 - 1500: ['msvcr90'], - # VS2010 / MSVC 10.0 - 1600: ['msvcr100'], - # VS2012 / MSVC 11.0 - 1700: ['msvcr110'], - # VS2013 / MSVC 12.0 - 1800: ['msvcr120'], - # VS2015 / MSVC 14.0 - 1900: ['vcruntime140'], - 2000: RangeMap.undefined_value, - }, -) - def get_msvcr(): - """Include the appropriate MSVC runtime library if Python was built - with MSVC 7.0 or later. - """ - match = re.search(r'MSC v\.(\d{4})', sys.version) - try: - msc_ver = int(match.group(1)) - except AttributeError: - return [] - try: - return _msvcr_lookup[msc_ver] - except KeyError: - raise ValueError(f"Unknown MS Compiler version {msc_ver} ") + """No longer needed, but kept for backward compatibility.""" + return [] _runtime_library_dirs_msg = ( @@ -109,8 +75,6 @@ def __init__(self, verbose=False, dry_run=False, force=False): linker_so=(f'{self.linker_dll} -mcygwin {shared_option}'), ) - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. self.dll_libraries = get_msvcr() @property diff --git a/distutils/tests/test_cygwinccompiler.py b/distutils/tests/test_cygwinccompiler.py index 2e1640b757..677bc0ac99 100644 --- a/distutils/tests/test_cygwinccompiler.py +++ b/distutils/tests/test_cygwinccompiler.py @@ -71,50 +71,8 @@ def test_check_config_h(self): assert check_config_h()[0] == CONFIG_H_OK def test_get_msvcr(self): - # [] - sys.version = ( - '2.6.1 (r261:67515, Dec 6 2008, 16:42:21) ' - '\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]' - ) assert get_msvcr() == [] - # MSVC 7.0 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1300 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr70'] - - # MSVC 7.1 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr71'] - - # VS2005 / MSVC 8.0 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1400 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr80'] - - # VS2008 / MSVC 9.0 - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1500 32 bits (Intel)]' - ) - assert get_msvcr() == ['msvcr90'] - - sys.version = ( - '3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 18:46:30) ' - '[MSC v.1929 32 bit (Intel)]' - ) - assert get_msvcr() == ['vcruntime140'] - - # unknown - sys.version = ( - '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.2000 32 bits (Intel)]' - ) - with pytest.raises(ValueError): - get_msvcr() - @pytest.mark.skipif('sys.platform != "cygwin"') def test_dll_libraries_not_none(self): from distutils.cygwinccompiler import CygwinCCompiler From f577bcb5e15dde5411128939ce6336e1a20b9736 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sun, 21 Jul 2024 13:22:12 +0200 Subject: [PATCH 44/48] venv_install_options: add missing clear_argv fixture Otherwise the test fails if arguments are passed to pytest, for example --no-cov: FAILED distutils/tests/test_dist.py::TestDistributionBehavior::test_venv_install_options - distutils.errors.DistutilsArgError: option --no-cov not recognized --- distutils/tests/test_dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/tests/test_dist.py b/distutils/tests/test_dist.py index 5bd206fec1..4d78a19803 100644 --- a/distutils/tests/test_dist.py +++ b/distutils/tests/test_dist.py @@ -88,7 +88,7 @@ def test_command_packages_cmdline(self, clear_argv): 'distutils' not in Distribution.parse_config_files.__module__, reason='Cannot test when virtualenv has monkey-patched Distribution', ) - def test_venv_install_options(self, tmp_path): + def test_venv_install_options(self, tmp_path, clear_argv): sys.argv.append("install") file = str(tmp_path / 'file') From 1f999b9c92f6850de5b9ba6be249324fed403103 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Sun, 21 Jul 2024 21:08:19 +0200 Subject: [PATCH 45/48] Remove unused RangeMap Its last use in cygwinccompiler was just removed. --- distutils/_collections.py | 145 -------------------------------------- 1 file changed, 145 deletions(-) diff --git a/distutils/_collections.py b/distutils/_collections.py index d11a83467c..863030b3cf 100644 --- a/distutils/_collections.py +++ b/distutils/_collections.py @@ -1,11 +1,7 @@ from __future__ import annotations import collections -import functools import itertools -import operator -from collections.abc import Mapping -from typing import Any # from jaraco.collections 3.5.1 @@ -60,144 +56,3 @@ def __contains__(self, other): def __len__(self): return len(list(iter(self))) - - -# from jaraco.collections 5.0.1 -class RangeMap(dict): - """ - A dictionary-like object that uses the keys as bounds for a range. - Inclusion of the value for that range is determined by the - key_match_comparator, which defaults to less-than-or-equal. - A value is returned for a key if it is the first key that matches in - the sorted list of keys. - - One may supply keyword parameters to be passed to the sort function used - to sort keys (i.e. key, reverse) as sort_params. - - Create a map that maps 1-3 -> 'a', 4-6 -> 'b' - - >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy - >>> r[1], r[2], r[3], r[4], r[5], r[6] - ('a', 'a', 'a', 'b', 'b', 'b') - - Even float values should work so long as the comparison operator - supports it. - - >>> r[4.5] - 'b' - - Notice that the way rangemap is defined, it must be open-ended - on one side. - - >>> r[0] - 'a' - >>> r[-1] - 'a' - - One can close the open-end of the RangeMap by using undefined_value - - >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'}) - >>> r[0] - Traceback (most recent call last): - ... - KeyError: 0 - - One can get the first or last elements in the range by using RangeMap.Item - - >>> last_item = RangeMap.Item(-1) - >>> r[last_item] - 'b' - - .last_item is a shortcut for Item(-1) - - >>> r[RangeMap.last_item] - 'b' - - Sometimes it's useful to find the bounds for a RangeMap - - >>> r.bounds() - (0, 6) - - RangeMap supports .get(key, default) - - >>> r.get(0, 'not found') - 'not found' - - >>> r.get(7, 'not found') - 'not found' - - One often wishes to define the ranges by their left-most values, - which requires use of sort params and a key_match_comparator. - - >>> r = RangeMap({1: 'a', 4: 'b'}, - ... sort_params=dict(reverse=True), - ... key_match_comparator=operator.ge) - >>> r[1], r[2], r[3], r[4], r[5], r[6] - ('a', 'a', 'a', 'b', 'b', 'b') - - That wasn't nearly as easy as before, so an alternate constructor - is provided: - - >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value}) - >>> r[1], r[2], r[3], r[4], r[5], r[6] - ('a', 'a', 'a', 'b', 'b', 'b') - - """ - - def __init__( - self, - source, - sort_params: Mapping[str, Any] = {}, - key_match_comparator=operator.le, - ): - dict.__init__(self, source) - self.sort_params = sort_params - self.match = key_match_comparator - - @classmethod - def left(cls, source): - return cls( - source, sort_params=dict(reverse=True), key_match_comparator=operator.ge - ) - - def __getitem__(self, item): - sorted_keys = sorted(self.keys(), **self.sort_params) - if isinstance(item, RangeMap.Item): - result = self.__getitem__(sorted_keys[item]) - else: - key = self._find_first_match_(sorted_keys, item) - result = dict.__getitem__(self, key) - if result is RangeMap.undefined_value: - raise KeyError(key) - return result - - def get(self, key, default=None): - """ - Return the value for key if key is in the dictionary, else default. - If default is not given, it defaults to None, so that this method - never raises a KeyError. - """ - try: - return self[key] - except KeyError: - return default - - def _find_first_match_(self, keys, item): - is_match = functools.partial(self.match, item) - matches = list(filter(is_match, keys)) - if matches: - return matches[0] - raise KeyError(item) - - def bounds(self): - sorted_keys = sorted(self.keys(), **self.sort_params) - return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item]) - - # some special values for the RangeMap - undefined_value = type('RangeValueUndefined', (), {})() - - class Item(int): - "RangeMap Item" - - first_item = Item(0) - last_item = Item(-1) From 32e5fea1c5b5e407292494908dfca5d6195e426e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Aug 2024 11:32:01 -0400 Subject: [PATCH 46/48] Rely on monkeysession to monkeypatch. --- conftest.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/conftest.py b/conftest.py index fd6cb6d61b..352366cb8f 100644 --- a/conftest.py +++ b/conftest.py @@ -165,7 +165,7 @@ def disable_macos_customization(monkeypatch): @pytest.fixture(autouse=True, scope="session") -def monkey_patch_get_default_compiler(): +def monkey_patch_get_default_compiler(monkeysession): """ Monkey patch distutils get_default_compiler to allow overriding the default compiler. Mainly to test mingw32 with a MSVC Python. @@ -174,14 +174,10 @@ def monkey_patch_get_default_compiler(): default_compiler = os.environ.get("DISTUTILS_TEST_DEFAULT_COMPILER") - if default_compiler is not None: + if default_compiler is None: + return - def patched_get_default_compiler(*args, **kwargs): - return default_compiler + def patched_getter(*args, **kwargs): + return default_compiler - original = ccompiler.get_default_compiler - ccompiler.get_default_compiler = patched_get_default_compiler - yield - ccompiler.get_default_compiler = original - else: - yield + monkeysession.setattr(ccompiler, 'get_default_compiler', patched_getter) From b7ee725f3a5ba336cfca89f55d8a3b7d3bf7d27b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Aug 2024 11:41:51 -0400 Subject: [PATCH 47/48] Apply isort. Ref pypa/distutils#240 --- distutils/ccompiler.py | 2 +- distutils/command/install_data.py | 1 - distutils/spawn.py | 1 - distutils/tests/test_archive_util.py | 2 +- distutils/tests/test_build.py | 3 +-- distutils/tests/test_build_ext.py | 2 +- distutils/tests/test_extension.py | 1 - distutils/tests/test_install_data.py | 5 ++--- distutils/tests/test_mingwccompiler.py | 8 ++++---- distutils/tests/test_spawn.py | 2 +- distutils/tests/test_sysconfig.py | 2 +- 11 files changed, 12 insertions(+), 17 deletions(-) diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index 42b0812454..bc4743bcbf 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -22,7 +22,7 @@ ) from .file_util import move_file from .spawn import spawn -from .util import execute, split_quoted, is_mingw +from .util import execute, is_mingw, split_quoted class CCompiler: diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index bd2932ab0b..a90ec3b4d0 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -9,7 +9,6 @@ import functools import os - from typing import Iterable from ..core import Command diff --git a/distutils/spawn.py b/distutils/spawn.py index 50d30a2761..107b011397 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -12,7 +12,6 @@ import subprocess import sys import warnings - from typing import Mapping from ._log import log diff --git a/distutils/tests/test_archive_util.py b/distutils/tests/test_archive_util.py index abbcd36cb0..389eba16e8 100644 --- a/distutils/tests/test_archive_util.py +++ b/distutils/tests/test_archive_util.py @@ -18,10 +18,10 @@ from distutils.spawn import spawn from distutils.tests import support from os.path import splitdrive -from test.support import patch import path import pytest +from test.support import patch from .compat.py38 import check_warnings from .unix_compat import UID_0_SUPPORT, grp, pwd, require_uid_0, require_unix_id diff --git a/distutils/tests/test_build.py b/distutils/tests/test_build.py index 8fb1bc1b77..d379aca0bb 100644 --- a/distutils/tests/test_build.py +++ b/distutils/tests/test_build.py @@ -4,8 +4,7 @@ import sys from distutils.command.build import build from distutils.tests import support -from sysconfig import get_config_var -from sysconfig import get_platform +from sysconfig import get_config_var, get_platform class TestBuild(support.TempdirManager): diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index 6c4c4ba869..8bd3cef855 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -25,11 +25,11 @@ fixup_build_ext, ) from io import StringIO -from test import support import jaraco.path import path import pytest +from test import support from .compat import py38 as import_helper diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 733709966c..41872e04e8 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -3,7 +3,6 @@ import os import pathlib import warnings - from distutils.extension import Extension, read_setup_file import pytest diff --git a/distutils/tests/test_install_data.py b/distutils/tests/test_install_data.py index 4b15a26945..c800f86c64 100644 --- a/distutils/tests/test_install_data.py +++ b/distutils/tests/test_install_data.py @@ -2,12 +2,11 @@ import os import pathlib - -import pytest - from distutils.command.install_data import install_data from distutils.tests import support +import pytest + @pytest.mark.usefixtures('save_env') class TestInstallData( diff --git a/distutils/tests/test_mingwccompiler.py b/distutils/tests/test_mingwccompiler.py index 28f4762b2a..3e3ad5058c 100644 --- a/distutils/tests/test_mingwccompiler.py +++ b/distutils/tests/test_mingwccompiler.py @@ -1,8 +1,8 @@ -import pytest - -from distutils.util import split_quoted, is_mingw -from distutils.errors import DistutilsPlatformError, CCompilerError from distutils import sysconfig +from distutils.errors import CCompilerError, DistutilsPlatformError +from distutils.util import is_mingw, split_quoted + +import pytest class TestMingw32CCompiler: diff --git a/distutils/tests/test_spawn.py b/distutils/tests/test_spawn.py index 2576bdd53d..fd7b669cbf 100644 --- a/distutils/tests/test_spawn.py +++ b/distutils/tests/test_spawn.py @@ -7,10 +7,10 @@ from distutils.errors import DistutilsExecError from distutils.spawn import find_executable, spawn from distutils.tests import support -from test.support import unix_shell import path import pytest +from test.support import unix_shell from .compat import py38 as os_helper diff --git a/distutils/tests/test_sysconfig.py b/distutils/tests/test_sysconfig.py index edc17992ef..49274a36ae 100644 --- a/distutils/tests/test_sysconfig.py +++ b/distutils/tests/test_sysconfig.py @@ -9,12 +9,12 @@ from distutils import sysconfig from distutils.ccompiler import new_compiler # noqa: F401 from distutils.unixccompiler import UnixCCompiler -from test.support import swap_item import jaraco.envs import path import pytest from jaraco.text import trim +from test.support import swap_item def _gen_makefile(root, contents): From d4ad24b09ed3fdeaef2e7f493e5446898fe5c504 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Aug 2024 11:51:46 -0400 Subject: [PATCH 48/48] Add news fragment. --- newsfragments/4538.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/4538.feature.rst diff --git a/newsfragments/4538.feature.rst b/newsfragments/4538.feature.rst new file mode 100644 index 0000000000..9c36ad5209 --- /dev/null +++ b/newsfragments/4538.feature.rst @@ -0,0 +1 @@ +Merged with distutils@d7ffdb9c7 including: Support for Pathlike objects in data files and extensions (pypa/distutils#272, pypa/distutils#237), native support for C++ compilers (pypa/distuils#228) and removed unused get_msvcr() (pypa/distutils#274).