From 19e95750f3465db016708f36f9ee51d155631994 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 01:31:32 -0500 Subject: [PATCH 01/11] Merge typeshed's `setuptools._distutils` annotations --- distutils/_modified.py | 28 +- distutils/archive_util.py | 42 +-- distutils/cmd.py | 154 ++++++++--- distutils/command/bdist.py | 25 +- distutils/command/bdist_dumb.py | 3 +- distutils/command/bdist_rpm.py | 11 +- distutils/command/build.py | 10 +- distutils/command/build_clib.py | 13 +- distutils/command/build_ext.py | 32 ++- distutils/command/build_py.py | 19 +- distutils/command/build_scripts.py | 2 +- distutils/command/check.py | 2 +- distutils/command/clean.py | 3 +- distutils/command/install.py | 39 +-- distutils/command/install_data.py | 7 +- distutils/command/install_headers.py | 2 +- distutils/command/install_lib.py | 16 +- distutils/command/install_scripts.py | 7 +- distutils/command/sdist.py | 36 +-- distutils/compat/__init__.py | 7 +- distutils/compilers/C/base.py | 388 ++++++++++++++++++--------- distutils/compilers/C/msvc.py | 46 ++-- distutils/compilers/C/unix.py | 25 +- distutils/dist.py | 230 ++++++++++------ distutils/errors.py | 21 +- distutils/extension.py | 33 +-- distutils/filelist.py | 93 +++++-- distutils/spawn.py | 24 +- distutils/sysconfig.py | 37 ++- distutils/tests/test_sdist.py | 2 +- distutils/util.py | 61 +++-- 31 files changed, 936 insertions(+), 482 deletions(-) diff --git a/distutils/_modified.py b/distutils/_modified.py index 7cdca939..57931897 100644 --- a/distutils/_modified.py +++ b/distutils/_modified.py @@ -2,12 +2,21 @@ import functools import os.path +from collections.abc import Callable, Iterable +from typing import Literal, TypeVar from jaraco.functools import splat from .compat.py39 import zip_strict from .errors import DistutilsFileError +_SourcesT = TypeVar( + "_SourcesT", bound=str | bytes | os.PathLike[str] | os.PathLike[bytes] +) +_TargetsT = TypeVar( + "_TargetsT", bound=str | bytes | os.PathLike[str] | os.PathLike[bytes] +) + def _newer(source, target): return not os.path.exists(target) or ( @@ -15,7 +24,10 @@ def _newer(source, target): ) -def newer(source, target): +def newer( + source: str | bytes | os.PathLike[str] | os.PathLike[bytes], + target: str | bytes | os.PathLike[str] | os.PathLike[bytes], +) -> bool: """ Is source modified more recently than target. @@ -25,12 +37,16 @@ def newer(source, target): Raises DistutilsFileError if 'source' does not exist. """ if not os.path.exists(source): - raise DistutilsFileError(f"file '{os.path.abspath(source)}' does not exist") + raise DistutilsFileError(f"file {os.path.abspath(source)!r} does not exist") return _newer(source, target) -def newer_pairwise(sources, targets, newer=newer): +def newer_pairwise( + sources: Iterable[_SourcesT], + targets: Iterable[_TargetsT], + newer: Callable[[_SourcesT, _TargetsT], bool] = newer, +) -> tuple[list[_SourcesT], list[_TargetsT]]: """ Filter filenames where sources are newer than targets. @@ -43,7 +59,11 @@ def newer_pairwise(sources, targets, newer=newer): return tuple(map(list, zip(*newer_pairs))) or ([], []) -def newer_group(sources, target, missing='error'): +def newer_group( + sources: Iterable[str | bytes | os.PathLike[str] | os.PathLike[bytes]], + target: str | bytes | os.PathLike[str] | os.PathLike[bytes], + missing: Literal["error", "ignore", "newer"] = "error", +) -> bool: """ Is target out-of-date with respect to any file in sources. diff --git a/distutils/archive_util.py b/distutils/archive_util.py index 5bb6df76..167a5c2e 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -4,6 +4,7 @@ that sort of thing).""" import os +from typing import Literal try: import zipfile @@ -54,14 +55,14 @@ def _get_uid(name): def make_tarball( - base_name, - base_dir, - compress="gzip", - verbose=False, - dry_run=False, - owner=None, - group=None, -): + base_name: str, + base_dir: str | os.PathLike[str], + compress: Literal["gzip", "bzip2", "xz"] | None = "gzip", + verbose: bool = False, + dry_run: bool = False, + owner: str | None = None, + group: str | None = None, +) -> str: """Create a (possibly compressed) tar file from all the files under 'base_dir'. @@ -122,7 +123,12 @@ def _set_uid_gid(tarinfo): return archive_name -def make_zipfile(base_name, base_dir, verbose=False, dry_run=False): # noqa: C901 +def make_zipfile( + base_name: str, + base_dir: str | os.PathLike[str], + verbose: bool = False, + dry_run: bool = False, +) -> str: # noqa: C901 """Create a zip file from all the files under 'base_dir'. The output zip file will be named 'base_name' + ".zip". Uses either the @@ -205,15 +211,15 @@ def check_archive_formats(formats): def make_archive( - base_name, - format, - root_dir=None, - base_dir=None, - verbose=False, - dry_run=False, - owner=None, - group=None, -): + base_name: str, + format: str, + root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None, + base_dir: str | None = None, + verbose: bool = False, + dry_run: bool = False, + owner: str | None = None, + group: str | None = None, +) -> str: """Create an archive file (eg. zip or tar). 'base_name' is the name of the file to create, minus any format-specific diff --git a/distutils/cmd.py b/distutils/cmd.py index 9c6fa656..db346730 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -10,13 +10,24 @@ import os import re import sys -from collections.abc import Callable -from typing import Any, ClassVar, TypeVar, overload +from abc import abstractmethod +from collections.abc import Callable, MutableSequence +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, overload from . import _modified, archive_util, dir_util, file_util, util from ._log import log from .errors import DistutilsOptionError +if TYPE_CHECKING: + # type-only import because of mutual dependence between these classes + from distutils.dist import Distribution + + from typing_extensions import TypeVarTuple, Unpack + + _Ts = TypeVarTuple("_Ts") + +_StrPathT = TypeVar("_StrPathT", bound="str | os.PathLike[str]") +_BytesPathT = TypeVar("_BytesPathT", bound="bytes | os.PathLike[bytes]") _CommandT = TypeVar("_CommandT", bound="Command") @@ -61,7 +72,7 @@ class Command: # -- Creation/initialization methods ------------------------------- - def __init__(self, dist): + def __init__(self, dist: Distribution) -> None: """Create and initialize a new Command object. Most importantly, invokes the 'initialize_options()' method, which is the real initializer and depends on the actual command being @@ -119,7 +130,7 @@ def __getattr__(self, attr): else: raise AttributeError(attr) - def ensure_finalized(self): + def ensure_finalized(self) -> None: if not self.finalized: self.finalize_options() self.finalized = True @@ -137,7 +148,8 @@ def ensure_finalized(self): # run the command: do whatever it is we're here to do, # controlled by the command's various option values - def initialize_options(self): + @abstractmethod + def initialize_options(self) -> None: """Set default values for all the options that this command supports. Note that these defaults may be overridden by other commands, by the setup script, by config files, or by the @@ -151,7 +163,8 @@ def initialize_options(self): f"abstract method -- subclass {self.__class__} must override" ) - def finalize_options(self): + @abstractmethod + def finalize_options(self) -> None: """Set final values for all the options that this command supports. This is always called as late as possible, ie. after any option assignments from the command-line or from other commands have been @@ -180,7 +193,8 @@ def dump_options(self, header=None, indent=""): value = getattr(self, option) self.announce(indent + f"{option} = {value}", level=logging.INFO) - def run(self): + @abstractmethod + def run(self) -> None: """A command's raison d'etre: carry out the action it exists to perform, controlled by the options initialized in 'initialize_options()', customized by other commands, the setup @@ -194,10 +208,10 @@ def run(self): f"abstract method -- subclass {self.__class__} must override" ) - def announce(self, msg, level=logging.DEBUG): + def announce(self, msg: object, level: int = logging.DEBUG) -> None: log.log(level, msg) - def debug_print(self, msg): + def debug_print(self, msg: object) -> None: """Print 'msg' to stdout if the global DEBUG (taken from the DISTUTILS_DEBUG environment variable) flag is true. """ @@ -229,13 +243,13 @@ def _ensure_stringlike(self, option, what, default=None): raise DistutilsOptionError(f"'{option}' must be a {what} (got `{val}`)") return val - def ensure_string(self, option, default=None): + def ensure_string(self, option: str, default: str | None = None) -> None: """Ensure that 'option' is a string; if not defined, set it to 'default'. """ self._ensure_stringlike(option, "string", default) - def ensure_string_list(self, option): + def ensure_string_list(self, option: str) -> None: r"""Ensure that 'option' is a list of strings. If 'option' is currently a string, we split it either on /,\s*/ or /\s+/, so "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become @@ -263,13 +277,13 @@ def _ensure_tested_string(self, option, tester, what, error_fmt, default=None): ("error in '%s' option: " + error_fmt) % (option, val) ) - def ensure_filename(self, option): + def ensure_filename(self, option: str) -> None: """Ensure that 'option' is the name of an existing file.""" self._ensure_tested_string( option, os.path.isfile, "filename", "'%s' does not exist or is not a file" ) - def ensure_dirname(self, option): + def ensure_dirname(self, option: str) -> None: self._ensure_tested_string( option, os.path.isdir, @@ -279,13 +293,15 @@ def ensure_dirname(self, option): # -- Convenience methods for commands ------------------------------ - def get_command_name(self): + def get_command_name(self) -> str: if hasattr(self, 'command_name'): return self.command_name else: return self.__class__.__name__ - def set_undefined_options(self, src_cmd, *option_pairs): + def set_undefined_options( + self, src_cmd: str, *option_pairs: tuple[str, str] + ) -> None: """Set the values of any "undefined" options from corresponding option values in some other command object. "Undefined" here means "is None", which is the convention used to indicate that an option @@ -306,7 +322,9 @@ def set_undefined_options(self, src_cmd, *option_pairs): if getattr(self, dst_option) is None: setattr(self, dst_option, getattr(src_cmd_obj, src_option)) - def get_finalized_command(self, command, create=True): + # NOTE: Because distutils is private setuptools implementation and we don't need to re-expose all commands here, + # we're not overloading each and every command possibility. + def get_finalized_command(self, command: str, create: bool = True) -> Command: """Wrapper around Distribution's 'get_command_obj()' method: find (create if necessary and 'create' is true) the command object for 'command', call its 'ensure_finalized()' method, and return the @@ -331,14 +349,14 @@ def reinitialize_command( ) -> Command: return self.distribution.reinitialize_command(command, reinit_subcommands) - def run_command(self, command): + def run_command(self, command: str) -> None: """Run some other command: uses the 'run_command()' method of Distribution, which creates and finalizes the command object if necessary and then invokes its 'run()' method. """ self.distribution.run_command(command) - def get_sub_commands(self): + def get_sub_commands(self) -> list[str]: """Determine the sub-commands that are relevant in the current distribution (ie., that need to be run). This is based on the 'sub_commands' class attribute: each tuple in that list may include @@ -353,24 +371,50 @@ def get_sub_commands(self): # -- External world manipulation ----------------------------------- - def warn(self, msg): + def warn(self, msg: object) -> None: log.warning("warning: %s: %s\n", self.get_command_name(), msg) - def execute(self, func, args, msg=None, level=1): + def execute( + self, + func: Callable[[Unpack[_Ts]], object], + args: tuple[Unpack[_Ts]], + msg: object = None, + level: int = 1, + ) -> None: util.execute(func, args, msg, dry_run=self.dry_run) - def mkpath(self, name, mode=0o777): + def mkpath(self, name: str, mode: int = 0o777) -> None: dir_util.mkpath(name, mode, dry_run=self.dry_run) + @overload + def copy_file( + self, + infile: str | os.PathLike[str], + outfile: _StrPathT, + preserve_mode: bool = True, + preserve_times: bool = True, + link: str | None = None, + level: int = 1, + ) -> tuple[_StrPathT | str, bool]: ... + @overload def copy_file( self, - infile, - outfile, - preserve_mode=True, - preserve_times=True, - link=None, - level=1, - ): + infile: bytes | os.PathLike[bytes], + outfile: _BytesPathT, + preserve_mode: bool = True, + preserve_times: bool = True, + link: str | None = None, + level: int = 1, + ) -> tuple[_BytesPathT | bytes, bool]: ... + def copy_file( + self, + infile: str | os.PathLike[str] | bytes | os.PathLike[bytes], + outfile: str | os.PathLike[str] | bytes | os.PathLike[bytes], + preserve_mode: bool = True, + preserve_times: bool = True, + link: str | None = None, + level: int = 1, + ) -> tuple[str | os.PathLike[str] | bytes | os.PathLike[bytes], bool]: """Copy a file respecting verbose, dry-run and force flags. (The former two default to whatever is in the Distribution object, and the latter defaults to false for commands that don't define it.)""" @@ -386,13 +430,13 @@ def copy_file( def copy_tree( self, - infile, - outfile, - preserve_mode=True, - preserve_times=True, - preserve_symlinks=False, - level=1, - ): + infile: str | os.PathLike[str], + outfile: str, + preserve_mode: bool = True, + preserve_times: bool = True, + preserve_symlinks: bool = False, + level: int = 1, + ) -> list[str]: """Copy an entire directory tree respecting verbose, dry-run, and force flags. """ @@ -406,19 +450,40 @@ def copy_tree( dry_run=self.dry_run, ) - def move_file(self, src, dst, level=1): + @overload + def move_file( + self, src: str | os.PathLike[str], dst: _StrPathT, level: int = 1 + ) -> _StrPathT | str: ... + @overload + def move_file( + self, src: bytes | os.PathLike[bytes], dst: _BytesPathT, level: int = 1 + ) -> _BytesPathT | bytes: ... + def move_file( + self, + src: str | os.PathLike[str] | bytes | os.PathLike[bytes], + dst: str | os.PathLike[str] | bytes | os.PathLike[bytes], + level: int = 1, + ) -> str | os.PathLike[str] | bytes | os.PathLike[bytes]: """Move a file respecting dry-run flag.""" return file_util.move_file(src, dst, dry_run=self.dry_run) - def spawn(self, cmd, search_path=True, level=1): + def spawn( + self, cmd: MutableSequence[str], search_path: bool = True, level: int = 1 + ) -> None: """Spawn an external command respecting dry-run flag.""" from distutils.spawn import spawn spawn(cmd, search_path, dry_run=self.dry_run) def make_archive( - self, base_name, format, root_dir=None, base_dir=None, owner=None, group=None - ): + self, + base_name: str, + format: str, + root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None, + base_dir: str | None = None, + owner: str | None = None, + group: str | None = None, + ) -> str: return archive_util.make_archive( base_name, format, @@ -430,8 +495,15 @@ def make_archive( ) def make_file( - self, infiles, outfile, func, args, exec_msg=None, skip_msg=None, level=1 - ): + self, + infiles: str | list[str] | tuple[str, ...], + outfile: str | os.PathLike[str] | bytes | os.PathLike[bytes], + func: Callable[[Unpack[_Ts]], object], + args: tuple[Unpack[_Ts]], + exec_msg: object = None, + skip_msg: object = None, + level: int = 1, + ) -> None: """Special case of 'execute()' for operations that process one or more input files and generate one output file. Works just like 'execute()', except the operation is skipped and a different diff --git a/distutils/command/bdist.py b/distutils/command/bdist.py index 1ec3c35f..230bdeaf 100644 --- a/distutils/command/bdist.py +++ b/distutils/command/bdist.py @@ -5,12 +5,20 @@ import os import warnings -from typing import ClassVar +from collections.abc import Callable +from typing import TYPE_CHECKING, ClassVar from ..core import Command from ..errors import DistutilsOptionError, DistutilsPlatformError from ..util import get_platform +if TYPE_CHECKING: + from typing_extensions import deprecated +else: + + def deprecated(fn): + return fn + def show_formats(): """Print list of available formats (arguments to "--format" option).""" @@ -26,9 +34,10 @@ def show_formats(): class ListCompat(dict[str, tuple[str, str]]): # adapter to allow for Setuptools compatibility in format_commands - def append(self, item): + @deprecated("format_commands is now a dict. append is deprecated.") + def append(self, item: object) -> None: warnings.warn( - """format_commands is now a dict. append is deprecated.""", + "format_commands is now a dict. append is deprecated.", DeprecationWarning, stacklevel=2, ) @@ -64,9 +73,9 @@ class bdist(Command): ), ] - boolean_options = ['skip-build'] + boolean_options: ClassVar[list[str]] = ['skip-build'] - help_options = [ + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [ ('help-formats', None, "lists available distribution formats", show_formats), ] @@ -75,7 +84,7 @@ class bdist(Command): # This won't do in reality: will need to distinguish RPM-ish Linux, # Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS. - default_format = {'posix': 'gztar', 'nt': 'zip'} + default_format: ClassVar[dict[str, str]] = {'posix': 'gztar', 'nt': 'zip'} # Define commands in preferred order for the --help-formats option format_commands = ListCompat({ @@ -100,7 +109,7 @@ def initialize_options(self): self.group = None self.owner = None - def finalize_options(self): + def finalize_options(self) -> None: # have to finalize 'plat_name' before 'bdist_base' if self.plat_name is None: if self.skip_build: @@ -128,7 +137,7 @@ def finalize_options(self): if self.dist_dir is None: self.dist_dir = "dist" - def run(self): + def run(self) -> None: # Figure out which sub-commands we need to run. commands = [] for format in self.formats: diff --git a/distutils/command/bdist_dumb.py b/distutils/command/bdist_dumb.py index 67b0c8cc..ccad66f4 100644 --- a/distutils/command/bdist_dumb.py +++ b/distutils/command/bdist_dumb.py @@ -6,6 +6,7 @@ import os from distutils._log import log +from typing import ClassVar from ..core import Command from ..dir_util import ensure_relative, remove_tree @@ -54,7 +55,7 @@ class bdist_dumb(Command): ), ] - boolean_options = ['keep-temp', 'skip-build', 'relative'] + boolean_options: ClassVar[list[str]] = ['keep-temp', 'skip-build', 'relative'] default_format = {'posix': 'gztar', 'nt': 'zip'} diff --git a/distutils/command/bdist_rpm.py b/distutils/command/bdist_rpm.py index d443eb09..357b4e86 100644 --- a/distutils/command/bdist_rpm.py +++ b/distutils/command/bdist_rpm.py @@ -7,6 +7,7 @@ import subprocess import sys from distutils._log import log +from typing import ClassVar from ..core import Command from ..debug import DEBUG @@ -136,7 +137,7 @@ class bdist_rpm(Command): ('quiet', 'q', "Run the INSTALL phase of RPM building in quiet mode"), ] - boolean_options = [ + boolean_options: ClassVar[list[str]] = [ 'keep-temp', 'use-rpm-opt-flags', 'rpm3-mode', @@ -144,7 +145,7 @@ class bdist_rpm(Command): 'quiet', ] - negative_opt = { + negative_opt: ClassVar[dict[str, str]] = { 'no-keep-temp': 'keep-temp', 'no-rpm-opt-flags': 'use-rpm-opt-flags', 'rpm2-mode': 'rpm3-mode', @@ -195,7 +196,7 @@ def initialize_options(self): self.force_arch = None self.quiet = False - def finalize_options(self): + def finalize_options(self) -> None: self.set_undefined_options('bdist', ('bdist_base', 'bdist_base')) if self.rpm_base is None: if not self.rpm3_mode: @@ -228,7 +229,7 @@ def finalize_options(self): self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) self.finalize_package_data() - def finalize_package_data(self): + def finalize_package_data(self) -> None: self.ensure_string('group', "Development/Libraries") self.ensure_string( 'vendor', @@ -274,7 +275,7 @@ def finalize_package_data(self): self.ensure_string('force_arch') - def run(self): # noqa: C901 + def run(self) -> None: # noqa: C901 if DEBUG: print("before _get_package_data():") print("vendor =", self.vendor) diff --git a/distutils/command/build.py b/distutils/command/build.py index ccd2c706..9493cefe 100644 --- a/distutils/command/build.py +++ b/distutils/command/build.py @@ -5,6 +5,8 @@ import os import sys import sysconfig +from collections.abc import Callable +from typing import ClassVar from ..core import Command from ..errors import DistutilsOptionError @@ -43,9 +45,9 @@ class build(Command): ('executable=', 'e', "specify final destination interpreter path (build.py)"), ] - boolean_options = ['debug', 'force'] + boolean_options: ClassVar[list[str]] = ['debug', 'force'] - help_options = [ + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [ ('help-compiler', None, "list available compilers", show_compilers), ] @@ -65,7 +67,7 @@ def initialize_options(self): self.executable = None self.parallel = None - def finalize_options(self): # noqa: C901 + def finalize_options(self) -> None: # noqa: C901 if self.plat_name is None: self.plat_name = get_platform() else: @@ -126,7 +128,7 @@ def finalize_options(self): # noqa: C901 except ValueError: raise DistutilsOptionError("parallel should be an integer") - def run(self): + def run(self) -> None: # Run all relevant sub-commands. This will be some subset of: # - build_py - pure Python modules # - build_clib - standalone C libraries diff --git a/distutils/command/build_clib.py b/distutils/command/build_clib.py index 3e183276..2a1643d6 100644 --- a/distutils/command/build_clib.py +++ b/distutils/command/build_clib.py @@ -15,6 +15,7 @@ # cut 'n paste. Sigh. import os +from collections.abc import Callable from distutils._log import log from typing import ClassVar @@ -40,9 +41,9 @@ class build_clib(Command): ('compiler=', 'c', "specify the compiler type"), ] - boolean_options = ['debug', 'force'] + boolean_options: ClassVar[list[str]] = ['debug', 'force'] - help_options = [ + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [ ('help-compiler', None, "list available compilers", show_compilers), ] @@ -61,7 +62,7 @@ def initialize_options(self): self.force = False self.compiler = None - def finalize_options(self): + def finalize_options(self) -> None: # This might be confusing: both build-clib and build-temp default # to build-temp as defined by the "build" command. This is because # I think that C libraries are really just temporary build @@ -88,7 +89,7 @@ def finalize_options(self): # XXX same as for build_ext -- what about 'self.define' and # 'self.undef' ? - def run(self): + def run(self) -> None: if not self.libraries: return @@ -112,7 +113,7 @@ def run(self): self.build_libraries(self.libraries) - def check_library_list(self, libraries): + def check_library_list(self, libraries) -> None: """Ensure that the list of libraries is valid. `library` is presumably provided as a command option 'libraries'. @@ -174,7 +175,7 @@ def get_source_files(self): filenames.extend(sources) return filenames - def build_libraries(self, libraries): + def build_libraries(self, libraries) -> None: for lib_name, build_info in libraries: sources = build_info.get('sources') if sources is None or not isinstance(sources, (list, tuple)): diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index 3ec3663d..c25352f6 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -8,8 +8,10 @@ import os import re import sys +from collections.abc import Callable from distutils._log import log from site import USER_BASE +from typing import ClassVar from .._modified import newer_group from ..core import Command @@ -98,9 +100,15 @@ class build_ext(Command): ('user', None, "add user include, library and rpath"), ] - boolean_options = ['inplace', 'debug', 'force', 'swig-cpp', 'user'] + boolean_options: ClassVar[list[str]] = [ + 'inplace', + 'debug', + 'force', + 'swig-cpp', + 'user', + ] - help_options = [ + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [ ('help-compiler', None, "list available compilers", show_compilers), ] @@ -153,7 +161,7 @@ def _python_lib_dir(sysconfig): # building third party extensions yield sysconfig.get_config_var('LIBDIR') - def finalize_options(self): # noqa: C901 + def finalize_options(self) -> None: # noqa: C901 from distutils import sysconfig self.set_undefined_options( @@ -292,7 +300,7 @@ def finalize_options(self): # noqa: C901 except ValueError: raise DistutilsOptionError("parallel should be an integer") - def run(self): # noqa: C901 + def run(self) -> None: # noqa: C901 from ..ccompiler import new_compiler # 'self.extensions', as supplied by setup.py, is a list of @@ -364,7 +372,7 @@ def run(self): # noqa: C901 # Now actually compile and link everything. self.build_extensions() - def check_extensions_list(self, extensions): # noqa: C901 + def check_extensions_list(self, extensions) -> None: # noqa: C901 """Ensure that the list of extensions (presumably provided as a command option 'extensions') is valid, i.e. it is a list of Extension objects. We also support the old-style list of 2-tuples, @@ -472,7 +480,7 @@ def get_outputs(self): # "build" tree. return [self.get_ext_fullpath(ext.name) for ext in self.extensions] - def build_extensions(self): + def build_extensions(self) -> None: # First, sanity-check the 'extensions' list self.check_extensions_list(self.extensions) if self.parallel: @@ -515,7 +523,7 @@ def _filter_build_errors(self, ext): raise self.warn(f'building extension "{ext.name}" failed: {e}') - def build_extension(self, ext): + def build_extension(self, ext) -> None: sources = ext.sources if sources is None or not isinstance(sources, (list, tuple)): raise DistutilsSetupError( @@ -676,7 +684,7 @@ def find_swig(self): # -- Name generators ----------------------------------------------- # (extension names, filenames, whatever) - def get_ext_fullpath(self, ext_name): + def get_ext_fullpath(self, ext_name: str) -> str: """Returns the path of the filename for a given extension. The file is located in `build_lib` or directly in the package @@ -703,7 +711,7 @@ def get_ext_fullpath(self, ext_name): # package_dir/filename return os.path.join(package_dir, filename) - def get_ext_fullname(self, ext_name): + def get_ext_fullname(self, ext_name: str) -> str: """Returns the fullname of a given extension name. Adds the `package.` prefix""" @@ -712,7 +720,7 @@ def get_ext_fullname(self, ext_name): else: return self.package + '.' + ext_name - def get_ext_filename(self, ext_name): + def get_ext_filename(self, ext_name: str) -> str: r"""Convert the name of an extension (eg. "foo.bar") into the name of the file from which it will be loaded (eg. "foo/bar.so", or "foo\bar.pyd"). @@ -723,7 +731,7 @@ def get_ext_filename(self, ext_name): ext_suffix = get_config_var('EXT_SUFFIX') return os.path.join(*ext_path) + ext_suffix - def get_export_symbols(self, ext): + def get_export_symbols(self, ext: Extension) -> list[str]: """Return the list of symbols that a shared extension has to export. This either uses 'ext.export_symbols' or, if it's not provided, "PyInit_" + module_name. Only relevant on Windows, where @@ -753,7 +761,7 @@ def _get_module_name_for_symbol(self, ext): return parts[-2] return parts[-1] - def get_libraries(self, ext): # noqa: C901 + def get_libraries(self, ext: Extension) -> list[str]: # noqa: C901 """Return the list of libraries to link against when building a shared extension. On most platforms, this is just 'ext.libraries'; on Windows, we add the Python library (eg. python20.dll). diff --git a/distutils/command/build_py.py b/distutils/command/build_py.py index 49d71034..a20b076f 100644 --- a/distutils/command/build_py.py +++ b/distutils/command/build_py.py @@ -7,6 +7,7 @@ import os import sys from distutils._log import log +from typing import ClassVar from ..core import Command from ..errors import DistutilsFileError, DistutilsOptionError @@ -29,8 +30,8 @@ class build_py(Command): ('force', 'f', "forcibly build everything (ignore file timestamps)"), ] - boolean_options = ['compile', 'force'] - negative_opt = {'no-compile': 'compile'} + boolean_options: ClassVar[list[str]] = ['compile', 'force'] + negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'} def initialize_options(self): self.build_lib = None @@ -42,7 +43,7 @@ def initialize_options(self): self.optimize = 0 self.force = None - def finalize_options(self): + def finalize_options(self) -> None: self.set_undefined_options( 'build', ('build_lib', 'build_lib'), ('force', 'force') ) @@ -67,7 +68,7 @@ def finalize_options(self): except (ValueError, AssertionError): raise DistutilsOptionError("optimize must be 0, 1, or 2") - def run(self): + def run(self) -> None: # XXX copy_file by default preserves atime and mtime. IMHO this is # the right thing to do, but perhaps it should be an option -- in # particular, a site administrator might want installed files to @@ -134,7 +135,7 @@ def find_data_files(self, package, src_dir): ]) return files - def build_package_data(self): + def build_package_data(self) -> None: """Copy data files into build directory""" for _package, src_dir, build_dir, filenames in self.data_files: for filename in filenames: @@ -306,7 +307,7 @@ def get_module_outfile(self, build_dir, package, module): outfile_path = [build_dir] + list(package) + [module + ".py"] return os.path.join(*outfile_path) - def get_outputs(self, include_bytecode=True): + def get_outputs(self, include_bytecode: bool = True) -> list[str]: modules = self.find_all_modules() outputs = [] for package, module, _module_file in modules: @@ -349,7 +350,7 @@ def build_module(self, module, module_file, package): self.mkpath(dir) return self.copy_file(module_file, outfile, preserve_mode=False) - def build_modules(self): + def build_modules(self) -> None: modules = self.find_modules() for package, module, module_file in modules: # Now "build" the module -- ie. copy the source file to @@ -358,7 +359,7 @@ def build_modules(self): # under self.build_lib.) self.build_module(module, module_file, package) - def build_packages(self): + def build_packages(self) -> None: for package in self.packages: # Get list of (package, module, module_file) tuples based on # scanning the package directory. 'package' is only included @@ -378,7 +379,7 @@ def build_packages(self): assert package == package_ self.build_module(module, module_file, package) - def byte_compile(self, files): + def byte_compile(self, files) -> None: if sys.dont_write_bytecode: self.warn('byte-compiling is disabled, skipping.') return diff --git a/distutils/command/build_scripts.py b/distutils/command/build_scripts.py index 1c6fd3ca..3f7aae0a 100644 --- a/distutils/command/build_scripts.py +++ b/distutils/command/build_scripts.py @@ -32,7 +32,7 @@ class build_scripts(Command): ('executable=', 'e', "specify final destination interpreter path"), ] - boolean_options = ['force'] + boolean_options: ClassVar[list[str]] = ['force'] def initialize_options(self): self.build_dir = None diff --git a/distutils/command/check.py b/distutils/command/check.py index 078c1ce8..b6789671 100644 --- a/distutils/command/check.py +++ b/distutils/command/check.py @@ -52,7 +52,7 @@ class check(Command): ('strict', 's', 'Will exit with an error if a check fails'), ] - boolean_options = ['metadata', 'restructuredtext', 'strict'] + boolean_options: ClassVar[list[str]] = ['metadata', 'restructuredtext', 'strict'] def initialize_options(self): """Sets default values for options.""" diff --git a/distutils/command/clean.py b/distutils/command/clean.py index fb54a60e..23427aba 100644 --- a/distutils/command/clean.py +++ b/distutils/command/clean.py @@ -6,6 +6,7 @@ import os from distutils._log import log +from typing import ClassVar from ..core import Command from ..dir_util import remove_tree @@ -30,7 +31,7 @@ class clean(Command): ('all', 'a', "remove all build output, not just temporary by-products"), ] - boolean_options = ['all'] + boolean_options: ClassVar[list[str]] = ['all'] def initialize_options(self): self.build_base = None diff --git a/distutils/command/install.py b/distutils/command/install.py index 94009950..12f9d1fe 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -9,6 +9,7 @@ import sysconfig from distutils._log import log from site import USER_BASE, USER_SITE +from typing import ClassVar import jaraco.collections @@ -238,7 +239,7 @@ class install(Command): ('record=', None, "filename in which to record list of installed files"), ] - boolean_options = ['compile', 'force', 'skip-build'] + boolean_options: ClassVar[list[str]] = ['compile', 'force', 'skip-build'] if HAS_USER_SITE: user_options.append(( @@ -248,15 +249,15 @@ class install(Command): )) boolean_options.append('user') - negative_opt = {'no-compile': 'compile'} + negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'} - def initialize_options(self): + def initialize_options(self) -> None: """Initializes options.""" # High-level options: these select both an installation base # and scheme. - self.prefix = None - self.exec_prefix = None - self.home = None + self.prefix: str | None = None + self.exec_prefix: str | None = None + self.home: str | None = None self.user = False # These select only the installation base; it's up to the user to @@ -264,7 +265,7 @@ def initialize_options(self): # the --install-{platlib,purelib,scripts,data} options). self.install_base = None self.install_platbase = None - self.root = None + self.root: str | None = None # These options are the actual installation directories; if not # supplied by the user, they are filled in using the installation @@ -273,7 +274,7 @@ def initialize_options(self): self.install_purelib = None # for pure module distributions self.install_platlib = None # non-pure (dists w/ extensions) self.install_headers = None # for C/C++ headers - self.install_lib = None # set to either purelib or platlib + self.install_lib: str | None = None # set to either purelib or platlib self.install_scripts = None self.install_data = None self.install_userbase = USER_BASE @@ -327,7 +328,7 @@ def initialize_options(self): # party Python modules on various platforms given a wide # array of user input is decided. Yes, it's quite complex!) - def finalize_options(self): # noqa: C901 + def finalize_options(self) -> None: # noqa: C901 """Finalizes options.""" # This method (and its helpers, like 'finalize_unix()', # 'finalize_other()', and 'select_scheme()') is where the default @@ -510,7 +511,7 @@ def finalize_options(self): # noqa: C901 # Punt on doc directories for now -- after all, we're punting on # documentation completely! - def dump_dirs(self, msg): + def dump_dirs(self, msg) -> None: """Dumps the list of user options.""" if not DEBUG: return @@ -530,7 +531,7 @@ def dump_dirs(self, msg): val = getattr(self, opt_name) log.debug(" %s: %s", opt_name, val) - def finalize_unix(self): + def finalize_unix(self) -> None: """Finalizes options for posix platforms.""" if self.install_base is not None or self.install_platbase is not None: incomplete_scheme = ( @@ -579,7 +580,7 @@ def finalize_unix(self): self.install_platbase = self.exec_prefix self.select_scheme("posix_prefix") - def finalize_other(self): + def finalize_other(self) -> None: """Finalizes options for non-posix platforms""" if self.user: if self.install_userbase is None: @@ -601,7 +602,7 @@ def finalize_other(self): f"I don't know how to install stuff on '{os.name}'" ) - def select_scheme(self, name): + def select_scheme(self, name) -> None: _select_scheme(self, name) def _expand_attrs(self, attrs): @@ -613,12 +614,12 @@ def _expand_attrs(self, attrs): val = subst_vars(val, self.config_vars) setattr(self, attr, val) - def expand_basedirs(self): + def expand_basedirs(self) -> None: """Calls `os.path.expanduser` on install_base, install_platbase and root.""" self._expand_attrs(['install_base', 'install_platbase', 'root']) - def expand_dirs(self): + def expand_dirs(self) -> None: """Calls `os.path.expanduser` on install dirs.""" self._expand_attrs([ 'install_purelib', @@ -629,13 +630,13 @@ def expand_dirs(self): 'install_data', ]) - def convert_paths(self, *names): + def convert_paths(self, *names) -> None: """Call `convert_path` over `names`.""" for name in names: attr = "install_" + name setattr(self, attr, convert_path(getattr(self, attr))) - def handle_extra_path(self): + def handle_extra_path(self) -> None: """Set `path_file` and `extra_dirs` using `extra_path`.""" if self.extra_path is None: self.extra_path = self.distribution.extra_path @@ -670,13 +671,13 @@ def handle_extra_path(self): self.path_file = path_file self.extra_dirs = extra_dirs - def change_roots(self, *names): + def change_roots(self, *names) -> None: """Change the install directories pointed by name using root.""" for name in names: attr = "install_" + name setattr(self, attr, change_root(self.root, getattr(self, attr))) - def create_home_path(self): + def create_home_path(self) -> None: """Create directories under ~.""" if not self.user: return diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index 36f5bcc8..4ad186e8 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -10,6 +10,7 @@ import functools import os from collections.abc import Iterable +from typing import ClassVar from ..core import Command from ..util import change_root, convert_path @@ -28,7 +29,7 @@ class install_data(Command): ('force', 'f', "force installation (overwrite existing files)"), ] - boolean_options = ['force'] + boolean_options: ClassVar[list[str]] = ['force'] def initialize_options(self): self.install_dir = None @@ -38,7 +39,7 @@ def initialize_options(self): self.data_files = self.distribution.data_files self.warn_dir = True - def finalize_options(self): + def finalize_options(self) -> None: self.set_undefined_options( 'install', ('install_data', 'install_dir'), @@ -46,7 +47,7 @@ def finalize_options(self): ('force', 'force'), ) - def run(self): + def run(self) -> None: self.mkpath(self.install_dir) for f in self.data_files: self._copy(f) diff --git a/distutils/command/install_headers.py b/distutils/command/install_headers.py index 586121e0..97af1371 100644 --- a/distutils/command/install_headers.py +++ b/distutils/command/install_headers.py @@ -17,7 +17,7 @@ class install_headers(Command): ('force', 'f', "force installation (overwrite existing files)"), ] - boolean_options = ['force'] + boolean_options: ClassVar[list[str]] = ['force'] def initialize_options(self): self.install_dir = None diff --git a/distutils/command/install_lib.py b/distutils/command/install_lib.py index 4c1230a2..318a9db8 100644 --- a/distutils/command/install_lib.py +++ b/distutils/command/install_lib.py @@ -6,6 +6,7 @@ import importlib.util import os import sys +from typing import Any, ClassVar from ..core import Command from ..errors import DistutilsOptionError @@ -47,8 +48,8 @@ class install_lib(Command): ('skip-build', None, "skip the build steps"), ] - boolean_options = ['force', 'compile', 'skip-build'] - negative_opt = {'no-compile': 'compile'} + boolean_options: ClassVar[list[str]] = ['force', 'compile', 'skip-build'] + negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'} def initialize_options(self): # let the 'install' command dictate our installation directory @@ -59,7 +60,7 @@ def initialize_options(self): self.optimize = None self.skip_build = None - def finalize_options(self): + def finalize_options(self) -> None: # Get all the information we need to install pure Python modules # from the umbrella 'install' command -- build (source) directory, # install (target) directory, and whether to compile .py files. @@ -86,7 +87,7 @@ def finalize_options(self): if self.optimize not in (0, 1, 2): raise DistutilsOptionError("optimize must be 0, 1, or 2") - def run(self): + def run(self) -> None: # Make sure we have built everything we need first self.build() @@ -102,14 +103,15 @@ def run(self): # -- Top-level worker functions ------------------------------------ # (called from 'run()') - def build(self): + def build(self) -> None: if not self.skip_build: if self.distribution.has_pure_modules(): self.run_command('build_py') if self.distribution.has_ext_modules(): self.run_command('build_ext') - def install(self): + # Any: https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#the-any-trick + def install(self) -> list[str] | Any: if os.path.isdir(self.build_dir): outfiles = self.copy_tree(self.build_dir, self.install_dir) else: @@ -119,7 +121,7 @@ def install(self): return return outfiles - def byte_compile(self, files): + def byte_compile(self, files) -> None: if sys.dont_write_bytecode: self.warn('byte-compiling is disabled, skipping.') return diff --git a/distutils/command/install_scripts.py b/distutils/command/install_scripts.py index bb43387f..92e86941 100644 --- a/distutils/command/install_scripts.py +++ b/distutils/command/install_scripts.py @@ -8,6 +8,7 @@ import os from distutils._log import log from stat import ST_MODE +from typing import ClassVar from ..core import Command @@ -22,7 +23,7 @@ class install_scripts(Command): ('skip-build', None, "skip the build steps"), ] - boolean_options = ['force', 'skip-build'] + boolean_options: ClassVar[list[str]] = ['force', 'skip-build'] def initialize_options(self): self.install_dir = None @@ -30,7 +31,7 @@ def initialize_options(self): self.build_dir = None self.skip_build = None - def finalize_options(self): + def finalize_options(self) -> None: self.set_undefined_options('build', ('build_scripts', 'build_dir')) self.set_undefined_options( 'install', @@ -39,7 +40,7 @@ def finalize_options(self): ('skip_build', 'skip_build'), ) - def run(self): + def run(self) -> None: if not self.skip_build: self.run_command('build_scripts') self.outfiles = self.copy_tree(self.build_dir, self.install_dir) diff --git a/distutils/command/sdist.py b/distutils/command/sdist.py index acb3a416..abb8dba5 100644 --- a/distutils/command/sdist.py +++ b/distutils/command/sdist.py @@ -4,6 +4,7 @@ import os import sys +from collections.abc import Callable from distutils import archive_util, dir_util, file_util from distutils._log import log from glob import glob @@ -34,7 +35,7 @@ def show_formats(): class sdist(Command): description = "create a source distribution (tarball, zip file, etc.)" - def checking_metadata(self): + def checking_metadata(self) -> bool: """Callable used for the check sub-command. Placed here so user_options can view it""" @@ -98,7 +99,7 @@ def checking_metadata(self): ), ] - boolean_options = [ + boolean_options: ClassVar[list[str]] = [ 'use-defaults', 'prune', 'manifest-only', @@ -107,11 +108,14 @@ def checking_metadata(self): 'metadata-check', ] - help_options = [ + help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], object]]]] = [ ('help-formats', None, "list available distribution formats", show_formats), ] - negative_opt = {'no-defaults': 'use-defaults', 'no-prune': 'prune'} + negative_opt: ClassVar[dict[str, str]] = { + 'no-defaults': 'use-defaults', + 'no-prune': 'prune', + } sub_commands = [('check', checking_metadata)] @@ -136,11 +140,11 @@ def initialize_options(self): self.dist_dir = None self.archive_files = None - self.metadata_check = 1 + self.metadata_check = True self.owner = None self.group = None - def finalize_options(self): + def finalize_options(self) -> None: if self.manifest is None: self.manifest = "MANIFEST" if self.template is None: @@ -155,7 +159,7 @@ def finalize_options(self): if self.dist_dir is None: self.dist_dir = "dist" - def run(self): + def run(self) -> None: # 'filelist' contains the list of files that will make up the # manifest self.filelist = FileList() @@ -177,7 +181,7 @@ def run(self): # or zipfile, or whatever. self.make_distribution() - def get_file_list(self): + def get_file_list(self) -> None: """Figure out the list of files to include in the source distribution, and put it in 'self.filelist'. This might involve reading the manifest template (and writing the manifest), or just @@ -218,7 +222,7 @@ def get_file_list(self): self.filelist.remove_duplicates() self.write_manifest() - def add_defaults(self): + def add_defaults(self) -> None: """Add all the default files to self.filelist: - README or README.txt - setup.py @@ -333,7 +337,7 @@ def _add_defaults_scripts(self): build_scripts = self.get_finalized_command('build_scripts') self.filelist.extend(build_scripts.get_source_files()) - def read_template(self): + def read_template(self) -> None: """Read and parse manifest template file named by self.template. (usually "MANIFEST.in") The parsing and processing is done by @@ -368,7 +372,7 @@ def read_template(self): finally: template.close() - def prune_file_list(self): + def prune_file_list(self) -> None: """Prune off branches that might slip into the file list as created by 'read_template()', but really don't belong there: * the build tree (typically "build") @@ -391,7 +395,7 @@ def prune_file_list(self): vcs_ptrn = r'(^|{})({})({}).*'.format(seps, '|'.join(vcs_dirs), seps) self.filelist.exclude_pattern(vcs_ptrn, is_regex=True) - def write_manifest(self): + def write_manifest(self) -> None: """Write the file list in 'self.filelist' (presumably as filled in by 'add_defaults()' and 'read_template()') to the manifest file named by 'self.manifest'. @@ -419,7 +423,7 @@ def _manifest_is_not_generated(self): first_line = next(fp) return first_line != '# file GENERATED by distutils, do NOT edit\n' - def read_manifest(self): + def read_manifest(self) -> None: """Read the manifest file (named by 'self.manifest') and use it to fill in 'self.filelist', the list of files to include in the source distribution. @@ -431,7 +435,7 @@ def read_manifest(self): filter(None, filterfalse(is_comment, map(str.strip, lines))) ) - def make_release_tree(self, base_dir, files): + def make_release_tree(self, base_dir, files) -> None: """Create the directory tree that will become the source distribution archive. All directories implied by the filenames in 'files' are created under 'base_dir', and then we hard link or copy @@ -473,7 +477,7 @@ def make_release_tree(self, base_dir, files): self.distribution.metadata.write_pkg_info(base_dir) - def make_distribution(self): + def make_distribution(self) -> None: """Create the source distribution(s). First, we create the release tree with 'make_release_tree()'; then, we create all required archive files (according to 'self.formats') from the release tree. @@ -511,5 +515,5 @@ def get_archive_files(self): return self.archive_files -def is_comment(line): +def is_comment(line: str) -> bool: return line.startswith('#') diff --git a/distutils/compat/__init__.py b/distutils/compat/__init__.py index c715ee9c..2c43729b 100644 --- a/distutils/compat/__init__.py +++ b/distutils/compat/__init__.py @@ -1,7 +1,12 @@ from __future__ import annotations +from collections.abc import Iterable +from typing import TypeVar -def consolidate_linker_args(args: list[str]) -> list[str] | str: +_IterableT = TypeVar("_IterableT", bound="Iterable[str]") + + +def consolidate_linker_args(args: _IterableT) -> _IterableT | str: """ Ensure the return value is a string for backward compatibility. diff --git a/distutils/compilers/C/base.py b/distutils/compilers/C/base.py index 84a19993..c6ec9055 100644 --- a/distutils/compilers/C/base.py +++ b/distutils/compilers/C/base.py @@ -3,12 +3,24 @@ Contains Compiler, an abstract base class that defines the interface for the Distutils compiler abstraction model.""" +from __future__ import annotations + import os import pathlib import re import sys -import types import warnings +from collections.abc import Callable, Iterable, MutableSequence, Sequence +from typing import ( + TYPE_CHECKING, + ClassVar, + Literal, + Tuple, + TypeAlias, + TypeVar, + Union, + overload, +) from more_itertools import always_iterable @@ -28,6 +40,15 @@ UnknownFileType, ) +if TYPE_CHECKING: + from typing_extensions import TypeVarTuple, Unpack + + _Ts = TypeVarTuple("_Ts") + +_Macro: TypeAlias = Union[Tuple[str], Tuple[str, str | None]] +_StrPathT = TypeVar("_StrPathT", bound=str | os.PathLike[str]) +_BytesPathT = TypeVar("_BytesPathT", bound=bytes | os.PathLike[bytes]) + class Compiler: """Abstract base class to define the interface that must be implemented @@ -51,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 = None + compiler_type: ClassVar[str] = None # type: ignore[assignment] # XXX things not handled by this compiler abstraction model: # * client can't provide additional options for a compiler, @@ -73,16 +94,18 @@ class Compiler: # think this is useless without the ability to null out the # library search path anyways. + executables: ClassVar[dict] + # Subclasses that rely on the standard filename generation methods # implemented below should override these; see the comment near # those methods ('object_filenames()' et. al.) for details: - src_extensions = None # list of strings - obj_extension = None # string - static_lib_extension = None - shared_lib_extension = None # string - static_lib_format = None # format string - shared_lib_format = None # prob. same as static_lib_format - exe_extension = None # string + src_extensions: ClassVar[list[str] | None] = None + obj_extension: ClassVar[str | None] = None + static_lib_extension: ClassVar[str | None] = None + shared_lib_extension: ClassVar[str | None] = None + static_lib_format: ClassVar[str | None] = None # format string + shared_lib_format: ClassVar[str | None] = None # prob. same as static_lib_format + exe_extension: ClassVar[str | None] = None # Default language settings. language_map is used to detect a source # file or Extension target language, checking source filenames. @@ -90,14 +113,14 @@ class Compiler: # what language to use when mixing source types. For example, if some # extension has two files with ".c" extension, and one with ".cpp", it # is still linked as c++. - language_map = { + language_map: ClassVar[dict[str, str]] = { ".c": "c", ".cc": "c++", ".cpp": "c++", ".cxx": "c++", ".m": "objc", } - language_order = ["c++", "objc", "c"] + language_order: ClassVar[list[str]] = ["c++", "objc", "c"] include_dirs = [] """ @@ -109,43 +132,45 @@ class Compiler: library dirs specific to this compiler class """ - def __init__(self, verbose=False, dry_run=False, force=False): + def __init__( + self, verbose: bool = False, dry_run: bool = False, force: bool = False + ) -> None: self.dry_run = dry_run self.force = force self.verbose = verbose # 'output_dir': a common output directory for object, library, # shared object, and shared library files - self.output_dir = None + self.output_dir: str | None = None # 'macros': a list of macro definitions (or undefinitions). A # macro definition is a 2-tuple (name, value), where the value is # either a string or None (no explicit value). A macro # undefinition is a 1-tuple (name,). - self.macros = [] + self.macros: list[_Macro] = [] # 'include_dirs': a list of directories to search for include files - self.include_dirs = [] + self.include_dirs: list[str] = [] # 'libraries': a list of libraries to include in any link # (library names, not filenames: eg. "foo" not "libfoo.a") - self.libraries = [] + self.libraries: list[str] = [] # 'library_dirs': a list of directories to search for libraries - self.library_dirs = [] + self.library_dirs: list[str] = [] # 'runtime_library_dirs': a list of directories to search for # shared libraries/objects at runtime - self.runtime_library_dirs = [] + self.runtime_library_dirs: list[str] = [] # 'objects': a list of object files (or similar, such as explicitly # named library files) to include on any link - self.objects = [] + self.objects: list[str] = [] for key in self.executables.keys(): self.set_executable(key, self.executables[key]) - def set_executables(self, **kwargs): + def set_executables(self, **kwargs: str) -> None: """Define the executables (and options for them) that will be run to perform the various stages of compilation. The exact set of executables that may be specified here depends on the compiler @@ -214,11 +239,11 @@ def _is_valid_macro(name, value=None): """ A valid macro is a ``name : str`` and a ``value : str | None``. """ - return isinstance(name, str) and isinstance(value, (str, types.NoneType)) + return isinstance(name, str) and isinstance(value, (str, None)) # -- Bookkeeping methods ------------------------------------------- - def define_macro(self, name, value=None): + def define_macro(self, name: str, value: str | None = None) -> None: """Define a preprocessor macro for all compilations driven by this compiler object. The optional parameter 'value' should be a string; if it is not supplied, then the macro will be defined @@ -233,7 +258,7 @@ def define_macro(self, name, value=None): self.macros.append((name, value)) - def undefine_macro(self, name): + def undefine_macro(self, name: str) -> None: """Undefine a preprocessor macro for all compilations driven by this compiler object. If the same macro is defined by 'define_macro()' and undefined by 'undefine_macro()' the last call @@ -251,7 +276,7 @@ def undefine_macro(self, name): undefn = (name,) self.macros.append(undefn) - def add_include_dir(self, dir): + def add_include_dir(self, dir: str) -> None: """Add 'dir' to the list of directories that will be searched for header files. The compiler is instructed to search directories in the order in which they are supplied by successive calls to @@ -259,7 +284,7 @@ def add_include_dir(self, dir): """ self.include_dirs.append(dir) - def set_include_dirs(self, dirs): + def set_include_dirs(self, dirs: list[str]) -> None: """Set the list of directories that will be searched to 'dirs' (a list of strings). Overrides any preceding calls to 'add_include_dir()'; subsequence calls to 'add_include_dir()' add @@ -269,7 +294,7 @@ def set_include_dirs(self, dirs): """ self.include_dirs = dirs[:] - def add_library(self, libname): + def add_library(self, libname: str) -> None: """Add 'libname' to the list of libraries that will be included in all links driven by this compiler object. Note that 'libname' should *not* be the name of a file containing a library, but the @@ -285,7 +310,7 @@ def add_library(self, libname): """ self.libraries.append(libname) - def set_libraries(self, libnames): + def set_libraries(self, libnames: list[str]) -> None: """Set the list of libraries to be included in all links driven by this compiler object to 'libnames' (a list of strings). This does not affect any standard system libraries that the linker may @@ -293,7 +318,7 @@ def set_libraries(self, libnames): """ self.libraries = libnames[:] - def add_library_dir(self, dir): + def add_library_dir(self, dir: str) -> None: """Add 'dir' to the list of directories that will be searched for libraries specified to 'add_library()' and 'set_libraries()'. The linker will be instructed to search for libraries in the order they @@ -301,20 +326,20 @@ def add_library_dir(self, dir): """ self.library_dirs.append(dir) - def set_library_dirs(self, dirs): + def set_library_dirs(self, dirs: list[str]) -> None: """Set the list of library search directories to 'dirs' (a list of strings). This does not affect any standard library search path that the linker may search by default. """ self.library_dirs = dirs[:] - def add_runtime_library_dir(self, dir): + def add_runtime_library_dir(self, dir: str) -> None: """Add 'dir' to the list of directories that will be searched for shared libraries at runtime. """ self.runtime_library_dirs.append(dir) - def set_runtime_library_dirs(self, dirs): + def set_runtime_library_dirs(self, dirs: list[str]) -> None: """Set the list of directories to search for shared libraries at runtime to 'dirs' (a list of strings). This does not affect any standard search path that the runtime linker may search by @@ -322,7 +347,7 @@ def set_runtime_library_dirs(self, dirs): """ self.runtime_library_dirs = dirs[:] - def add_link_object(self, object): + def add_link_object(self, object: str) -> None: """Add 'object' to the list of object files (or analogues, such as explicitly named library files or the output of "resource compilers") to be included in every link driven by this compiler @@ -330,7 +355,7 @@ def add_link_object(self, object): """ self.objects.append(object) - def set_link_objects(self, objects): + def set_link_objects(self, objects: list[str]) -> None: """Set the list of object files (or analogues) to be included in every link to 'objects'. This does not affect any standard object files that the linker may include by default (such as system @@ -343,7 +368,15 @@ def set_link_objects(self, objects): # Helper method to prep compiler in subclass compile() methods - def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra): + def _setup_compile( + self, + outdir: str | None, + macros: list[_Macro] | None, + incdirs: list[str] | tuple[str, ...] | None, + sources, + depends, + extra, + ): """Process arguments and decide which source files to compile.""" outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs) @@ -375,7 +408,12 @@ def _get_cc_args(self, pp_opts, debug, before): cc_args[:0] = before return cc_args - def _fix_compile_args(self, output_dir, macros, include_dirs): + def _fix_compile_args( + self, + output_dir: str | None, + macros: list[_Macro] | None, + include_dirs: list[str] | tuple[str, ...] | None, + ) -> tuple[str, list[_Macro], list[str]]: """Typecheck and fix-up some of the arguments to the 'compile()' method, and return fixed-up values. Specifically: if 'output_dir' is None, replaces it with 'self.output_dir'; ensures that 'macros' @@ -425,7 +463,9 @@ def _prep_compile(self, sources, output_dir, depends=None): # return value to preserve API compatibility. return objects, {} - def _fix_object_args(self, objects, output_dir): + def _fix_object_args( + self, objects: list[str] | tuple[str, ...], output_dir: str | None + ) -> tuple[list[str], str]: """Typecheck and fix up some arguments supplied to various methods. Specifically: ensure that 'objects' is a list; if output_dir is None, replace with self.output_dir. Return fixed versions of @@ -442,7 +482,12 @@ def _fix_object_args(self, objects, output_dir): return (objects, output_dir) - def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs): + def _fix_lib_args( + self, + libraries: list[str] | tuple[str, ...] | None, + library_dirs: list[str] | tuple[str, ...] | None, + runtime_library_dirs: list[str] | tuple[str, ...] | None, + ) -> tuple[list[str], list[str], list[str]]: """Typecheck and fix up some of the arguments supplied to the 'link_*' methods. Specifically: ensure that all arguments are lists, and augment them with their permanent versions @@ -492,7 +537,7 @@ def _need_link(self, objects, output_file): newer = newer_group(objects, output_file) return newer - def detect_language(self, sources): + def detect_language(self, sources: str | list[str]) -> str | None: """Detect the language of a given file, or list of files. Uses language_map, and language_order to do the job. """ @@ -517,12 +562,12 @@ def detect_language(self, sources): def preprocess( self, - source, - output_file=None, - macros=None, - include_dirs=None, - extra_preargs=None, - extra_postargs=None, + source: str | os.PathLike[str], + output_file: str | os.PathLike[str] | None = None, + macros: list[_Macro] | None = None, + include_dirs: list[str] | tuple[str, ...] | None = None, + extra_preargs: list[str] | None = None, + extra_postargs: Iterable[str] | None = None, ): """Preprocess a single C/C++ source file, named in 'source'. Output will be written to file named 'output_file', or stdout if @@ -537,15 +582,15 @@ def preprocess( def compile( self, - sources, - output_dir=None, - macros=None, - include_dirs=None, - debug=False, - extra_preargs=None, - extra_postargs=None, - depends=None, - ): + sources: Sequence[str | os.PathLike[str]], + output_dir: str | None = None, + macros: list[_Macro] | None = None, + include_dirs: list[str] | tuple[str, ...] | None = None, + debug: bool = False, + extra_preargs: list[str] | None = None, + extra_postargs: list[str] | None = None, + depends: list[str] | tuple[str, ...] | None = None, + ) -> list[str]: """Compile one or more source files. 'sources' must be a list of filenames, most likely C/C++ @@ -618,8 +663,13 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): pass def create_static_lib( - self, objects, output_libname, output_dir=None, debug=False, target_lang=None - ): + self, + objects: list[str] | tuple[str, ...], + output_libname: str, + output_dir: str | None = None, + debug: bool = False, + target_lang: str | None = None, + ) -> None: """Link a bunch of stuff together to create a static library file. The "bunch of stuff" consists of the list of object files supplied as 'objects', the extra object files supplied to @@ -651,19 +701,19 @@ def create_static_lib( def link( self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=False, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None, + target_desc: str, + objects: list[str] | tuple[str, ...], + output_filename: str, + output_dir: str | None = None, + libraries: list[str] | tuple[str, ...] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + runtime_library_dirs: list[str] | tuple[str, ...] | None = None, + export_symbols: Iterable[str] | None = None, + debug: bool = False, + extra_preargs: list[str] | None = None, + extra_postargs: list[str] | None = None, + build_temp: str | os.PathLike[str] | None = None, + target_lang: str | None = None, ): """Link a bunch of stuff together to create an executable or shared library file. @@ -714,18 +764,18 @@ def link( def link_shared_lib( self, - objects, - output_libname, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=False, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None, + objects: list[str] | tuple[str, ...], + output_libname: str, + output_dir: str | None = None, + libraries: list[str] | tuple[str, ...] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + runtime_library_dirs: list[str] | tuple[str, ...] | None = None, + export_symbols: Iterable[str] | None = None, + debug: bool = False, + extra_preargs: list[str] | None = None, + extra_postargs: list[str] | None = None, + build_temp: str | os.PathLike[str] | None = None, + target_lang: str | None = None, ): self.link( Compiler.SHARED_LIBRARY, @@ -745,18 +795,18 @@ def link_shared_lib( def link_shared_object( self, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=False, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None, + objects: list[str] | tuple[str, ...], + output_filename: str, + output_dir: str | None = None, + libraries: list[str] | tuple[str, ...] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + runtime_library_dirs: list[str] | tuple[str, ...] | None = None, + export_symbols: Iterable[str] | None = None, + debug: bool = False, + extra_preargs: list[str] | None = None, + extra_postargs: list[str] | None = None, + build_temp: str | os.PathLike[str] | None = None, + target_lang: str | None = None, ): self.link( Compiler.SHARED_OBJECT, @@ -776,16 +826,16 @@ def link_shared_object( def link_executable( self, - objects, - output_progname, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - debug=False, - extra_preargs=None, - extra_postargs=None, - target_lang=None, + objects: list[str] | tuple[str, ...], + output_progname: str, + output_dir: str | None = None, + libraries: list[str] | tuple[str, ...] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + runtime_library_dirs: list[str] | tuple[str, ...] | None = None, + debug: bool = False, + extra_preargs: list[str] | None = None, + extra_postargs: list[str] | None = None, + target_lang: str | None = None, ): self.link( Compiler.EXECUTABLE, @@ -808,19 +858,19 @@ def link_executable( # no appropriate default implementation so subclasses should # implement all of these. - def library_dir_option(self, dir): + def library_dir_option(self, dir: str) -> str: """Return the compiler option to add 'dir' to the list of directories searched for libraries. """ raise NotImplementedError - def runtime_library_dir_option(self, dir): + def runtime_library_dir_option(self, dir: str) -> str: """Return the compiler option to add 'dir' to the list of directories searched for runtime libraries. """ raise NotImplementedError - def library_option(self, lib): + def library_option(self, lib: str) -> str: """Return the compiler option to add 'lib' to the list of libraries linked into the shared library or executable. """ @@ -828,12 +878,12 @@ def library_option(self, lib): def has_function( # noqa: C901 self, - funcname, - includes=None, - include_dirs=None, - libraries=None, - library_dirs=None, - ): + funcname: str, + includes: Iterable[str] | None = None, + include_dirs: list[str] | tuple[str, ...] | None = None, + libraries: list[str] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + ) -> bool: """Return a boolean indicating whether funcname is provided as a symbol on the current platform. The optional arguments can be used to augment the compilation environment. @@ -916,7 +966,9 @@ def has_function( # noqa: C901 os.remove(fn) return True - def find_library_file(self, dirs, lib, debug=False): + def find_library_file( + self, dirs: Iterable[str], lib: str, debug: bool = False + ) -> str | None: """Search the specified list of directories for a static or shared library file 'lib' and return the full path to that file. If 'debug' true, look for a debugging version (if that makes sense on @@ -959,7 +1011,12 @@ def find_library_file(self, dirs, lib, debug=False): # * exe_extension - # extension for executable files, eg. '' or '.exe' - def object_filenames(self, source_filenames, strip_dir=False, output_dir=''): + def object_filenames( + self, + source_filenames: Iterable[str | os.PathLike[str]], + strip_dir: bool = False, + output_dir: str | os.PathLike[str] | None = '', + ) -> list[str]: if output_dir is None: output_dir = '' return list( @@ -1000,13 +1057,51 @@ def _make_out_path_exts(cls, output_dir, strip_dir, src_name, extensions): def _make_relative(base: pathlib.Path): return base.relative_to(base.anchor) - def shared_object_filename(self, basename, strip_dir=False, output_dir=''): + @overload + def shared_object_filename( + self, + basename: str, + strip_dir: Literal[False] = False, + output_dir: str | os.PathLike[str] = "", + ) -> str: ... + @overload + def shared_object_filename( + self, + basename: str | os.PathLike[str], + strip_dir: Literal[True], + output_dir: str | os.PathLike[str] = "", + ) -> str: ... + def shared_object_filename( + self, + basename: str | os.PathLike[str], + strip_dir: bool = False, + output_dir: str | os.PathLike[str] = '', + ) -> str: assert output_dir is not None if strip_dir: basename = os.path.basename(basename) return os.path.join(output_dir, basename + self.shared_lib_extension) - def executable_filename(self, basename, strip_dir=False, output_dir=''): + @overload + def executable_filename( + self, + basename: str, + strip_dir: Literal[False] = False, + output_dir: str | os.PathLike[str] = "", + ) -> str: ... + @overload + def executable_filename( + self, + basename: str | os.PathLike[str], + strip_dir: Literal[True], + output_dir: str | os.PathLike[str] = "", + ) -> str: ... + def executable_filename( + self, + basename: str | os.PathLike[str], + strip_dir: bool = False, + output_dir: str | os.PathLike[str] = '', + ) -> str: assert output_dir is not None if strip_dir: basename = os.path.basename(basename) @@ -1014,10 +1109,10 @@ def executable_filename(self, basename, strip_dir=False, output_dir=''): def library_filename( self, - libname, - lib_type='static', - strip_dir=False, - output_dir='', # or 'shared' + libname: str, + lib_type: str = "static", + strip_dir: bool = False, + output_dir: str | os.PathLike[str] = "", # or 'shared' ): assert output_dir is not None expected = '"static", "shared", "dylib", "xcode_stub"' @@ -1035,25 +1130,45 @@ def library_filename( # -- Utility methods ----------------------------------------------- - def announce(self, msg, level=1): + def announce(self, msg: object, level: int = 1) -> None: log.debug(msg) - def debug_print(self, msg): + def debug_print(self, msg: object) -> None: from distutils.debug import DEBUG if DEBUG: print(msg) - def warn(self, msg): + def warn(self, msg: object) -> None: sys.stderr.write(f"warning: {msg}\n") - def execute(self, func, args, msg=None, level=1): + def execute( + self, + func: Callable[[Unpack[_Ts]], object], + args: tuple[Unpack[_Ts]], + msg: object = None, + level: int = 1, + ) -> None: execute(func, args, msg, self.dry_run) - def spawn(self, cmd, **kwargs): + def spawn( + self, cmd: MutableSequence[bytes | str | os.PathLike[str]], **kwargs + ) -> None: spawn(cmd, dry_run=self.dry_run, **kwargs) - def move_file(self, src, dst): + @overload + def move_file( + self, src: str | os.PathLike[str], dst: _StrPathT + ) -> _StrPathT | str: ... + @overload + def move_file( + self, src: bytes | os.PathLike[bytes], dst: _BytesPathT + ) -> _BytesPathT | bytes: ... + def move_file( + self, + src: str | os.PathLike[str] | bytes | os.PathLike[bytes], + dst: str | os.PathLike[str] | bytes | os.PathLike[bytes], + ) -> str | os.PathLike[str] | bytes | os.PathLike[bytes]: return move_file(src, dst, dry_run=self.dry_run) def mkpath(self, name, mode=0o777): @@ -1076,7 +1191,7 @@ def mkpath(self, name, mode=0o777): ) -def get_default_compiler(osname=None, platform=None): +def get_default_compiler(osname: str | None = None, platform: str | None = None) -> str: """Determine the default compiler to use for the given platform. osname should be one of the standard Python OS names (i.e. the @@ -1125,7 +1240,7 @@ def get_default_compiler(osname=None, platform=None): } -def show_compilers(): +def show_compilers() -> None: """Print list of available compilers (used by the "--help-compiler" options to "build", "build_ext", "build_clib"). """ @@ -1142,7 +1257,13 @@ def show_compilers(): pretty_printer.print_help("List of available compilers:") -def new_compiler(plat=None, compiler=None, verbose=False, dry_run=False, force=False): +def new_compiler( + plat: str | None = None, + compiler: str | None = None, + verbose: bool = False, + dry_run: bool = False, + force: bool = False, +) -> Compiler: """Generate an instance of some CCompiler subclass for the supplied platform/compiler combination. 'plat' defaults to 'os.name' (eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler @@ -1188,7 +1309,9 @@ def new_compiler(plat=None, compiler=None, verbose=False, dry_run=False, force=F return klass(None, dry_run, force) -def gen_preprocess_options(macros, include_dirs): +def gen_preprocess_options( + macros: Iterable[_Macro], include_dirs: Iterable[str] +) -> list[str]: """Generate C pre-processor options (-D, -U, -I) as used by at least two types of compilers: the typical Unix compiler and Visual C++. 'macros' is the usual thing, a list of 1- or 2-tuples, where (name,) @@ -1232,7 +1355,12 @@ def gen_preprocess_options(macros, include_dirs): return pp_opts -def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries): +def gen_lib_options( + compiler: Compiler, + library_dirs: Iterable[str], + runtime_library_dirs: Iterable[str], + libraries: Iterable[str], +) -> list[str]: """Generate linker options for searching library directories and linking with specific libraries. 'libraries' and 'library_dirs' are, respectively, lists of library names (not filenames!) and search diff --git a/distutils/compilers/C/msvc.py b/distutils/compilers/C/msvc.py index 2bdc6576..ea1a342d 100644 --- a/distutils/compilers/C/msvc.py +++ b/distutils/compilers/C/msvc.py @@ -17,6 +17,7 @@ import subprocess import unittest.mock as mock import warnings +from collections.abc import Iterable with contextlib.suppress(ImportError): import winreg @@ -255,10 +256,10 @@ class Compiler(base.Compiler): obj_extension = '.obj' static_lib_extension = '.lib' shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' + static_lib_format = static_lib_format = '%s%s' exe_extension = '.exe' - def __init__(self, verbose=False, dry_run=False, force=False): + def __init__(self, verbose=False, dry_run=False, force=False) -> None: super().__init__(verbose, dry_run, force) # target platform (.plat_name is consistent with 'bdist') self.plat_name = None @@ -276,7 +277,7 @@ def _configure(cls, vc_env): def _parse_path(val): return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir] - def initialize(self, plat_name=None): + def initialize(self, plat_name: str | None = None) -> None: # multi-init means we would need to check platform same each time... assert not self.initialized, "don't init multiple times" if plat_name is None: @@ -358,7 +359,7 @@ def initialize(self, plat_name=None): # -- Worker methods ------------------------------------------------ @property - def out_extensions(self): + def out_extensions(self) -> dict[str, str]: return { **super().out_extensions, **{ @@ -462,8 +463,13 @@ def compile( # noqa: C901 return objects def create_static_lib( - self, objects, output_libname, output_dir=None, debug=False, target_lang=None - ): + self, + objects: list[str] | tuple[str, ...], + output_libname: str, + output_dir: str | None = None, + debug: bool = False, + target_lang: str | None = None, + ) -> None: if not self.initialized: self.initialize() objects, output_dir = self._fix_object_args(objects, output_dir) @@ -483,20 +489,20 @@ def create_static_lib( def link( self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=False, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None, - ): + target_desc: str, + objects: list[str] | tuple[str, ...], + output_filename: str, + output_dir: str | None = None, + libraries: list[str] | tuple[str, ...] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + runtime_library_dirs: list[str] | tuple[str, ...] | None = None, + export_symbols: Iterable[str] | None = None, + debug: bool = False, + extra_preargs: list[str] | None = None, + extra_postargs: Iterable[str] | None = None, + build_temp: str | os.PathLike[str] | None = None, + target_lang: str | None = None, + ) -> None: if not self.initialized: self.initialize() objects, output_dir = self._fix_object_args(objects, output_dir) diff --git a/distutils/compilers/C/unix.py b/distutils/compilers/C/unix.py index 1ba93e6a..4bf709cb 100644 --- a/distutils/compilers/C/unix.py +++ b/distutils/compilers/C/unix.py @@ -20,6 +20,7 @@ import re import shlex import sys +from collections.abc import Iterable from ... import sysconfig from ..._log import log @@ -28,7 +29,7 @@ from ...compat import consolidate_linker_args from ...errors import DistutilsExecError from . import base -from .base import gen_lib_options, gen_preprocess_options +from .base import _Macro, gen_lib_options, gen_preprocess_options from .errors import ( CompileError, LibError, @@ -159,12 +160,12 @@ class Compiler(base.Compiler): def preprocess( self, - source, - output_file=None, - macros=None, - include_dirs=None, - extra_preargs=None, - extra_postargs=None, + source: str | os.PathLike[str], + output_file: str | os.PathLike[str] | None = None, + macros: list[_Macro] | None = None, + include_dirs: list[str] | tuple[str, ...] | None = None, + extra_preargs: list[str] | None = None, + extra_postargs: Iterable[str] | None = None, ): fixed_args = self._fix_compile_args(None, macros, include_dirs) ignore, macros, include_dirs = fixed_args @@ -234,12 +235,12 @@ def create_static_lib( def link( self, target_desc, - objects, + objects: list[str] | tuple[str, ...], output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, + output_dir: str | None = None, + libraries: list[str] | tuple[str, ...] | None = None, + library_dirs: list[str] | tuple[str, ...] | None = None, + runtime_library_dirs: list[str] | tuple[str, ...] | None = None, export_symbols=None, debug=False, extra_preargs=None, diff --git a/distutils/dist.py b/distutils/dist.py index 33ed8ebd..72bd13ce 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -13,9 +13,21 @@ import re import sys import warnings -from collections.abc import Iterable +from collections.abc import Iterable, MutableMapping from email import message_from_file -from typing import TYPE_CHECKING, Literal, TypeVar, overload +from typing import ( + IO, + TYPE_CHECKING, + Any, + ClassVar, + List, + Literal, + Tuple, + TypeAlias, + TypeVar, + Union, + overload, +) from packaging.utils import canonicalize_name, canonicalize_version @@ -31,10 +43,16 @@ from .util import check_environ, rfc822_escape, strtobool if TYPE_CHECKING: + from _typeshed import SupportsWrite + # type-only import because of mutual dependence between these modules from .cmd import Command _CommandT = TypeVar("_CommandT", bound="Command") +_OptionsList: TypeAlias = List[ + Tuple[str, Union[str, None], str, int] | Tuple[str, Union[str, None], str] +] + # Regex to define acceptable Distutils command names. This is not *quite* # the same as a Python NAME -- I don't allow leading underscores. The fact @@ -43,7 +61,7 @@ command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') -def _ensure_list(value, fieldname): +def _ensure_list(value: str | Iterable[str], fieldname) -> str | list[str]: if isinstance(value, str): # a string containing comma separated values is okay. It will # be converted to a list by Distribution.finalize_options(). @@ -80,7 +98,7 @@ class Distribution: # don't want to pollute the commands with too many options that they # have minimal control over. # The fourth entry for verbose means that it can be repeated. - global_options = [ + global_options: ClassVar[_OptionsList] = [ ('verbose', 'v', "run verbosely (default)", 1), ('quiet', 'q', "run quietly (turns verbosity off)"), ('dry-run', 'n', "don't actually do anything"), @@ -90,7 +108,7 @@ class Distribution: # 'common_usage' is a short (2-3 line) string describing the common # usage of the setup script. - common_usage = """\ + common_usage: ClassVar[str] = """\ Common commands: (see '--help-commands' for more) setup.py build will build the package underneath 'build/' @@ -98,7 +116,7 @@ class Distribution: """ # options that are not propagated to the commands - display_options = [ + display_options: ClassVar[_OptionsList] = [ ('help-commands', None, "list all available commands"), ('name', None, "print package name"), ('version', 'V', "print package version"), @@ -125,14 +143,17 @@ class Distribution: ('requires', None, "print the list of packages/modules required"), ('obsoletes', None, "print the list of packages/modules made obsolete"), ] - display_option_names = [translate_longopt(x[0]) for x in display_options] + display_option_names: ClassVar[list[str]] = [ + translate_longopt(x[0]) for x in display_options + ] # negative options are options that exclude other options - negative_opt = {'quiet': 'verbose'} + negative_opt: ClassVar[dict[str, str]] = {'quiet': 'verbose'} # -- Creation/initialization methods ------------------------------- - def __init__(self, attrs=None): # noqa: C901 + # Can't Unpack a TypedDict with optional properties, so using Any instead + def __init__(self, attrs: MutableMapping[str, Any] | None = None) -> None: # noqa: C901 """Construct a new Distribution instance: initialize all the attributes of a Distribution, and then use 'attrs' (a dictionary mapping attribute names to values) to assign some of those @@ -172,12 +193,12 @@ def __init__(self, attrs=None): # noqa: C901 # named here. This list is searched from the left; an error # is raised if no named package provides the command being # searched for. (Always access using get_command_packages().) - self.command_packages = None + self.command_packages: str | list[str] | None = None # 'script_name' and 'script_args' are usually set to sys.argv[0] # and sys.argv[1:], but they can be overridden when the caller is # not necessarily a setup script run from the command-line. - self.script_name = None + self.script_name: str | os.PathLike[str] | None = None self.script_args: list[str] | None = None # 'command_options' is where we store command options between @@ -185,7 +206,7 @@ def __init__(self, attrs=None): # noqa: C901 # they are actually needed -- ie. when the command in question is # instantiated. It is a dictionary of dictionaries of 2-tuples: # command_options = { command_name : { option : (source, value) } } - self.command_options = {} + self.command_options: dict[str, dict[str, tuple[str, str]]] = {} # 'dist_files' is the list of (command, pyversion, file) that # have been created by any dist commands run so far. This is @@ -196,13 +217,13 @@ def __init__(self, attrs=None): # noqa: C901 # file. pyversion should not be used to specify minimum or # maximum required Python versions; use the metainfo for that # instead. - self.dist_files = [] + self.dist_files: list[tuple[str, str, str]] = [] # These options are really the business of various commands, rather # than of the Distribution itself. We provide aliases for them in # Distribution as a convenience to the developer. self.packages = None - self.package_data = {} + self.package_data: dict[str, list[str]] = {} self.package_dir = None self.py_modules = None self.libraries = None @@ -219,7 +240,7 @@ def __init__(self, attrs=None): # noqa: C901 # the caller at all. 'command_obj' maps command names to # Command instances -- that's how we enforce that every command # class is a singleton. - self.command_obj = {} + self.command_obj: dict[str, Command] = {} # 'have_run' maps command names to boolean values; it keeps track # of whether we have actually run a particular command, to make it @@ -231,7 +252,7 @@ def __init__(self, attrs=None): # noqa: C901 # command object is created, and replaced with a true value when # the command is successfully run. Thus it's probably best to use # '.get()' rather than a straight lookup. - self.have_run = {} + self.have_run: dict[str, bool] = {} # Now we'll use the attrs dictionary (ultimately, keyword args from # the setup script) to possibly override any or all of these @@ -300,7 +321,7 @@ def get_option_dict(self, command): dict = self.command_options[command] = {} return dict - def dump_option_dicts(self, header=None, commands=None, indent=""): + def dump_option_dicts(self, header=None, commands=None, indent: str = "") -> None: from pprint import pformat if commands is None: # dump all command option dicts @@ -615,7 +636,7 @@ def _parse_command_opts(self, parser, args): # noqa: C901 return args - def finalize_options(self): + def finalize_options(self) -> None: """Set final values for all the options on the Distribution instance, analogous to the .finalize_options() method of Command objects. @@ -718,7 +739,7 @@ def handle_display_options(self, option_order): return any_display_options - def print_command_list(self, commands, header, max_length): + def print_command_list(self, commands, header, max_length) -> None: """Print a subset of the list of all commands -- used by 'print_commands()'. """ @@ -735,7 +756,7 @@ def print_command_list(self, commands, header, max_length): print(f" {cmd:<{max_length}} {description}") - def print_commands(self): + def print_commands(self) -> None: """Print out a help message listing all available commands with a description of each. The list is divided into "standard commands" (listed in distutils.command.__all__) and "extra commands" @@ -802,7 +823,7 @@ def get_command_packages(self): self.command_packages = pkgs return pkgs - def get_command_class(self, command): + def get_command_class(self, command: str) -> type[Command]: """Return the class that implements the Distutils command named by 'command'. First we check the 'cmdclass' dictionary; if the command is mentioned there, we fetch the class object from the @@ -971,10 +992,10 @@ def reinitialize_command( # -- Methods that operate on the Distribution ---------------------- - def announce(self, msg, level=logging.INFO): + def announce(self, msg, level: int = logging.INFO) -> None: log.log(level, msg) - def run_commands(self): + def run_commands(self) -> None: """Run each command that was seen on the setup script command line. Uses the list of commands found and cache of command objects created by 'get_command_obj()'. @@ -984,7 +1005,7 @@ def run_commands(self): # -- Methods that operate on its Commands -------------------------- - def run_command(self, command): + def run_command(self, command: str) -> None: """Do whatever it takes to run a command (including nothing at all, if the command has already been run). Specifically: if we have already created and run the command named by 'command', return @@ -1004,28 +1025,28 @@ def run_command(self, command): # -- Distribution query methods ------------------------------------ - def has_pure_modules(self): + def has_pure_modules(self) -> bool: return len(self.packages or self.py_modules or []) > 0 - def has_ext_modules(self): + def has_ext_modules(self) -> bool: return self.ext_modules and len(self.ext_modules) > 0 - def has_c_libraries(self): + def has_c_libraries(self) -> bool: return self.libraries and len(self.libraries) > 0 - def has_modules(self): + def has_modules(self) -> bool: return self.has_pure_modules() or self.has_ext_modules() - def has_headers(self): + def has_headers(self) -> bool: return self.headers and len(self.headers) > 0 - def has_scripts(self): + def has_scripts(self) -> bool: return self.scripts and len(self.scripts) > 0 - def has_data_files(self): + def has_data_files(self) -> bool: return self.data_files and len(self.data_files) > 0 - def is_pure(self): + def is_pure(self) -> bool: return ( self.has_pure_modules() and not self.has_ext_modules() @@ -1038,6 +1059,53 @@ def is_pure(self): # they are defined in a sneaky way: the constructor binds self.get_XXX # to self.metadata.get_XXX. The actual code is in the # DistributionMetadata class, below. + if TYPE_CHECKING: + # Unfortunately this means we need to specify them manually or not expose statically + def _(self) -> None: + self.get_name = self.metadata.get_name + self.get_version = self.metadata.get_version + self.get_fullname = self.metadata.get_fullname + self.get_author = self.metadata.get_author + self.get_author_email = self.metadata.get_author_email + self.get_maintainer = self.metadata.get_maintainer + self.get_maintainer_email = self.metadata.get_maintainer_email + self.get_contact = self.metadata.get_contact + self.get_contact_email = self.metadata.get_contact_email + self.get_url = self.metadata.get_url + self.get_license = self.metadata.get_license + self.get_licence = self.metadata.get_licence + self.get_description = self.metadata.get_description + self.get_long_description = self.metadata.get_long_description + self.get_keywords = self.metadata.get_keywords + self.get_platforms = self.metadata.get_platforms + self.get_classifiers = self.metadata.get_classifiers + self.get_download_url = self.metadata.get_download_url + self.get_requires = self.metadata.get_requires + self.get_provides = self.metadata.get_provides + self.get_obsoletes = self.metadata.get_obsoletes + + # Default attributes generated in __init__ from self.display_option_names + help_commands: bool + name: str | Literal[False] + version: str | Literal[False] + fullname: str | Literal[False] + author: str | Literal[False] + author_email: str | Literal[False] + maintainer: str | Literal[False] + maintainer_email: str | Literal[False] + contact: str | Literal[False] + contact_email: str | Literal[False] + url: str | Literal[False] + license: str | Literal[False] + licence: str | Literal[False] + description: str | Literal[False] + long_description: str | Literal[False] + platforms: str | list[str] | Literal[False] + classifiers: str | list[str] | Literal[False] + keywords: str | list[str] | Literal[False] + provides: list[str] | Literal[False] + requires: list[str] | Literal[False] + obsoletes: list[str] | Literal[False] class DistributionMetadata: @@ -1069,34 +1137,36 @@ class DistributionMetadata: "obsoletes", ) - def __init__(self, path=None): + def __init__( + self, path: str | bytes | os.PathLike[str] | os.PathLike[bytes] | None = None + ) -> None: if path is not None: self.read_pkg_file(open(path)) else: - self.name = None - self.version = None - self.author = None - self.author_email = None - self.maintainer = None - self.maintainer_email = None - self.url = None - self.license = None - self.description = None - self.long_description = None - self.keywords = None - self.platforms = None - self.classifiers = None - self.download_url = None + self.name: str | None = None + self.version: str | None = None + self.author: str | None = None + self.author_email: str | None = None + self.maintainer: str | None = None + self.maintainer_email: str | None = None + self.url: str | None = None + self.license: str | None = None + self.description: str | None = None + self.long_description: str | None = None + self.keywords: str | list[str] | None = None + self.platforms: str | list[str] | None = None + self.classifiers: str | list[str] | None = None + self.download_url: str | None = None # PEP 314 - self.provides = None - self.requires = None - self.obsoletes = None + self.provides: str | list[str] | None = None + self.requires: str | list[str] | None = None + self.obsoletes: str | list[str] | None = None - def read_pkg_file(self, file): + def read_pkg_file(self, file: IO[str]) -> None: """Reads the metadata values from a file object.""" msg = message_from_file(file) - def _read_field(name): + def _read_field(name: str) -> str | None: value = msg[name] if value and value != "UNKNOWN": return value @@ -1143,14 +1213,14 @@ def _read_list(name): self.provides = None self.obsoletes = None - def write_pkg_info(self, base_dir): + def write_pkg_info(self, base_dir: str | os.PathLike[str]) -> None: """Write the PKG-INFO file into the release tree.""" with open( os.path.join(base_dir, 'PKG-INFO'), 'w', encoding='UTF-8' ) as pkg_info: self.write_pkg_file(pkg_info) - def write_pkg_file(self, file): + def write_pkg_file(self, file: SupportsWrite[str]) -> None: """Write the PKG-INFO format data to a file object.""" version = '1.0' if ( @@ -1196,13 +1266,13 @@ def _write_list(self, file, name, values): # -- Metadata query methods ---------------------------------------- - def get_name(self): + def get_name(self) -> str: return self.name or "UNKNOWN" - def get_version(self): + def get_version(self) -> str: return self.version or "0.0.0" - def get_fullname(self): + def get_fullname(self) -> str: return self._fullname(self.get_name(), self.get_version()) @staticmethod @@ -1224,74 +1294,74 @@ def _fullname(name: str, version: str) -> str: canonicalize_version(version, strip_trailing_zero=False), ) - def get_author(self): + def get_author(self) -> str | None: return self.author - def get_author_email(self): + def get_author_email(self) -> str | None: return self.author_email - def get_maintainer(self): + def get_maintainer(self) -> str | None: return self.maintainer - def get_maintainer_email(self): + def get_maintainer_email(self) -> str | None: return self.maintainer_email - def get_contact(self): + def get_contact(self) -> str | None: return self.maintainer or self.author - def get_contact_email(self): + def get_contact_email(self) -> str | None: return self.maintainer_email or self.author_email - def get_url(self): + def get_url(self) -> str | None: return self.url - def get_license(self): + def get_license(self) -> str | None: return self.license get_licence = get_license - def get_description(self): + def get_description(self) -> str | None: return self.description - def get_long_description(self): + def get_long_description(self) -> str | None: return self.long_description - def get_keywords(self): + def get_keywords(self) -> str | list[str]: return self.keywords or [] - def set_keywords(self, value): + def set_keywords(self, value: str | Iterable[str]) -> None: self.keywords = _ensure_list(value, 'keywords') - def get_platforms(self): + def get_platforms(self) -> str | list[str] | None: return self.platforms - def set_platforms(self, value): + def set_platforms(self, value: str | Iterable[str]) -> None: self.platforms = _ensure_list(value, 'platforms') - def get_classifiers(self): + def get_classifiers(self) -> str | list[str]: return self.classifiers or [] - def set_classifiers(self, value): + def set_classifiers(self, value: str | Iterable[str]) -> None: self.classifiers = _ensure_list(value, 'classifiers') - def get_download_url(self): + def get_download_url(self) -> str | None: return self.download_url # PEP 314 - def get_requires(self): + def get_requires(self) -> str | list[str]: return self.requires or [] - def set_requires(self, value): + def set_requires(self, value: Iterable[str]) -> None: import distutils.versionpredicate for v in value: distutils.versionpredicate.VersionPredicate(v) self.requires = list(value) - def get_provides(self): + def get_provides(self) -> str | list[str]: return self.provides or [] - def set_provides(self, value): + def set_provides(self, value: Iterable[str]) -> None: value = [v.strip() for v in value] for v in value: import distutils.versionpredicate @@ -1299,10 +1369,10 @@ def set_provides(self, value): distutils.versionpredicate.split_provision(v) self.provides = value - def get_obsoletes(self): + def get_obsoletes(self) -> str | list[str]: return self.obsoletes or [] - def set_obsoletes(self, value): + def set_obsoletes(self, value: Iterable[str]) -> None: import distutils.versionpredicate for v in value: diff --git a/distutils/errors.py b/distutils/errors.py index 7c6ee258..409d21fa 100644 --- a/distutils/errors.py +++ b/distutils/errors.py @@ -6,18 +6,15 @@ """ # compiler exceptions aliased for compatibility -from .compilers.C.errors import ( - CompileError, # noqa: F401 - LibError, # noqa: F401 - LinkError, # noqa: F401 - PreprocessError, # noqa: F401 -) -from .compilers.C.errors import ( - Error as CCompilerError, # noqa: F401 -) -from .compilers.C.errors import ( - UnknownFileType as UnknownFileError, # noqa: F401 -) +from .compilers.C.errors import CompileError as CompileError +from .compilers.C.errors import Error as _Error +from .compilers.C.errors import LibError as LibError +from .compilers.C.errors import LinkError as LinkError +from .compilers.C.errors import PreprocessError as PreprocessError +from .compilers.C.errors import UnknownFileType as _UnknownFileType + +CCompilerError = _Error +UnknownFileError = _UnknownFileType class DistutilsError(Exception): diff --git a/distutils/extension.py b/distutils/extension.py index e0532734..0885e33b 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -5,6 +5,7 @@ import os import warnings +from collections.abc import Iterable # This class is really only used by the "build_ext" command, so it might # make sense to put it in distutils.command.build_ext. However, that @@ -88,22 +89,22 @@ class Extension: # setup_keywords in core.py. def __init__( self, - name, - sources, - include_dirs=None, - define_macros=None, - undef_macros=None, - library_dirs=None, - libraries=None, - runtime_library_dirs=None, - extra_objects=None, - extra_compile_args=None, - extra_link_args=None, - export_symbols=None, - swig_opts=None, - depends=None, - language=None, - optional=None, + name: str, + sources: Iterable[str | os.PathLike[str]], + include_dirs: list[str] | None = None, + define_macros: list[tuple[str, str | None]] | None = None, + undef_macros: list[str] | None = None, + library_dirs: list[str] | None = None, + libraries: list[str] | None = None, + runtime_library_dirs: list[str] | None = None, + extra_objects: list[str] | None = None, + extra_compile_args: list[str] | None = None, + extra_link_args: list[str] | None = None, + export_symbols: list[str] | None = None, + swig_opts: list[str] | None = None, + depends: list[str] | None = None, + language: str | None = None, + optional: bool | None = None, **kw, # To catch unknown keywords ): if not isinstance(name, str): diff --git a/distutils/filelist.py b/distutils/filelist.py index 9857b195..3b8f8e69 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -8,6 +8,8 @@ import functools import os import re +from collections.abc import Iterable +from typing import Literal, overload from ._log import log from .errors import DistutilsInternalError, DistutilsTemplateError @@ -29,19 +31,19 @@ class FileList: filtering applied) """ - def __init__(self, warn=None, debug_print=None): + def __init__(self, warn: object = None, debug_print: object = None) -> None: # ignore argument to FileList, but keep them for backwards # compatibility - self.allfiles = None - self.files = [] + self.allfiles: Iterable[str] | None = None + self.files: list[str] = [] - def set_allfiles(self, allfiles): + def set_allfiles(self, allfiles: Iterable[str]) -> None: self.allfiles = allfiles - def findall(self, dir=os.curdir): + def findall(self, dir: str | os.PathLike[str] = os.curdir) -> None: self.allfiles = findall(dir) - def debug_print(self, msg): + def debug_print(self, msg: object) -> None: """Print 'msg' to stdout if the global DEBUG (taken from the DISTUTILS_DEBUG environment variable) flag is true. """ @@ -52,13 +54,13 @@ def debug_print(self, msg): # Collection methods - def append(self, item): + def append(self, item: str) -> None: self.files.append(item) - def extend(self, items): + def extend(self, items: Iterable[str]) -> None: self.files.extend(items) - def sort(self): + def sort(self) -> None: # Not a strict lexical sort! sortable_files = sorted(map(os.path.split, self.files)) self.files = [] @@ -67,7 +69,7 @@ def sort(self): # Other miscellaneous utility methods - def remove_duplicates(self): + def remove_duplicates(self) -> None: # Assumes list has been sorted! for i in range(len(self.files) - 1, 0, -1): if self.files[i] == self.files[i - 1]: @@ -105,7 +107,7 @@ def _parse_template_line(self, line): return (action, patterns, dir, dir_pattern) - def process_template_line(self, line): # noqa: C901 + def process_template_line(self, line: str) -> None: # noqa: C901 # Parse the line: split it up, make sure the right number of words # is there, and return the relevant words. 'action' is always # defined: it's the first word of the line. Which of the other @@ -193,8 +195,38 @@ def process_template_line(self, line): # noqa: C901 ) # Filtering/selection methods - - def include_pattern(self, pattern, anchor=True, prefix=None, is_regex=False): + @overload + def include_pattern( + self, + pattern: str, + anchor: bool = True, + prefix: str | None = None, + is_regex: Literal[False] = False, + ) -> bool: ... + @overload + def include_pattern( + self, + pattern: str | re.Pattern[str], + anchor: bool = True, + prefix: str | None = None, + *, + is_regex: Literal[True], + ) -> bool: ... + @overload + def include_pattern( + self, + pattern: str | re.Pattern[str], + anchor: bool, + prefix: str | None, + is_regex: Literal[True], + ) -> bool: ... + def include_pattern( + self, + pattern: str | re.Pattern, + anchor: bool = True, + prefix: str | None = None, + is_regex: bool = False, + ) -> bool: """Select strings (presumably filenames) from 'self.files' that match 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not quite the same as implemented by the 'fnmatch' module: '*' @@ -235,7 +267,38 @@ def include_pattern(self, pattern, anchor=True, prefix=None, is_regex=False): files_found = True return files_found - def exclude_pattern(self, pattern, anchor=True, prefix=None, is_regex=False): + @overload + def exclude_pattern( + self, + pattern: str, + anchor: bool = True, + prefix: str | None = None, + is_regex: Literal[False] = False, + ) -> bool: ... + @overload + def exclude_pattern( + self, + pattern: str | re.Pattern[str], + anchor: bool = True, + prefix: str | None = None, + *, + is_regex: Literal[True], + ) -> bool: ... + @overload + def exclude_pattern( + self, + pattern: str | re.Pattern[str], + anchor: bool, + prefix: str | None, + is_regex: Literal[True], + ) -> bool: ... + def exclude_pattern( + self, + pattern: str | re.Pattern, + anchor: bool = True, + prefix: str | None = None, + is_regex: bool = False, + ) -> bool: """Remove strings (presumably filenames) from 'files' that match 'pattern'. Other parameters are the same as for 'include_pattern()', above. @@ -294,7 +357,7 @@ def filter(cls, items): return filter(cls(), items) -def findall(dir=os.curdir): +def findall(dir: str | os.PathLike[str] = os.curdir): """ Find all files under 'dir' and return the list of full filenames. Unless dir is '.', return full filenames with dir prepended. diff --git a/distutils/spawn.py b/distutils/spawn.py index ba280334..fc662817 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -12,12 +12,16 @@ import subprocess import sys import warnings -from collections.abc import Mapping +from collections.abc import Mapping, MutableSequence +from subprocess import _ENV +from typing import TypeVar, overload from ._log import log from .debug import DEBUG from .errors import DistutilsExecError +_MappingT = TypeVar("_MappingT", bound=Mapping) + def _debug(cmd): """ @@ -26,7 +30,7 @@ def _debug(cmd): return cmd if DEBUG else cmd[0] -def _inject_macos_ver(env: Mapping[str:str] | None) -> Mapping[str:str] | None: +def _inject_macos_ver(env: _MappingT | None) -> _MappingT | dict[str, str | int] | None: if platform.system() != 'Darwin': return env @@ -37,11 +41,21 @@ def _inject_macos_ver(env: Mapping[str:str] | None) -> Mapping[str:str] | None: return {**_resolve(env), **update} -def _resolve(env: Mapping[str:str] | None) -> Mapping[str:str]: +@overload +def _resolve(env: None) -> os._Environ[str]: ... +@overload +def _resolve(env: _MappingT) -> _MappingT: ... +def _resolve(env: _MappingT | None) -> _MappingT | os._Environ[str]: return os.environ if env is None else env -def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): +def spawn( + cmd: MutableSequence[bytes | str | os.PathLike[str]], + search_path: bool = True, + verbose: bool = False, + dry_run: bool = False, + env: _ENV | None = None, +) -> 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. @@ -78,7 +92,7 @@ def spawn(cmd, search_path=True, verbose=False, dry_run=False, env=None): ) from err -def find_executable(executable, path=None): +def find_executable(executable: str, path: str | None = None) -> str | None: """Tries to find 'executable' in the directories listed in 'path'. A string listing directories separated by 'os.pathsep'; defaults to diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index ef3def83..c27e7799 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -15,13 +15,23 @@ import re import sys import sysconfig +from typing import TYPE_CHECKING, Literal, overload from jaraco.functools import pass_none +from .ccompiler import CCompiler from .compat import py39 from .errors import DistutilsPlatformError from .util import is_mingw +if TYPE_CHECKING: + from typing_extensions import deprecated +else: + + def deprecated(fn): + return fn + + IS_PYPY = '__pypy__' in sys.builtin_module_names # These are needed in a couple of spots, so just compute them once. @@ -110,7 +120,7 @@ def get_python_version(): return f'{sys.version_info.major}.{sys.version_info.minor}' -def get_python_inc(plat_specific=False, prefix=None): +def get_python_inc(plat_specific: bool = False, prefix: str | None = None) -> str: """Return the directory containing installed Python header files. If 'plat_specific' is false (the default), this is the path to the @@ -217,7 +227,9 @@ def _posix_lib(standard_lib, libpython, early_prefix, prefix): return os.path.join(libpython, "site-packages") -def get_python_lib(plat_specific=False, standard_lib=False, prefix=None): +def get_python_lib( + plat_specific: bool = False, standard_lib: bool = False, prefix: str | None = None +) -> str: """Return the directory containing the Python library (standard or site additions). @@ -288,7 +300,7 @@ def _customize_macos(): ) -def customize_compiler(compiler): +def customize_compiler(compiler: CCompiler) -> None: """Do any platform-specific customization of a CCompiler instance. Mainly needed on Unix, so we can plug in the information that @@ -375,12 +387,12 @@ def customize_compiler(compiler): compiler.shared_lib_extension = shlib_suffix -def get_config_h_filename(): +def get_config_h_filename() -> str: """Return full pathname of installed pyconfig.h file.""" return sysconfig.get_config_h_filename() -def get_makefile_filename(): +def get_makefile_filename() -> str: """Return full pathname of installed Makefile from the Python build.""" return sysconfig.get_makefile_filename() @@ -542,7 +554,11 @@ def expand_makefile_vars(s, vars): _config_vars = None -def get_config_vars(*args): +@overload +def get_config_vars() -> dict[str, str | int]: ... +@overload +def get_config_vars(arg: str, /, *args: str) -> list[str | int]: ... +def get_config_vars(*args: str) -> list[str | int] | dict[str, str | int]: """With no arguments, return a dictionary of all configuration variables relevant for the current platform. Generally this includes everything needed to build extensions and install both pure modules and @@ -560,7 +576,14 @@ def get_config_vars(*args): return [_config_vars.get(name) for name in args] if args else _config_vars -def get_config_var(name): +@overload +@deprecated( + "SO is deprecated, use EXT_SUFFIX. Support will be removed when this module is synchronized with stdlib Python 3.11" +) +def get_config_var(name: Literal["SO"]) -> int | str | None: ... +@overload +def get_config_var(name: str) -> int | str | None: ... +def get_config_var(name: str) -> int | str | None: """Return the value of a single variable using the dictionary returned by 'get_config_vars()'. Equivalent to get_config_vars().get(name) diff --git a/distutils/tests/test_sdist.py b/distutils/tests/test_sdist.py index 5aca43e3..6b1a376b 100644 --- a/distutils/tests/test_sdist.py +++ b/distutils/tests/test_sdist.py @@ -270,7 +270,7 @@ def test_metadata_check_option(self, caplog): caplog.clear() dist, cmd = self.get_cmd() cmd.ensure_finalized() - cmd.metadata_check = 0 + cmd.metadata_check = False cmd.run() assert len(self.warnings(caplog.messages, 'warning: check: ')) == 0 diff --git a/distutils/util.py b/distutils/util.py index 83ad39e9..4d32fb2e 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -16,6 +16,8 @@ import sys import sysconfig import tempfile +from collections.abc import Callable, Iterable, Mapping +from typing import TYPE_CHECKING, AnyStr from jaraco.functools import pass_none @@ -24,6 +26,11 @@ from .errors import DistutilsByteCompileError, DistutilsPlatformError from .spawn import spawn +if TYPE_CHECKING: + from typing_extensions import TypeVarTuple, Unpack + + _Ts = TypeVarTuple("_Ts") + def get_host_platform() -> str: """ @@ -39,7 +46,7 @@ def get_host_platform() -> str: return sysconfig.get_platform() -def get_platform(): +def get_platform() -> str: if os.name == 'nt': TARGET_TO_PLAT = { 'x86': 'win32', @@ -108,13 +115,13 @@ def get_macosx_target_ver(): return syscfg_ver -def split_version(s): +def split_version(s: str) -> list[int]: """Convert a dot-separated string into a list of numbers for comparisons""" return [int(n) for n in s.split('.')] @pass_none -def convert_path(pathname: str | os.PathLike) -> str: +def convert_path(pathname: str | os.PathLike[str]) -> str: r""" Allow for pathlib.Path inputs, coax to a native path string. @@ -132,7 +139,9 @@ def convert_path(pathname: str | os.PathLike) -> str: return os.fspath(pathlib.PurePath(pathname)) -def change_root(new_root, pathname): +def change_root( + new_root: AnyStr | os.PathLike[AnyStr], pathname: AnyStr | os.PathLike[AnyStr] +) -> AnyStr: """Return 'pathname' with 'new_root' prepended. If 'pathname' is relative, this is equivalent to "os.path.join(new_root,pathname)". Otherwise, it requires making 'pathname' relative and then joining the @@ -154,7 +163,7 @@ def change_root(new_root, pathname): @functools.lru_cache -def check_environ(): +def check_environ() -> None: """Ensure that 'os.environ' has all the environment variables we guarantee that users can use in config files, command-line options, etc. Currently this includes: @@ -176,7 +185,7 @@ def check_environ(): os.environ['PLAT'] = get_platform() -def subst_vars(s, local_vars): +def subst_vars(s, local_vars: Mapping[str, object]) -> str: """ Perform variable substitution on 'string'. Variables are indicated by format-style braces ("{var}"). @@ -215,7 +224,7 @@ def _subst(match): return repl -def grok_environment_error(exc, prefix="error: "): +def grok_environment_error(exc: object, prefix: str = "error: ") -> str: # Function kept for backward compatibility. # Used to try clever things with EnvironmentErrors, # but nowadays str(exception) produces good messages. @@ -233,7 +242,7 @@ def _init_regex(): _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"') -def split_quoted(s): +def split_quoted(s: str) -> list[str]: """Split a string up according to Unix shell-like rules for quotes and backslashes. In short: words are delimited by spaces, as long as those spaces are not escaped by a backslash, or inside a quoted string. @@ -299,7 +308,13 @@ def split_quoted(s): # split_quoted () -def execute(func, args, msg=None, verbose=False, dry_run=False): +def execute( + func: Callable[[Unpack[_Ts]], object], + args: tuple[Unpack[_Ts]], + msg: object = None, + verbose: bool = False, + dry_run: bool = False, +) -> None: """Perform some action that affects the outside world (eg. by writing to the filesystem). Such actions are special because they are disabled by the 'dry_run' flag. This method takes care of all @@ -318,7 +333,7 @@ def execute(func, args, msg=None, verbose=False, dry_run=False): func(*args) -def strtobool(val): +def strtobool(val: str) -> bool: """Convert a string representation of truth to true (1) or false (0). True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values @@ -327,23 +342,23 @@ def strtobool(val): """ val = val.lower() if val in ('y', 'yes', 't', 'true', 'on', '1'): - return 1 + return True elif val in ('n', 'no', 'f', 'false', 'off', '0'): - return 0 + return False else: raise ValueError(f"invalid truth value {val!r}") def byte_compile( # noqa: C901 - py_files, - optimize=0, - force=False, - prefix=None, - base_dir=None, - verbose=True, - dry_run=False, - direct=None, -): + py_files: Iterable[str], + optimize: int = 0, + force: bool = False, + prefix: str | None = None, + base_dir: str | None = None, + verbose: bool = True, + dry_run: bool = False, + direct: bool | None = None, +) -> None: """Byte-compile a collection of Python source files to .pyc files in a __pycache__ subdirectory. 'py_files' is a list of files to compile; any files that don't end in ".py" are silently @@ -473,7 +488,7 @@ def byte_compile( # noqa: C901 log.debug("skipping byte-compilation of %s to %s", file, cfile_base) -def rfc822_escape(header): +def rfc822_escape(header: str) -> str: """Return a version of the string escaped for inclusion in an RFC-822 header, by ensuring there are 8 spaces space after each newline. """ @@ -488,7 +503,7 @@ def rfc822_escape(header): return indent.join(lines) + suffix -def is_mingw(): +def is_mingw() -> bool: """Returns True if the current platform is mingw. Python compiled with Mingw-w64 has sys.platform == 'win32' and From c26fedb4f32f2167f3c8306864d4c06c5b0f3036 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 18:43:16 -0500 Subject: [PATCH 02/11] Add make_archive overloads --- distutils/archive_util.py | 26 ++++++++++++++++++++++++-- distutils/cmd.py | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/distutils/archive_util.py b/distutils/archive_util.py index 167a5c2e..92856660 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -4,7 +4,7 @@ that sort of thing).""" import os -from typing import Literal +from typing import Literal, overload try: import zipfile @@ -128,7 +128,7 @@ def make_zipfile( base_dir: str | os.PathLike[str], verbose: bool = False, dry_run: bool = False, -) -> str: # noqa: C901 +) -> str: """Create a zip file from all the files under 'base_dir'. The output zip file will be named 'base_name' + ".zip". Uses either the @@ -210,6 +210,7 @@ def check_archive_formats(formats): return None +@overload def make_archive( base_name: str, format: str, @@ -219,6 +220,27 @@ def make_archive( dry_run: bool = False, owner: str | None = None, group: str | None = None, +) -> str: ... +@overload +def make_archive( + base_name: str | os.PathLike[str], + format: str, + root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes], + base_dir: str | None = None, + verbose: bool = False, + dry_run: bool = False, + owner: str | None = None, + group: str | None = None, +) -> str: ... +def make_archive( + base_name: str | os.PathLike[str], + format: str, + root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None, + base_dir: str | None = None, + verbose: bool = False, + dry_run: bool = False, + owner: str | None = None, + group: str | None = None, ) -> str: """Create an archive file (eg. zip or tar). diff --git a/distutils/cmd.py b/distutils/cmd.py index db346730..5d4af6a4 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -475,6 +475,7 @@ def spawn( spawn(cmd, search_path, dry_run=self.dry_run) + @overload def make_archive( self, base_name: str, @@ -483,6 +484,25 @@ def make_archive( base_dir: str | None = None, owner: str | None = None, group: str | None = None, + ) -> str: ... + @overload + def make_archive( + self, + base_name: str | os.PathLike[str], + format: str, + root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes], + base_dir: str | None = None, + owner: str | None = None, + group: str | None = None, + ) -> str: ... + def make_archive( + self, + base_name: str | os.PathLike[str], + format: str, + root_dir: str | os.PathLike[str] | bytes | os.PathLike[bytes] | None = None, + base_dir: str | None = None, + owner: str | None = None, + group: str | None = None, ) -> str: return archive_util.make_archive( base_name, From 21c981cf506ffaf5d040d09e97bd5e0d98d1acfc Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 18:45:52 -0500 Subject: [PATCH 03/11] Fix impossible _Env import --- distutils/spawn.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/distutils/spawn.py b/distutils/spawn.py index fc662817..973668f2 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -13,13 +13,16 @@ import sys import warnings from collections.abc import Mapping, MutableSequence -from subprocess import _ENV -from typing import TypeVar, overload +from typing import TYPE_CHECKING, TypeVar, overload from ._log import log from .debug import DEBUG from .errors import DistutilsExecError +if TYPE_CHECKING: + from subprocess import _ENV + + _MappingT = TypeVar("_MappingT", bound=Mapping) From f1a6ec96f4fbd4f8820622910f64d1441f79e694 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 18:48:29 -0500 Subject: [PATCH 04/11] Fix runtime bound typevars --- distutils/_modified.py | 6 ++++-- distutils/compilers/C/base.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/distutils/_modified.py b/distutils/_modified.py index 57931897..f64cab7d 100644 --- a/distutils/_modified.py +++ b/distutils/_modified.py @@ -1,5 +1,7 @@ """Timestamp comparison of files and groups of files.""" +from __future__ import annotations + import functools import os.path from collections.abc import Callable, Iterable @@ -11,10 +13,10 @@ from .errors import DistutilsFileError _SourcesT = TypeVar( - "_SourcesT", bound=str | bytes | os.PathLike[str] | os.PathLike[bytes] + "_SourcesT", bound="str | bytes | os.PathLike[str] | os.PathLike[bytes]" ) _TargetsT = TypeVar( - "_TargetsT", bound=str | bytes | os.PathLike[str] | os.PathLike[bytes] + "_TargetsT", bound="str | bytes | os.PathLike[str] | os.PathLike[bytes]" ) diff --git a/distutils/compilers/C/base.py b/distutils/compilers/C/base.py index c6ec9055..421f7abb 100644 --- a/distutils/compilers/C/base.py +++ b/distutils/compilers/C/base.py @@ -46,8 +46,8 @@ _Ts = TypeVarTuple("_Ts") _Macro: TypeAlias = Union[Tuple[str], Tuple[str, str | None]] -_StrPathT = TypeVar("_StrPathT", bound=str | os.PathLike[str]) -_BytesPathT = TypeVar("_BytesPathT", bound=bytes | os.PathLike[bytes]) +_StrPathT = TypeVar("_StrPathT", bound="str | os.PathLike[str]") +_BytesPathT = TypeVar("_BytesPathT", bound="bytes | os.PathLike[bytes]") class Compiler: From 02b856ab08e87668f5ad7eb7b580fdfce6f39ef3 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 18:52:12 -0500 Subject: [PATCH 05/11] Fix deprecated --- distutils/command/bdist.py | 4 ++-- distutils/compilers/C/base.py | 3 +-- distutils/dist.py | 6 ++---- distutils/sysconfig.py | 4 ++-- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/distutils/command/bdist.py b/distutils/command/bdist.py index 230bdeaf..a34129f7 100644 --- a/distutils/command/bdist.py +++ b/distutils/command/bdist.py @@ -16,8 +16,8 @@ from typing_extensions import deprecated else: - def deprecated(fn): - return fn + def deprecated(message): + return lambda fn: fn def show_formats(): diff --git a/distutils/compilers/C/base.py b/distutils/compilers/C/base.py index 421f7abb..1828b993 100644 --- a/distutils/compilers/C/base.py +++ b/distutils/compilers/C/base.py @@ -15,7 +15,6 @@ TYPE_CHECKING, ClassVar, Literal, - Tuple, TypeAlias, TypeVar, Union, @@ -45,7 +44,7 @@ _Ts = TypeVarTuple("_Ts") -_Macro: TypeAlias = Union[Tuple[str], Tuple[str, str | None]] +_Macro: TypeAlias = Union[tuple[str], tuple[str, Union[str, None]]] _StrPathT = TypeVar("_StrPathT", bound="str | os.PathLike[str]") _BytesPathT = TypeVar("_BytesPathT", bound="bytes | os.PathLike[bytes]") diff --git a/distutils/dist.py b/distutils/dist.py index 72bd13ce..b1630b4c 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -20,9 +20,7 @@ TYPE_CHECKING, Any, ClassVar, - List, Literal, - Tuple, TypeAlias, TypeVar, Union, @@ -49,8 +47,8 @@ from .cmd import Command _CommandT = TypeVar("_CommandT", bound="Command") -_OptionsList: TypeAlias = List[ - Tuple[str, Union[str, None], str, int] | Tuple[str, Union[str, None], str] +_OptionsList: TypeAlias = list[ + Union[tuple[str, Union[str, None], str, int], tuple[str, Union[str, None], str]] ] diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index c27e7799..08b7d045 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -28,8 +28,8 @@ from typing_extensions import deprecated else: - def deprecated(fn): - return fn + def deprecated(message): + return lambda fn: fn IS_PYPY = '__pypy__' in sys.builtin_module_names From 509245fe5f5f1caf613a1e7cfa0c5ef16c6a0dc6 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 18:54:37 -0500 Subject: [PATCH 06/11] Fix TypeAlias import --- distutils/compilers/C/base.py | 3 +-- distutils/dist.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/distutils/compilers/C/base.py b/distutils/compilers/C/base.py index 1828b993..4767b7f3 100644 --- a/distutils/compilers/C/base.py +++ b/distutils/compilers/C/base.py @@ -15,7 +15,6 @@ TYPE_CHECKING, ClassVar, Literal, - TypeAlias, TypeVar, Union, overload, @@ -40,7 +39,7 @@ ) if TYPE_CHECKING: - from typing_extensions import TypeVarTuple, Unpack + from typing_extensions import TypeAlias, TypeVarTuple, Unpack _Ts = TypeVarTuple("_Ts") diff --git a/distutils/dist.py b/distutils/dist.py index b1630b4c..69d42016 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -21,7 +21,6 @@ Any, ClassVar, Literal, - TypeAlias, TypeVar, Union, overload, @@ -42,6 +41,7 @@ if TYPE_CHECKING: from _typeshed import SupportsWrite + from typing_extensions import TypeAlias # type-only import because of mutual dependence between these modules from .cmd import Command From c6768e6181919f673fd67c67a99ade1f51bc1b85 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 18:57:52 -0500 Subject: [PATCH 07/11] from __future__ import annotations Add a few missing --- distutils/archive_util.py | 2 ++ distutils/command/bdist.py | 2 ++ distutils/command/build.py | 2 ++ distutils/command/build_clib.py | 2 +- distutils/command/build_ext.py | 2 ++ distutils/command/install.py | 2 ++ distutils/command/sdist.py | 2 ++ distutils/compilers/C/msvc.py | 1 + distutils/extension.py | 2 ++ distutils/filelist.py | 2 ++ distutils/sysconfig.py | 2 ++ 11 files changed, 20 insertions(+), 1 deletion(-) diff --git a/distutils/archive_util.py b/distutils/archive_util.py index 92856660..aa2d81ff 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -3,6 +3,8 @@ Utility functions for creating archive files (tarballs, zip files, that sort of thing).""" +from __future__ import annotations + import os from typing import Literal, overload diff --git a/distutils/command/bdist.py b/distutils/command/bdist.py index a34129f7..07811aab 100644 --- a/distutils/command/bdist.py +++ b/distutils/command/bdist.py @@ -3,6 +3,8 @@ Implements the Distutils 'bdist' command (create a built [binary] distribution).""" +from __future__ import annotations + import os import warnings from collections.abc import Callable diff --git a/distutils/command/build.py b/distutils/command/build.py index 9493cefe..61f2431a 100644 --- a/distutils/command/build.py +++ b/distutils/command/build.py @@ -2,6 +2,8 @@ Implements the Distutils 'build' command.""" +from __future__ import annotations + import os import sys import sysconfig diff --git a/distutils/command/build_clib.py b/distutils/command/build_clib.py index 2a1643d6..0d6d1c8a 100644 --- a/distutils/command/build_clib.py +++ b/distutils/command/build_clib.py @@ -4,7 +4,6 @@ that is included in the module distribution and needed by an extension module.""" - # XXX this module has *lots* of code ripped-off quite transparently from # build_ext.py -- not surprisingly really, as the work required to build # a static library from a collection of C source files is not really all @@ -13,6 +12,7 @@ # necessary refactoring to account for the overlap in code between the # two modules, mainly because a number of subtle details changed in the # cut 'n paste. Sigh. +from __future__ import annotations import os from collections.abc import Callable diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index c25352f6..55dec90e 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -4,6 +4,8 @@ modules (currently limited to C extensions, should accommodate C++ extensions ASAP).""" +from __future__ import annotations + import contextlib import os import re diff --git a/distutils/command/install.py b/distutils/command/install.py index 12f9d1fe..b09048cf 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -2,6 +2,8 @@ Implements the Distutils 'install' command.""" +from __future__ import annotations + import contextlib import itertools import os diff --git a/distutils/command/sdist.py b/distutils/command/sdist.py index abb8dba5..b3bf0c32 100644 --- a/distutils/command/sdist.py +++ b/distutils/command/sdist.py @@ -2,6 +2,8 @@ Implements the Distutils 'sdist' command (create a source distribution).""" +from __future__ import annotations + import os import sys from collections.abc import Callable diff --git a/distutils/compilers/C/msvc.py b/distutils/compilers/C/msvc.py index ea1a342d..ebf2568b 100644 --- a/distutils/compilers/C/msvc.py +++ b/distutils/compilers/C/msvc.py @@ -11,6 +11,7 @@ # finding DevStudio (through the registry) # ported to VS 2005 and VS 2008 by Christian Heimes # ported to VS 2015 by Steve Dower +from __future__ import annotations import contextlib import os diff --git a/distutils/extension.py b/distutils/extension.py index 0885e33b..f5141126 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -3,6 +3,8 @@ Provides the Extension class, used to describe C/C++ extension modules in setup scripts.""" +from __future__ import annotations + import os import warnings from collections.abc import Iterable diff --git a/distutils/filelist.py b/distutils/filelist.py index 3b8f8e69..70dc0fde 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -4,6 +4,8 @@ and building lists of files. """ +from __future__ import annotations + import fnmatch import functools import os diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 08b7d045..e5facaec 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -9,6 +9,8 @@ Email: """ +from __future__ import annotations + import functools import os import pathlib From d1071cbe4a9610648ff092acf2fb40de522f91de Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 19:05:39 -0500 Subject: [PATCH 08/11] from __future__ import annotations Another missing --- distutils/command/install_lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/distutils/command/install_lib.py b/distutils/command/install_lib.py index 318a9db8..2aababf8 100644 --- a/distutils/command/install_lib.py +++ b/distutils/command/install_lib.py @@ -3,6 +3,8 @@ Implements the Distutils 'install_lib' command (install all Python modules).""" +from __future__ import annotations + import importlib.util import os import sys From 7f00d9555c2859f9c48c60d6720097b25732915a Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 24 Feb 2025 19:12:22 -0500 Subject: [PATCH 09/11] Missing noqa --- distutils/archive_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/archive_util.py b/distutils/archive_util.py index aa2d81ff..d860f552 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -125,7 +125,7 @@ def _set_uid_gid(tarinfo): return archive_name -def make_zipfile( +def make_zipfile( # noqa: C901 base_name: str, base_dir: str | os.PathLike[str], verbose: bool = False, From 6a64635b65c575e7abe034c8e08b7177dabd0901 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 3 Mar 2025 19:28:58 -0500 Subject: [PATCH 10/11] Fix condition post-merge --- distutils/compilers/C/tests/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/compilers/C/tests/test_base.py b/distutils/compilers/C/tests/test_base.py index b73ec4e4..301fc1ec 100644 --- a/distutils/compilers/C/tests/test_base.py +++ b/distutils/compilers/C/tests/test_base.py @@ -15,7 +15,7 @@ def _make_strs(paths): """ Convert paths to strings for legacy compatibility. """ - if sys.version_info > (3, 8) and platform.system() != "Windows": + if sys.version_info >= (3, 8) and platform.system() != "Windows": return paths return list(map(os.fspath, paths)) From 3817dedb78f114d887499a3f7fb684acfcddc136 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Mar 2025 19:20:32 -0500 Subject: [PATCH 11/11] Reword note. --- distutils/cmd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distutils/cmd.py b/distutils/cmd.py index 5d4af6a4..241621bd 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -322,8 +322,8 @@ def set_undefined_options( if getattr(self, dst_option) is None: setattr(self, dst_option, getattr(src_cmd_obj, src_option)) - # NOTE: Because distutils is private setuptools implementation and we don't need to re-expose all commands here, - # we're not overloading each and every command possibility. + # NOTE: Because distutils is private to Setuptools and not all commands are exposed here, + # not every possible command is enumerated in the signature. def get_finalized_command(self, command: str, create: bool = True) -> Command: """Wrapper around Distribution's 'get_command_obj()' method: find (create if necessary and 'create' is true) the command object for