python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
setuptools/
dist.py
       1  # -*- coding: utf-8 -*-
       2  __all__ = ['Distribution']
       3  
       4  import io
       5  import sys
       6  import re
       7  import os
       8  import warnings
       9  import numbers
      10  import distutils.log
      11  import distutils.core
      12  import distutils.cmd
      13  import distutils.dist
      14  import distutils.command
      15  from distutils.util import strtobool
      16  from distutils.debug import DEBUG
      17  from distutils.fancy_getopt import translate_longopt
      18  from glob import iglob
      19  import itertools
      20  import textwrap
      21  from typing import List, Optional, TYPE_CHECKING
      22  from pathlib import Path
      23  
      24  from collections import defaultdict
      25  from email import message_from_file
      26  
      27  from distutils.errors import DistutilsOptionError, DistutilsSetupError
      28  from distutils.util import rfc822_escape
      29  
      30  from setuptools.extern import packaging
      31  from setuptools.extern import ordered_set
      32  from setuptools.extern.more_itertools import unique_everseen, partition
      33  
      34  from ._importlib import metadata
      35  
      36  from . import SetuptoolsDeprecationWarning
      37  
      38  import setuptools
      39  import setuptools.command
      40  from setuptools import windows_support
      41  from setuptools.monkey import get_unpatched
      42  from setuptools.config import setupcfg, pyprojecttoml
      43  from setuptools.discovery import ConfigDiscovery
      44  
      45  import pkg_resources
      46  from setuptools.extern.packaging import version
      47  from . import _reqs
      48  from . import _entry_points
      49  
      50  if TYPE_CHECKING:
      51      from email.message import Message
      52  
      53  __import__('setuptools.extern.packaging.specifiers')
      54  __import__('setuptools.extern.packaging.version')
      55  
      56  
      57  def _get_unpatched(cls):
      58      warnings.warn("Do not call this function", DistDeprecationWarning)
      59      return get_unpatched(cls)
      60  
      61  
      62  def get_metadata_version(self):
      63      mv = getattr(self, 'metadata_version', None)
      64      if mv is None:
      65          mv = version.Version('2.1')
      66          self.metadata_version = mv
      67      return mv
      68  
      69  
      70  def rfc822_unescape(content: str) -> str:
      71      """Reverse RFC-822 escaping by removing leading whitespaces from content."""
      72      lines = content.splitlines()
      73      if len(lines) == 1:
      74          return lines[0].lstrip()
      75      return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:]))))
      76  
      77  
      78  def _read_field_from_msg(msg: "Message", field: str) -> Optional[str]:
      79      """Read Message header field."""
      80      value = msg[field]
      81      if value == 'UNKNOWN':
      82          return None
      83      return value
      84  
      85  
      86  def _read_field_unescaped_from_msg(msg: "Message", field: str) -> Optional[str]:
      87      """Read Message header field and apply rfc822_unescape."""
      88      value = _read_field_from_msg(msg, field)
      89      if value is None:
      90          return value
      91      return rfc822_unescape(value)
      92  
      93  
      94  def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]:
      95      """Read Message header field and return all results as list."""
      96      values = msg.get_all(field, None)
      97      if values == []:
      98          return None
      99      return values
     100  
     101  
     102  def _read_payload_from_msg(msg: "Message") -> Optional[str]:
     103      value = msg.get_payload().strip()
     104      if value == 'UNKNOWN' or not value:
     105          return None
     106      return value
     107  
     108  
     109  def read_pkg_file(self, file):
     110      """Reads the metadata values from a file object."""
     111      msg = message_from_file(file)
     112  
     113      self.metadata_version = version.Version(msg['metadata-version'])
     114      self.name = _read_field_from_msg(msg, 'name')
     115      self.version = _read_field_from_msg(msg, 'version')
     116      self.description = _read_field_from_msg(msg, 'summary')
     117      # we are filling author only.
     118      self.author = _read_field_from_msg(msg, 'author')
     119      self.maintainer = None
     120      self.author_email = _read_field_from_msg(msg, 'author-email')
     121      self.maintainer_email = None
     122      self.url = _read_field_from_msg(msg, 'home-page')
     123      self.download_url = _read_field_from_msg(msg, 'download-url')
     124      self.license = _read_field_unescaped_from_msg(msg, 'license')
     125  
     126      self.long_description = _read_field_unescaped_from_msg(msg, 'description')
     127      if (
     128          self.long_description is None and
     129          self.metadata_version >= version.Version('2.1')
     130      ):
     131          self.long_description = _read_payload_from_msg(msg)
     132      self.description = _read_field_from_msg(msg, 'summary')
     133  
     134      if 'keywords' in msg:
     135          self.keywords = _read_field_from_msg(msg, 'keywords').split(',')
     136  
     137      self.platforms = _read_list_from_msg(msg, 'platform')
     138      self.classifiers = _read_list_from_msg(msg, 'classifier')
     139  
     140      # PEP 314 - these fields only exist in 1.1
     141      if self.metadata_version == version.Version('1.1'):
     142          self.requires = _read_list_from_msg(msg, 'requires')
     143          self.provides = _read_list_from_msg(msg, 'provides')
     144          self.obsoletes = _read_list_from_msg(msg, 'obsoletes')
     145      else:
     146          self.requires = None
     147          self.provides = None
     148          self.obsoletes = None
     149  
     150      self.license_files = _read_list_from_msg(msg, 'license-file')
     151  
     152  
     153  def single_line(val):
     154      """
     155      Quick and dirty validation for Summary pypa/setuptools#1390.
     156      """
     157      if '\n' in val:
     158          # TODO: Replace with `raise ValueError("newlines not allowed")`
     159          # after reviewing #2893.
     160          warnings.warn("newlines not allowed and will break in the future")
     161          val = val.strip().split('\n')[0]
     162      return val
     163  
     164  
     165  # Based on Python 3.5 version
     166  def write_pkg_file(self, file):  # noqa: C901  # is too complex (14)  # FIXME
     167      """Write the PKG-INFO format data to a file object."""
     168      version = self.get_metadata_version()
     169  
     170      def write_field(key, value):
     171          file.write("%s: %s\n" % (key, value))
     172  
     173      write_field('Metadata-Version', str(version))
     174      write_field('Name', self.get_name())
     175      write_field('Version', self.get_version())
     176  
     177      summary = self.get_description()
     178      if summary:
     179          write_field('Summary', single_line(summary))
     180  
     181      optional_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      )
     189  
     190      for field, attr in optional_fields:
     191          attr_val = getattr(self, attr, None)
     192          if attr_val is not None:
     193              write_field(field, attr_val)
     194  
     195      license = self.get_license()
     196      if license:
     197          write_field('License', rfc822_escape(license))
     198  
     199      for project_url in self.project_urls.items():
     200          write_field('Project-URL', '%s, %s' % project_url)
     201  
     202      keywords = ','.join(self.get_keywords())
     203      if keywords:
     204          write_field('Keywords', keywords)
     205  
     206      platforms = self.get_platforms() or []
     207      for platform in platforms:
     208          write_field('Platform', platform)
     209  
     210      self._write_list(file, 'Classifier', self.get_classifiers())
     211  
     212      # PEP 314
     213      self._write_list(file, 'Requires', self.get_requires())
     214      self._write_list(file, 'Provides', self.get_provides())
     215      self._write_list(file, 'Obsoletes', self.get_obsoletes())
     216  
     217      # Setuptools specific for PEP 345
     218      if hasattr(self, 'python_requires'):
     219          write_field('Requires-Python', self.python_requires)
     220  
     221      # PEP 566
     222      if self.long_description_content_type:
     223          write_field('Description-Content-Type', self.long_description_content_type)
     224      if self.provides_extras:
     225          for extra in self.provides_extras:
     226              write_field('Provides-Extra', extra)
     227  
     228      self._write_list(file, 'License-File', self.license_files or [])
     229  
     230      long_description = self.get_long_description()
     231      if long_description:
     232          file.write("\n%s" % long_description)
     233          if not long_description.endswith("\n"):
     234              file.write("\n")
     235  
     236  
     237  sequence = tuple, list
     238  
     239  
     240  def check_importable(dist, attr, value):
     241      try:
     242          ep = metadata.EntryPoint(value=value, name=None, group=None)
     243          assert not ep.extras
     244      except (TypeError, ValueError, AttributeError, AssertionError) as e:
     245          raise DistutilsSetupError(
     246              "%r must be importable 'module:attrs' string (got %r)" % (attr, value)
     247          ) from e
     248  
     249  
     250  def assert_string_list(dist, attr, value):
     251      """Verify that value is a string list"""
     252      try:
     253          # verify that value is a list or tuple to exclude unordered
     254          # or single-use iterables
     255          assert isinstance(value, (list, tuple))
     256          # verify that elements of value are strings
     257          assert ''.join(value) != value
     258      except (TypeError, ValueError, AttributeError, AssertionError) as e:
     259          raise DistutilsSetupError(
     260              "%r must be a list of strings (got %r)" % (attr, value)
     261          ) from e
     262  
     263  
     264  def check_nsp(dist, attr, value):
     265      """Verify that namespace packages are valid"""
     266      ns_packages = value
     267      assert_string_list(dist, attr, ns_packages)
     268      for nsp in ns_packages:
     269          if not dist.has_contents_for(nsp):
     270              raise DistutilsSetupError(
     271                  "Distribution contains no modules or packages for "
     272                  + "namespace package %r" % nsp
     273              )
     274          parent, sep, child = nsp.rpartition('.')
     275          if parent and parent not in ns_packages:
     276              distutils.log.warn(
     277                  "WARNING: %r is declared as a package namespace, but %r"
     278                  " is not: please correct this in setup.py",
     279                  nsp,
     280                  parent,
     281              )
     282          msg = (
     283              "The namespace_packages parameter is deprecated, "
     284              "consider using implicit namespaces instead (PEP 420)."
     285          )
     286          warnings.warn(msg, SetuptoolsDeprecationWarning)
     287  
     288  
     289  def check_extras(dist, attr, value):
     290      """Verify that extras_require mapping is valid"""
     291      try:
     292          list(itertools.starmap(_check_extra, value.items()))
     293      except (TypeError, ValueError, AttributeError) as e:
     294          raise 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 e
     299  
     300  
     301  def _check_extra(extra, reqs):
     302      name, sep, marker = extra.partition(':')
     303      if marker and pkg_resources.invalid_marker(marker):
     304          raise DistutilsSetupError("Invalid environment marker: " + marker)
     305      list(_reqs.parse(reqs))
     306  
     307  
     308  def assert_bool(dist, attr, value):
     309      """Verify that value is True, False, 0, or 1"""
     310      if bool(value) != value:
     311          tmpl = "{attr!r} must be a boolean value (got {value!r})"
     312          raise DistutilsSetupError(tmpl.format(attr=attr, value=value))
     313  
     314  
     315  def invalid_unless_false(dist, attr, value):
     316      if not value:
     317          warnings.warn(f"{attr} is ignored.", DistDeprecationWarning)
     318          return
     319      raise DistutilsSetupError(f"{attr} is invalid.")
     320  
     321  
     322  def check_requirements(dist, attr, value):
     323      """Verify that install_requires is a valid requirements list"""
     324      try:
     325          list(_reqs.parse(value))
     326          if isinstance(value, (dict, set)):
     327              raise TypeError("Unordered types are not allowed")
     328      except (TypeError, ValueError) as error:
     329          tmpl = (
     330              "{attr!r} must be a string or list of strings "
     331              "containing valid project/version requirement specifiers; {error}"
     332          )
     333          raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error
     334  
     335  
     336  def check_specifier(dist, attr, value):
     337      """Verify that value is a valid version specifier"""
     338      try:
     339          packaging.specifiers.SpecifierSet(value)
     340      except (packaging.specifiers.InvalidSpecifier, AttributeError) as error:
     341          tmpl = (
     342              "{attr!r} must be a string " "containing valid version specifiers; {error}"
     343          )
     344          raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error
     345  
     346  
     347  def check_entry_points(dist, attr, value):
     348      """Verify that entry_points map is parseable"""
     349      try:
     350          _entry_points.load(value)
     351      except Exception as e:
     352          raise DistutilsSetupError(e) from e
     353  
     354  
     355  def check_test_suite(dist, attr, value):
     356      if not isinstance(value, str):
     357          raise DistutilsSetupError("test_suite must be a string")
     358  
     359  
     360  def check_package_data(dist, attr, value):
     361      """Verify that value is a dictionary of package names to glob lists"""
     362      if not isinstance(value, dict):
     363          raise DistutilsSetupError(
     364              "{!r} must be a dictionary mapping package names to lists of "
     365              "string wildcard patterns".format(attr)
     366          )
     367      for k, v in value.items():
     368          if not isinstance(k, str):
     369              raise DistutilsSetupError(
     370                  "keys of {!r} dict must be strings (got {!r})".format(attr, k)
     371              )
     372          assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
     373  
     374  
     375  def check_packages(dist, attr, value):
     376      for pkgname in value:
     377          if not re.match(r'\w+(\.\w+)*', pkgname):
     378              distutils.log.warn(
     379                  "WARNING: %r not a valid package name; please use only "
     380                  ".-separated package names in setup.py",
     381                  pkgname,
     382              )
     383  
     384  
     385  _Distribution = get_unpatched(distutils.core.Distribution)
     386  
     387  
     388  class ESC[4;38;5;81mDistribution(ESC[4;38;5;149m_Distribution):
     389      """Distribution with support for tests and package data
     390  
     391      This is an enhanced version of 'distutils.dist.Distribution' that
     392      effectively adds the following new optional keyword arguments to 'setup()':
     393  
     394       'install_requires' -- a string or sequence of strings specifying project
     395          versions that the distribution requires when installed, in the format
     396          used by 'pkg_resources.require()'.  They will be installed
     397          automatically when the package is installed.  If you wish to use
     398          packages that are not available in PyPI, or want to give your users an
     399          alternate download location, you can add a 'find_links' option to the
     400          '[easy_install]' section of your project's 'setup.cfg' file, and then
     401          setuptools will scan the listed web pages for links that satisfy the
     402          requirements.
     403  
     404       'extras_require' -- a dictionary mapping names of optional "extras" to the
     405          additional requirement(s) that using those extras incurs. For example,
     406          this::
     407  
     408              extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
     409  
     410          indicates that the distribution can optionally provide an extra
     411          capability called "reST", but it can only be used if docutils and
     412          reSTedit are installed.  If the user installs your package using
     413          EasyInstall and requests one of your extras, the corresponding
     414          additional requirements will be installed if needed.
     415  
     416       'test_suite' -- the name of a test suite to run for the 'test' command.
     417          If the user runs 'python setup.py test', the package will be installed,
     418          and the named test suite will be run.  The format is the same as
     419          would be used on a 'unittest.py' command line.  That is, it is the
     420          dotted name of an object to import and call to generate a test suite.
     421  
     422       'package_data' -- a dictionary mapping package names to lists of filenames
     423          or globs to use to find data files contained in the named packages.
     424          If the dictionary has filenames or globs listed under '""' (the empty
     425          string), those names will be searched for in every package, in addition
     426          to any names for the specific package.  Data files found using these
     427          names/globs will be installed along with the package, in the same
     428          location as the package.  Note that globs are allowed to reference
     429          the contents of non-package subdirectories, as long as you use '/' as
     430          a path separator.  (Globs are automatically converted to
     431          platform-specific paths at runtime.)
     432  
     433      In addition to these new keywords, this class also has several new methods
     434      for manipulating the distribution's contents.  For example, the 'include()'
     435      and 'exclude()' methods can be thought of as in-place add and subtract
     436      commands that add or remove packages, modules, extensions, and so on from
     437      the distribution.
     438      """
     439  
     440      _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      }
     447  
     448      _patched_dist = None
     449  
     450      def patch_missing_pkg_info(self, attrs):
     451          # Fake up a replacement for the data that would normally come from
     452          # PKG-INFO, but which might not yet be built if this is a fresh
     453          # checkout.
     454          #
     455          if not attrs or 'name' not in attrs or 'version' not in attrs:
     456              return
     457          key = pkg_resources.safe_name(str(attrs['name'])).lower()
     458          dist = pkg_resources.working_set.by_key.get(key)
     459          if dist is not None and not dist.has_metadata('PKG-INFO'):
     460              dist._version = pkg_resources.safe_version(str(attrs['version']))
     461              self._patched_dist = dist
     462  
     463      def __init__(self, attrs=None):
     464          have_package_data = hasattr(self, "package_data")
     465          if not have_package_data:
     466              self.package_data = {}
     467          attrs = attrs or {}
     468          self.dist_files = []
     469          # Filter-out setuptools' specific options.
     470          self.src_root = attrs.pop("src_root", None)
     471          self.patch_missing_pkg_info(attrs)
     472          self.dependency_links = attrs.pop('dependency_links', [])
     473          self.setup_requires = attrs.pop('setup_requires', [])
     474          for ep in metadata.entry_points(group='distutils.setup_keywords'):
     475              vars(self).setdefault(ep.name, None)
     476          _Distribution.__init__(
     477              self,
     478              {
     479                  k: v
     480                  for k, v in attrs.items()
     481                  if k not in self._DISTUTILS_UNSUPPORTED_METADATA
     482              },
     483          )
     484  
     485          # Save the original dependencies before they are processed into the egg format
     486          self._orig_extras_require = {}
     487          self._orig_install_requires = []
     488          self._tmp_extras_require = defaultdict(ordered_set.OrderedSet)
     489  
     490          self.set_defaults = ConfigDiscovery(self)
     491  
     492          self._set_metadata_defaults(attrs)
     493  
     494          self.metadata.version = self._normalize_version(
     495              self._validate_version(self.metadata.version)
     496          )
     497          self._finalize_requires()
     498  
     499      def _validate_metadata(self):
     500          required = {"name"}
     501          provided = {
     502              key
     503              for key in vars(self.metadata)
     504              if getattr(self.metadata, key, None) is not None
     505          }
     506          missing = required - provided
     507  
     508          if missing:
     509              msg = f"Required package metadata is missing: {missing}"
     510              raise DistutilsSetupError(msg)
     511  
     512      def _set_metadata_defaults(self, attrs):
     513          """
     514          Fill-in missing metadata fields not supported by distutils.
     515          Some fields may have been set by other tools (e.g. pbr).
     516          Those fields (vars(self.metadata)) take precedence to
     517          supplied attrs.
     518          """
     519          for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():
     520              vars(self.metadata).setdefault(option, attrs.get(option, default()))
     521  
     522      @staticmethod
     523      def _normalize_version(version):
     524          if isinstance(version, setuptools.sic) or version is None:
     525              return version
     526  
     527          normalized = str(packaging.version.Version(version))
     528          if version != normalized:
     529              tmpl = "Normalizing '{version}' to '{normalized}'"
     530              warnings.warn(tmpl.format(**locals()))
     531              return normalized
     532          return version
     533  
     534      @staticmethod
     535      def _validate_version(version):
     536          if isinstance(version, numbers.Number):
     537              # Some people apparently take "version number" too literally :)
     538              version = str(version)
     539  
     540          if version is not None:
     541              try:
     542                  packaging.version.Version(version)
     543              except (packaging.version.InvalidVersion, TypeError):
     544                  warnings.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." % version
     549                  )
     550                  return setuptools.sic(version)
     551          return version
     552  
     553      def _finalize_requires(self):
     554          """
     555          Set `metadata.python_requires` and fix environment markers
     556          in `install_requires` and `extras_require`.
     557          """
     558          if getattr(self, 'python_requires', None):
     559              self.metadata.python_requires = self.python_requires
     560  
     561          if getattr(self, 'extras_require', None):
     562              # Save original before it is messed by _convert_extras_requirements
     563              self._orig_extras_require = self._orig_extras_require or self.extras_require
     564              for extra in self.extras_require.keys():
     565                  # Since this gets called multiple times at points where the
     566                  # keys have become 'converted' extras, ensure that we are only
     567                  # truly adding extras we haven't seen before here.
     568                  extra = extra.split(':')[0]
     569                  if extra:
     570                      self.metadata.provides_extras.add(extra)
     571  
     572          if getattr(self, 'install_requires', None) and not self._orig_install_requires:
     573              # Save original before it is messed by _move_install_requirements_markers
     574              self._orig_install_requires = self.install_requires
     575  
     576          self._convert_extras_requirements()
     577          self._move_install_requirements_markers()
     578  
     579      def _convert_extras_requirements(self):
     580          """
     581          Convert requirements in `extras_require` of the form
     582          `"extra": ["barbazquux; {marker}"]` to
     583          `"extra:{marker}": ["barbazquux"]`.
     584          """
     585          spec_ext_reqs = getattr(self, 'extras_require', None) or {}
     586          tmp = defaultdict(ordered_set.OrderedSet)
     587          self._tmp_extras_require = getattr(self, '_tmp_extras_require', tmp)
     588          for section, v in spec_ext_reqs.items():
     589              # Do not strip empty sections.
     590              self._tmp_extras_require[section]
     591              for r in _reqs.parse(v):
     592                  suffix = self._suffix_for(r)
     593                  self._tmp_extras_require[section + suffix].append(r)
     594  
     595      @staticmethod
     596      def _suffix_for(req):
     597          """
     598          For a requirement, return the 'extras_require' suffix for
     599          that requirement.
     600          """
     601          return ':' + str(req.marker) if req.marker else ''
     602  
     603      def _move_install_requirements_markers(self):
     604          """
     605          Move requirements in `install_requires` that are using environment
     606          markers `extras_require`.
     607          """
     608  
     609          # divide the install_requires into two sets, simple ones still
     610          # handled by install_requires and more complex ones handled
     611          # by extras_require.
     612  
     613          def is_simple_req(req):
     614              return not req.marker
     615  
     616          spec_inst_reqs = getattr(self, 'install_requires', None) or ()
     617          inst_reqs = list(_reqs.parse(spec_inst_reqs))
     618          simple_reqs = filter(is_simple_req, inst_reqs)
     619          complex_reqs = itertools.filterfalse(is_simple_req, inst_reqs)
     620          self.install_requires = list(map(str, simple_reqs))
     621  
     622          for r in complex_reqs:
     623              self._tmp_extras_require[':' + str(r.marker)].append(r)
     624          self.extras_require = dict(
     625              # list(dict.fromkeys(...))  ensures a list of unique strings
     626              (k, list(dict.fromkeys(str(r) for r in map(self._clean_req, v))))
     627              for k, v in self._tmp_extras_require.items()
     628          )
     629  
     630      def _clean_req(self, req):
     631          """
     632          Given a Requirement, remove environment markers and return it.
     633          """
     634          req.marker = None
     635          return req
     636  
     637      def _finalize_license_files(self):
     638          """Compute names of all license files which should be included."""
     639          license_files: Optional[List[str]] = self.metadata.license_files
     640          patterns: List[str] = license_files if license_files else []
     641  
     642          license_file: Optional[str] = self.metadata.license_file
     643          if license_file and license_file not in patterns:
     644              patterns.append(license_file)
     645  
     646          if license_files is None and license_file is None:
     647              # Default patterns match the ones wheel uses
     648              # See https://wheel.readthedocs.io/en/stable/user_guide.html
     649              # -> 'Including license files in the generated wheel file'
     650              patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
     651  
     652          self.metadata.license_files = list(
     653              unique_everseen(self._expand_patterns(patterns))
     654          )
     655  
     656      @staticmethod
     657      def _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          """
     664          return (
     665              path
     666              for pattern in patterns
     667              for path in sorted(iglob(pattern))
     668              if not path.endswith('~') and os.path.isfile(path)
     669          )
     670  
     671      # FIXME: 'Distribution._parse_config_files' is too complex (14)
     672      def _parse_config_files(self, filenames=None):  # noqa: C901
     673          """
     674          Adapted from distutils.dist.Distribution.parse_config_files,
     675          this method provides the same functionality in subtly-improved
     676          ways.
     677          """
     678          from configparser import ConfigParser
     679  
     680          # Ignore install directory options if we have a venv
     681          ignore_options = (
     682              []
     683              if sys.prefix == sys.base_prefix
     684              else [
     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          )
     700  
     701          ignore_options = frozenset(ignore_options)
     702  
     703          if filenames is None:
     704              filenames = self.find_config_files()
     705  
     706          if DEBUG:
     707              self.announce("Distribution.parse_config_files():")
     708  
     709          parser = ConfigParser()
     710          parser.optionxform = str
     711          for filename in filenames:
     712              with io.open(filename, encoding='utf-8') as reader:
     713                  if DEBUG:
     714                      self.announce("  reading {filename}".format(**locals()))
     715                  parser.read_file(reader)
     716              for section in parser.sections():
     717                  options = parser.options(section)
     718                  opt_dict = self.get_option_dict(section)
     719  
     720                  for opt in options:
     721                      if opt == '__name__' or opt in ignore_options:
     722                          continue
     723  
     724                      val = parser.get(section, opt)
     725                      opt = self.warn_dash_deprecation(opt, section)
     726                      opt = self.make_option_lowercase(opt, section)
     727                      opt_dict[opt] = (filename, val)
     728  
     729              # Make the ConfigParser forget everything (so we retain
     730              # the original filenames that options come from)
     731              parser.__init__()
     732  
     733          if 'global' not in self.command_options:
     734              return
     735  
     736          # If there was a "global" section in the config file, use it
     737          # to set Distribution options.
     738  
     739          for (opt, (src, val)) in self.command_options['global'].items():
     740              alias = self.negative_opt.get(opt)
     741              if alias:
     742                  val = not strtobool(val)
     743              elif opt in ('verbose', 'dry_run'):  # ugh!
     744                  val = strtobool(val)
     745  
     746              try:
     747                  setattr(self, alias or opt, val)
     748              except ValueError as e:
     749                  raise DistutilsOptionError(e) from e
     750  
     751      def warn_dash_deprecation(self, opt, section):
     752          if section in (
     753              'options.extras_require',
     754              'options.data_files',
     755          ):
     756              return opt
     757  
     758          underscore_opt = opt.replace('-', '_')
     759          commands = list(itertools.chain(
     760              distutils.command.__all__,
     761              self._setuptools_commands(),
     762          ))
     763          if (
     764              not section.startswith('options')
     765              and section != 'metadata'
     766              and section not in commands
     767          ):
     768              return underscore_opt
     769  
     770          if '-' in opt:
     771              warnings.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              )
     776          return underscore_opt
     777  
     778      def _setuptools_commands(self):
     779          try:
     780              return metadata.distribution('setuptools').entry_points.names
     781          except metadata.PackageNotFoundError:
     782              # during bootstrapping, distribution doesn't exist
     783              return []
     784  
     785      def make_option_lowercase(self, opt, section):
     786          if section != 'metadata' or opt.islower():
     787              return opt
     788  
     789          lowercase_opt = opt.lower()
     790          warnings.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          )
     795          return lowercase_opt
     796  
     797      # FIXME: 'Distribution._set_command_options' is too complex (14)
     798      def _set_command_options(self, command_obj, option_dict=None):  # noqa: C901
     799          """
     800          Set the options for 'command_obj' from 'option_dict'.  Basically
     801          this means copying elements of a dictionary ('option_dict') to
     802          attributes of an instance ('command').
     803  
     804          'command_obj' must be a Command instance.  If 'option_dict' is not
     805          supplied, uses the standard option dictionary for this command
     806          (from 'self.command_options').
     807  
     808          (Adopted from distutils.dist.Distribution._set_command_options)
     809          """
     810          command_name = command_obj.get_command_name()
     811          if option_dict is None:
     812              option_dict = self.get_option_dict(command_name)
     813  
     814          if DEBUG:
     815              self.announce("  setting options for '%s' command:" % command_name)
     816          for (option, (source, value)) in option_dict.items():
     817              if DEBUG:
     818                  self.announce("    %s = %s (from %s)" % (option, value, source))
     819              try:
     820                  bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
     821              except AttributeError:
     822                  bool_opts = []
     823              try:
     824                  neg_opt = command_obj.negative_opt
     825              except AttributeError:
     826                  neg_opt = {}
     827  
     828              try:
     829                  is_string = isinstance(value, str)
     830                  if option in neg_opt and is_string:
     831                      setattr(command_obj, neg_opt[option], not strtobool(value))
     832                  elif option in bool_opts and is_string:
     833                      setattr(command_obj, option, strtobool(value))
     834                  elif hasattr(command_obj, option):
     835                      setattr(command_obj, option, value)
     836                  else:
     837                      raise DistutilsOptionError(
     838                          "error in %s: command '%s' has no such option '%s'"
     839                          % (source, command_name, option)
     840                      )
     841              except ValueError as e:
     842                  raise DistutilsOptionError(e) from e
     843  
     844      def _get_project_config_files(self, filenames):
     845          """Add default file and split between INI and TOML"""
     846          tomlfiles = []
     847          standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml")
     848          if filenames is not None:
     849              parts = partition(lambda f: Path(f).suffix == ".toml", filenames)
     850              filenames = list(parts[0])  # 1st element => predicate is False
     851              tomlfiles = list(parts[1])  # 2nd element => predicate is True
     852          elif standard_project_metadata.exists():
     853              tomlfiles = [standard_project_metadata]
     854          return filenames, tomlfiles
     855  
     856      def parse_config_files(self, filenames=None, ignore_option_errors=False):
     857          """Parses configuration files from various levels
     858          and loads configuration.
     859          """
     860          inifiles, tomlfiles = self._get_project_config_files(filenames)
     861  
     862          self._parse_config_files(filenames=inifiles)
     863  
     864          setupcfg.parse_configuration(
     865              self, self.command_options, ignore_option_errors=ignore_option_errors
     866          )
     867          for filename in tomlfiles:
     868              pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
     869  
     870          self._finalize_requires()
     871          self._finalize_license_files()
     872  
     873      def fetch_build_eggs(self, requires):
     874          """Resolve pre-setup requirements"""
     875          resolved_dists = pkg_resources.working_set.resolve(
     876              _reqs.parse(requires),
     877              installer=self.fetch_build_egg,
     878              replace_conflicting=True,
     879          )
     880          for dist in resolved_dists:
     881              pkg_resources.working_set.add(dist, replace=True)
     882          return resolved_dists
     883  
     884      def finalize_options(self):
     885          """
     886          Allow plugins to apply arbitrary operations to the
     887          distribution. Each hook may optionally define a 'order'
     888          to influence the order of execution. Smaller numbers
     889          go first and the default is 0.
     890          """
     891          group = 'setuptools.finalize_distribution_options'
     892  
     893          def by_order(hook):
     894              return getattr(hook, 'order', 0)
     895  
     896          defined = metadata.entry_points(group=group)
     897          filtered = itertools.filterfalse(self._removed, defined)
     898          loaded = map(lambda e: e.load(), filtered)
     899          for ep in sorted(loaded, key=by_order):
     900              ep(self)
     901  
     902      @staticmethod
     903      def _removed(ep):
     904          """
     905          When removing an entry point, if metadata is loaded
     906          from an older version of Setuptools, that removed
     907          entry point will attempt to be loaded and will fail.
     908          See #2765 for more details.
     909          """
     910          removed = {
     911              # removed 2021-09-05
     912              '2to3_doctests',
     913          }
     914          return ep.name in removed
     915  
     916      def _finalize_setup_keywords(self):
     917          for ep in metadata.entry_points(group='distutils.setup_keywords'):
     918              value = getattr(self, ep.name, None)
     919              if value is not None:
     920                  ep.load()(self, ep.name, value)
     921  
     922      def get_egg_cache_dir(self):
     923          egg_cache_dir = os.path.join(os.curdir, '.eggs')
     924          if not os.path.exists(egg_cache_dir):
     925              os.mkdir(egg_cache_dir)
     926              windows_support.hide_file(egg_cache_dir)
     927              readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt')
     928              with open(readme_txt_filename, 'w') as f:
     929                  f.write(
     930                      'This directory contains eggs that were downloaded '
     931                      'by setuptools to build, test, and run plug-ins.\n\n'
     932                  )
     933                  f.write(
     934                      'This directory caches those eggs to prevent '
     935                      'repeated downloads.\n\n'
     936                  )
     937                  f.write('However, it is safe to delete this directory.\n\n')
     938  
     939          return egg_cache_dir
     940  
     941      def fetch_build_egg(self, req):
     942          """Fetch an egg needed for building"""
     943          from setuptools.installer import fetch_build_egg
     944  
     945          return fetch_build_egg(self, req)
     946  
     947      def get_command_class(self, command):
     948          """Pluggable version of get_command_class()"""
     949          if command in self.cmdclass:
     950              return self.cmdclass[command]
     951  
     952          eps = metadata.entry_points(group='distutils.commands', name=command)
     953          for ep in eps:
     954              self.cmdclass[command] = cmdclass = ep.load()
     955              return cmdclass
     956          else:
     957              return _Distribution.get_command_class(self, command)
     958  
     959      def print_commands(self):
     960          for ep in metadata.entry_points(group='distutils.commands'):
     961              if ep.name not in self.cmdclass:
     962                  cmdclass = ep.load()
     963                  self.cmdclass[ep.name] = cmdclass
     964          return _Distribution.print_commands(self)
     965  
     966      def get_command_list(self):
     967          for ep in metadata.entry_points(group='distutils.commands'):
     968              if ep.name not in self.cmdclass:
     969                  cmdclass = ep.load()
     970                  self.cmdclass[ep.name] = cmdclass
     971          return _Distribution.get_command_list(self)
     972  
     973      def include(self, **attrs):
     974          """Add items to distribution that are named in keyword arguments
     975  
     976          For example, 'dist.include(py_modules=["x"])' would add 'x' to
     977          the distribution's 'py_modules' attribute, if it was not already
     978          there.
     979  
     980          Currently, this method only supports inclusion for attributes that are
     981          lists or tuples.  If you need to add support for adding to other
     982          attributes in this or a subclass, you can add an '_include_X' method,
     983          where 'X' is the name of the attribute.  The method will be called with
     984          the value passed to 'include()'.  So, 'dist.include(foo={"bar":"baz"})'
     985          will try to call 'dist._include_foo({"bar":"baz"})', which can then
     986          handle whatever special inclusion logic is needed.
     987          """
     988          for k, v in attrs.items():
     989              include = getattr(self, '_include_' + k, None)
     990              if include:
     991                  include(v)
     992              else:
     993                  self._include_misc(k, v)
     994  
     995      def exclude_package(self, package):
     996          """Remove packages, modules, and extensions in named package"""
     997  
     998          pfx = package + '.'
     999          if self.packages:
    1000              self.packages = [
    1001                  p for p in self.packages if p != package and not p.startswith(pfx)
    1002              ]
    1003  
    1004          if self.py_modules:
    1005              self.py_modules = [
    1006                  p for p in self.py_modules if p != package and not p.startswith(pfx)
    1007              ]
    1008  
    1009          if self.ext_modules:
    1010              self.ext_modules = [
    1011                  p
    1012                  for p in self.ext_modules
    1013                  if p.name != package and not p.name.startswith(pfx)
    1014              ]
    1015  
    1016      def has_contents_for(self, package):
    1017          """Return true if 'exclude_package(package)' would do something"""
    1018  
    1019          pfx = package + '.'
    1020  
    1021          for p in self.iter_distribution_names():
    1022              if p == package or p.startswith(pfx):
    1023                  return True
    1024  
    1025      def _exclude_misc(self, name, value):
    1026          """Handle 'exclude()' for list/tuple attrs without a special handler"""
    1027          if not isinstance(value, sequence):
    1028              raise DistutilsSetupError(
    1029                  "%s: setting must be a list or tuple (%r)" % (name, value)
    1030              )
    1031          try:
    1032              old = getattr(self, name)
    1033          except AttributeError as e:
    1034              raise DistutilsSetupError("%s: No such distribution setting" % name) from e
    1035          if old is not None and not isinstance(old, sequence):
    1036              raise DistutilsSetupError(
    1037                  name + ": this setting cannot be changed via include/exclude"
    1038              )
    1039          elif old:
    1040              setattr(self, name, [item for item in old if item not in value])
    1041  
    1042      def _include_misc(self, name, value):
    1043          """Handle 'include()' for list/tuple attrs without a special handler"""
    1044  
    1045          if not isinstance(value, sequence):
    1046              raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value))
    1047          try:
    1048              old = getattr(self, name)
    1049          except AttributeError as e:
    1050              raise DistutilsSetupError("%s: No such distribution setting" % name) from e
    1051          if old is None:
    1052              setattr(self, name, value)
    1053          elif not isinstance(old, sequence):
    1054              raise DistutilsSetupError(
    1055                  name + ": this setting cannot be changed via include/exclude"
    1056              )
    1057          else:
    1058              new = [item for item in value if item not in old]
    1059              setattr(self, name, old + new)
    1060  
    1061      def exclude(self, **attrs):
    1062          """Remove items from distribution that are named in keyword arguments
    1063  
    1064          For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from
    1065          the distribution's 'py_modules' attribute.  Excluding packages uses
    1066          the 'exclude_package()' method, so all of the package's contained
    1067          packages, modules, and extensions are also excluded.
    1068  
    1069          Currently, this method only supports exclusion from attributes that are
    1070          lists or tuples.  If you need to add support for excluding from other
    1071          attributes in this or a subclass, you can add an '_exclude_X' method,
    1072          where 'X' is the name of the attribute.  The method will be called with
    1073          the value passed to 'exclude()'.  So, 'dist.exclude(foo={"bar":"baz"})'
    1074          will try to call 'dist._exclude_foo({"bar":"baz"})', which can then
    1075          handle whatever special exclusion logic is needed.
    1076          """
    1077          for k, v in attrs.items():
    1078              exclude = getattr(self, '_exclude_' + k, None)
    1079              if exclude:
    1080                  exclude(v)
    1081              else:
    1082                  self._exclude_misc(k, v)
    1083  
    1084      def _exclude_packages(self, packages):
    1085          if not isinstance(packages, sequence):
    1086              raise DistutilsSetupError(
    1087                  "packages: setting must be a list or tuple (%r)" % (packages,)
    1088              )
    1089          list(map(self.exclude_package, packages))
    1090  
    1091      def _parse_command_opts(self, parser, args):
    1092          # Remove --with-X/--without-X options when processing command args
    1093          self.global_options = self.__class__.global_options
    1094          self.negative_opt = self.__class__.negative_opt
    1095  
    1096          # First, expand any aliases
    1097          command = args[0]
    1098          aliases = self.get_option_dict('aliases')
    1099          while command in aliases:
    1100              src, alias = aliases[command]
    1101              del aliases[command]  # ensure each alias can expand only once!
    1102              import shlex
    1103  
    1104              args[:1] = shlex.split(alias, True)
    1105              command = args[0]
    1106  
    1107          nargs = _Distribution._parse_command_opts(self, parser, args)
    1108  
    1109          # Handle commands that want to consume all remaining arguments
    1110          cmd_class = self.get_command_class(command)
    1111          if getattr(cmd_class, 'command_consumes_arguments', None):
    1112              self.get_option_dict(command)['args'] = ("command line", nargs)
    1113              if nargs is not None:
    1114                  return []
    1115  
    1116          return nargs
    1117  
    1118      def get_cmdline_options(self):
    1119          """Return a '{cmd: {opt:val}}' map of all command-line options
    1120  
    1121          Option names are all long, but do not include the leading '--', and
    1122          contain dashes rather than underscores.  If the option doesn't take
    1123          an argument (e.g. '--quiet'), the 'val' is 'None'.
    1124  
    1125          Note that options provided by config files are intentionally excluded.
    1126          """
    1127  
    1128          d = {}
    1129  
    1130          for cmd, opts in self.command_options.items():
    1131  
    1132              for opt, (src, val) in opts.items():
    1133  
    1134                  if src != "command line":
    1135                      continue
    1136  
    1137                  opt = opt.replace('_', '-')
    1138  
    1139                  if val == 0:
    1140                      cmdobj = self.get_command_obj(cmd)
    1141                      neg_opt = self.negative_opt.copy()
    1142                      neg_opt.update(getattr(cmdobj, 'negative_opt', {}))
    1143                      for neg, pos in neg_opt.items():
    1144                          if pos == opt:
    1145                              opt = neg
    1146                              val = None
    1147                              break
    1148                      else:
    1149                          raise AssertionError("Shouldn't be able to get here")
    1150  
    1151                  elif val == 1:
    1152                      val = None
    1153  
    1154                  d.setdefault(cmd, {})[opt] = val
    1155  
    1156          return d
    1157  
    1158      def iter_distribution_names(self):
    1159          """Yield all packages, modules, and extension names in distribution"""
    1160  
    1161          for pkg in self.packages or ():
    1162              yield pkg
    1163  
    1164          for module in self.py_modules or ():
    1165              yield module
    1166  
    1167          for ext in self.ext_modules or ():
    1168              if isinstance(ext, tuple):
    1169                  name, buildinfo = ext
    1170              else:
    1171                  name = ext.name
    1172              if name.endswith('module'):
    1173                  name = name[:-6]
    1174              yield name
    1175  
    1176      def handle_display_options(self, option_order):
    1177          """If there were any non-global "display-only" options
    1178          (--help-commands or the metadata display options) on the command
    1179          line, display the requested info and return true; else return
    1180          false.
    1181          """
    1182          import sys
    1183  
    1184          if self.help_commands:
    1185              return _Distribution.handle_display_options(self, option_order)
    1186  
    1187          # Stdout may be StringIO (e.g. in tests)
    1188          if not isinstance(sys.stdout, io.TextIOWrapper):
    1189              return _Distribution.handle_display_options(self, option_order)
    1190  
    1191          # Don't wrap stdout if utf-8 is already the encoding. Provides
    1192          #  workaround for #334.
    1193          if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):
    1194              return _Distribution.handle_display_options(self, option_order)
    1195  
    1196          # Print metadata in UTF-8 no matter the platform
    1197          encoding = sys.stdout.encoding
    1198          errors = sys.stdout.errors
    1199          newline = sys.platform != 'win32' and '\n' or None
    1200          line_buffering = sys.stdout.line_buffering
    1201  
    1202          sys.stdout = io.TextIOWrapper(
    1203              sys.stdout.detach(), 'utf-8', errors, newline, line_buffering
    1204          )
    1205          try:
    1206              return _Distribution.handle_display_options(self, option_order)
    1207          finally:
    1208              sys.stdout = io.TextIOWrapper(
    1209                  sys.stdout.detach(), encoding, errors, newline, line_buffering
    1210              )
    1211  
    1212      def run_command(self, command):
    1213          self.set_defaults()
    1214          # Postpone defaults until all explicit configuration is considered
    1215          # (setup() args, config files, command line and plugins)
    1216  
    1217          super().run_command(command)
    1218  
    1219  
    1220  class ESC[4;38;5;81mDistDeprecationWarning(ESC[4;38;5;149mSetuptoolsDeprecationWarning):
    1221      """Class for warning about deprecations in dist in
    1222      setuptools. Not ignored by default, unlike DeprecationWarning."""