diff --git a/bootstrap.egg-info/entry_points.txt b/bootstrap.egg-info/entry_points.txt index a21ca22709..c84c0e8ab9 100644 --- a/bootstrap.egg-info/entry_points.txt +++ b/bootstrap.egg-info/entry_points.txt @@ -1,5 +1,6 @@ [distutils.commands] egg_info = setuptools.command.egg_info:egg_info +dist_info = setuptools.command.dist_info:dist_info build_py = setuptools.command.build_py:build_py sdist = setuptools.command.sdist:sdist editable_wheel = setuptools.command.editable_wheel:editable_wheel diff --git a/setuptools/__init__.py b/setuptools/__init__.py index f1b9bfe9b8..c0aeb7e15a 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -11,8 +11,7 @@ import os import sys from abc import abstractmethod -from collections.abc import Mapping -from typing import TYPE_CHECKING, TypeVar, overload +from typing import TYPE_CHECKING, Any, 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 @@ -21,6 +20,7 @@ import _distutils_hack.override # noqa: F401 from . import logging, monkey +from .compat import py310 from .depends import Require from .discovery import PackageFinder, PEP420PackageFinder from .dist import Distribution @@ -49,40 +49,19 @@ find_namespace_packages = PEP420PackageFinder.find -def _install_setup_requires(attrs): - # Note: do not use `setuptools.Distribution` directly, as - # our PEP 517 backend patch `distutils.core.Distribution`. - class MinimalDistribution(distutils.core.Distribution): - """ - A minimal version of a distribution for supporting the - fetch_build_eggs interface. - """ +def _expand_setupcfg(attrs: dict[str, Any]) -> Distribution: + """Bare minimum setup.cfg parsing so that we can extract setup_requires""" + from setuptools.config.setupcfg import _apply + + dist = Distribution(attrs) + dist.set_defaults._disable() + if os.path.exists("setup.cfg"): # Assumes no other config contains setup_requires + _apply(dist, "setup.cfg", ignore_option_errors=True) + return dist + - def __init__(self, attrs: Mapping[str, object]) -> None: - _incl = 'dependency_links', 'setup_requires' - filtered = {k: attrs[k] for k in set(_incl) & set(attrs)} - super().__init__(filtered) - # Prevent accidentally triggering discovery with incomplete set of attrs - self.set_defaults._disable() - - 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) - except Exception: - return filenames, () - return cfg, () - - def finalize_options(self): - """ - Disable finalize_options to avoid building the working set. - Ref #2158. - """ - - dist = MinimalDistribution(attrs) - - # Honor setup.cfg's options. - dist.parse_config_files(ignore_option_errors=True) +def _install_setup_requires(attrs: dict[str, Any]) -> None: + dist = _expand_setupcfg(attrs) if dist.setup_requires: _fetch_build_eggs(dist) @@ -101,17 +80,21 @@ def _fetch_build_eggs(dist: Distribution): 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") + py310.add_note(ex, msg) raise def setup(**attrs): + if "--private-interrupt-setuppy" in sys.argv: + raise _SetupPyInterruption(_expand_setupcfg(attrs)) + logging.configure() - # Make sure we have any requirements needed to interpret 'attrs'. - _install_setup_requires(attrs) + + if "--private-skip-setup-requires" in sys.argv: + sys.argv.remove("--private-skip-setup-requires") + else: + # Make sure we have any requirements needed to interpret 'attrs'. + _install_setup_requires(attrs) return distutils.core.setup(**attrs) @@ -244,5 +227,10 @@ class sic(str): """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)""" +class _SetupPyInterruption(Exception): + def __init__(self, dist: Distribution): + self.dist = dist + + # Apply monkey patches monkey.patch_all() diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 8f2e930c73..84438df583 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -48,7 +48,6 @@ from ._reqs import parse_strings from .warnings import SetuptoolsDeprecationWarning -import distutils from distutils.util import strtobool if TYPE_CHECKING: @@ -64,53 +63,9 @@ 'prepare_metadata_for_build_editable', 'build_editable', '__legacy__', - 'SetupRequirementsError', ] -class SetupRequirementsError(BaseException): - def __init__(self, specifiers) -> None: - self.specifiers = specifiers - - -class Distribution(setuptools.dist.Distribution): - def fetch_build_eggs(self, specifiers): - specifier_list = list(parse_strings(specifiers)) - - raise SetupRequirementsError(specifier_list) - - @classmethod - @contextlib.contextmanager - def patch(cls): - """ - Replace - distutils.dist.Distribution with this class - for the duration of this context. - """ - orig = distutils.core.Distribution - distutils.core.Distribution = cls # type: ignore[misc] # monkeypatching - try: - yield - finally: - distutils.core.Distribution = orig # type: ignore[misc] # monkeypatching - - -@contextlib.contextmanager -def no_install_setup_requires(): - """Temporarily disable installing setup_requires - - Under PEP 517, the backend reports build dependencies to the frontend, - and the frontend is responsible for ensuring they're installed. - So setuptools (acting as a backend) should not try to install them. - """ - orig = setuptools._install_setup_requires - setuptools._install_setup_requires = lambda attrs: None - try: - yield - finally: - setuptools._install_setup_requires = orig - - def _get_immediate_subdirectories(a_dir): return [ name for name in os.listdir(a_dir) if os.path.isdir(os.path.join(a_dir, name)) @@ -291,16 +246,14 @@ class _BuildMetaBackend(_ConfigSettingsTranslator): def _get_build_requires( self, config_settings: _ConfigSettings, requirements: list[str] ): - sys.argv = [ - *sys.argv[:1], - *self._global_args(config_settings), - "egg_info", - ] + sys.argv = [*sys.argv[:1], "--private-interrupt-setuppy"] try: - with Distribution.patch(): - self.run_setup() - except SetupRequirementsError as e: - requirements += e.specifiers + self.run_setup() + except setuptools._SetupPyInterruption as ex: + setup_requires = parse_strings(ex.dist.setup_requires or "") + return requirements + list(setup_requires) + except Exception: + pass # Ignore other arbitrary exceptions from setup.py, e.g. SystemExit return requirements @@ -313,6 +266,8 @@ def run_setup(self, setup_script: str = 'setup.py'): with _open_setup_script(__file__) as f: code = f.read().replace(r'\r\n', r'\n') + sys.argv.append("--private-skip-setup-requires") + try: exec(code, locals()) except SystemExit as e: @@ -370,8 +325,7 @@ def prepare_metadata_for_build_wheel( str(metadata_directory), "--keep-egg-info", ] - with no_install_setup_requires(): - self.run_setup() + self.run_setup() self._bubble_up_info_directory(metadata_directory, ".egg-info") return self._bubble_up_info_directory(metadata_directory, ".dist-info") @@ -400,8 +354,7 @@ def _build_with_temp_dir( tmp_dist_dir, *arbitrary_args, ] - with no_install_setup_requires(): - self.run_setup() + self.run_setup() result_basename = _file_with_extension(tmp_dist_dir, result_extension) result_path = os.path.join(result_directory, result_basename) @@ -546,3 +499,20 @@ class _IncompatibleBdistWheel(SetuptoolsDeprecationWarning): # The legacy backend __legacy__ = _BuildMetaLegacyBackend() + + +def __getattr__(name): + if name == "SetupRequirementsError": + SetuptoolsDeprecationWarning.emit( + "SetupRequirementsError is no longer part of the public API.", + "Please do not import SetupRequirementsError.", + due_date=(2026, 5, 12), + ) + + class SetupRequirementsError(BaseException): + def __init__(self, specifiers) -> None: + self.specifiers = specifiers + + return SetupRequirementsError + + raise AttributeError(name)