From 7a6f42b3e6ed87032129aa1419c4286ca74b5d3f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2024 20:37:49 -0400 Subject: [PATCH 01/19] =?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/unixccompiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 0248bde8..da97688c 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -22,11 +22,11 @@ import sys from . import sysconfig -from .compat import consolidate_linker_args from ._log import log from ._macos_compat import compiler_fixup from ._modified import newer from .ccompiler import CCompiler, gen_lib_options, gen_preprocess_options +from .compat import consolidate_linker_args from .errors import CompileError, DistutilsExecError, LibError, LinkError # XXX Things not currently handled: From a996148852f113b0a78cb222b8a2cd4dea54594b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2024 20:41:52 -0400 Subject: [PATCH 02/19] Move compatibility modules into compat package. --- distutils/_modified.py | 2 +- distutils/compat/py38.py | 10 ++++++++++ distutils/{py39compat.py => compat/py39.py} | 0 distutils/py38compat.py | 8 -------- distutils/sysconfig.py | 4 ++-- distutils/util.py | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) rename distutils/{py39compat.py => compat/py39.py} (100%) delete mode 100644 distutils/py38compat.py diff --git a/distutils/_modified.py b/distutils/_modified.py index 78485dc2..9b375181 100644 --- a/distutils/_modified.py +++ b/distutils/_modified.py @@ -5,7 +5,7 @@ from ._functools import splat from .errors import DistutilsFileError -from .py39compat import zip_strict +from .compat.py39 import zip_strict def _newer(source, target): diff --git a/distutils/compat/py38.py b/distutils/compat/py38.py index 0af38140..79afc3b2 100644 --- a/distutils/compat/py38.py +++ b/distutils/compat/py38.py @@ -21,3 +21,13 @@ def removesuffix(self, suffix): def removeprefix(self, prefix): return self.removeprefix(prefix) + + +def aix_platform(osname, version, release): + try: + import _aix_support + + return _aix_support.aix_platform() + except ImportError: + pass + return f"{osname}-{version}.{release}" diff --git a/distutils/py39compat.py b/distutils/compat/py39.py similarity index 100% rename from distutils/py39compat.py rename to distutils/compat/py39.py diff --git a/distutils/py38compat.py b/distutils/py38compat.py deleted file mode 100644 index ab12119f..00000000 --- a/distutils/py38compat.py +++ /dev/null @@ -1,8 +0,0 @@ -def aix_platform(osname, version, release): - try: - import _aix_support - - return _aix_support.aix_platform() - except ImportError: - pass - return f"{osname}-{version}.{release}" diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 1a38e9fa..514e06e3 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -16,7 +16,7 @@ import sys import sysconfig -from . import py39compat +from .compat import py39 from ._functools import pass_none from .errors import DistutilsPlatformError @@ -538,7 +538,7 @@ def get_config_vars(*args): global _config_vars if _config_vars is None: _config_vars = sysconfig.get_config_vars().copy() - py39compat.add_ext_suffix(_config_vars) + py39.add_ext_suffix(_config_vars) return [_config_vars.get(name) for name in args] if args else _config_vars diff --git a/distutils/util.py b/distutils/util.py index 9ee77721..2cdea143 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -34,7 +34,7 @@ def get_host_platform(): if os.name == "posix" and hasattr(os, 'uname'): osname, host, release, version, machine = os.uname() if osname[:3] == "aix": - from .py38compat import aix_platform + from .compat.py38 import aix_platform return aix_platform(osname, version, release) From b164d6637dde7a25d99196e3687d949c674c0413 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2024 20:45:42 -0400 Subject: [PATCH 03/19] Move compatibility module into compat package. --- conftest.py | 2 +- distutils/tests/compat/__init__.py | 0 distutils/tests/{py38compat.py => compat/py38.py} | 0 distutils/tests/test_archive_util.py | 2 +- distutils/tests/test_bdist_rpm.py | 2 +- distutils/tests/test_build_ext.py | 2 +- distutils/tests/test_extension.py | 2 +- distutils/tests/test_filelist.py | 2 +- distutils/tests/test_sdist.py | 2 +- distutils/tests/test_spawn.py | 2 +- distutils/tests/test_unixccompiler.py | 2 +- 11 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 distutils/tests/compat/__init__.py rename distutils/tests/{py38compat.py => compat/py38.py} (100%) diff --git a/conftest.py b/conftest.py index 3ce34115..4a3bbd34 100644 --- a/conftest.py +++ b/conftest.py @@ -56,7 +56,7 @@ def _save_cwd(): @pytest.fixture def distutils_managed_tempdir(request): - from distutils.tests import py38compat as os_helper + from distutils.tests.compat import py38 as os_helper self = request.instance self.tempdirs = [] diff --git a/distutils/tests/compat/__init__.py b/distutils/tests/compat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/distutils/tests/py38compat.py b/distutils/tests/compat/py38.py similarity index 100% rename from distutils/tests/py38compat.py rename to distutils/tests/compat/py38.py diff --git a/distutils/tests/test_archive_util.py b/distutils/tests/test_archive_util.py index 145cce91..02af2aa0 100644 --- a/distutils/tests/test_archive_util.py +++ b/distutils/tests/test_archive_util.py @@ -23,7 +23,7 @@ import path import pytest -from .py38compat import check_warnings +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_bdist_rpm.py b/distutils/tests/test_bdist_rpm.py index 769623cb..a5cb42c3 100644 --- a/distutils/tests/test_bdist_rpm.py +++ b/distutils/tests/test_bdist_rpm.py @@ -9,7 +9,7 @@ import pytest -from .py38compat import requires_zlib +from .compat.py38 import requires_zlib SETUP_PY = """\ from distutils.core import setup diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index ca5d9d57..cc83e7fb 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -31,7 +31,7 @@ import path import pytest -from . import py38compat as import_helper +from .compat import py38 as import_helper @pytest.fixture() diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index 77bb147b..527a1355 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -6,7 +6,7 @@ import pytest -from .py38compat import check_warnings +from .compat.py38 import check_warnings class TestExtension: diff --git a/distutils/tests/test_filelist.py b/distutils/tests/test_filelist.py index 6a379a63..ec7e5cf3 100644 --- a/distutils/tests/test_filelist.py +++ b/distutils/tests/test_filelist.py @@ -10,7 +10,7 @@ import jaraco.path import pytest -from . import py38compat as os_helper +from .compat import py38 as os_helper MANIFEST_IN = """\ include ok diff --git a/distutils/tests/test_sdist.py b/distutils/tests/test_sdist.py index 66a41947..a85997f1 100644 --- a/distutils/tests/test_sdist.py +++ b/distutils/tests/test_sdist.py @@ -20,7 +20,7 @@ import pytest from more_itertools import ilen -from .py38compat import check_warnings +from .compat.py38 import check_warnings from .unix_compat import grp, pwd, require_uid_0, require_unix_id SETUP_PY = """ diff --git a/distutils/tests/test_spawn.py b/distutils/tests/test_spawn.py index abbac4c2..7ec58626 100644 --- a/distutils/tests/test_spawn.py +++ b/distutils/tests/test_spawn.py @@ -12,7 +12,7 @@ import path import pytest -from . import py38compat as os_helper +from .compat import py38 as os_helper class TestSpawn(support.TempdirManager): diff --git a/distutils/tests/test_unixccompiler.py b/distutils/tests/test_unixccompiler.py index 6f05fa69..543aa20d 100644 --- a/distutils/tests/test_unixccompiler.py +++ b/distutils/tests/test_unixccompiler.py @@ -12,7 +12,7 @@ import pytest from . import support -from .py38compat import EnvironmentVarGuard +from .compat.py38 import EnvironmentVarGuard @pytest.fixture(autouse=True) From 89522f9748ea637d119b6361253a2021cdd38553 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2024 21:24:11 -0400 Subject: [PATCH 04/19] Fix return type to match implementation. --- distutils/compat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/compat/__init__.py b/distutils/compat/__init__.py index b1ee3fe8..4a7321fe 100644 --- a/distutils/compat/__init__.py +++ b/distutils/compat/__init__.py @@ -3,7 +3,7 @@ from .py38 import removeprefix -def consolidate_linker_args(args: list[str]) -> str: +def consolidate_linker_args(args: list[str]) -> list[str] | str: """ Ensure the return value is a string for backward compatibility. From c6b23d017b520b1c02863e7b331c3be8b42c74a5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2024 21:31:40 -0400 Subject: [PATCH 05/19] =?UTF-8?q?=F0=9F=A7=8E=E2=80=8D=E2=99=80=EF=B8=8F?= =?UTF-8?q?=20Genuflect=20to=20the=20types.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- distutils/compat/py38.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/compat/py38.py b/distutils/compat/py38.py index 79afc3b2..2d442111 100644 --- a/distutils/compat/py38.py +++ b/distutils/compat/py38.py @@ -25,7 +25,7 @@ def removeprefix(self, prefix): def aix_platform(osname, version, release): try: - import _aix_support + import _aix_support # type: ignore return _aix_support.aix_platform() except ImportError: From e5268b516ca3c04f6b4840d094623d494c54a235 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2024 21:59:38 -0400 Subject: [PATCH 06/19] Oops. Meant 2025. --- distutils/compat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/compat/__init__.py b/distutils/compat/__init__.py index 4a7321fe..e12534a3 100644 --- a/distutils/compat/__init__.py +++ b/distutils/compat/__init__.py @@ -7,7 +7,7 @@ def consolidate_linker_args(args: list[str]) -> list[str] | str: """ Ensure the return value is a string for backward compatibility. - Retain until at least 2024-04-31. See pypa/distutils#246 + Retain until at least 2025-04-31. See pypa/distutils#246 """ if not all(arg.startswith('-Wl,') for arg in args): From 6c1cb088509d55f27c8e8e7777cba5fac24b2be7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 18 Apr 2024 16:38:01 -0400 Subject: [PATCH 07/19] Migrated config to pyproject.toml using jaraco.develop.migrate-config and ini2toml. --- pyproject.toml | 54 +++++++++++++++++++++++++++++++++++++++++++++++++- setup.cfg | 50 ---------------------------------------------- 2 files changed, 53 insertions(+), 51 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 1faf0ec2..738546e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,59 @@ [build-system] -requires = ["setuptools>=56", "setuptools_scm[toml]>=3.4.1"] +requires = ["setuptools>=61.2", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" +[project] +name = "distutils" +authors = [ + { name = "Jason R. Coombs", email = "jaraco@jaraco.com" }, +] +description = "Distribution utilities formerly from standard library" +readme = "README.rst" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", +] +requires-python = ">=3.8" +dependencies = [] +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/pypa/distutils" + +[project.optional-dependencies] +testing = [ + # upstream + "pytest >= 6, != 8.1.1", + "pytest-checkdocs >= 2.4", + "pytest-cov", + "pytest-mypy", + "pytest-enabler >= 2.2", + "pytest-ruff >= 0.2.1", + + # local + "pytest >= 7.4.3", # 186 + "jaraco.envs>=2.4", + "jaraco.path", + "jaraco.text", + "path >= 10.6", + "docutils", + "pyfakefs", + "more_itertools", +] +docs = [ + # upstream + "sphinx >= 3.5", + "jaraco.packaging >= 9.3", + "rst.linker >= 1.9", + "furo", + "sphinx-lint", + + # local +] + [tool.setuptools_scm] [tool.pytest-enabler.mypy] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index fc6d67ea..00000000 --- a/setup.cfg +++ /dev/null @@ -1,50 +0,0 @@ -[metadata] -name = distutils -author = Jason R. Coombs -author_email = jaraco@jaraco.com -description = Distribution utilities formerly from standard library -long_description = file:README.rst -url = https://github.com/pypa/distutils -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: MIT License - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - -[options] -include_package_data = true -python_requires = >=3.8 -install_requires = - -[options.extras_require] -testing = - # upstream - pytest >= 6, != 8.1.1 - pytest-checkdocs >= 2.4 - pytest-cov - pytest-mypy - pytest-enabler >= 2.2 - pytest-ruff >= 0.2.1 - - # local - pytest >= 7.4.3 #186 - jaraco.envs>=2.4 - jaraco.path - jaraco.text - path >= 10.6 - docutils - pyfakefs - more_itertools - -docs = - # upstream - sphinx >= 3.5 - jaraco.packaging >= 9.3 - rst.linker >= 1.9 - furo - sphinx-lint - - # local - -[options.entry_points] From 7dcde5e3cc2210f30b88fcb203c842c41027681b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 15:44:11 -0400 Subject: [PATCH 08/19] Extract _make_executable for TestSpawn. --- distutils/tests/test_spawn.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/distutils/tests/test_spawn.py b/distutils/tests/test_spawn.py index 7ec58626..1f623837 100644 --- a/distutils/tests/test_spawn.py +++ b/distutils/tests/test_spawn.py @@ -45,14 +45,9 @@ def test_spawn(self): spawn([exe]) # should work without any error def test_find_executable(self, tmp_path): - program_noeext = 'program' - # Give the temporary program an ".exe" suffix for all. - # It's needed on Windows and not harmful on other platforms. - program = program_noeext + ".exe" - - program_path = tmp_path / program - program_path.write_text("", encoding='utf-8') - program_path.chmod(stat.S_IXUSR) + program_path = self._make_executable(tmp_path, '.exe') + program = program_path.name + program_noeext = program_path.with_suffix('').name filename = str(program_path) tmp_dir = path.Path(tmp_path) @@ -121,6 +116,15 @@ def test_find_executable(self, tmp_path): rv = find_executable(program) assert rv == filename + @staticmethod + def _make_executable(tmp_path, ext): + # Give the temporary program a suffix regardless of platform. + # It's needed on Windows and not harmful on others. + program = tmp_path.joinpath('program').with_suffix(ext) + program.write_text("", encoding='utf-8') + program.chmod(stat.S_IXUSR) + return program + def test_spawn_missing_exe(self): with pytest.raises(DistutilsExecError) as ctx: spawn(['does-not-exist']) From 041c42e7e4b12350dadfe5ac680c9637d1c56858 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 18:10:02 -0400 Subject: [PATCH 09/19] Move and reword comment for brevity and clarity. --- distutils/spawn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index 046b5bbb..76a2dc3f 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -83,14 +83,13 @@ def find_executable(executable, path=None): if path is None: path = os.environ.get('PATH', None) + # bpo-35755: Don't fall through if PATH is the empty string if path is None: try: path = os.confstr("CS_PATH") except (AttributeError, ValueError): # os.confstr() or CS_PATH is not available path = os.defpath - # bpo-35755: Don't use os.defpath if the PATH environment variable is - # set to an empty string # PATH='' doesn't match, whereas PATH=':' looks in the current directory if not path: From b07b4edd742e47e8a721c60fdaaf9f0d4189fd09 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 21:52:29 -0400 Subject: [PATCH 10/19] Remove C901 exclusion; code is now compliant. --- distutils/spawn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index 76a2dc3f..3927c1fe 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -15,7 +15,7 @@ from .errors import DistutilsExecError -def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): # noqa: C901 +def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): """Run another program, specified as a command list 'cmd', in a new process. 'cmd' is just the argument list for the new process, ie. From ef8f235a98c06ca164d64b5debde30fdab4bedf2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 21:54:38 -0400 Subject: [PATCH 11/19] Remove apparently unnecessary cast to list. --- distutils/spawn.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index 3927c1fe..a321c5f0 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -31,10 +31,6 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): Raise DistutilsExecError if running the program fails in any way; just return on success. """ - # cmd is documented as a list, but just in case some code passes a tuple - # in, protect our %-formatting code against horrible death - cmd = list(cmd) - log.info(subprocess.list2cmdline(cmd)) if dry_run: return From 03f1d85b720a86a23908c91c690cecbb754960a7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 21:56:20 -0400 Subject: [PATCH 12/19] Use proper boolean literals. --- distutils/spawn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index a321c5f0..a7e21d2e 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -15,7 +15,7 @@ from .errors import DistutilsExecError -def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): +def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): """Run another program, specified as a command list 'cmd', in a new process. 'cmd' is just the argument list for the new process, ie. From e85efeebdd24bb05ab2e6fed687a06d349775eaa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 22:00:36 -0400 Subject: [PATCH 13/19] Replace Popen with check_call. --- distutils/spawn.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index a7e21d2e..0d86552e 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -50,18 +50,17 @@ def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): env[MACOSX_VERSION_VAR] = macosx_target_ver try: - proc = subprocess.Popen(cmd, env=env) - proc.wait() - exitcode = proc.returncode + subprocess.check_call(cmd, env=env) except OSError as exc: if not DEBUG: cmd = cmd[0] raise DistutilsExecError(f"command {cmd!r} failed: {exc.args[-1]}") from exc - - if exitcode: + except subprocess.CalledProcessError as err: if not DEBUG: cmd = cmd[0] - raise DistutilsExecError(f"command {cmd!r} failed with exit code {exitcode}") + raise DistutilsExecError( + f"command {cmd!r} failed with exit code {err.returncode}" + ) from err def find_executable(executable, path=None): From 976e935fa29c7a8c23298c28537f9d2ce5f399b9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 22:03:14 -0400 Subject: [PATCH 14/19] Extract function for _debug wrapper. --- distutils/spawn.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index 0d86552e..76050655 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -15,6 +15,13 @@ from .errors import DistutilsExecError +def _debug(cmd): + """ + Render a subprocess command differently depending on DEBUG. + """ + return cmd if DEBUG else cmd[0] + + def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): """Run another program, specified as a command list 'cmd', in a new process. @@ -52,14 +59,12 @@ def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): try: subprocess.check_call(cmd, env=env) except OSError as exc: - if not DEBUG: - cmd = cmd[0] - raise DistutilsExecError(f"command {cmd!r} failed: {exc.args[-1]}") from exc + raise DistutilsExecError( + f"command {_debug(cmd)!r} failed: {exc.args[-1]}" + ) from exc except subprocess.CalledProcessError as err: - if not DEBUG: - cmd = cmd[0] raise DistutilsExecError( - f"command {cmd!r} failed with exit code {err.returncode}" + f"command {_debug(cmd)!r} failed with exit code {err.returncode}" ) from err From d6652a4b1b15e27c27a38d4d989f3505541a156d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 20 Apr 2024 22:23:19 -0400 Subject: [PATCH 15/19] Extract function to inject macos version. --- distutils/spawn.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index 76050655..081e2549 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -6,10 +6,15 @@ executable name. """ +from __future__ import annotations + import os +import platform import subprocess import sys +from typing import Mapping + from ._log import log from .debug import DEBUG from .errors import DistutilsExecError @@ -22,6 +27,21 @@ def _debug(cmd): return cmd if DEBUG else cmd[0] +def _inject_macos_ver(env: Mapping[str:str] | None) -> Mapping[str:str] | None: + if platform.system() != 'Darwin': + return env + + from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver + + target_ver = get_macosx_target_ver() + update = {MACOSX_VERSION_VAR: target_ver} if target_ver else {} + return {**_resolve(env), **update} + + +def _resolve(env: Mapping[str:str] | None) -> Mapping[str:str]: + return os.environ if env is None else env + + def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): """Run another program, specified as a command list 'cmd', in a new process. @@ -47,17 +67,8 @@ def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): if executable is not None: cmd[0] = executable - env = env if env is not None else dict(os.environ) - - if sys.platform == 'darwin': - from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver - - macosx_target_ver = get_macosx_target_ver() - if macosx_target_ver: - env[MACOSX_VERSION_VAR] = macosx_target_ver - try: - subprocess.check_call(cmd, env=env) + subprocess.check_call(cmd, env=_inject_macos_ver(env)) except OSError as exc: raise DistutilsExecError( f"command {_debug(cmd)!r} failed: {exc.args[-1]}" From 806b1ca68e7d7668e7e371131ec08b4d4b86354c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Apr 2024 02:42:25 -0400 Subject: [PATCH 16/19] =?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/_modified.py | 2 +- distutils/spawn.py | 1 - distutils/sysconfig.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/distutils/_modified.py b/distutils/_modified.py index 9b375181..07b2ead0 100644 --- a/distutils/_modified.py +++ b/distutils/_modified.py @@ -4,8 +4,8 @@ import os.path from ._functools import splat -from .errors import DistutilsFileError from .compat.py39 import zip_strict +from .errors import DistutilsFileError def _newer(source, target): diff --git a/distutils/spawn.py b/distutils/spawn.py index 081e2549..234d5cd1 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -12,7 +12,6 @@ import platform import subprocess import sys - from typing import Mapping from ._log import log diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 514e06e3..4ed51c1f 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -16,8 +16,8 @@ import sys import sysconfig -from .compat import py39 from ._functools import pass_none +from .compat import py39 from .errors import DistutilsPlatformError IS_PYPY = '__pypy__' in sys.builtin_module_names From 4549de12976703a135c0b9db71e5aaa3ba0f34a6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 29 May 2024 12:24:29 -0400 Subject: [PATCH 17/19] Use mkstemp unconditionally. mktemp has been deprecated since Python 2.3. --- distutils/util.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/distutils/util.py b/distutils/util.py index 2cdea143..0a8b3d69 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -12,6 +12,7 @@ import subprocess import sys import sysconfig +import tempfile from ._log import log from ._modified import newer @@ -405,20 +406,10 @@ def byte_compile( # noqa: C901 # "Indirect" byte-compilation: write a temporary script and then # run it with the appropriate flags. if not direct: - try: - from tempfile import mkstemp - - (script_fd, script_name) = mkstemp(".py") - except ImportError: - from tempfile import mktemp - - (script_fd, script_name) = None, mktemp(".py") + (script_fd, script_name) = tempfile.mkstemp(".py") log.info("writing byte-compilation script '%s'", script_name) if not dry_run: - if script_fd is not None: - script = os.fdopen(script_fd, "w", encoding='utf-8') - else: # pragma: no cover - script = open(script_name, "w", encoding='utf-8') + script = os.fdopen(script_fd, "w", encoding='utf-8') with script: script.write( From a37185d950533623d77c08159ab23bbd99a465c1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 19 Jun 2024 13:46:47 -0400 Subject: [PATCH 18/19] Pin to pytest<8.1. Closes pypa/distutils#259 Ref pytest-dev/pytest#12490 --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index cda381ab..30bfd7d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ test = [ "docutils", "pyfakefs", "more_itertools", + + # workaround for pytest-dev/pytest#12490 + "pytest < 8.1; python_version < '3.12'", ] doc = [ # upstream From 745640eb2afaec907623cd9b9fe5b20840c7c11b Mon Sep 17 00:00:00 2001 From: rmorotti Date: Fri, 31 May 2024 11:28:56 +0100 Subject: [PATCH 19/19] PERF: remove isdir() check in copy_file(), about 10% of the run time was checking if the file to copy is a directory. --- distutils/command/bdist_rpm.py | 6 ++++-- distutils/command/install_data.py | 6 ++++-- distutils/command/install_headers.py | 5 ++++- distutils/file_util.py | 16 +++------------- distutils/tests/test_dir_util.py | 3 ++- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/distutils/command/bdist_rpm.py b/distutils/command/bdist_rpm.py index 649968a5..03bb0f3a 100644 --- a/distutils/command/bdist_rpm.py +++ b/distutils/command/bdist_rpm.py @@ -316,11 +316,13 @@ def run(self): # noqa: C901 source = sdist.get_archive_files()[0] source_dir = rpm_dir['SOURCES'] - self.copy_file(source, source_dir) + dest = os.path.join(source_dir, os.path.basename(source)) + self.copy_file(source, dest) if self.icon: if os.path.exists(self.icon): - self.copy_file(self.icon, source_dir) + dest = os.path.join(source_dir, os.path.basename(self.icon)) + self.copy_file(self.icon, dest) else: raise DistutilsFileError("icon file '%s' does not exist" % self.icon) diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index b63a1af2..cc6ac697 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -54,7 +54,8 @@ def run(self): "setup script did not provide a directory for " f"'{f}' -- installing right in '{self.install_dir}'" ) - (out, _) = self.copy_file(f, self.install_dir) + dst = os.path.join(self.install_dir, os.path.basename(f)) + (out, _) = self.copy_file(f, dst) self.outfiles.append(out) else: # it's a tuple with path to install to and a list of files @@ -74,7 +75,8 @@ def run(self): # 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) + dst = os.path.join(dir, os.path.basename(data)) + (out, _) = self.copy_file(data, dst) self.outfiles.append(out) def get_inputs(self): diff --git a/distutils/command/install_headers.py b/distutils/command/install_headers.py index 085272c1..b7fb4b41 100644 --- a/distutils/command/install_headers.py +++ b/distutils/command/install_headers.py @@ -3,6 +3,8 @@ Implements the Distutils 'install_headers' command, to install C/C++ header files to the Python include directory.""" +import os + from ..core import Command @@ -34,7 +36,8 @@ def run(self): self.mkpath(self.install_dir) for header in headers: - (out, _) = self.copy_file(header, self.install_dir) + dst = os.path.join(self.install_dir, os.path.basename(header)) + (out, _) = self.copy_file(header, dst) self.outfiles.append(out) def get_inputs(self): diff --git a/distutils/file_util.py b/distutils/file_util.py index 960def9c..e2239ac7 100644 --- a/distutils/file_util.py +++ b/distutils/file_util.py @@ -70,9 +70,8 @@ def copy_file( # noqa: C901 verbose=1, dry_run=0, ): - """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is - copied there with the same name; otherwise, it must be a filename. (If - the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' + """Copy a file 'src' to 'dst'. + (If the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' is true (the default), the file's mode (type and permission bits, or whatever is analogous on the current platform) is copied. If 'preserve_times' is true (the default), the last-modified and @@ -109,12 +108,6 @@ def copy_file( # noqa: C901 "can't copy '%s': doesn't exist or not a regular file" % src ) - if os.path.isdir(dst): - dir = dst - dst = os.path.join(dst, os.path.basename(src)) - else: - dir = os.path.dirname(dst) - if update and not newer(src, dst): if verbose >= 1: log.debug("not copying %s (output up-to-date)", src) @@ -126,10 +119,7 @@ def copy_file( # noqa: C901 raise ValueError("invalid value '%s' for 'link' argument" % link) if verbose >= 1: - if os.path.basename(dst) == os.path.basename(src): - log.info("%s %s -> %s", action, src, dir) - else: - log.info("%s %s -> %s", action, src, dst) + log.info("%s %s -> %s", action, src, dst) if dry_run: return (dst, 1) diff --git a/distutils/tests/test_dir_util.py b/distutils/tests/test_dir_util.py index 84cda619..8d4dc9fd 100644 --- a/distutils/tests/test_dir_util.py +++ b/distutils/tests/test_dir_util.py @@ -73,9 +73,10 @@ def test_copy_tree_verbosity(self, caplog): mkpath(self.target, verbose=0) a_file = path.Path(self.target) / 'ok.txt' + to_file = path.Path(self.target2) / 'ok.txt' jaraco.path.build({'ok.txt': 'some content'}, self.target) - wanted = [f'copying {a_file} -> {self.target2}'] + wanted = [f'copying {a_file} -> {to_file}'] copy_tree(self.target, self.target2, verbose=1) assert caplog.messages == wanted