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