annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/site-packages/setuptools/dist.py @ 69:33d812a61356

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