diff --git a/distutils/cmd.py b/distutils/cmd.py index 241621bd..47e84a11 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -11,8 +11,8 @@ import re import sys from abc import abstractmethod -from collections.abc import Callable, MutableSequence -from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, overload +from collections.abc import Callable, MutableSequence, Sequence +from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeVar, overload from . import _modified, archive_util, dir_util, file_util, util from ._log import log @@ -467,6 +467,20 @@ def move_file( """Move a file respecting dry-run flag.""" return file_util.move_file(src, dst, dry_run=self.dry_run) + @overload + def spawn( + self, + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + search_path: Literal[False], + level: int = 1, + ) -> None: ... + @overload + def spawn( + self, + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + search_path: Literal[True] = True, + level: int = 1, + ) -> None: ... def spawn( self, cmd: MutableSequence[str], search_path: bool = True, level: int = 1 ) -> None: diff --git a/distutils/compilers/C/base.py b/distutils/compilers/C/base.py index 93385e13..f62bb471 100644 --- a/distutils/compilers/C/base.py +++ b/distutils/compilers/C/base.py @@ -10,7 +10,7 @@ import re import sys import warnings -from collections.abc import Callable, Iterable, MutableSequence, Sequence +from collections.abc import Callable, Iterable, Sequence from typing import ( TYPE_CHECKING, ClassVar, @@ -39,6 +39,8 @@ ) if TYPE_CHECKING: + from subprocess import _ENV + from typing_extensions import TypeAlias, TypeVarTuple, Unpack _Ts = TypeVarTuple("_Ts") @@ -70,7 +72,7 @@ class Compiler: # dictionary (see below -- used by the 'new_compiler()' factory # function) -- authors of new compiler interface classes are # responsible for updating 'compiler_class'! - compiler_type: ClassVar[str] = None # type: ignore[assignment] + compiler_type: ClassVar[str] = None # XXX things not handled by this compiler abstraction model: # * client can't provide additional options for a compiler, @@ -1152,8 +1154,28 @@ def execute( ) -> None: execute(func, args, msg, self.dry_run) + @overload + def spawn( + self, + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + *, + search_path: Literal[False], + verbose: bool = False, + env: _ENV | None = None, + ) -> None: ... + @overload + def spawn( + self, + cmd: Sequence[bytes | str | os.PathLike[str]], + *, + search_path: Literal[True] = True, + verbose: bool = False, + env: _ENV | None = None, + ) -> None: ... def spawn( - self, cmd: MutableSequence[bytes | str | os.PathLike[str]], **kwargs + self, + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + **kwargs, ) -> None: spawn(cmd, dry_run=self.dry_run, **kwargs) diff --git a/distutils/compilers/C/msvc.py b/distutils/compilers/C/msvc.py index 6db062a9..fd088c60 100644 --- a/distutils/compilers/C/msvc.py +++ b/distutils/compilers/C/msvc.py @@ -18,7 +18,8 @@ import subprocess import unittest.mock as mock import warnings -from collections.abc import Iterable +from collections.abc import Iterable, Sequence +from typing import Literal, overload with contextlib.suppress(ImportError): import winreg @@ -95,7 +96,8 @@ def _find_vc2017(): subprocess.CalledProcessError, OSError, UnicodeDecodeError ): path = ( - subprocess.check_output([ + subprocess + .check_output([ os.path.join( root, "Microsoft Visual Studio", "Installer", "vswhere.exe" ), @@ -557,14 +559,34 @@ def link( else: log.debug("skipping %s (up-to-date)", output_filename) - def spawn(self, cmd): + @overload # type: ignore[override] # env param not available + def spawn( + self, + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + *, + search_path: Literal[False], + verbose: bool = False, + ) -> None: ... + @overload + def spawn( + self, + cmd: Sequence[bytes | str | os.PathLike[str]], + *, + search_path: Literal[True] = True, + verbose: bool = False, + ) -> None: ... + def spawn( + self, + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + **kwargs, + ): env = dict(os.environ, PATH=self._paths) - with self._fallback_spawn(cmd, env) as fallback: - return super().spawn(cmd, env=env) + with self._fallback_spawn(cmd, env, **kwargs) as fallback: + return super().spawn(cmd, env=env, **kwargs) return fallback.value @contextlib.contextmanager - def _fallback_spawn(self, cmd, env): + def _fallback_spawn(self, cmd, env, **kwargs): """ Discovered in pypa/distutils#15, some tools monkeypatch the compiler, so the 'env' kwarg causes a TypeError. Detect this condition and @@ -580,7 +602,7 @@ def _fallback_spawn(self, cmd, env): return warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.") with mock.patch.dict('os.environ', env): - bag.value = super().spawn(cmd) + bag.value = super().spawn(cmd, **kwargs) # -- Miscellaneous methods ----------------------------------------- # These are all used by the 'gen_lib_options() function, in diff --git a/distutils/compilers/C/tests/test_base.py b/distutils/compilers/C/tests/test_base.py index a762e2b6..64a17364 100644 --- a/distutils/compilers/C/tests/test_base.py +++ b/distutils/compilers/C/tests/test_base.py @@ -18,7 +18,8 @@ def c_file(tmp_path): all_headers = gen_headers + plat_headers headers = '\n'.join(f'#include <{header}>\n' for header in all_headers) payload = ( - textwrap.dedent( + textwrap + .dedent( """ #headers void PyInit_foo(void) {} diff --git a/distutils/spawn.py b/distutils/spawn.py index 973668f2..7074698c 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -12,8 +12,8 @@ import subprocess import sys import warnings -from collections.abc import Mapping, MutableSequence -from typing import TYPE_CHECKING, TypeVar, overload +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Literal, TypeVar, overload from ._log import log from .debug import DEBUG @@ -52,8 +52,24 @@ def _resolve(env: _MappingT | None) -> _MappingT | os._Environ[str]: return os.environ if env is None else env +@overload +def spawn( + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], + search_path: Literal[False], + verbose: bool = False, + dry_run: bool = False, + env: _ENV | None = None, +) -> None: ... +@overload +def spawn( + cmd: Sequence[bytes | str | os.PathLike[str]], + search_path: Literal[True] = True, + verbose: bool = False, + dry_run: bool = False, + env: _ENV | None = None, +) -> None: ... def spawn( - cmd: MutableSequence[bytes | str | os.PathLike[str]], + cmd: Sequence[bytes | os.PathLike[bytes] | str | os.PathLike[str]], search_path: bool = True, verbose: bool = False, dry_run: bool = False, @@ -81,7 +97,7 @@ def spawn( if search_path: executable = shutil.which(cmd[0]) if executable is not None: - cmd[0] = executable + cmd = [executable, *cmd[1:]] try: subprocess.check_call(cmd, env=_inject_macos_ver(env))