This commit is contained in:
lz_db
2026-10-16 22:48:30 +08:00
parent 190de72571
commit 0b36fd3383
3161 changed files with 12787 additions and 77274 deletions
@@ -1,36 +1,26 @@
"""Extensions to the 'distutils' for large or complex distributions"""
# mypy: disable_error_code=override
# Command.reinitialize_command has an extra **kw param that distutils doesn't have
# Can't disable on the exact line because distutils doesn't exists on Python 3.12
# and mypy isn't aware of distutils_hack, causing distutils.core.Command to be Any,
# and a [unused-ignore] to be raised on 3.12+
from __future__ import annotations
import functools
import os
import re
import sys
from abc import abstractmethod
from collections.abc import Mapping
from typing import TYPE_CHECKING, TypeVar, overload
sys.path.extend(((vendor_path := os.path.join(os.path.dirname(os.path.dirname(__file__)), 'setuptools', '_vendor')) not in sys.path) * [vendor_path]) # fmt: skip
# workaround for #4476
sys.modules.pop('backports', None)
import warnings
import _distutils_hack.override # noqa: F401
from . import logging, monkey
from .depends import Require
from .discovery import PackageFinder, PEP420PackageFinder
from .dist import Distribution
from .extension import Extension
from .version import __version__ as __version__
from .warnings import SetuptoolsDeprecationWarning
import distutils.core
from distutils.errors import DistutilsOptionError
from distutils.util import convert_path as _convert_path
from ._deprecation_warning import SetuptoolsDeprecationWarning
import setuptools.version
from setuptools.extension import Extension
from setuptools.dist import Distribution
from setuptools.depends import Require
from setuptools.discovery import PackageFinder, PEP420PackageFinder
from . import monkey
from . import logging
__all__ = [
'setup',
@@ -43,10 +33,11 @@ __all__ = [
'find_namespace_packages',
]
_CommandT = TypeVar("_CommandT", bound="_Command")
__version__ = setuptools.version.__version__
bootstrap_install_from = None
find_packages = PackageFinder.find
find_namespace_packages = PEP420PackageFinder.find
@@ -60,7 +51,7 @@ def _install_setup_requires(attrs):
fetch_build_eggs interface.
"""
def __init__(self, attrs: Mapping[str, object]) -> None:
def __init__(self, attrs):
_incl = 'dependency_links', 'setup_requires'
filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
super().__init__(filtered)
@@ -70,10 +61,10 @@ def _install_setup_requires(attrs):
def _get_project_config_files(self, filenames=None):
"""Ignore ``pyproject.toml``, they are not related to setup_requires"""
try:
cfg, _toml = super()._split_standard_project_metadata(filenames)
cfg, toml = super()._split_standard_project_metadata(filenames)
return cfg, ()
except Exception:
return filenames, ()
return cfg, ()
def finalize_options(self):
"""
@@ -86,44 +77,20 @@ def _install_setup_requires(attrs):
# Honor setup.cfg's options.
dist.parse_config_files(ignore_option_errors=True)
if dist.setup_requires:
_fetch_build_eggs(dist)
def _fetch_build_eggs(dist: Distribution):
try:
dist.fetch_build_eggs(dist.setup_requires)
except Exception as ex:
msg = """
It is possible a package already installed in your system
contains an version that is invalid according to PEP 440.
You can try `pip install --use-pep517` as a workaround for this problem,
or rely on a new virtual environment.
If the problem refers to a package that is not installed yet,
please contact that package's maintainers or distributors.
"""
if "InvalidVersion" in ex.__class__.__name__:
if hasattr(ex, "add_note"):
ex.add_note(msg) # PEP 678
else:
dist.announce(f"\n{msg}\n")
raise
def setup(**attrs):
logging.configure()
# Make sure we have any requirements needed to interpret 'attrs'.
logging.configure()
_install_setup_requires(attrs)
return distutils.core.setup(**attrs)
setup.__doc__ = distutils.core.setup.__doc__
if TYPE_CHECKING:
# Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962
from distutils.core import Command as _Command
else:
_Command = monkey.get_unpatched(distutils.core.Command)
_Command = monkey.get_unpatched(distutils.core.Command)
class Command(_Command):
@@ -139,25 +106,42 @@ class Command(_Command):
When creating a new command from scratch, custom defined classes **SHOULD** inherit
from ``setuptools.Command`` and implement a few mandatory methods.
Between these mandatory methods, are listed:
:meth:`initialize_options`, :meth:`finalize_options` and :meth:`run`.
.. method:: initialize_options(self)
Set or (reset) all options/attributes/caches used by the command
to their default values. Note that these values may be overwritten during
the build.
.. method:: finalize_options(self)
Set final values for all options/attributes used by the command.
Most of the time, each option/attribute/cache should only be set if it does not
have any value yet (e.g. ``if self.attr is None: self.attr = val``).
.. method:: run(self)
Execute the actions intended by the command.
(Side effects **SHOULD** only take place when ``run`` is executed,
for example, creating new files or writing to the terminal output).
A useful analogy for command classes is to think of them as subroutines with local
variables called "options". The options are "declared" in :meth:`initialize_options`
and "defined" (given their final values, aka "finalized") in :meth:`finalize_options`,
variables called "options". The options are "declared" in ``initialize_options()``
and "defined" (given their final values, aka "finalized") in ``finalize_options()``,
both of which must be defined by every command class. The "body" of the subroutine,
(where it does all the work) is the :meth:`run` method.
Between :meth:`initialize_options` and :meth:`finalize_options`, ``setuptools`` may set
(where it does all the work) is the ``run()`` method.
Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set
the values for options/attributes based on user's input (or circumstance),
which means that the implementation should be careful to not overwrite values in
:meth:`finalize_options` unless necessary.
``finalize_options`` unless necessary.
Please note that other commands (or other parts of setuptools) may also overwrite
the values of the command's options/attributes multiple times during the build
process.
Therefore it is important to consistently implement :meth:`initialize_options` and
:meth:`finalize_options`. For example, all derived attributes (or attributes that
Therefore it is important to consistently implement ``initialize_options()`` and
``finalize_options()``. For example, all derived attributes (or attributes that
depend on the value of other attributes) **SHOULD** be recomputed in
:meth:`finalize_options`.
``finalize_options``.
When overwriting existing commands, custom defined classes **MUST** abide by the
same APIs implemented by the original class. They also **SHOULD** inherit from the
@@ -165,9 +149,8 @@ class Command(_Command):
"""
command_consumes_arguments = False
distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
def __init__(self, dist: Distribution, **kw) -> None:
def __init__(self, dist, **kw):
"""
Construct the command for dist, updating
vars(self) with any keyword parameters.
@@ -181,10 +164,12 @@ class Command(_Command):
setattr(self, option, default)
return default
elif not isinstance(val, str):
raise DistutilsOptionError(f"'{option}' must be a {what} (got `{val}`)")
raise DistutilsOptionError(
"'%s' must be a %s (got `%s`)" % (option, what, val)
)
return val
def ensure_string_list(self, option: str) -> None:
def ensure_string_list(self, option):
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
@@ -208,50 +193,13 @@ class Command(_Command):
ok = False
if not ok:
raise DistutilsOptionError(
f"'{option}' must be a list of strings (got {val!r})"
"'%s' must be a list of strings (got %r)" % (option, val)
)
@overload
def reinitialize_command(
self, command: str, reinit_subcommands: bool = False, **kw
) -> _Command: ...
@overload
def reinitialize_command(
self, command: _CommandT, reinit_subcommands: bool = False, **kw
) -> _CommandT: ...
def reinitialize_command(
self, command: str | _Command, reinit_subcommands: bool = False, **kw
) -> _Command:
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw)
return cmd # pyright: ignore[reportReturnType] # pypa/distutils#307
@abstractmethod
def initialize_options(self) -> None:
"""
Set or (reset) all options/attributes/caches used by the command
to their default values. Note that these values may be overwritten during
the build.
"""
raise NotImplementedError
@abstractmethod
def finalize_options(self) -> None:
"""
Set final values for all options/attributes used by the command.
Most of the time, each option/attribute/cache should only be set if it does not
have any value yet (e.g. ``if self.attr is None: self.attr = val``).
"""
raise NotImplementedError
@abstractmethod
def run(self) -> None:
"""
Execute the actions intended by the command.
(Side effects **SHOULD** only take place when :meth:`run` is executed,
for example, creating new files or writing to the terminal output).
"""
raise NotImplementedError
return cmd
def _find_all_simple(path):
@@ -278,6 +226,19 @@ def findall(dir=os.curdir):
return list(files)
@functools.wraps(_convert_path)
def convert_path(pathname):
from inspect import cleandoc
msg = """
The function `convert_path` is considered internal and not part of the public API.
Its direct usage by 3rd-party packages is considered deprecated and the function
may be removed in the future.
"""
warnings.warn(cleandoc(msg), SetuptoolsDeprecationWarning)
return _convert_path(pathname)
class sic(str):
"""Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""