annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/site-packages/setuptools/dist.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
jpayne@68 1 from __future__ import annotations
jpayne@68 2
jpayne@68 3 import io
jpayne@68 4 import itertools
jpayne@68 5 import numbers
jpayne@68 6 import os
jpayne@68 7 import re
jpayne@68 8 import sys
jpayne@68 9 from collections.abc import Iterable
jpayne@68 10 from glob import iglob
jpayne@68 11 from pathlib import Path
jpayne@68 12 from typing import (
jpayne@68 13 TYPE_CHECKING,
jpayne@68 14 Any,
jpayne@68 15 Dict,
jpayne@68 16 List,
jpayne@68 17 MutableMapping,
jpayne@68 18 Sequence,
jpayne@68 19 Tuple,
jpayne@68 20 Union,
jpayne@68 21 )
jpayne@68 22
jpayne@68 23 from more_itertools import partition, unique_everseen
jpayne@68 24 from packaging.markers import InvalidMarker, Marker
jpayne@68 25 from packaging.specifiers import InvalidSpecifier, SpecifierSet
jpayne@68 26 from packaging.version import Version
jpayne@68 27
jpayne@68 28 from setuptools._path import StrPath
jpayne@68 29
jpayne@68 30 from . import (
jpayne@68 31 _entry_points,
jpayne@68 32 _reqs,
jpayne@68 33 command as _, # noqa: F401 # imported for side-effects
jpayne@68 34 )
jpayne@68 35 from ._importlib import metadata
jpayne@68 36 from ._reqs import _StrOrIter
jpayne@68 37 from .config import pyprojecttoml, setupcfg
jpayne@68 38 from .discovery import ConfigDiscovery
jpayne@68 39 from .monkey import get_unpatched
jpayne@68 40 from .warnings import InformationOnly, SetuptoolsDeprecationWarning
jpayne@68 41
jpayne@68 42 import distutils.cmd
jpayne@68 43 import distutils.command
jpayne@68 44 import distutils.core
jpayne@68 45 import distutils.dist
jpayne@68 46 import distutils.log
jpayne@68 47 from distutils.debug import DEBUG
jpayne@68 48 from distutils.errors import DistutilsOptionError, DistutilsSetupError
jpayne@68 49 from distutils.fancy_getopt import translate_longopt
jpayne@68 50 from distutils.util import strtobool
jpayne@68 51
jpayne@68 52 if TYPE_CHECKING:
jpayne@68 53 from typing_extensions import TypeAlias
jpayne@68 54
jpayne@68 55 __all__ = ['Distribution']
jpayne@68 56
jpayne@68 57 _sequence = tuple, list
jpayne@68 58 """
jpayne@68 59 :meta private:
jpayne@68 60
jpayne@68 61 Supported iterable types that are known to be:
jpayne@68 62 - ordered (which `set` isn't)
jpayne@68 63 - not match a str (which `Sequence[str]` does)
jpayne@68 64 - not imply a nested type (like `dict`)
jpayne@68 65 for use with `isinstance`.
jpayne@68 66 """
jpayne@68 67 _Sequence: TypeAlias = Union[Tuple[str, ...], List[str]]
jpayne@68 68 # This is how stringifying _Sequence would look in Python 3.10
jpayne@68 69 _sequence_type_repr = "tuple[str, ...] | list[str]"
jpayne@68 70 _OrderedStrSequence: TypeAlias = Union[str, Dict[str, Any], Sequence[str]]
jpayne@68 71 """
jpayne@68 72 :meta private:
jpayne@68 73 Avoid single-use iterable. Disallow sets.
jpayne@68 74 A poor approximation of an OrderedSequence (dict doesn't match a Sequence).
jpayne@68 75 """
jpayne@68 76
jpayne@68 77
jpayne@68 78 def __getattr__(name: str) -> Any: # pragma: no cover
jpayne@68 79 if name == "sequence":
jpayne@68 80 SetuptoolsDeprecationWarning.emit(
jpayne@68 81 "`setuptools.dist.sequence` is an internal implementation detail.",
jpayne@68 82 "Please define your own `sequence = tuple, list` instead.",
jpayne@68 83 due_date=(2025, 8, 28), # Originally added on 2024-08-27
jpayne@68 84 )
jpayne@68 85 return _sequence
jpayne@68 86 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
jpayne@68 87
jpayne@68 88
jpayne@68 89 def check_importable(dist, attr, value):
jpayne@68 90 try:
jpayne@68 91 ep = metadata.EntryPoint(value=value, name=None, group=None)
jpayne@68 92 assert not ep.extras
jpayne@68 93 except (TypeError, ValueError, AttributeError, AssertionError) as e:
jpayne@68 94 raise DistutilsSetupError(
jpayne@68 95 "%r must be importable 'module:attrs' string (got %r)" % (attr, value)
jpayne@68 96 ) from e
jpayne@68 97
jpayne@68 98
jpayne@68 99 def assert_string_list(dist, attr: str, value: _Sequence) -> None:
jpayne@68 100 """Verify that value is a string list"""
jpayne@68 101 try:
jpayne@68 102 # verify that value is a list or tuple to exclude unordered
jpayne@68 103 # or single-use iterables
jpayne@68 104 assert isinstance(value, _sequence)
jpayne@68 105 # verify that elements of value are strings
jpayne@68 106 assert ''.join(value) != value
jpayne@68 107 except (TypeError, ValueError, AttributeError, AssertionError) as e:
jpayne@68 108 raise DistutilsSetupError(
jpayne@68 109 f"{attr!r} must be of type <{_sequence_type_repr}> (got {value!r})"
jpayne@68 110 ) from e
jpayne@68 111
jpayne@68 112
jpayne@68 113 def check_nsp(dist, attr, value):
jpayne@68 114 """Verify that namespace packages are valid"""
jpayne@68 115 ns_packages = value
jpayne@68 116 assert_string_list(dist, attr, ns_packages)
jpayne@68 117 for nsp in ns_packages:
jpayne@68 118 if not dist.has_contents_for(nsp):
jpayne@68 119 raise DistutilsSetupError(
jpayne@68 120 "Distribution contains no modules or packages for "
jpayne@68 121 + "namespace package %r" % nsp
jpayne@68 122 )
jpayne@68 123 parent, sep, child = nsp.rpartition('.')
jpayne@68 124 if parent and parent not in ns_packages:
jpayne@68 125 distutils.log.warn(
jpayne@68 126 "WARNING: %r is declared as a package namespace, but %r"
jpayne@68 127 " is not: please correct this in setup.py",
jpayne@68 128 nsp,
jpayne@68 129 parent,
jpayne@68 130 )
jpayne@68 131 SetuptoolsDeprecationWarning.emit(
jpayne@68 132 "The namespace_packages parameter is deprecated.",
jpayne@68 133 "Please replace its usage with implicit namespaces (PEP 420).",
jpayne@68 134 see_docs="references/keywords.html#keyword-namespace-packages",
jpayne@68 135 # TODO: define due_date, it may break old packages that are no longer
jpayne@68 136 # maintained (e.g. sphinxcontrib extensions) when installed from source.
jpayne@68 137 # Warning officially introduced in May 2022, however the deprecation
jpayne@68 138 # was mentioned much earlier in the docs (May 2020, see #2149).
jpayne@68 139 )
jpayne@68 140
jpayne@68 141
jpayne@68 142 def check_extras(dist, attr, value):
jpayne@68 143 """Verify that extras_require mapping is valid"""
jpayne@68 144 try:
jpayne@68 145 list(itertools.starmap(_check_extra, value.items()))
jpayne@68 146 except (TypeError, ValueError, AttributeError) as e:
jpayne@68 147 raise DistutilsSetupError(
jpayne@68 148 "'extras_require' must be a dictionary whose values are "
jpayne@68 149 "strings or lists of strings containing valid project/version "
jpayne@68 150 "requirement specifiers."
jpayne@68 151 ) from e
jpayne@68 152
jpayne@68 153
jpayne@68 154 def _check_extra(extra, reqs):
jpayne@68 155 name, sep, marker = extra.partition(':')
jpayne@68 156 try:
jpayne@68 157 _check_marker(marker)
jpayne@68 158 except InvalidMarker:
jpayne@68 159 msg = f"Invalid environment marker: {marker} ({extra!r})"
jpayne@68 160 raise DistutilsSetupError(msg) from None
jpayne@68 161 list(_reqs.parse(reqs))
jpayne@68 162
jpayne@68 163
jpayne@68 164 def _check_marker(marker):
jpayne@68 165 if not marker:
jpayne@68 166 return
jpayne@68 167 m = Marker(marker)
jpayne@68 168 m.evaluate()
jpayne@68 169
jpayne@68 170
jpayne@68 171 def assert_bool(dist, attr, value):
jpayne@68 172 """Verify that value is True, False, 0, or 1"""
jpayne@68 173 if bool(value) != value:
jpayne@68 174 raise DistutilsSetupError(f"{attr!r} must be a boolean value (got {value!r})")
jpayne@68 175
jpayne@68 176
jpayne@68 177 def invalid_unless_false(dist, attr, value):
jpayne@68 178 if not value:
jpayne@68 179 DistDeprecationWarning.emit(f"{attr} is ignored.")
jpayne@68 180 # TODO: should there be a `due_date` here?
jpayne@68 181 return
jpayne@68 182 raise DistutilsSetupError(f"{attr} is invalid.")
jpayne@68 183
jpayne@68 184
jpayne@68 185 def check_requirements(dist, attr: str, value: _OrderedStrSequence) -> None:
jpayne@68 186 """Verify that install_requires is a valid requirements list"""
jpayne@68 187 try:
jpayne@68 188 list(_reqs.parse(value))
jpayne@68 189 if isinstance(value, set):
jpayne@68 190 raise TypeError("Unordered types are not allowed")
jpayne@68 191 except (TypeError, ValueError) as error:
jpayne@68 192 msg = (
jpayne@68 193 f"{attr!r} must be a string or iterable of strings "
jpayne@68 194 f"containing valid project/version requirement specifiers; {error}"
jpayne@68 195 )
jpayne@68 196 raise DistutilsSetupError(msg) from error
jpayne@68 197
jpayne@68 198
jpayne@68 199 def check_specifier(dist, attr, value):
jpayne@68 200 """Verify that value is a valid version specifier"""
jpayne@68 201 try:
jpayne@68 202 SpecifierSet(value)
jpayne@68 203 except (InvalidSpecifier, AttributeError) as error:
jpayne@68 204 msg = f"{attr!r} must be a string containing valid version specifiers; {error}"
jpayne@68 205 raise DistutilsSetupError(msg) from error
jpayne@68 206
jpayne@68 207
jpayne@68 208 def check_entry_points(dist, attr, value):
jpayne@68 209 """Verify that entry_points map is parseable"""
jpayne@68 210 try:
jpayne@68 211 _entry_points.load(value)
jpayne@68 212 except Exception as e:
jpayne@68 213 raise DistutilsSetupError(e) from e
jpayne@68 214
jpayne@68 215
jpayne@68 216 def check_package_data(dist, attr, value):
jpayne@68 217 """Verify that value is a dictionary of package names to glob lists"""
jpayne@68 218 if not isinstance(value, dict):
jpayne@68 219 raise DistutilsSetupError(
jpayne@68 220 "{!r} must be a dictionary mapping package names to lists of "
jpayne@68 221 "string wildcard patterns".format(attr)
jpayne@68 222 )
jpayne@68 223 for k, v in value.items():
jpayne@68 224 if not isinstance(k, str):
jpayne@68 225 raise DistutilsSetupError(
jpayne@68 226 "keys of {!r} dict must be strings (got {!r})".format(attr, k)
jpayne@68 227 )
jpayne@68 228 assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
jpayne@68 229
jpayne@68 230
jpayne@68 231 def check_packages(dist, attr, value):
jpayne@68 232 for pkgname in value:
jpayne@68 233 if not re.match(r'\w+(\.\w+)*', pkgname):
jpayne@68 234 distutils.log.warn(
jpayne@68 235 "WARNING: %r not a valid package name; please use only "
jpayne@68 236 ".-separated package names in setup.py",
jpayne@68 237 pkgname,
jpayne@68 238 )
jpayne@68 239
jpayne@68 240
jpayne@68 241 if TYPE_CHECKING:
jpayne@68 242 # Work around a mypy issue where type[T] can't be used as a base: https://github.com/python/mypy/issues/10962
jpayne@68 243 from distutils.core import Distribution as _Distribution
jpayne@68 244 else:
jpayne@68 245 _Distribution = get_unpatched(distutils.core.Distribution)
jpayne@68 246
jpayne@68 247
jpayne@68 248 class Distribution(_Distribution):
jpayne@68 249 """Distribution with support for tests and package data
jpayne@68 250
jpayne@68 251 This is an enhanced version of 'distutils.dist.Distribution' that
jpayne@68 252 effectively adds the following new optional keyword arguments to 'setup()':
jpayne@68 253
jpayne@68 254 'install_requires' -- a string or sequence of strings specifying project
jpayne@68 255 versions that the distribution requires when installed, in the format
jpayne@68 256 used by 'pkg_resources.require()'. They will be installed
jpayne@68 257 automatically when the package is installed. If you wish to use
jpayne@68 258 packages that are not available in PyPI, or want to give your users an
jpayne@68 259 alternate download location, you can add a 'find_links' option to the
jpayne@68 260 '[easy_install]' section of your project's 'setup.cfg' file, and then
jpayne@68 261 setuptools will scan the listed web pages for links that satisfy the
jpayne@68 262 requirements.
jpayne@68 263
jpayne@68 264 'extras_require' -- a dictionary mapping names of optional "extras" to the
jpayne@68 265 additional requirement(s) that using those extras incurs. For example,
jpayne@68 266 this::
jpayne@68 267
jpayne@68 268 extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
jpayne@68 269
jpayne@68 270 indicates that the distribution can optionally provide an extra
jpayne@68 271 capability called "reST", but it can only be used if docutils and
jpayne@68 272 reSTedit are installed. If the user installs your package using
jpayne@68 273 EasyInstall and requests one of your extras, the corresponding
jpayne@68 274 additional requirements will be installed if needed.
jpayne@68 275
jpayne@68 276 'package_data' -- a dictionary mapping package names to lists of filenames
jpayne@68 277 or globs to use to find data files contained in the named packages.
jpayne@68 278 If the dictionary has filenames or globs listed under '""' (the empty
jpayne@68 279 string), those names will be searched for in every package, in addition
jpayne@68 280 to any names for the specific package. Data files found using these
jpayne@68 281 names/globs will be installed along with the package, in the same
jpayne@68 282 location as the package. Note that globs are allowed to reference
jpayne@68 283 the contents of non-package subdirectories, as long as you use '/' as
jpayne@68 284 a path separator. (Globs are automatically converted to
jpayne@68 285 platform-specific paths at runtime.)
jpayne@68 286
jpayne@68 287 In addition to these new keywords, this class also has several new methods
jpayne@68 288 for manipulating the distribution's contents. For example, the 'include()'
jpayne@68 289 and 'exclude()' methods can be thought of as in-place add and subtract
jpayne@68 290 commands that add or remove packages, modules, extensions, and so on from
jpayne@68 291 the distribution.
jpayne@68 292 """
jpayne@68 293
jpayne@68 294 _DISTUTILS_UNSUPPORTED_METADATA = {
jpayne@68 295 'long_description_content_type': lambda: None,
jpayne@68 296 'project_urls': dict,
jpayne@68 297 'provides_extras': dict, # behaves like an ordered set
jpayne@68 298 'license_file': lambda: None,
jpayne@68 299 'license_files': lambda: None,
jpayne@68 300 'install_requires': list,
jpayne@68 301 'extras_require': dict,
jpayne@68 302 }
jpayne@68 303
jpayne@68 304 # Used by build_py, editable_wheel and install_lib commands for legacy namespaces
jpayne@68 305 namespace_packages: list[str] #: :meta private: DEPRECATED
jpayne@68 306
jpayne@68 307 # Any: Dynamic assignment results in Incompatible types in assignment
jpayne@68 308 def __init__(self, attrs: MutableMapping[str, Any] | None = None) -> None:
jpayne@68 309 have_package_data = hasattr(self, "package_data")
jpayne@68 310 if not have_package_data:
jpayne@68 311 self.package_data: dict[str, list[str]] = {}
jpayne@68 312 attrs = attrs or {}
jpayne@68 313 self.dist_files: list[tuple[str, str, str]] = []
jpayne@68 314 self.include_package_data: bool | None = None
jpayne@68 315 self.exclude_package_data: dict[str, list[str]] | None = None
jpayne@68 316 # Filter-out setuptools' specific options.
jpayne@68 317 self.src_root: str | None = attrs.pop("src_root", None)
jpayne@68 318 self.dependency_links: list[str] = attrs.pop('dependency_links', [])
jpayne@68 319 self.setup_requires: list[str] = attrs.pop('setup_requires', [])
jpayne@68 320 for ep in metadata.entry_points(group='distutils.setup_keywords'):
jpayne@68 321 vars(self).setdefault(ep.name, None)
jpayne@68 322
jpayne@68 323 metadata_only = set(self._DISTUTILS_UNSUPPORTED_METADATA)
jpayne@68 324 metadata_only -= {"install_requires", "extras_require"}
jpayne@68 325 dist_attrs = {k: v for k, v in attrs.items() if k not in metadata_only}
jpayne@68 326 _Distribution.__init__(self, dist_attrs)
jpayne@68 327
jpayne@68 328 # Private API (setuptools-use only, not restricted to Distribution)
jpayne@68 329 # Stores files that are referenced by the configuration and need to be in the
jpayne@68 330 # sdist (e.g. `version = file: VERSION.txt`)
jpayne@68 331 self._referenced_files: set[str] = set()
jpayne@68 332
jpayne@68 333 self.set_defaults = ConfigDiscovery(self)
jpayne@68 334
jpayne@68 335 self._set_metadata_defaults(attrs)
jpayne@68 336
jpayne@68 337 self.metadata.version = self._normalize_version(self.metadata.version)
jpayne@68 338 self._finalize_requires()
jpayne@68 339
jpayne@68 340 def _validate_metadata(self):
jpayne@68 341 required = {"name"}
jpayne@68 342 provided = {
jpayne@68 343 key
jpayne@68 344 for key in vars(self.metadata)
jpayne@68 345 if getattr(self.metadata, key, None) is not None
jpayne@68 346 }
jpayne@68 347 missing = required - provided
jpayne@68 348
jpayne@68 349 if missing:
jpayne@68 350 msg = f"Required package metadata is missing: {missing}"
jpayne@68 351 raise DistutilsSetupError(msg)
jpayne@68 352
jpayne@68 353 def _set_metadata_defaults(self, attrs):
jpayne@68 354 """
jpayne@68 355 Fill-in missing metadata fields not supported by distutils.
jpayne@68 356 Some fields may have been set by other tools (e.g. pbr).
jpayne@68 357 Those fields (vars(self.metadata)) take precedence to
jpayne@68 358 supplied attrs.
jpayne@68 359 """
jpayne@68 360 for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():
jpayne@68 361 vars(self.metadata).setdefault(option, attrs.get(option, default()))
jpayne@68 362
jpayne@68 363 @staticmethod
jpayne@68 364 def _normalize_version(version):
jpayne@68 365 from . import sic
jpayne@68 366
jpayne@68 367 if isinstance(version, numbers.Number):
jpayne@68 368 # Some people apparently take "version number" too literally :)
jpayne@68 369 version = str(version)
jpayne@68 370 elif isinstance(version, sic) or version is None:
jpayne@68 371 return version
jpayne@68 372
jpayne@68 373 normalized = str(Version(version))
jpayne@68 374 if version != normalized:
jpayne@68 375 InformationOnly.emit(f"Normalizing '{version}' to '{normalized}'")
jpayne@68 376 return normalized
jpayne@68 377 return version
jpayne@68 378
jpayne@68 379 def _finalize_requires(self):
jpayne@68 380 """
jpayne@68 381 Set `metadata.python_requires` and fix environment markers
jpayne@68 382 in `install_requires` and `extras_require`.
jpayne@68 383 """
jpayne@68 384 if getattr(self, 'python_requires', None):
jpayne@68 385 self.metadata.python_requires = self.python_requires
jpayne@68 386
jpayne@68 387 self._normalize_requires()
jpayne@68 388 self.metadata.install_requires = self.install_requires
jpayne@68 389 self.metadata.extras_require = self.extras_require
jpayne@68 390
jpayne@68 391 if self.extras_require:
jpayne@68 392 for extra in self.extras_require.keys():
jpayne@68 393 # Setuptools allows a weird "<name>:<env markers> syntax for extras
jpayne@68 394 extra = extra.split(':')[0]
jpayne@68 395 if extra:
jpayne@68 396 self.metadata.provides_extras.setdefault(extra)
jpayne@68 397
jpayne@68 398 def _normalize_requires(self):
jpayne@68 399 """Make sure requirement-related attributes exist and are normalized"""
jpayne@68 400 install_requires = getattr(self, "install_requires", None) or []
jpayne@68 401 extras_require = getattr(self, "extras_require", None) or {}
jpayne@68 402 self.install_requires = list(map(str, _reqs.parse(install_requires)))
jpayne@68 403 self.extras_require = {
jpayne@68 404 k: list(map(str, _reqs.parse(v or []))) for k, v in extras_require.items()
jpayne@68 405 }
jpayne@68 406
jpayne@68 407 def _finalize_license_files(self) -> None:
jpayne@68 408 """Compute names of all license files which should be included."""
jpayne@68 409 license_files: list[str] | None = self.metadata.license_files
jpayne@68 410 patterns: list[str] = license_files if license_files else []
jpayne@68 411
jpayne@68 412 license_file: str | None = self.metadata.license_file
jpayne@68 413 if license_file and license_file not in patterns:
jpayne@68 414 patterns.append(license_file)
jpayne@68 415
jpayne@68 416 if license_files is None and license_file is None:
jpayne@68 417 # Default patterns match the ones wheel uses
jpayne@68 418 # See https://wheel.readthedocs.io/en/stable/user_guide.html
jpayne@68 419 # -> 'Including license files in the generated wheel file'
jpayne@68 420 patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']
jpayne@68 421
jpayne@68 422 self.metadata.license_files = list(
jpayne@68 423 unique_everseen(self._expand_patterns(patterns))
jpayne@68 424 )
jpayne@68 425
jpayne@68 426 @staticmethod
jpayne@68 427 def _expand_patterns(patterns):
jpayne@68 428 """
jpayne@68 429 >>> list(Distribution._expand_patterns(['LICENSE']))
jpayne@68 430 ['LICENSE']
jpayne@68 431 >>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*']))
jpayne@68 432 ['pyproject.toml', 'LICENSE']
jpayne@68 433 """
jpayne@68 434 return (
jpayne@68 435 path
jpayne@68 436 for pattern in patterns
jpayne@68 437 for path in sorted(iglob(pattern))
jpayne@68 438 if not path.endswith('~') and os.path.isfile(path)
jpayne@68 439 )
jpayne@68 440
jpayne@68 441 # FIXME: 'Distribution._parse_config_files' is too complex (14)
jpayne@68 442 def _parse_config_files(self, filenames=None): # noqa: C901
jpayne@68 443 """
jpayne@68 444 Adapted from distutils.dist.Distribution.parse_config_files,
jpayne@68 445 this method provides the same functionality in subtly-improved
jpayne@68 446 ways.
jpayne@68 447 """
jpayne@68 448 from configparser import ConfigParser
jpayne@68 449
jpayne@68 450 # Ignore install directory options if we have a venv
jpayne@68 451 ignore_options = (
jpayne@68 452 []
jpayne@68 453 if sys.prefix == sys.base_prefix
jpayne@68 454 else [
jpayne@68 455 'install-base',
jpayne@68 456 'install-platbase',
jpayne@68 457 'install-lib',
jpayne@68 458 'install-platlib',
jpayne@68 459 'install-purelib',
jpayne@68 460 'install-headers',
jpayne@68 461 'install-scripts',
jpayne@68 462 'install-data',
jpayne@68 463 'prefix',
jpayne@68 464 'exec-prefix',
jpayne@68 465 'home',
jpayne@68 466 'user',
jpayne@68 467 'root',
jpayne@68 468 ]
jpayne@68 469 )
jpayne@68 470
jpayne@68 471 ignore_options = frozenset(ignore_options)
jpayne@68 472
jpayne@68 473 if filenames is None:
jpayne@68 474 filenames = self.find_config_files()
jpayne@68 475
jpayne@68 476 if DEBUG:
jpayne@68 477 self.announce("Distribution.parse_config_files():")
jpayne@68 478
jpayne@68 479 parser = ConfigParser()
jpayne@68 480 parser.optionxform = str
jpayne@68 481 for filename in filenames:
jpayne@68 482 with open(filename, encoding='utf-8') as reader:
jpayne@68 483 if DEBUG:
jpayne@68 484 self.announce(" reading {filename}".format(**locals()))
jpayne@68 485 parser.read_file(reader)
jpayne@68 486 for section in parser.sections():
jpayne@68 487 options = parser.options(section)
jpayne@68 488 opt_dict = self.get_option_dict(section)
jpayne@68 489
jpayne@68 490 for opt in options:
jpayne@68 491 if opt == '__name__' or opt in ignore_options:
jpayne@68 492 continue
jpayne@68 493
jpayne@68 494 val = parser.get(section, opt)
jpayne@68 495 opt = self.warn_dash_deprecation(opt, section)
jpayne@68 496 opt = self.make_option_lowercase(opt, section)
jpayne@68 497 opt_dict[opt] = (filename, val)
jpayne@68 498
jpayne@68 499 # Make the ConfigParser forget everything (so we retain
jpayne@68 500 # the original filenames that options come from)
jpayne@68 501 parser.__init__()
jpayne@68 502
jpayne@68 503 if 'global' not in self.command_options:
jpayne@68 504 return
jpayne@68 505
jpayne@68 506 # If there was a "global" section in the config file, use it
jpayne@68 507 # to set Distribution options.
jpayne@68 508
jpayne@68 509 for opt, (src, val) in self.command_options['global'].items():
jpayne@68 510 alias = self.negative_opt.get(opt)
jpayne@68 511 if alias:
jpayne@68 512 val = not strtobool(val)
jpayne@68 513 elif opt in ('verbose', 'dry_run'): # ugh!
jpayne@68 514 val = strtobool(val)
jpayne@68 515
jpayne@68 516 try:
jpayne@68 517 setattr(self, alias or opt, val)
jpayne@68 518 except ValueError as e:
jpayne@68 519 raise DistutilsOptionError(e) from e
jpayne@68 520
jpayne@68 521 def warn_dash_deprecation(self, opt: str, section: str):
jpayne@68 522 if section in (
jpayne@68 523 'options.extras_require',
jpayne@68 524 'options.data_files',
jpayne@68 525 ):
jpayne@68 526 return opt
jpayne@68 527
jpayne@68 528 underscore_opt = opt.replace('-', '_')
jpayne@68 529 commands = list(
jpayne@68 530 itertools.chain(
jpayne@68 531 distutils.command.__all__,
jpayne@68 532 self._setuptools_commands(),
jpayne@68 533 )
jpayne@68 534 )
jpayne@68 535 if (
jpayne@68 536 not section.startswith('options')
jpayne@68 537 and section != 'metadata'
jpayne@68 538 and section not in commands
jpayne@68 539 ):
jpayne@68 540 return underscore_opt
jpayne@68 541
jpayne@68 542 if '-' in opt:
jpayne@68 543 SetuptoolsDeprecationWarning.emit(
jpayne@68 544 "Invalid dash-separated options",
jpayne@68 545 f"""
jpayne@68 546 Usage of dash-separated {opt!r} will not be supported in future
jpayne@68 547 versions. Please use the underscore name {underscore_opt!r} instead.
jpayne@68 548 """,
jpayne@68 549 see_docs="userguide/declarative_config.html",
jpayne@68 550 due_date=(2025, 3, 3),
jpayne@68 551 # Warning initially introduced in 3 Mar 2021
jpayne@68 552 )
jpayne@68 553 return underscore_opt
jpayne@68 554
jpayne@68 555 def _setuptools_commands(self):
jpayne@68 556 try:
jpayne@68 557 entry_points = metadata.distribution('setuptools').entry_points
jpayne@68 558 return {ep.name for ep in entry_points} # Avoid newer API for compatibility
jpayne@68 559 except metadata.PackageNotFoundError:
jpayne@68 560 # during bootstrapping, distribution doesn't exist
jpayne@68 561 return []
jpayne@68 562
jpayne@68 563 def make_option_lowercase(self, opt: str, section: str):
jpayne@68 564 if section != 'metadata' or opt.islower():
jpayne@68 565 return opt
jpayne@68 566
jpayne@68 567 lowercase_opt = opt.lower()
jpayne@68 568 SetuptoolsDeprecationWarning.emit(
jpayne@68 569 "Invalid uppercase configuration",
jpayne@68 570 f"""
jpayne@68 571 Usage of uppercase key {opt!r} in {section!r} will not be supported in
jpayne@68 572 future versions. Please use lowercase {lowercase_opt!r} instead.
jpayne@68 573 """,
jpayne@68 574 see_docs="userguide/declarative_config.html",
jpayne@68 575 due_date=(2025, 3, 3),
jpayne@68 576 # Warning initially introduced in 6 Mar 2021
jpayne@68 577 )
jpayne@68 578 return lowercase_opt
jpayne@68 579
jpayne@68 580 # FIXME: 'Distribution._set_command_options' is too complex (14)
jpayne@68 581 def _set_command_options(self, command_obj, option_dict=None): # noqa: C901
jpayne@68 582 """
jpayne@68 583 Set the options for 'command_obj' from 'option_dict'. Basically
jpayne@68 584 this means copying elements of a dictionary ('option_dict') to
jpayne@68 585 attributes of an instance ('command').
jpayne@68 586
jpayne@68 587 'command_obj' must be a Command instance. If 'option_dict' is not
jpayne@68 588 supplied, uses the standard option dictionary for this command
jpayne@68 589 (from 'self.command_options').
jpayne@68 590
jpayne@68 591 (Adopted from distutils.dist.Distribution._set_command_options)
jpayne@68 592 """
jpayne@68 593 command_name = command_obj.get_command_name()
jpayne@68 594 if option_dict is None:
jpayne@68 595 option_dict = self.get_option_dict(command_name)
jpayne@68 596
jpayne@68 597 if DEBUG:
jpayne@68 598 self.announce(" setting options for '%s' command:" % command_name)
jpayne@68 599 for option, (source, value) in option_dict.items():
jpayne@68 600 if DEBUG:
jpayne@68 601 self.announce(" %s = %s (from %s)" % (option, value, source))
jpayne@68 602 try:
jpayne@68 603 bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
jpayne@68 604 except AttributeError:
jpayne@68 605 bool_opts = []
jpayne@68 606 try:
jpayne@68 607 neg_opt = command_obj.negative_opt
jpayne@68 608 except AttributeError:
jpayne@68 609 neg_opt = {}
jpayne@68 610
jpayne@68 611 try:
jpayne@68 612 is_string = isinstance(value, str)
jpayne@68 613 if option in neg_opt and is_string:
jpayne@68 614 setattr(command_obj, neg_opt[option], not strtobool(value))
jpayne@68 615 elif option in bool_opts and is_string:
jpayne@68 616 setattr(command_obj, option, strtobool(value))
jpayne@68 617 elif hasattr(command_obj, option):
jpayne@68 618 setattr(command_obj, option, value)
jpayne@68 619 else:
jpayne@68 620 raise DistutilsOptionError(
jpayne@68 621 "error in %s: command '%s' has no such option '%s'"
jpayne@68 622 % (source, command_name, option)
jpayne@68 623 )
jpayne@68 624 except ValueError as e:
jpayne@68 625 raise DistutilsOptionError(e) from e
jpayne@68 626
jpayne@68 627 def _get_project_config_files(self, filenames: Iterable[StrPath] | None):
jpayne@68 628 """Add default file and split between INI and TOML"""
jpayne@68 629 tomlfiles = []
jpayne@68 630 standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml")
jpayne@68 631 if filenames is not None:
jpayne@68 632 parts = partition(lambda f: Path(f).suffix == ".toml", filenames)
jpayne@68 633 filenames = list(parts[0]) # 1st element => predicate is False
jpayne@68 634 tomlfiles = list(parts[1]) # 2nd element => predicate is True
jpayne@68 635 elif standard_project_metadata.exists():
jpayne@68 636 tomlfiles = [standard_project_metadata]
jpayne@68 637 return filenames, tomlfiles
jpayne@68 638
jpayne@68 639 def parse_config_files(
jpayne@68 640 self,
jpayne@68 641 filenames: Iterable[StrPath] | None = None,
jpayne@68 642 ignore_option_errors: bool = False,
jpayne@68 643 ):
jpayne@68 644 """Parses configuration files from various levels
jpayne@68 645 and loads configuration.
jpayne@68 646 """
jpayne@68 647 inifiles, tomlfiles = self._get_project_config_files(filenames)
jpayne@68 648
jpayne@68 649 self._parse_config_files(filenames=inifiles)
jpayne@68 650
jpayne@68 651 setupcfg.parse_configuration(
jpayne@68 652 self, self.command_options, ignore_option_errors=ignore_option_errors
jpayne@68 653 )
jpayne@68 654 for filename in tomlfiles:
jpayne@68 655 pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
jpayne@68 656
jpayne@68 657 self._finalize_requires()
jpayne@68 658 self._finalize_license_files()
jpayne@68 659
jpayne@68 660 def fetch_build_eggs(self, requires: _StrOrIter):
jpayne@68 661 """Resolve pre-setup requirements"""
jpayne@68 662 from .installer import _fetch_build_eggs
jpayne@68 663
jpayne@68 664 return _fetch_build_eggs(self, requires)
jpayne@68 665
jpayne@68 666 def finalize_options(self):
jpayne@68 667 """
jpayne@68 668 Allow plugins to apply arbitrary operations to the
jpayne@68 669 distribution. Each hook may optionally define a 'order'
jpayne@68 670 to influence the order of execution. Smaller numbers
jpayne@68 671 go first and the default is 0.
jpayne@68 672 """
jpayne@68 673 group = 'setuptools.finalize_distribution_options'
jpayne@68 674
jpayne@68 675 def by_order(hook):
jpayne@68 676 return getattr(hook, 'order', 0)
jpayne@68 677
jpayne@68 678 defined = metadata.entry_points(group=group)
jpayne@68 679 filtered = itertools.filterfalse(self._removed, defined)
jpayne@68 680 loaded = map(lambda e: e.load(), filtered)
jpayne@68 681 for ep in sorted(loaded, key=by_order):
jpayne@68 682 ep(self)
jpayne@68 683
jpayne@68 684 @staticmethod
jpayne@68 685 def _removed(ep):
jpayne@68 686 """
jpayne@68 687 When removing an entry point, if metadata is loaded
jpayne@68 688 from an older version of Setuptools, that removed
jpayne@68 689 entry point will attempt to be loaded and will fail.
jpayne@68 690 See #2765 for more details.
jpayne@68 691 """
jpayne@68 692 removed = {
jpayne@68 693 # removed 2021-09-05
jpayne@68 694 '2to3_doctests',
jpayne@68 695 }
jpayne@68 696 return ep.name in removed
jpayne@68 697
jpayne@68 698 def _finalize_setup_keywords(self):
jpayne@68 699 for ep in metadata.entry_points(group='distutils.setup_keywords'):
jpayne@68 700 value = getattr(self, ep.name, None)
jpayne@68 701 if value is not None:
jpayne@68 702 ep.load()(self, ep.name, value)
jpayne@68 703
jpayne@68 704 def get_egg_cache_dir(self):
jpayne@68 705 from . import windows_support
jpayne@68 706
jpayne@68 707 egg_cache_dir = os.path.join(os.curdir, '.eggs')
jpayne@68 708 if not os.path.exists(egg_cache_dir):
jpayne@68 709 os.mkdir(egg_cache_dir)
jpayne@68 710 windows_support.hide_file(egg_cache_dir)
jpayne@68 711 readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt')
jpayne@68 712 with open(readme_txt_filename, 'w', encoding="utf-8") as f:
jpayne@68 713 f.write(
jpayne@68 714 'This directory contains eggs that were downloaded '
jpayne@68 715 'by setuptools to build, test, and run plug-ins.\n\n'
jpayne@68 716 )
jpayne@68 717 f.write(
jpayne@68 718 'This directory caches those eggs to prevent '
jpayne@68 719 'repeated downloads.\n\n'
jpayne@68 720 )
jpayne@68 721 f.write('However, it is safe to delete this directory.\n\n')
jpayne@68 722
jpayne@68 723 return egg_cache_dir
jpayne@68 724
jpayne@68 725 def fetch_build_egg(self, req):
jpayne@68 726 """Fetch an egg needed for building"""
jpayne@68 727 from .installer import fetch_build_egg
jpayne@68 728
jpayne@68 729 return fetch_build_egg(self, req)
jpayne@68 730
jpayne@68 731 def get_command_class(self, command: str):
jpayne@68 732 """Pluggable version of get_command_class()"""
jpayne@68 733 if command in self.cmdclass:
jpayne@68 734 return self.cmdclass[command]
jpayne@68 735
jpayne@68 736 # Special case bdist_wheel so it's never loaded from "wheel"
jpayne@68 737 if command == 'bdist_wheel':
jpayne@68 738 from .command.bdist_wheel import bdist_wheel
jpayne@68 739
jpayne@68 740 return bdist_wheel
jpayne@68 741
jpayne@68 742 eps = metadata.entry_points(group='distutils.commands', name=command)
jpayne@68 743 for ep in eps:
jpayne@68 744 self.cmdclass[command] = cmdclass = ep.load()
jpayne@68 745 return cmdclass
jpayne@68 746 else:
jpayne@68 747 return _Distribution.get_command_class(self, command)
jpayne@68 748
jpayne@68 749 def print_commands(self):
jpayne@68 750 for ep in metadata.entry_points(group='distutils.commands'):
jpayne@68 751 if ep.name not in self.cmdclass:
jpayne@68 752 cmdclass = ep.load()
jpayne@68 753 self.cmdclass[ep.name] = cmdclass
jpayne@68 754 return _Distribution.print_commands(self)
jpayne@68 755
jpayne@68 756 def get_command_list(self):
jpayne@68 757 for ep in metadata.entry_points(group='distutils.commands'):
jpayne@68 758 if ep.name not in self.cmdclass:
jpayne@68 759 cmdclass = ep.load()
jpayne@68 760 self.cmdclass[ep.name] = cmdclass
jpayne@68 761 return _Distribution.get_command_list(self)
jpayne@68 762
jpayne@68 763 def include(self, **attrs):
jpayne@68 764 """Add items to distribution that are named in keyword arguments
jpayne@68 765
jpayne@68 766 For example, 'dist.include(py_modules=["x"])' would add 'x' to
jpayne@68 767 the distribution's 'py_modules' attribute, if it was not already
jpayne@68 768 there.
jpayne@68 769
jpayne@68 770 Currently, this method only supports inclusion for attributes that are
jpayne@68 771 lists or tuples. If you need to add support for adding to other
jpayne@68 772 attributes in this or a subclass, you can add an '_include_X' method,
jpayne@68 773 where 'X' is the name of the attribute. The method will be called with
jpayne@68 774 the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})'
jpayne@68 775 will try to call 'dist._include_foo({"bar":"baz"})', which can then
jpayne@68 776 handle whatever special inclusion logic is needed.
jpayne@68 777 """
jpayne@68 778 for k, v in attrs.items():
jpayne@68 779 include = getattr(self, '_include_' + k, None)
jpayne@68 780 if include:
jpayne@68 781 include(v)
jpayne@68 782 else:
jpayne@68 783 self._include_misc(k, v)
jpayne@68 784
jpayne@68 785 def exclude_package(self, package: str):
jpayne@68 786 """Remove packages, modules, and extensions in named package"""
jpayne@68 787
jpayne@68 788 pfx = package + '.'
jpayne@68 789 if self.packages:
jpayne@68 790 self.packages = [
jpayne@68 791 p for p in self.packages if p != package and not p.startswith(pfx)
jpayne@68 792 ]
jpayne@68 793
jpayne@68 794 if self.py_modules:
jpayne@68 795 self.py_modules = [
jpayne@68 796 p for p in self.py_modules if p != package and not p.startswith(pfx)
jpayne@68 797 ]
jpayne@68 798
jpayne@68 799 if self.ext_modules:
jpayne@68 800 self.ext_modules = [
jpayne@68 801 p
jpayne@68 802 for p in self.ext_modules
jpayne@68 803 if p.name != package and not p.name.startswith(pfx)
jpayne@68 804 ]
jpayne@68 805
jpayne@68 806 def has_contents_for(self, package: str):
jpayne@68 807 """Return true if 'exclude_package(package)' would do something"""
jpayne@68 808
jpayne@68 809 pfx = package + '.'
jpayne@68 810
jpayne@68 811 for p in self.iter_distribution_names():
jpayne@68 812 if p == package or p.startswith(pfx):
jpayne@68 813 return True
jpayne@68 814
jpayne@68 815 return False
jpayne@68 816
jpayne@68 817 def _exclude_misc(self, name: str, value: _Sequence) -> None:
jpayne@68 818 """Handle 'exclude()' for list/tuple attrs without a special handler"""
jpayne@68 819 if not isinstance(value, _sequence):
jpayne@68 820 raise DistutilsSetupError(
jpayne@68 821 f"{name}: setting must be of type <{_sequence_type_repr}> (got {value!r})"
jpayne@68 822 )
jpayne@68 823 try:
jpayne@68 824 old = getattr(self, name)
jpayne@68 825 except AttributeError as e:
jpayne@68 826 raise DistutilsSetupError("%s: No such distribution setting" % name) from e
jpayne@68 827 if old is not None and not isinstance(old, _sequence):
jpayne@68 828 raise DistutilsSetupError(
jpayne@68 829 name + ": this setting cannot be changed via include/exclude"
jpayne@68 830 )
jpayne@68 831 elif old:
jpayne@68 832 setattr(self, name, [item for item in old if item not in value])
jpayne@68 833
jpayne@68 834 def _include_misc(self, name: str, value: _Sequence) -> None:
jpayne@68 835 """Handle 'include()' for list/tuple attrs without a special handler"""
jpayne@68 836
jpayne@68 837 if not isinstance(value, _sequence):
jpayne@68 838 raise DistutilsSetupError(
jpayne@68 839 f"{name}: setting must be of type <{_sequence_type_repr}> (got {value!r})"
jpayne@68 840 )
jpayne@68 841 try:
jpayne@68 842 old = getattr(self, name)
jpayne@68 843 except AttributeError as e:
jpayne@68 844 raise DistutilsSetupError("%s: No such distribution setting" % name) from e
jpayne@68 845 if old is None:
jpayne@68 846 setattr(self, name, value)
jpayne@68 847 elif not isinstance(old, _sequence):
jpayne@68 848 raise DistutilsSetupError(
jpayne@68 849 name + ": this setting cannot be changed via include/exclude"
jpayne@68 850 )
jpayne@68 851 else:
jpayne@68 852 new = [item for item in value if item not in old]
jpayne@68 853 setattr(self, name, list(old) + new)
jpayne@68 854
jpayne@68 855 def exclude(self, **attrs):
jpayne@68 856 """Remove items from distribution that are named in keyword arguments
jpayne@68 857
jpayne@68 858 For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from
jpayne@68 859 the distribution's 'py_modules' attribute. Excluding packages uses
jpayne@68 860 the 'exclude_package()' method, so all of the package's contained
jpayne@68 861 packages, modules, and extensions are also excluded.
jpayne@68 862
jpayne@68 863 Currently, this method only supports exclusion from attributes that are
jpayne@68 864 lists or tuples. If you need to add support for excluding from other
jpayne@68 865 attributes in this or a subclass, you can add an '_exclude_X' method,
jpayne@68 866 where 'X' is the name of the attribute. The method will be called with
jpayne@68 867 the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})'
jpayne@68 868 will try to call 'dist._exclude_foo({"bar":"baz"})', which can then
jpayne@68 869 handle whatever special exclusion logic is needed.
jpayne@68 870 """
jpayne@68 871 for k, v in attrs.items():
jpayne@68 872 exclude = getattr(self, '_exclude_' + k, None)
jpayne@68 873 if exclude:
jpayne@68 874 exclude(v)
jpayne@68 875 else:
jpayne@68 876 self._exclude_misc(k, v)
jpayne@68 877
jpayne@68 878 def _exclude_packages(self, packages: _Sequence) -> None:
jpayne@68 879 if not isinstance(packages, _sequence):
jpayne@68 880 raise DistutilsSetupError(
jpayne@68 881 f"packages: setting must be of type <{_sequence_type_repr}> (got {packages!r})"
jpayne@68 882 )
jpayne@68 883 list(map(self.exclude_package, packages))
jpayne@68 884
jpayne@68 885 def _parse_command_opts(self, parser, args):
jpayne@68 886 # Remove --with-X/--without-X options when processing command args
jpayne@68 887 self.global_options = self.__class__.global_options
jpayne@68 888 self.negative_opt = self.__class__.negative_opt
jpayne@68 889
jpayne@68 890 # First, expand any aliases
jpayne@68 891 command = args[0]
jpayne@68 892 aliases = self.get_option_dict('aliases')
jpayne@68 893 while command in aliases:
jpayne@68 894 src, alias = aliases[command]
jpayne@68 895 del aliases[command] # ensure each alias can expand only once!
jpayne@68 896 import shlex
jpayne@68 897
jpayne@68 898 args[:1] = shlex.split(alias, True)
jpayne@68 899 command = args[0]
jpayne@68 900
jpayne@68 901 nargs = _Distribution._parse_command_opts(self, parser, args)
jpayne@68 902
jpayne@68 903 # Handle commands that want to consume all remaining arguments
jpayne@68 904 cmd_class = self.get_command_class(command)
jpayne@68 905 if getattr(cmd_class, 'command_consumes_arguments', None):
jpayne@68 906 self.get_option_dict(command)['args'] = ("command line", nargs)
jpayne@68 907 if nargs is not None:
jpayne@68 908 return []
jpayne@68 909
jpayne@68 910 return nargs
jpayne@68 911
jpayne@68 912 def get_cmdline_options(self):
jpayne@68 913 """Return a '{cmd: {opt:val}}' map of all command-line options
jpayne@68 914
jpayne@68 915 Option names are all long, but do not include the leading '--', and
jpayne@68 916 contain dashes rather than underscores. If the option doesn't take
jpayne@68 917 an argument (e.g. '--quiet'), the 'val' is 'None'.
jpayne@68 918
jpayne@68 919 Note that options provided by config files are intentionally excluded.
jpayne@68 920 """
jpayne@68 921
jpayne@68 922 d = {}
jpayne@68 923
jpayne@68 924 for cmd, opts in self.command_options.items():
jpayne@68 925 for opt, (src, val) in opts.items():
jpayne@68 926 if src != "command line":
jpayne@68 927 continue
jpayne@68 928
jpayne@68 929 opt = opt.replace('_', '-')
jpayne@68 930
jpayne@68 931 if val == 0:
jpayne@68 932 cmdobj = self.get_command_obj(cmd)
jpayne@68 933 neg_opt = self.negative_opt.copy()
jpayne@68 934 neg_opt.update(getattr(cmdobj, 'negative_opt', {}))
jpayne@68 935 for neg, pos in neg_opt.items():
jpayne@68 936 if pos == opt:
jpayne@68 937 opt = neg
jpayne@68 938 val = None
jpayne@68 939 break
jpayne@68 940 else:
jpayne@68 941 raise AssertionError("Shouldn't be able to get here")
jpayne@68 942
jpayne@68 943 elif val == 1:
jpayne@68 944 val = None
jpayne@68 945
jpayne@68 946 d.setdefault(cmd, {})[opt] = val
jpayne@68 947
jpayne@68 948 return d
jpayne@68 949
jpayne@68 950 def iter_distribution_names(self):
jpayne@68 951 """Yield all packages, modules, and extension names in distribution"""
jpayne@68 952
jpayne@68 953 yield from self.packages or ()
jpayne@68 954
jpayne@68 955 yield from self.py_modules or ()
jpayne@68 956
jpayne@68 957 for ext in self.ext_modules or ():
jpayne@68 958 if isinstance(ext, tuple):
jpayne@68 959 name, buildinfo = ext
jpayne@68 960 else:
jpayne@68 961 name = ext.name
jpayne@68 962 if name.endswith('module'):
jpayne@68 963 name = name[:-6]
jpayne@68 964 yield name
jpayne@68 965
jpayne@68 966 def handle_display_options(self, option_order):
jpayne@68 967 """If there were any non-global "display-only" options
jpayne@68 968 (--help-commands or the metadata display options) on the command
jpayne@68 969 line, display the requested info and return true; else return
jpayne@68 970 false.
jpayne@68 971 """
jpayne@68 972 import sys
jpayne@68 973
jpayne@68 974 if self.help_commands:
jpayne@68 975 return _Distribution.handle_display_options(self, option_order)
jpayne@68 976
jpayne@68 977 # Stdout may be StringIO (e.g. in tests)
jpayne@68 978 if not isinstance(sys.stdout, io.TextIOWrapper):
jpayne@68 979 return _Distribution.handle_display_options(self, option_order)
jpayne@68 980
jpayne@68 981 # Don't wrap stdout if utf-8 is already the encoding. Provides
jpayne@68 982 # workaround for #334.
jpayne@68 983 if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):
jpayne@68 984 return _Distribution.handle_display_options(self, option_order)
jpayne@68 985
jpayne@68 986 # Print metadata in UTF-8 no matter the platform
jpayne@68 987 encoding = sys.stdout.encoding
jpayne@68 988 sys.stdout.reconfigure(encoding='utf-8')
jpayne@68 989 try:
jpayne@68 990 return _Distribution.handle_display_options(self, option_order)
jpayne@68 991 finally:
jpayne@68 992 sys.stdout.reconfigure(encoding=encoding)
jpayne@68 993
jpayne@68 994 def run_command(self, command):
jpayne@68 995 self.set_defaults()
jpayne@68 996 # Postpone defaults until all explicit configuration is considered
jpayne@68 997 # (setup() args, config files, command line and plugins)
jpayne@68 998
jpayne@68 999 super().run_command(command)
jpayne@68 1000
jpayne@68 1001
jpayne@68 1002 class DistDeprecationWarning(SetuptoolsDeprecationWarning):
jpayne@68 1003 """Class for warning about deprecations in dist in
jpayne@68 1004 setuptools. Not ignored by default, unlike DeprecationWarning."""