Path: blob/main/test/lib/python3.9/site-packages/setuptools/build_meta.py
4798 views
"""A PEP 517 interface to setuptools12Previously, when a user or a command line tool (let's call it a "frontend")3needed to make a request of setuptools to take a certain action, for4example, generating a list of installation requirements, the frontend would5would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.67PEP 517 defines a different method of interfacing with setuptools. Rather8than calling "setup.py" directly, the frontend should:9101. Set the current directory to the directory with a setup.py file112. Import this module into a safe python interpreter (one in which12setuptools can potentially set global variables or crash hard).133. Call one of the functions defined in PEP 517.1415What each function does is defined in PEP 517. However, here is a "casual"16definition of the functions (this definition should not be relied on for17bug reports or API stability):1819- `build_wheel`: build a wheel in the folder and return the basename20- `get_requires_for_build_wheel`: get the `setup_requires` to build21- `prepare_metadata_for_build_wheel`: get the `install_requires`22- `build_sdist`: build an sdist in the folder and return the basename23- `get_requires_for_build_sdist`: get the `setup_requires` to build2425Again, this is not a formal definition! Just a "taste" of the module.26"""2728import io29import os30import sys31import tokenize32import shutil33import contextlib34import tempfile35import warnings3637import setuptools38import distutils39from ._reqs import parse_strings40from .extern.more_itertools import always_iterable414243__all__ = ['get_requires_for_build_sdist',44'get_requires_for_build_wheel',45'prepare_metadata_for_build_wheel',46'build_wheel',47'build_sdist',48'__legacy__',49'SetupRequirementsError']505152class SetupRequirementsError(BaseException):53def __init__(self, specifiers):54self.specifiers = specifiers555657class Distribution(setuptools.dist.Distribution):58def fetch_build_eggs(self, specifiers):59specifier_list = list(parse_strings(specifiers))6061raise SetupRequirementsError(specifier_list)6263@classmethod64@contextlib.contextmanager65def patch(cls):66"""67Replace68distutils.dist.Distribution with this class69for the duration of this context.70"""71orig = distutils.core.Distribution72distutils.core.Distribution = cls73try:74yield75finally:76distutils.core.Distribution = orig777879@contextlib.contextmanager80def no_install_setup_requires():81"""Temporarily disable installing setup_requires8283Under PEP 517, the backend reports build dependencies to the frontend,84and the frontend is responsible for ensuring they're installed.85So setuptools (acting as a backend) should not try to install them.86"""87orig = setuptools._install_setup_requires88setuptools._install_setup_requires = lambda attrs: None89try:90yield91finally:92setuptools._install_setup_requires = orig939495def _get_immediate_subdirectories(a_dir):96return [name for name in os.listdir(a_dir)97if os.path.isdir(os.path.join(a_dir, name))]9899100def _file_with_extension(directory, extension):101matching = (102f for f in os.listdir(directory)103if f.endswith(extension)104)105try:106file, = matching107except ValueError:108raise ValueError(109'No distribution was found. Ensure that `setup.py` '110'is not empty and that it calls `setup()`.')111return file112113114def _open_setup_script(setup_script):115if not os.path.exists(setup_script):116# Supply a default setup.py117return io.StringIO(u"from setuptools import setup; setup()")118119return getattr(tokenize, 'open', open)(setup_script)120121122@contextlib.contextmanager123def suppress_known_deprecation():124with warnings.catch_warnings():125warnings.filterwarnings('ignore', 'setup.py install is deprecated')126yield127128129class _BuildMetaBackend:130131@staticmethod132def _fix_config(config_settings):133"""134Ensure config settings meet certain expectations.135136>>> fc = _BuildMetaBackend._fix_config137>>> fc(None)138{'--global-option': []}139>>> fc({})140{'--global-option': []}141>>> fc({'--global-option': 'foo'})142{'--global-option': ['foo']}143>>> fc({'--global-option': ['foo']})144{'--global-option': ['foo']}145"""146config_settings = config_settings or {}147config_settings['--global-option'] = list(always_iterable(148config_settings.get('--global-option')))149return config_settings150151def _get_build_requires(self, config_settings, requirements):152config_settings = self._fix_config(config_settings)153154sys.argv = sys.argv[:1] + ['egg_info'] + \155config_settings["--global-option"]156try:157with Distribution.patch():158self.run_setup()159except SetupRequirementsError as e:160requirements += e.specifiers161162return requirements163164def run_setup(self, setup_script='setup.py'):165# Note that we can reuse our build directory between calls166# Correctness comes first, then optimization later167__file__ = setup_script168__name__ = '__main__'169170with _open_setup_script(__file__) as f:171code = f.read().replace(r'\r\n', r'\n')172173exec(compile(code, __file__, 'exec'), locals())174175def get_requires_for_build_wheel(self, config_settings=None):176return self._get_build_requires(177config_settings, requirements=['wheel'])178179def get_requires_for_build_sdist(self, config_settings=None):180return self._get_build_requires(config_settings, requirements=[])181182def prepare_metadata_for_build_wheel(self, metadata_directory,183config_settings=None):184sys.argv = sys.argv[:1] + [185'dist_info', '--egg-base', metadata_directory]186with no_install_setup_requires():187self.run_setup()188189dist_info_directory = metadata_directory190while True:191dist_infos = [f for f in os.listdir(dist_info_directory)192if f.endswith('.dist-info')]193194if (195len(dist_infos) == 0 and196len(_get_immediate_subdirectories(dist_info_directory)) == 1197):198199dist_info_directory = os.path.join(200dist_info_directory, os.listdir(dist_info_directory)[0])201continue202203assert len(dist_infos) == 1204break205206# PEP 517 requires that the .dist-info directory be placed in the207# metadata_directory. To comply, we MUST copy the directory to the root208if dist_info_directory != metadata_directory:209shutil.move(210os.path.join(dist_info_directory, dist_infos[0]),211metadata_directory)212shutil.rmtree(dist_info_directory, ignore_errors=True)213214return dist_infos[0]215216def _build_with_temp_dir(self, setup_command, result_extension,217result_directory, config_settings):218config_settings = self._fix_config(config_settings)219result_directory = os.path.abspath(result_directory)220221# Build in a temporary directory, then copy to the target.222os.makedirs(result_directory, exist_ok=True)223with tempfile.TemporaryDirectory(dir=result_directory) as tmp_dist_dir:224sys.argv = (sys.argv[:1] + setup_command +225['--dist-dir', tmp_dist_dir] +226config_settings["--global-option"])227with no_install_setup_requires():228self.run_setup()229230result_basename = _file_with_extension(231tmp_dist_dir, result_extension)232result_path = os.path.join(result_directory, result_basename)233if os.path.exists(result_path):234# os.rename will fail overwriting on non-Unix.235os.remove(result_path)236os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)237238return result_basename239240def build_wheel(self, wheel_directory, config_settings=None,241metadata_directory=None):242with suppress_known_deprecation():243return self._build_with_temp_dir(['bdist_wheel'], '.whl',244wheel_directory, config_settings)245246def build_sdist(self, sdist_directory, config_settings=None):247return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],248'.tar.gz', sdist_directory,249config_settings)250251252class _BuildMetaLegacyBackend(_BuildMetaBackend):253"""Compatibility backend for setuptools254255This is a version of setuptools.build_meta that endeavors256to maintain backwards257compatibility with pre-PEP 517 modes of invocation. It258exists as a temporary259bridge between the old packaging mechanism and the new260packaging mechanism,261and will eventually be removed.262"""263def run_setup(self, setup_script='setup.py'):264# In order to maintain compatibility with scripts assuming that265# the setup.py script is in a directory on the PYTHONPATH, inject266# '' into sys.path. (pypa/setuptools#1642)267sys_path = list(sys.path) # Save the original path268269script_dir = os.path.dirname(os.path.abspath(setup_script))270if script_dir not in sys.path:271sys.path.insert(0, script_dir)272273# Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to274# get the directory of the source code. They expect it to refer to the275# setup.py script.276sys_argv_0 = sys.argv[0]277sys.argv[0] = setup_script278279try:280super(_BuildMetaLegacyBackend,281self).run_setup(setup_script=setup_script)282finally:283# While PEP 517 frontends should be calling each hook in a fresh284# subprocess according to the standard (and thus it should not be285# strictly necessary to restore the old sys.path), we'll restore286# the original path so that the path manipulation does not persist287# within the hook after run_setup is called.288sys.path[:] = sys_path289sys.argv[0] = sys_argv_0290291292# The primary backend293_BACKEND = _BuildMetaBackend()294295get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel296get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist297prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel298build_wheel = _BACKEND.build_wheel299build_sdist = _BACKEND.build_sdist300301302# The legacy backend303__legacy__ = _BuildMetaLegacyBackend()304305306