Path: blob/main/test/lib/python3.9/site-packages/setuptools/dist.py
4798 views
# -*- coding: utf-8 -*-1__all__ = ['Distribution']23import io4import sys5import re6import os7import warnings8import numbers9import distutils.log10import distutils.core11import distutils.cmd12import distutils.dist13import distutils.command14from distutils.util import strtobool15from distutils.debug import DEBUG16from distutils.fancy_getopt import translate_longopt17from glob import iglob18import itertools19import textwrap20from typing import List, Optional, TYPE_CHECKING21from pathlib import Path2223from collections import defaultdict24from email import message_from_file2526from distutils.errors import DistutilsOptionError, DistutilsSetupError27from distutils.util import rfc822_escape2829from setuptools.extern import packaging30from setuptools.extern import ordered_set31from setuptools.extern.more_itertools import unique_everseen, partition32from setuptools.extern import nspektr3334from ._importlib import metadata3536from . import SetuptoolsDeprecationWarning3738import setuptools39import setuptools.command40from setuptools import windows_support41from setuptools.monkey import get_unpatched42from setuptools.config import setupcfg, pyprojecttoml43from setuptools.discovery import ConfigDiscovery4445import pkg_resources46from setuptools.extern.packaging import version47from . import _reqs48from . import _entry_points4950if TYPE_CHECKING:51from email.message import Message5253__import__('setuptools.extern.packaging.specifiers')54__import__('setuptools.extern.packaging.version')555657def _get_unpatched(cls):58warnings.warn("Do not call this function", DistDeprecationWarning)59return get_unpatched(cls)606162def get_metadata_version(self):63mv = getattr(self, 'metadata_version', None)64if mv is None:65mv = version.Version('2.1')66self.metadata_version = mv67return mv686970def rfc822_unescape(content: str) -> str:71"""Reverse RFC-822 escaping by removing leading whitespaces from content."""72lines = content.splitlines()73if len(lines) == 1:74return lines[0].lstrip()75return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:]))))767778def _read_field_from_msg(msg: "Message", field: str) -> Optional[str]:79"""Read Message header field."""80value = msg[field]81if value == 'UNKNOWN':82return None83return value848586def _read_field_unescaped_from_msg(msg: "Message", field: str) -> Optional[str]:87"""Read Message header field and apply rfc822_unescape."""88value = _read_field_from_msg(msg, field)89if value is None:90return value91return rfc822_unescape(value)929394def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]:95"""Read Message header field and return all results as list."""96values = msg.get_all(field, None)97if values == []:98return None99return values100101102def _read_payload_from_msg(msg: "Message") -> Optional[str]:103value = msg.get_payload().strip()104if value == 'UNKNOWN' or not value:105return None106return value107108109def read_pkg_file(self, file):110"""Reads the metadata values from a file object."""111msg = message_from_file(file)112113self.metadata_version = version.Version(msg['metadata-version'])114self.name = _read_field_from_msg(msg, 'name')115self.version = _read_field_from_msg(msg, 'version')116self.description = _read_field_from_msg(msg, 'summary')117# we are filling author only.118self.author = _read_field_from_msg(msg, 'author')119self.maintainer = None120self.author_email = _read_field_from_msg(msg, 'author-email')121self.maintainer_email = None122self.url = _read_field_from_msg(msg, 'home-page')123self.download_url = _read_field_from_msg(msg, 'download-url')124self.license = _read_field_unescaped_from_msg(msg, 'license')125126self.long_description = _read_field_unescaped_from_msg(msg, 'description')127if (128self.long_description is None and129self.metadata_version >= version.Version('2.1')130):131self.long_description = _read_payload_from_msg(msg)132self.description = _read_field_from_msg(msg, 'summary')133134if 'keywords' in msg:135self.keywords = _read_field_from_msg(msg, 'keywords').split(',')136137self.platforms = _read_list_from_msg(msg, 'platform')138self.classifiers = _read_list_from_msg(msg, 'classifier')139140# PEP 314 - these fields only exist in 1.1141if self.metadata_version == version.Version('1.1'):142self.requires = _read_list_from_msg(msg, 'requires')143self.provides = _read_list_from_msg(msg, 'provides')144self.obsoletes = _read_list_from_msg(msg, 'obsoletes')145else:146self.requires = None147self.provides = None148self.obsoletes = None149150self.license_files = _read_list_from_msg(msg, 'license-file')151152153def single_line(val):154"""155Quick and dirty validation for Summary pypa/setuptools#1390.156"""157if '\n' in val:158# TODO: Replace with `raise ValueError("newlines not allowed")`159# after reviewing #2893.160warnings.warn("newlines not allowed and will break in the future")161val = val.strip().split('\n')[0]162return val163164165# Based on Python 3.5 version166def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME167"""Write the PKG-INFO format data to a file object."""168version = self.get_metadata_version()169170def write_field(key, value):171file.write("%s: %s\n" % (key, value))172173write_field('Metadata-Version', str(version))174write_field('Name', self.get_name())175write_field('Version', self.get_version())176177summary = self.get_description()178if summary:179write_field('Summary', single_line(summary))180181optional_fields = (182('Home-page', 'url'),183('Download-URL', 'download_url'),184('Author', 'author'),185('Author-email', 'author_email'),186('Maintainer', 'maintainer'),187('Maintainer-email', 'maintainer_email'),188)189190for field, attr in optional_fields:191attr_val = getattr(self, attr, None)192if attr_val is not None:193write_field(field, attr_val)194195license = self.get_license()196if license:197write_field('License', rfc822_escape(license))198199for project_url in self.project_urls.items():200write_field('Project-URL', '%s, %s' % project_url)201202keywords = ','.join(self.get_keywords())203if keywords:204write_field('Keywords', keywords)205206platforms = self.get_platforms() or []207for platform in platforms:208write_field('Platform', platform)209210self._write_list(file, 'Classifier', self.get_classifiers())211212# PEP 314213self._write_list(file, 'Requires', self.get_requires())214self._write_list(file, 'Provides', self.get_provides())215self._write_list(file, 'Obsoletes', self.get_obsoletes())216217# Setuptools specific for PEP 345218if hasattr(self, 'python_requires'):219write_field('Requires-Python', self.python_requires)220221# PEP 566222if self.long_description_content_type:223write_field('Description-Content-Type', self.long_description_content_type)224if self.provides_extras:225for extra in self.provides_extras:226write_field('Provides-Extra', extra)227228self._write_list(file, 'License-File', self.license_files or [])229230long_description = self.get_long_description()231if long_description:232file.write("\n%s" % long_description)233if not long_description.endswith("\n"):234file.write("\n")235236237sequence = tuple, list238239240def check_importable(dist, attr, value):241try:242ep = metadata.EntryPoint(value=value, name=None, group=None)243assert not ep.extras244except (TypeError, ValueError, AttributeError, AssertionError) as e:245raise DistutilsSetupError(246"%r must be importable 'module:attrs' string (got %r)" % (attr, value)247) from e248249250def assert_string_list(dist, attr, value):251"""Verify that value is a string list"""252try:253# verify that value is a list or tuple to exclude unordered254# or single-use iterables255assert isinstance(value, (list, tuple))256# verify that elements of value are strings257assert ''.join(value) != value258except (TypeError, ValueError, AttributeError, AssertionError) as e:259raise DistutilsSetupError(260"%r must be a list of strings (got %r)" % (attr, value)261) from e262263264def check_nsp(dist, attr, value):265"""Verify that namespace packages are valid"""266ns_packages = value267assert_string_list(dist, attr, ns_packages)268for nsp in ns_packages:269if not dist.has_contents_for(nsp):270raise DistutilsSetupError(271"Distribution contains no modules or packages for "272+ "namespace package %r" % nsp273)274parent, sep, child = nsp.rpartition('.')275if parent and parent not in ns_packages:276distutils.log.warn(277"WARNING: %r is declared as a package namespace, but %r"278" is not: please correct this in setup.py",279nsp,280parent,281)282msg = (283"The namespace_packages parameter is deprecated, "284"consider using implicit namespaces instead (PEP 420)."285)286warnings.warn(msg, SetuptoolsDeprecationWarning)287288289def check_extras(dist, attr, value):290"""Verify that extras_require mapping is valid"""291try:292list(itertools.starmap(_check_extra, value.items()))293except (TypeError, ValueError, AttributeError) as e:294raise DistutilsSetupError(295"'extras_require' must be a dictionary whose values are "296"strings or lists of strings containing valid project/version "297"requirement specifiers."298) from e299300301def _check_extra(extra, reqs):302name, sep, marker = extra.partition(':')303if marker and pkg_resources.invalid_marker(marker):304raise DistutilsSetupError("Invalid environment marker: " + marker)305list(_reqs.parse(reqs))306307308def assert_bool(dist, attr, value):309"""Verify that value is True, False, 0, or 1"""310if bool(value) != value:311tmpl = "{attr!r} must be a boolean value (got {value!r})"312raise DistutilsSetupError(tmpl.format(attr=attr, value=value))313314315def invalid_unless_false(dist, attr, value):316if not value:317warnings.warn(f"{attr} is ignored.", DistDeprecationWarning)318return319raise DistutilsSetupError(f"{attr} is invalid.")320321322def check_requirements(dist, attr, value):323"""Verify that install_requires is a valid requirements list"""324try:325list(_reqs.parse(value))326if isinstance(value, (dict, set)):327raise TypeError("Unordered types are not allowed")328except (TypeError, ValueError) as error:329tmpl = (330"{attr!r} must be a string or list of strings "331"containing valid project/version requirement specifiers; {error}"332)333raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error334335336def check_specifier(dist, attr, value):337"""Verify that value is a valid version specifier"""338try:339packaging.specifiers.SpecifierSet(value)340except (packaging.specifiers.InvalidSpecifier, AttributeError) as error:341tmpl = (342"{attr!r} must be a string " "containing valid version specifiers; {error}"343)344raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error345346347def check_entry_points(dist, attr, value):348"""Verify that entry_points map is parseable"""349try:350_entry_points.load(value)351except Exception as e:352raise DistutilsSetupError(e) from e353354355def check_test_suite(dist, attr, value):356if not isinstance(value, str):357raise DistutilsSetupError("test_suite must be a string")358359360def check_package_data(dist, attr, value):361"""Verify that value is a dictionary of package names to glob lists"""362if not isinstance(value, dict):363raise DistutilsSetupError(364"{!r} must be a dictionary mapping package names to lists of "365"string wildcard patterns".format(attr)366)367for k, v in value.items():368if not isinstance(k, str):369raise DistutilsSetupError(370"keys of {!r} dict must be strings (got {!r})".format(attr, k)371)372assert_string_list(dist, 'values of {!r} dict'.format(attr), v)373374375def check_packages(dist, attr, value):376for pkgname in value:377if not re.match(r'\w+(\.\w+)*', pkgname):378distutils.log.warn(379"WARNING: %r not a valid package name; please use only "380".-separated package names in setup.py",381pkgname,382)383384385_Distribution = get_unpatched(distutils.core.Distribution)386387388class Distribution(_Distribution):389"""Distribution with support for tests and package data390391This is an enhanced version of 'distutils.dist.Distribution' that392effectively adds the following new optional keyword arguments to 'setup()':393394'install_requires' -- a string or sequence of strings specifying project395versions that the distribution requires when installed, in the format396used by 'pkg_resources.require()'. They will be installed397automatically when the package is installed. If you wish to use398packages that are not available in PyPI, or want to give your users an399alternate download location, you can add a 'find_links' option to the400'[easy_install]' section of your project's 'setup.cfg' file, and then401setuptools will scan the listed web pages for links that satisfy the402requirements.403404'extras_require' -- a dictionary mapping names of optional "extras" to the405additional requirement(s) that using those extras incurs. For example,406this::407408extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])409410indicates that the distribution can optionally provide an extra411capability called "reST", but it can only be used if docutils and412reSTedit are installed. If the user installs your package using413EasyInstall and requests one of your extras, the corresponding414additional requirements will be installed if needed.415416'test_suite' -- the name of a test suite to run for the 'test' command.417If the user runs 'python setup.py test', the package will be installed,418and the named test suite will be run. The format is the same as419would be used on a 'unittest.py' command line. That is, it is the420dotted name of an object to import and call to generate a test suite.421422'package_data' -- a dictionary mapping package names to lists of filenames423or globs to use to find data files contained in the named packages.424If the dictionary has filenames or globs listed under '""' (the empty425string), those names will be searched for in every package, in addition426to any names for the specific package. Data files found using these427names/globs will be installed along with the package, in the same428location as the package. Note that globs are allowed to reference429the contents of non-package subdirectories, as long as you use '/' as430a path separator. (Globs are automatically converted to431platform-specific paths at runtime.)432433In addition to these new keywords, this class also has several new methods434for manipulating the distribution's contents. For example, the 'include()'435and 'exclude()' methods can be thought of as in-place add and subtract436commands that add or remove packages, modules, extensions, and so on from437the distribution.438"""439440_DISTUTILS_UNSUPPORTED_METADATA = {441'long_description_content_type': lambda: None,442'project_urls': dict,443'provides_extras': ordered_set.OrderedSet,444'license_file': lambda: None,445'license_files': lambda: None,446}447448_patched_dist = None449450def patch_missing_pkg_info(self, attrs):451# Fake up a replacement for the data that would normally come from452# PKG-INFO, but which might not yet be built if this is a fresh453# checkout.454#455if not attrs or 'name' not in attrs or 'version' not in attrs:456return457key = pkg_resources.safe_name(str(attrs['name'])).lower()458dist = pkg_resources.working_set.by_key.get(key)459if dist is not None and not dist.has_metadata('PKG-INFO'):460dist._version = pkg_resources.safe_version(str(attrs['version']))461self._patched_dist = dist462463def __init__(self, attrs=None):464have_package_data = hasattr(self, "package_data")465if not have_package_data:466self.package_data = {}467attrs = attrs or {}468self.dist_files = []469# Filter-out setuptools' specific options.470self.src_root = attrs.pop("src_root", None)471self.patch_missing_pkg_info(attrs)472self.dependency_links = attrs.pop('dependency_links', [])473self.setup_requires = attrs.pop('setup_requires', [])474for ep in metadata.entry_points(group='distutils.setup_keywords'):475vars(self).setdefault(ep.name, None)476_Distribution.__init__(477self,478{479k: v480for k, v in attrs.items()481if k not in self._DISTUTILS_UNSUPPORTED_METADATA482},483)484485# Save the original dependencies before they are processed into the egg format486self._orig_extras_require = {}487self._orig_install_requires = []488self._tmp_extras_require = defaultdict(ordered_set.OrderedSet)489490self.set_defaults = ConfigDiscovery(self)491492self._set_metadata_defaults(attrs)493494self.metadata.version = self._normalize_version(495self._validate_version(self.metadata.version)496)497self._finalize_requires()498499def _validate_metadata(self):500required = {"name"}501provided = {502key503for key in vars(self.metadata)504if getattr(self.metadata, key, None) is not None505}506missing = required - provided507508if missing:509msg = f"Required package metadata is missing: {missing}"510raise DistutilsSetupError(msg)511512def _set_metadata_defaults(self, attrs):513"""514Fill-in missing metadata fields not supported by distutils.515Some fields may have been set by other tools (e.g. pbr).516Those fields (vars(self.metadata)) take precedence to517supplied attrs.518"""519for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():520vars(self.metadata).setdefault(option, attrs.get(option, default()))521522@staticmethod523def _normalize_version(version):524if isinstance(version, setuptools.sic) or version is None:525return version526527normalized = str(packaging.version.Version(version))528if version != normalized:529tmpl = "Normalizing '{version}' to '{normalized}'"530warnings.warn(tmpl.format(**locals()))531return normalized532return version533534@staticmethod535def _validate_version(version):536if isinstance(version, numbers.Number):537# Some people apparently take "version number" too literally :)538version = str(version)539540if version is not None:541try:542packaging.version.Version(version)543except (packaging.version.InvalidVersion, TypeError):544warnings.warn(545"The version specified (%r) is an invalid version, this "546"may not work as expected with newer versions of "547"setuptools, pip, and PyPI. Please see PEP 440 for more "548"details." % version549)550return setuptools.sic(version)551return version552553def _finalize_requires(self):554"""555Set `metadata.python_requires` and fix environment markers556in `install_requires` and `extras_require`.557"""558if getattr(self, 'python_requires', None):559self.metadata.python_requires = self.python_requires560561if getattr(self, 'extras_require', None):562# Save original before it is messed by _convert_extras_requirements563self._orig_extras_require = self._orig_extras_require or self.extras_require564for extra in self.extras_require.keys():565# Since this gets called multiple times at points where the566# keys have become 'converted' extras, ensure that we are only567# truly adding extras we haven't seen before here.568extra = extra.split(':')[0]569if extra:570self.metadata.provides_extras.add(extra)571572if getattr(self, 'install_requires', None) and not self._orig_install_requires:573# Save original before it is messed by _move_install_requirements_markers574self._orig_install_requires = self.install_requires575576self._convert_extras_requirements()577self._move_install_requirements_markers()578579def _convert_extras_requirements(self):580"""581Convert requirements in `extras_require` of the form582`"extra": ["barbazquux; {marker}"]` to583`"extra:{marker}": ["barbazquux"]`.584"""585spec_ext_reqs = getattr(self, 'extras_require', None) or {}586tmp = defaultdict(ordered_set.OrderedSet)587self._tmp_extras_require = getattr(self, '_tmp_extras_require', tmp)588for section, v in spec_ext_reqs.items():589# Do not strip empty sections.590self._tmp_extras_require[section]591for r in _reqs.parse(v):592suffix = self._suffix_for(r)593self._tmp_extras_require[section + suffix].append(r)594595@staticmethod596def _suffix_for(req):597"""598For a requirement, return the 'extras_require' suffix for599that requirement.600"""601return ':' + str(req.marker) if req.marker else ''602603def _move_install_requirements_markers(self):604"""605Move requirements in `install_requires` that are using environment606markers `extras_require`.607"""608609# divide the install_requires into two sets, simple ones still610# handled by install_requires and more complex ones handled611# by extras_require.612613def is_simple_req(req):614return not req.marker615616spec_inst_reqs = getattr(self, 'install_requires', None) or ()617inst_reqs = list(_reqs.parse(spec_inst_reqs))618simple_reqs = filter(is_simple_req, inst_reqs)619complex_reqs = itertools.filterfalse(is_simple_req, inst_reqs)620self.install_requires = list(map(str, simple_reqs))621622for r in complex_reqs:623self._tmp_extras_require[':' + str(r.marker)].append(r)624self.extras_require = dict(625# list(dict.fromkeys(...)) ensures a list of unique strings626(k, list(dict.fromkeys(str(r) for r in map(self._clean_req, v))))627for k, v in self._tmp_extras_require.items()628)629630def _clean_req(self, req):631"""632Given a Requirement, remove environment markers and return it.633"""634req.marker = None635return req636637def _finalize_license_files(self):638"""Compute names of all license files which should be included."""639license_files: Optional[List[str]] = self.metadata.license_files640patterns: List[str] = license_files if license_files else []641642license_file: Optional[str] = self.metadata.license_file643if license_file and license_file not in patterns:644patterns.append(license_file)645646if license_files is None and license_file is None:647# Default patterns match the ones wheel uses648# See https://wheel.readthedocs.io/en/stable/user_guide.html649# -> 'Including license files in the generated wheel file'650patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')651652self.metadata.license_files = list(653unique_everseen(self._expand_patterns(patterns))654)655656@staticmethod657def _expand_patterns(patterns):658"""659>>> list(Distribution._expand_patterns(['LICENSE']))660['LICENSE']661>>> list(Distribution._expand_patterns(['setup.cfg', 'LIC*']))662['setup.cfg', 'LICENSE']663"""664return (665path666for pattern in patterns667for path in sorted(iglob(pattern))668if not path.endswith('~') and os.path.isfile(path)669)670671# FIXME: 'Distribution._parse_config_files' is too complex (14)672def _parse_config_files(self, filenames=None): # noqa: C901673"""674Adapted from distutils.dist.Distribution.parse_config_files,675this method provides the same functionality in subtly-improved676ways.677"""678from configparser import ConfigParser679680# Ignore install directory options if we have a venv681ignore_options = (682[]683if sys.prefix == sys.base_prefix684else [685'install-base',686'install-platbase',687'install-lib',688'install-platlib',689'install-purelib',690'install-headers',691'install-scripts',692'install-data',693'prefix',694'exec-prefix',695'home',696'user',697'root',698]699)700701ignore_options = frozenset(ignore_options)702703if filenames is None:704filenames = self.find_config_files()705706if DEBUG:707self.announce("Distribution.parse_config_files():")708709parser = ConfigParser()710parser.optionxform = str711for filename in filenames:712with io.open(filename, encoding='utf-8') as reader:713if DEBUG:714self.announce(" reading {filename}".format(**locals()))715parser.read_file(reader)716for section in parser.sections():717options = parser.options(section)718opt_dict = self.get_option_dict(section)719720for opt in options:721if opt == '__name__' or opt in ignore_options:722continue723724val = parser.get(section, opt)725opt = self.warn_dash_deprecation(opt, section)726opt = self.make_option_lowercase(opt, section)727opt_dict[opt] = (filename, val)728729# Make the ConfigParser forget everything (so we retain730# the original filenames that options come from)731parser.__init__()732733if 'global' not in self.command_options:734return735736# If there was a "global" section in the config file, use it737# to set Distribution options.738739for (opt, (src, val)) in self.command_options['global'].items():740alias = self.negative_opt.get(opt)741if alias:742val = not strtobool(val)743elif opt in ('verbose', 'dry_run'): # ugh!744val = strtobool(val)745746try:747setattr(self, alias or opt, val)748except ValueError as e:749raise DistutilsOptionError(e) from e750751def warn_dash_deprecation(self, opt, section):752if section in (753'options.extras_require',754'options.data_files',755):756return opt757758underscore_opt = opt.replace('-', '_')759commands = list(itertools.chain(760distutils.command.__all__,761self._setuptools_commands(),762))763if (764not section.startswith('options')765and section != 'metadata'766and section not in commands767):768return underscore_opt769770if '-' in opt:771warnings.warn(772"Usage of dash-separated '%s' will not be supported in future "773"versions. Please use the underscore name '%s' instead"774% (opt, underscore_opt)775)776return underscore_opt777778def _setuptools_commands(self):779try:780return metadata.distribution('setuptools').entry_points.names781except metadata.PackageNotFoundError:782# during bootstrapping, distribution doesn't exist783return []784785def make_option_lowercase(self, opt, section):786if section != 'metadata' or opt.islower():787return opt788789lowercase_opt = opt.lower()790warnings.warn(791"Usage of uppercase key '%s' in '%s' will be deprecated in future "792"versions. Please use lowercase '%s' instead"793% (opt, section, lowercase_opt)794)795return lowercase_opt796797# FIXME: 'Distribution._set_command_options' is too complex (14)798def _set_command_options(self, command_obj, option_dict=None): # noqa: C901799"""800Set the options for 'command_obj' from 'option_dict'. Basically801this means copying elements of a dictionary ('option_dict') to802attributes of an instance ('command').803804'command_obj' must be a Command instance. If 'option_dict' is not805supplied, uses the standard option dictionary for this command806(from 'self.command_options').807808(Adopted from distutils.dist.Distribution._set_command_options)809"""810command_name = command_obj.get_command_name()811if option_dict is None:812option_dict = self.get_option_dict(command_name)813814if DEBUG:815self.announce(" setting options for '%s' command:" % command_name)816for (option, (source, value)) in option_dict.items():817if DEBUG:818self.announce(" %s = %s (from %s)" % (option, value, source))819try:820bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]821except AttributeError:822bool_opts = []823try:824neg_opt = command_obj.negative_opt825except AttributeError:826neg_opt = {}827828try:829is_string = isinstance(value, str)830if option in neg_opt and is_string:831setattr(command_obj, neg_opt[option], not strtobool(value))832elif option in bool_opts and is_string:833setattr(command_obj, option, strtobool(value))834elif hasattr(command_obj, option):835setattr(command_obj, option, value)836else:837raise DistutilsOptionError(838"error in %s: command '%s' has no such option '%s'"839% (source, command_name, option)840)841except ValueError as e:842raise DistutilsOptionError(e) from e843844def _get_project_config_files(self, filenames):845"""Add default file and split between INI and TOML"""846tomlfiles = []847standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml")848if filenames is not None:849parts = partition(lambda f: Path(f).suffix == ".toml", filenames)850filenames = list(parts[0]) # 1st element => predicate is False851tomlfiles = list(parts[1]) # 2nd element => predicate is True852elif standard_project_metadata.exists():853tomlfiles = [standard_project_metadata]854return filenames, tomlfiles855856def parse_config_files(self, filenames=None, ignore_option_errors=False):857"""Parses configuration files from various levels858and loads configuration.859"""860inifiles, tomlfiles = self._get_project_config_files(filenames)861862self._parse_config_files(filenames=inifiles)863864setupcfg.parse_configuration(865self, self.command_options, ignore_option_errors=ignore_option_errors866)867for filename in tomlfiles:868pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)869870self._finalize_requires()871self._finalize_license_files()872873def fetch_build_eggs(self, requires):874"""Resolve pre-setup requirements"""875resolved_dists = pkg_resources.working_set.resolve(876_reqs.parse(requires),877installer=self.fetch_build_egg,878replace_conflicting=True,879)880for dist in resolved_dists:881pkg_resources.working_set.add(dist, replace=True)882return resolved_dists883884def finalize_options(self):885"""886Allow plugins to apply arbitrary operations to the887distribution. Each hook may optionally define a 'order'888to influence the order of execution. Smaller numbers889go first and the default is 0.890"""891group = 'setuptools.finalize_distribution_options'892893def by_order(hook):894return getattr(hook, 'order', 0)895896defined = metadata.entry_points(group=group)897filtered = itertools.filterfalse(self._removed, defined)898loaded = map(lambda e: e.load(), filtered)899for ep in sorted(loaded, key=by_order):900ep(self)901902@staticmethod903def _removed(ep):904"""905When removing an entry point, if metadata is loaded906from an older version of Setuptools, that removed907entry point will attempt to be loaded and will fail.908See #2765 for more details.909"""910removed = {911# removed 2021-09-05912'2to3_doctests',913}914return ep.name in removed915916def _finalize_setup_keywords(self):917for ep in metadata.entry_points(group='distutils.setup_keywords'):918value = getattr(self, ep.name, None)919if value is not None:920self._install_dependencies(ep)921ep.load()(self, ep.name, value)922923def _install_dependencies(self, ep):924"""925Given an entry point, ensure that any declared extras for926its distribution are installed.927"""928for req in nspektr.missing(ep):929# fetch_build_egg expects pkg_resources.Requirement930self.fetch_build_egg(pkg_resources.Requirement(str(req)))931932def get_egg_cache_dir(self):933egg_cache_dir = os.path.join(os.curdir, '.eggs')934if not os.path.exists(egg_cache_dir):935os.mkdir(egg_cache_dir)936windows_support.hide_file(egg_cache_dir)937readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt')938with open(readme_txt_filename, 'w') as f:939f.write(940'This directory contains eggs that were downloaded '941'by setuptools to build, test, and run plug-ins.\n\n'942)943f.write(944'This directory caches those eggs to prevent '945'repeated downloads.\n\n'946)947f.write('However, it is safe to delete this directory.\n\n')948949return egg_cache_dir950951def fetch_build_egg(self, req):952"""Fetch an egg needed for building"""953from setuptools.installer import fetch_build_egg954955return fetch_build_egg(self, req)956957def get_command_class(self, command):958"""Pluggable version of get_command_class()"""959if command in self.cmdclass:960return self.cmdclass[command]961962eps = metadata.entry_points(group='distutils.commands', name=command)963for ep in eps:964self._install_dependencies(ep)965self.cmdclass[command] = cmdclass = ep.load()966return cmdclass967else:968return _Distribution.get_command_class(self, command)969970def print_commands(self):971for ep in metadata.entry_points(group='distutils.commands'):972if ep.name not in self.cmdclass:973cmdclass = ep.load()974self.cmdclass[ep.name] = cmdclass975return _Distribution.print_commands(self)976977def get_command_list(self):978for ep in metadata.entry_points(group='distutils.commands'):979if ep.name not in self.cmdclass:980cmdclass = ep.load()981self.cmdclass[ep.name] = cmdclass982return _Distribution.get_command_list(self)983984def include(self, **attrs):985"""Add items to distribution that are named in keyword arguments986987For example, 'dist.include(py_modules=["x"])' would add 'x' to988the distribution's 'py_modules' attribute, if it was not already989there.990991Currently, this method only supports inclusion for attributes that are992lists or tuples. If you need to add support for adding to other993attributes in this or a subclass, you can add an '_include_X' method,994where 'X' is the name of the attribute. The method will be called with995the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})'996will try to call 'dist._include_foo({"bar":"baz"})', which can then997handle whatever special inclusion logic is needed.998"""999for k, v in attrs.items():1000include = getattr(self, '_include_' + k, None)1001if include:1002include(v)1003else:1004self._include_misc(k, v)10051006def exclude_package(self, package):1007"""Remove packages, modules, and extensions in named package"""10081009pfx = package + '.'1010if self.packages:1011self.packages = [1012p for p in self.packages if p != package and not p.startswith(pfx)1013]10141015if self.py_modules:1016self.py_modules = [1017p for p in self.py_modules if p != package and not p.startswith(pfx)1018]10191020if self.ext_modules:1021self.ext_modules = [1022p1023for p in self.ext_modules1024if p.name != package and not p.name.startswith(pfx)1025]10261027def has_contents_for(self, package):1028"""Return true if 'exclude_package(package)' would do something"""10291030pfx = package + '.'10311032for p in self.iter_distribution_names():1033if p == package or p.startswith(pfx):1034return True10351036def _exclude_misc(self, name, value):1037"""Handle 'exclude()' for list/tuple attrs without a special handler"""1038if not isinstance(value, sequence):1039raise DistutilsSetupError(1040"%s: setting must be a list or tuple (%r)" % (name, value)1041)1042try:1043old = getattr(self, name)1044except AttributeError as e:1045raise DistutilsSetupError("%s: No such distribution setting" % name) from e1046if old is not None and not isinstance(old, sequence):1047raise DistutilsSetupError(1048name + ": this setting cannot be changed via include/exclude"1049)1050elif old:1051setattr(self, name, [item for item in old if item not in value])10521053def _include_misc(self, name, value):1054"""Handle 'include()' for list/tuple attrs without a special handler"""10551056if not isinstance(value, sequence):1057raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value))1058try:1059old = getattr(self, name)1060except AttributeError as e:1061raise DistutilsSetupError("%s: No such distribution setting" % name) from e1062if old is None:1063setattr(self, name, value)1064elif not isinstance(old, sequence):1065raise DistutilsSetupError(1066name + ": this setting cannot be changed via include/exclude"1067)1068else:1069new = [item for item in value if item not in old]1070setattr(self, name, old + new)10711072def exclude(self, **attrs):1073"""Remove items from distribution that are named in keyword arguments10741075For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from1076the distribution's 'py_modules' attribute. Excluding packages uses1077the 'exclude_package()' method, so all of the package's contained1078packages, modules, and extensions are also excluded.10791080Currently, this method only supports exclusion from attributes that are1081lists or tuples. If you need to add support for excluding from other1082attributes in this or a subclass, you can add an '_exclude_X' method,1083where 'X' is the name of the attribute. The method will be called with1084the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})'1085will try to call 'dist._exclude_foo({"bar":"baz"})', which can then1086handle whatever special exclusion logic is needed.1087"""1088for k, v in attrs.items():1089exclude = getattr(self, '_exclude_' + k, None)1090if exclude:1091exclude(v)1092else:1093self._exclude_misc(k, v)10941095def _exclude_packages(self, packages):1096if not isinstance(packages, sequence):1097raise DistutilsSetupError(1098"packages: setting must be a list or tuple (%r)" % (packages,)1099)1100list(map(self.exclude_package, packages))11011102def _parse_command_opts(self, parser, args):1103# Remove --with-X/--without-X options when processing command args1104self.global_options = self.__class__.global_options1105self.negative_opt = self.__class__.negative_opt11061107# First, expand any aliases1108command = args[0]1109aliases = self.get_option_dict('aliases')1110while command in aliases:1111src, alias = aliases[command]1112del aliases[command] # ensure each alias can expand only once!1113import shlex11141115args[:1] = shlex.split(alias, True)1116command = args[0]11171118nargs = _Distribution._parse_command_opts(self, parser, args)11191120# Handle commands that want to consume all remaining arguments1121cmd_class = self.get_command_class(command)1122if getattr(cmd_class, 'command_consumes_arguments', None):1123self.get_option_dict(command)['args'] = ("command line", nargs)1124if nargs is not None:1125return []11261127return nargs11281129def get_cmdline_options(self):1130"""Return a '{cmd: {opt:val}}' map of all command-line options11311132Option names are all long, but do not include the leading '--', and1133contain dashes rather than underscores. If the option doesn't take1134an argument (e.g. '--quiet'), the 'val' is 'None'.11351136Note that options provided by config files are intentionally excluded.1137"""11381139d = {}11401141for cmd, opts in self.command_options.items():11421143for opt, (src, val) in opts.items():11441145if src != "command line":1146continue11471148opt = opt.replace('_', '-')11491150if val == 0:1151cmdobj = self.get_command_obj(cmd)1152neg_opt = self.negative_opt.copy()1153neg_opt.update(getattr(cmdobj, 'negative_opt', {}))1154for neg, pos in neg_opt.items():1155if pos == opt:1156opt = neg1157val = None1158break1159else:1160raise AssertionError("Shouldn't be able to get here")11611162elif val == 1:1163val = None11641165d.setdefault(cmd, {})[opt] = val11661167return d11681169def iter_distribution_names(self):1170"""Yield all packages, modules, and extension names in distribution"""11711172for pkg in self.packages or ():1173yield pkg11741175for module in self.py_modules or ():1176yield module11771178for ext in self.ext_modules or ():1179if isinstance(ext, tuple):1180name, buildinfo = ext1181else:1182name = ext.name1183if name.endswith('module'):1184name = name[:-6]1185yield name11861187def handle_display_options(self, option_order):1188"""If there were any non-global "display-only" options1189(--help-commands or the metadata display options) on the command1190line, display the requested info and return true; else return1191false.1192"""1193import sys11941195if self.help_commands:1196return _Distribution.handle_display_options(self, option_order)11971198# Stdout may be StringIO (e.g. in tests)1199if not isinstance(sys.stdout, io.TextIOWrapper):1200return _Distribution.handle_display_options(self, option_order)12011202# Don't wrap stdout if utf-8 is already the encoding. Provides1203# workaround for #334.1204if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):1205return _Distribution.handle_display_options(self, option_order)12061207# Print metadata in UTF-8 no matter the platform1208encoding = sys.stdout.encoding1209errors = sys.stdout.errors1210newline = sys.platform != 'win32' and '\n' or None1211line_buffering = sys.stdout.line_buffering12121213sys.stdout = io.TextIOWrapper(1214sys.stdout.detach(), 'utf-8', errors, newline, line_buffering1215)1216try:1217return _Distribution.handle_display_options(self, option_order)1218finally:1219sys.stdout = io.TextIOWrapper(1220sys.stdout.detach(), encoding, errors, newline, line_buffering1221)12221223def run_command(self, command):1224self.set_defaults()1225# Postpone defaults until all explicit configuration is considered1226# (setup() args, config files, command line and plugins)12271228super().run_command(command)122912301231class DistDeprecationWarning(SetuptoolsDeprecationWarning):1232"""Class for warning about deprecations in dist in1233setuptools. Not ignored by default, unlike DeprecationWarning."""123412351236