Mercurial > repos > rliterman > csp2
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.""" |