1 __all__
= ['Distribution']
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
19 from contextlib
import suppress
20 from typing
import List
, Optional
, Set
, TYPE_CHECKING
21 from pathlib
import Path
23 from collections
import defaultdict
24 from email
import message_from_file
26 from distutils
.errors
import DistutilsOptionError
, DistutilsSetupError
27 from distutils
.util
import rfc822_escape
29 from setuptools
.extern
import packaging
30 from setuptools
.extern
import ordered_set
31 from setuptools
.extern
.more_itertools
import unique_everseen
, partition
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
40 from setuptools
.extern
.packaging
import version
42 from . import _entry_points
43 from . import _normalization
44 from ._importlib
import metadata
45 from .warnings
import InformationOnly
, SetuptoolsDeprecationWarning
48 from email
.message
import Message
50 __import__('setuptools.extern.packaging.specifiers')
51 __import__('setuptools.extern.packaging.version')
54 def get_metadata_version(self
):
55 mv
= getattr(self
, 'metadata_version', None)
57 mv
= version
.Version('2.1')
58 self
.metadata_version
= mv
62 def rfc822_unescape(content
: str) -> str:
63 """Reverse RFC-822 escaping by removing leading whitespaces from content."""
64 lines
= content
.splitlines()
66 return lines
[0].lstrip()
67 return '\n'.join((lines
[0].lstrip(), textwrap
.dedent('\n'.join(lines
[1:]))))
70 def _read_field_from_msg(msg
: "Message", field
: str) -> Optional
[str]:
71 """Read Message header field."""
73 if value
== 'UNKNOWN':
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
)
83 return rfc822_unescape(value
)
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)
94 def _read_payload_from_msg(msg
: "Message") -> Optional
[str]:
95 value
= msg
.get_payload().strip()
96 if value
== 'UNKNOWN' or not value
:
101 def read_pkg_file(self
, file):
102 """Reads the metadata values from a file object."""
103 msg
= message_from_file(file)
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')
118 self
.long_description
= _read_field_unescaped_from_msg(msg
, 'description')
119 if self
.long_description
is None and self
.metadata_version
>= version
.Version(
122 self
.long_description
= _read_payload_from_msg(msg
)
123 self
.description
= _read_field_from_msg(msg
, 'summary')
125 if 'keywords' in msg
:
126 self
.keywords
= _read_field_from_msg(msg
, 'keywords').split(',')
128 self
.platforms
= _read_list_from_msg(msg
, 'platform')
129 self
.classifiers
= _read_list_from_msg(msg
, 'classifier')
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')
139 self
.obsoletes
= None
141 self
.license_files
= _read_list_from_msg(msg
, 'license-file')
144 def single_line(val
):
146 Quick and dirty validation for Summary pypa/setuptools#1390.
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]
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()
163 def write_field(key
, value
):
164 file.write("%s: %s\n" % (key
, value
))
166 write_field('Metadata-Version', str(version
))
167 write_field('Name', self
.get_name())
168 write_field('Version', self
.get_version())
170 summary
= self
.get_description()
172 write_field('Summary', single_line(summary
))
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'),
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
)
188 license
= self
.get_license()
190 write_field('License', rfc822_escape(license
))
192 for project_url
in self
.project_urls
.items():
193 write_field('Project-URL', '%s, %s' % project_url
)
195 keywords
= ','.join(self
.get_keywords())
197 write_field('Keywords', keywords
)
199 platforms
= self
.get_platforms() or []
200 for platform
in platforms
:
201 write_field('Platform', platform
)
203 self
._write
_list
(file, 'Classifier', self
.get_classifiers())
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())
210 # Setuptools specific for PEP 345
211 if hasattr(self
, 'python_requires'):
212 write_field('Requires-Python', self
.python_requires
)
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
)
221 self
._write
_list
(file, 'License-File', self
.license_files
or [])
223 long_description
= self
.get_long_description()
225 file.write("\n%s" % long_description
)
226 if not long_description
.endswith("\n"):
230 sequence
= tuple, list
233 def check_importable(dist
, attr
, value
):
235 ep
= metadata
.EntryPoint(value
=value
, name
=None, group
=None)
237 except (TypeError, ValueError, AttributeError, AssertionError) as e
:
238 raise DistutilsSetupError(
239 "%r must be importable 'module:attrs' string (got %r)" % (attr
, value
)
243 def assert_string_list(dist
, attr
, value
):
244 """Verify that value is a string list"""
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
)
257 def check_nsp(dist
, attr
, value
):
258 """Verify that namespace packages are valid"""
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
267 parent
, sep
, child
= nsp
.rpartition('.')
268 if parent
and parent
not in ns_packages
:
270 "WARNING: %r is declared as a package namespace, but %r"
271 " is not: please correct this in setup.py",
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).
286 def check_extras(dist
, attr
, value
):
287 """Verify that extras_require mapping is valid"""
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."
298 def _check_extra(extra
, reqs
):
299 name
, sep
, marker
= extra
.partition(':')
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
))
308 def _check_marker(marker
):
311 m
= packaging
.markers
.Marker(marker
)
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
))
322 def invalid_unless_false(dist
, attr
, value
):
324 DistDeprecationWarning
.emit(f
"{attr} is ignored.")
325 # TODO: should there be a `due_date` here?
327 raise DistutilsSetupError(f
"{attr} is invalid.")
330 def check_requirements(dist
, attr
, value
):
331 """Verify that install_requires is a valid requirements list"""
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
:
338 "{attr!r} must be a string or list of strings "
339 "containing valid project/version requirement specifiers; {error}"
341 raise DistutilsSetupError(tmpl
.format(attr
=attr
, error
=error
)) from error
344 def check_specifier(dist
, attr
, value
):
345 """Verify that value is a valid version specifier"""
347 packaging
.specifiers
.SpecifierSet(value
)
348 except (packaging
.specifiers
.InvalidSpecifier
, AttributeError) as error
:
350 "{attr!r} must be a string " "containing valid version specifiers; {error}"
352 raise DistutilsSetupError(tmpl
.format(attr
=attr
, error
=error
)) from error
355 def check_entry_points(dist
, attr
, value
):
356 """Verify that entry_points map is parseable"""
358 _entry_points
.load(value
)
359 except Exception as e
:
360 raise DistutilsSetupError(e
) from e
363 def check_test_suite(dist
, attr
, value
):
364 if not isinstance(value
, str):
365 raise DistutilsSetupError("test_suite must be a string")
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
)
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
)
380 assert_string_list(dist
, 'values of {!r} dict'.format(attr
), v
)
383 def check_packages(dist
, attr
, value
):
384 for pkgname
in value
:
385 if not re
.match(r
'\w+(\.\w+)*', pkgname
):
387 "WARNING: %r not a valid package name; please use only "
388 ".-separated package names in setup.py",
393 _Distribution
= get_unpatched(distutils
.core
.Distribution
)
396 class Distribution(_Distribution
):
397 """Distribution with support for tests and package data
399 This is an enhanced version of 'distutils.dist.Distribution' that
400 effectively adds the following new optional keyword arguments to 'setup()':
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
412 'extras_require' -- a dictionary mapping names of optional "extras" to the
413 additional requirement(s) that using those extras incurs. For example,
416 extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
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.
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.
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.)
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
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,
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
463 if not attrs
or 'name' not in attrs
or 'version' not in attrs
:
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
472 def __init__(self
, attrs
=None):
473 have_package_data
= hasattr(self
, "package_data")
474 if not have_package_data
:
475 self
.package_data
= {}
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
__(
489 for k
, v
in attrs
.items()
490 if k
not in self
._DISTUTILS
_UNSUPPORTED
_METADATA
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()
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
)
504 self
.set_defaults
= ConfigDiscovery(self
)
506 self
._set
_metadata
_defaults
(attrs
)
508 self
.metadata
.version
= self
._normalize
_version
(
509 self
._validate
_version
(self
.metadata
.version
)
511 self
._finalize
_requires
()
513 def _validate_metadata(self
):
517 for key
in vars(self
.metadata
)
518 if getattr(self
.metadata
, key
, None) is not None
520 missing
= required
- provided
523 msg
= f
"Required package metadata is missing: {missing}"
524 raise DistutilsSetupError(msg
)
526 def _set_metadata_defaults(self
, attrs
):
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
533 for option
, default
in self
._DISTUTILS
_UNSUPPORTED
_METADATA
.items():
534 vars(self
.metadata
).setdefault(option
, attrs
.get(option
, default()))
537 def _normalize_version(version
):
538 if isinstance(version
, setuptools
.sic
) or version
is None:
541 normalized
= str(packaging
.version
.Version(version
))
542 if version
!= normalized
:
543 InformationOnly
.emit(f
"Normalizing '{version}' to '{normalized}'")
548 def _validate_version(version
):
549 if isinstance(version
, numbers
.Number
):
550 # Some people apparently take "version number" too literally :)
551 version
= str(version
)
553 if version
is not None:
555 packaging
.version
.Version(version
)
556 except (packaging
.version
.InvalidVersion
, TypeError):
557 SetuptoolsDeprecationWarning
.emit(
558 f
"Invalid version: {version!r}.",
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.
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.
569 return setuptools
.sic(version
)
572 def _finalize_requires(self
):
574 Set `metadata.python_requires` and fix environment markers
575 in `install_requires` and `extras_require`.
577 if getattr(self
, 'python_requires', None):
578 self
.metadata
.python_requires
= self
.python_requires
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]
589 self
.metadata
.provides_extras
.add(extra
)
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
595 self
._convert
_extras
_requirements
()
596 self
._move
_install
_requirements
_markers
()
598 def _convert_extras_requirements(self
):
600 Convert requirements in `extras_require` of the form
601 `"extra": ["barbazquux; {marker}"]` to
602 `"extra:{marker}": ["barbazquux"]`.
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
)
615 def _suffix_for(req
):
617 For a requirement, return the 'extras_require' suffix for
620 return ':' + str(req
.marker
) if req
.marker
else ''
622 def _move_install_requirements_markers(self
):
624 Move requirements in `install_requires` that are using environment
625 markers `extras_require`.
628 # divide the install_requires into two sets, simple ones still
629 # handled by install_requires and more complex ones handled
632 def is_simple_req(req
):
633 return not req
.marker
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
))
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()
649 def _clean_req(self
, req
):
651 Given a Requirement, remove environment markers and return it.
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 []
661 license_file
: Optional
[str] = self
.metadata
.license_file
662 if license_file
and license_file
not in patterns
:
663 patterns
.append(license_file
)
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*')
671 self
.metadata
.license_files
= list(
672 unique_everseen(self
._expand
_patterns
(patterns
))
676 def _expand_patterns(patterns
):
678 >>> list(Distribution._expand_patterns(['LICENSE']))
680 >>> list(Distribution._expand_patterns(['setup.cfg', 'LIC*']))
681 ['setup.cfg', 'LICENSE']
685 for pattern
in patterns
686 for path
in sorted(iglob(pattern
))
687 if not path
.endswith('~') and os
.path
.isfile(path
)
690 # FIXME: 'Distribution._parse_config_files' is too complex (14)
691 def _parse_config_files(self
, filenames
=None): # noqa: C901
693 Adapted from distutils.dist.Distribution.parse_config_files,
694 this method provides the same functionality in subtly-improved
697 from configparser
import ConfigParser
699 # Ignore install directory options if we have a venv
702 if sys
.prefix
== sys
.base_prefix
720 ignore_options
= frozenset(ignore_options
)
722 if filenames
is None:
723 filenames
= self
.find_config_files()
726 self
.announce("Distribution.parse_config_files():")
728 parser
= ConfigParser()
729 parser
.optionxform
= str
730 for filename
in filenames
:
731 with io
.open(filename
, encoding
='utf-8') as reader
:
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
)
740 if opt
== '__name__' or opt
in ignore_options
:
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
)
748 # Make the ConfigParser forget everything (so we retain
749 # the original filenames that options come from)
752 if 'global' not in self
.command_options
:
755 # If there was a "global" section in the config file, use it
756 # to set Distribution options.
758 for opt
, (src
, val
) in self
.command_options
['global'].items():
759 alias
= self
.negative_opt
.get(opt
)
761 val
= not strtobool(val
)
762 elif opt
in ('verbose', 'dry_run'): # ugh!
766 setattr(self
, alias
or opt
, val
)
767 except ValueError as e
:
768 raise DistutilsOptionError(e
) from e
770 def warn_dash_deprecation(self
, opt
, section
):
772 'options.extras_require',
773 'options.data_files',
777 underscore_opt
= opt
.replace('-', '_')
780 distutils
.command
.__all
__,
781 self
._setuptools
_commands
(),
785 not section
.startswith('options')
786 and section
!= 'metadata'
787 and section
not in commands
789 return underscore_opt
792 SetuptoolsDeprecationWarning
.emit(
793 "Invalid dash-separated options",
795 Usage of dash-separated {opt!r} will not be supported in future
796 versions. Please use the underscore name {underscore_opt!r} instead.
798 see_docs
="userguide/declarative_config.html",
799 due_date
=(2023, 9, 26),
800 # Warning initially introduced in 3 Mar 2021
802 return underscore_opt
804 def _setuptools_commands(self
):
806 return metadata
.distribution('setuptools').entry_points
.names
807 except metadata
.PackageNotFoundError
:
808 # during bootstrapping, distribution doesn't exist
811 def make_option_lowercase(self
, opt
, section
):
812 if section
!= 'metadata' or opt
.islower():
815 lowercase_opt
= opt
.lower()
816 SetuptoolsDeprecationWarning
.emit(
817 "Invalid uppercase configuration",
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.
822 see_docs
="userguide/declarative_config.html",
823 due_date
=(2023, 9, 26),
824 # Warning initially introduced in 6 Mar 2021
828 # FIXME: 'Distribution._set_command_options' is too complex (14)
829 def _set_command_options(self
, command_obj
, option_dict
=None): # noqa: C901
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').
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').
839 (Adopted from distutils.dist.Distribution._set_command_options)
841 command_name
= command_obj
.get_command_name()
842 if option_dict
is None:
843 option_dict
= self
.get_option_dict(command_name
)
846 self
.announce(" setting options for '%s' command:" % command_name
)
847 for option
, (source
, value
) in option_dict
.items():
849 self
.announce(" %s = %s (from %s)" % (option
, value
, source
))
851 bool_opts
= [translate_longopt(o
) for o
in command_obj
.boolean_options
]
852 except AttributeError:
855 neg_opt
= command_obj
.negative_opt
856 except AttributeError:
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
)
868 raise DistutilsOptionError(
869 "error in %s: command '%s' has no such option '%s'"
870 % (source
, command_name
, option
)
872 except ValueError as e
:
873 raise DistutilsOptionError(e
) from e
875 def _get_project_config_files(self
, filenames
):
876 """Add default file and split between INI and TOML"""
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
887 def parse_config_files(self
, filenames
=None, ignore_option_errors
=False):
888 """Parses configuration files from various levels
889 and loads configuration.
891 inifiles
, tomlfiles
= self
._get
_project
_config
_files
(filenames
)
893 self
._parse
_config
_files
(filenames
=inifiles
)
895 setupcfg
.parse_configuration(
896 self
, self
.command_options
, ignore_option_errors
=ignore_option_errors
898 for filename
in tomlfiles
:
899 pyprojecttoml
.apply_configuration(self
, filename
, ignore_option_errors
)
901 self
._finalize
_requires
()
902 self
._finalize
_license
_files
()
904 def fetch_build_eggs(self
, requires
):
905 """Resolve pre-setup requirements"""
906 from setuptools
.installer
import _fetch_build_eggs
908 return _fetch_build_eggs(self
, requires
)
910 def finalize_options(self
):
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.
917 group
= 'setuptools.finalize_distribution_options'
920 return getattr(hook
, 'order', 0)
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
):
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.
940 return ep
.name
in removed
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
)
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
:
956 'This directory contains eggs that were downloaded '
957 'by setuptools to build, test, and run plug-ins.\n\n'
960 'This directory caches those eggs to prevent '
961 'repeated downloads.\n\n'
963 f
.write('However, it is safe to delete this directory.\n\n')
967 def fetch_build_egg(self
, req
):
968 """Fetch an egg needed for building"""
969 from setuptools
.installer
import fetch_build_egg
971 return fetch_build_egg(self
, req
)
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
]
978 eps
= metadata
.entry_points(group
='distutils.commands', name
=command
)
980 self
.cmdclass
[command
] = cmdclass
= ep
.load()
983 return _Distribution
.get_command_class(self
, command
)
985 def print_commands(self
):
986 for ep
in metadata
.entry_points(group
='distutils.commands'):
987 if ep
.name
not in self
.cmdclass
:
989 self
.cmdclass
[ep
.name
] = cmdclass
990 return _Distribution
.print_commands(self
)
992 def get_command_list(self
):
993 for ep
in metadata
.entry_points(group
='distutils.commands'):
994 if ep
.name
not in self
.cmdclass
:
996 self
.cmdclass
[ep
.name
] = cmdclass
997 return _Distribution
.get_command_list(self
)
999 def include(self
, **attrs
):
1000 """Add items to distribution that are named in keyword arguments
1002 For example, 'dist.include(py_modules=["x"])' would add 'x' to
1003 the distribution's 'py_modules' attribute, if it was not already
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.
1014 for k
, v
in attrs
.items():
1015 include
= getattr(self
, '_include_' + k
, None)
1019 self
._include
_misc
(k
, v
)
1021 def exclude_package(self
, package
):
1022 """Remove packages, modules, and extensions in named package"""
1027 p
for p
in self
.packages
if p
!= package
and not p
.startswith(pfx
)
1032 p
for p
in self
.py_modules
if p
!= package
and not p
.startswith(pfx
)
1035 if self
.ext_modules
:
1036 self
.ext_modules
= [
1038 for p
in self
.ext_modules
1039 if p
.name
!= package
and not p
.name
.startswith(pfx
)
1042 def has_contents_for(self
, package
):
1043 """Return true if 'exclude_package(package)' would do something"""
1047 for p
in self
.iter_distribution_names():
1048 if p
== package
or p
.startswith(pfx
):
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
)
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"
1066 setattr(self
, name
, [item
for item
in old
if item
not in value
])
1068 def _include_misc(self
, name
, value
):
1069 """Handle 'include()' for list/tuple attrs without a special handler"""
1071 if not isinstance(value
, sequence
):
1072 raise DistutilsSetupError("%s: setting must be a list (%r)" % (name
, value
))
1074 old
= getattr(self
, name
)
1075 except AttributeError as e
:
1076 raise DistutilsSetupError("%s: No such distribution setting" % name
) from e
1078 setattr(self
, name
, value
)
1079 elif not isinstance(old
, sequence
):
1080 raise DistutilsSetupError(
1081 name
+ ": this setting cannot be changed via include/exclude"
1084 new
= [item
for item
in value
if item
not in old
]
1085 setattr(self
, name
, old
+ new
)
1087 def exclude(self
, **attrs
):
1088 """Remove items from distribution that are named in keyword arguments
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.
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.
1103 for k
, v
in attrs
.items():
1104 exclude
= getattr(self
, '_exclude_' + k
, None)
1108 self
._exclude
_misc
(k
, v
)
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
,)
1115 list(map(self
.exclude_package
, packages
))
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
1122 # First, expand any aliases
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!
1130 args
[:1] = shlex
.split(alias
, True)
1133 nargs
= _Distribution
._parse
_command
_opts
(self
, parser
, args
)
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:
1144 def get_cmdline_options(self
):
1145 """Return a '{cmd: {opt:val}}' map of all command-line options
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'.
1151 Note that options provided by config files are intentionally excluded.
1156 for cmd
, opts
in self
.command_options
.items():
1157 for opt
, (src
, val
) in opts
.items():
1158 if src
!= "command line":
1161 opt
= opt
.replace('_', '-')
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():
1173 raise AssertionError("Shouldn't be able to get here")
1178 d
.setdefault(cmd
, {})[opt
] = val
1182 def iter_distribution_names(self
):
1183 """Yield all packages, modules, and extension names in distribution"""
1185 for pkg
in self
.packages
or ():
1188 for module
in self
.py_modules
or ():
1191 for ext
in self
.ext_modules
or ():
1192 if isinstance(ext
, tuple):
1193 name
, buildinfo
= ext
1196 if name
.endswith('module'):
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
1208 if self
.help_commands
:
1209 return _Distribution
.handle_display_options(self
, option_order
)
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
)
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
)
1220 # Print metadata in UTF-8 no matter the platform
1221 encoding
= sys
.stdout
.encoding
1222 sys
.stdout
.reconfigure(encoding
='utf-8')
1224 return _Distribution
.handle_display_options(self
, option_order
)
1226 sys
.stdout
.reconfigure(encoding
=encoding
)
1228 def run_command(self
, command
):
1230 # Postpone defaults until all explicit configuration is considered
1231 # (setup() args, config files, command line and plugins)
1233 super().run_command(command
)
1236 class DistDeprecationWarning(SetuptoolsDeprecationWarning
):
1237 """Class for warning about deprecations in dist in
1238 setuptools. Not ignored by default, unlike DeprecationWarning."""