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