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