OLD | NEW |
(Empty) | |
| 1 __all__ = ['Distribution'] |
| 2 |
| 3 import re |
| 4 import os |
| 5 import warnings |
| 6 import numbers |
| 7 import distutils.log |
| 8 import distutils.core |
| 9 import distutils.cmd |
| 10 import distutils.dist |
| 11 from distutils.errors import (DistutilsOptionError, DistutilsPlatformError, |
| 12 DistutilsSetupError) |
| 13 from distutils.util import rfc822_escape |
| 14 |
| 15 import six |
| 16 from six.moves import map |
| 17 import packaging |
| 18 |
| 19 from setuptools.depends import Require |
| 20 from setuptools import windows_support |
| 21 from setuptools.monkey import get_unpatched |
| 22 from setuptools.config import parse_configuration |
| 23 import pkg_resources |
| 24 from .py36compat import Distribution_parse_config_files |
| 25 |
| 26 |
| 27 def _get_unpatched(cls): |
| 28 warnings.warn("Do not call this function", DeprecationWarning) |
| 29 return get_unpatched(cls) |
| 30 |
| 31 |
| 32 # Based on Python 3.5 version |
| 33 def write_pkg_file(self, file): |
| 34 """Write the PKG-INFO format data to a file object. |
| 35 """ |
| 36 version = '1.0' |
| 37 if (self.provides or self.requires or self.obsoletes or |
| 38 self.classifiers or self.download_url): |
| 39 version = '1.1' |
| 40 # Setuptools specific for PEP 345 |
| 41 if hasattr(self, 'python_requires'): |
| 42 version = '1.2' |
| 43 |
| 44 file.write('Metadata-Version: %s\n' % version) |
| 45 file.write('Name: %s\n' % self.get_name()) |
| 46 file.write('Version: %s\n' % self.get_version()) |
| 47 file.write('Summary: %s\n' % self.get_description()) |
| 48 file.write('Home-page: %s\n' % self.get_url()) |
| 49 file.write('Author: %s\n' % self.get_contact()) |
| 50 file.write('Author-email: %s\n' % self.get_contact_email()) |
| 51 file.write('License: %s\n' % self.get_license()) |
| 52 if self.download_url: |
| 53 file.write('Download-URL: %s\n' % self.download_url) |
| 54 |
| 55 long_desc = rfc822_escape(self.get_long_description()) |
| 56 file.write('Description: %s\n' % long_desc) |
| 57 |
| 58 keywords = ','.join(self.get_keywords()) |
| 59 if keywords: |
| 60 file.write('Keywords: %s\n' % keywords) |
| 61 |
| 62 self._write_list(file, 'Platform', self.get_platforms()) |
| 63 self._write_list(file, 'Classifier', self.get_classifiers()) |
| 64 |
| 65 # PEP 314 |
| 66 self._write_list(file, 'Requires', self.get_requires()) |
| 67 self._write_list(file, 'Provides', self.get_provides()) |
| 68 self._write_list(file, 'Obsoletes', self.get_obsoletes()) |
| 69 |
| 70 # Setuptools specific for PEP 345 |
| 71 if hasattr(self, 'python_requires'): |
| 72 file.write('Requires-Python: %s\n' % self.python_requires) |
| 73 |
| 74 |
| 75 # from Python 3.4 |
| 76 def write_pkg_info(self, base_dir): |
| 77 """Write the PKG-INFO file into the release tree. |
| 78 """ |
| 79 with open(os.path.join(base_dir, 'PKG-INFO'), 'w', |
| 80 encoding='UTF-8') as pkg_info: |
| 81 self.write_pkg_file(pkg_info) |
| 82 |
| 83 |
| 84 sequence = tuple, list |
| 85 |
| 86 |
| 87 def check_importable(dist, attr, value): |
| 88 try: |
| 89 ep = pkg_resources.EntryPoint.parse('x=' + value) |
| 90 assert not ep.extras |
| 91 except (TypeError, ValueError, AttributeError, AssertionError): |
| 92 raise DistutilsSetupError( |
| 93 "%r must be importable 'module:attrs' string (got %r)" |
| 94 % (attr, value) |
| 95 ) |
| 96 |
| 97 |
| 98 def assert_string_list(dist, attr, value): |
| 99 """Verify that value is a string list or None""" |
| 100 try: |
| 101 assert ''.join(value) != value |
| 102 except (TypeError, ValueError, AttributeError, AssertionError): |
| 103 raise DistutilsSetupError( |
| 104 "%r must be a list of strings (got %r)" % (attr, value) |
| 105 ) |
| 106 |
| 107 |
| 108 def check_nsp(dist, attr, value): |
| 109 """Verify that namespace packages are valid""" |
| 110 ns_packages = value |
| 111 assert_string_list(dist, attr, ns_packages) |
| 112 for nsp in ns_packages: |
| 113 if not dist.has_contents_for(nsp): |
| 114 raise DistutilsSetupError( |
| 115 "Distribution contains no modules or packages for " + |
| 116 "namespace package %r" % nsp |
| 117 ) |
| 118 parent, sep, child = nsp.rpartition('.') |
| 119 if parent and parent not in ns_packages: |
| 120 distutils.log.warn( |
| 121 "WARNING: %r is declared as a package namespace, but %r" |
| 122 " is not: please correct this in setup.py", nsp, parent |
| 123 ) |
| 124 |
| 125 |
| 126 def check_extras(dist, attr, value): |
| 127 """Verify that extras_require mapping is valid""" |
| 128 try: |
| 129 for k, v in value.items(): |
| 130 if ':' in k: |
| 131 k, m = k.split(':', 1) |
| 132 if pkg_resources.invalid_marker(m): |
| 133 raise DistutilsSetupError("Invalid environment marker: " + m
) |
| 134 list(pkg_resources.parse_requirements(v)) |
| 135 except (TypeError, ValueError, AttributeError): |
| 136 raise DistutilsSetupError( |
| 137 "'extras_require' must be a dictionary whose values are " |
| 138 "strings or lists of strings containing valid project/version " |
| 139 "requirement specifiers." |
| 140 ) |
| 141 |
| 142 |
| 143 def assert_bool(dist, attr, value): |
| 144 """Verify that value is True, False, 0, or 1""" |
| 145 if bool(value) != value: |
| 146 tmpl = "{attr!r} must be a boolean value (got {value!r})" |
| 147 raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) |
| 148 |
| 149 |
| 150 def check_requirements(dist, attr, value): |
| 151 """Verify that install_requires is a valid requirements list""" |
| 152 try: |
| 153 list(pkg_resources.parse_requirements(value)) |
| 154 except (TypeError, ValueError) as error: |
| 155 tmpl = ( |
| 156 "{attr!r} must be a string or list of strings " |
| 157 "containing valid project/version requirement specifiers; {error}" |
| 158 ) |
| 159 raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) |
| 160 |
| 161 |
| 162 def check_specifier(dist, attr, value): |
| 163 """Verify that value is a valid version specifier""" |
| 164 try: |
| 165 packaging.specifiers.SpecifierSet(value) |
| 166 except packaging.specifiers.InvalidSpecifier as error: |
| 167 tmpl = ( |
| 168 "{attr!r} must be a string or list of strings " |
| 169 "containing valid version specifiers; {error}" |
| 170 ) |
| 171 raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) |
| 172 |
| 173 |
| 174 def check_entry_points(dist, attr, value): |
| 175 """Verify that entry_points map is parseable""" |
| 176 try: |
| 177 pkg_resources.EntryPoint.parse_map(value) |
| 178 except ValueError as e: |
| 179 raise DistutilsSetupError(e) |
| 180 |
| 181 |
| 182 def check_test_suite(dist, attr, value): |
| 183 if not isinstance(value, six.string_types): |
| 184 raise DistutilsSetupError("test_suite must be a string") |
| 185 |
| 186 |
| 187 def check_package_data(dist, attr, value): |
| 188 """Verify that value is a dictionary of package names to glob lists""" |
| 189 if isinstance(value, dict): |
| 190 for k, v in value.items(): |
| 191 if not isinstance(k, str): |
| 192 break |
| 193 try: |
| 194 iter(v) |
| 195 except TypeError: |
| 196 break |
| 197 else: |
| 198 return |
| 199 raise DistutilsSetupError( |
| 200 attr + " must be a dictionary mapping package names to lists of " |
| 201 "wildcard patterns" |
| 202 ) |
| 203 |
| 204 |
| 205 def check_packages(dist, attr, value): |
| 206 for pkgname in value: |
| 207 if not re.match(r'\w+(\.\w+)*', pkgname): |
| 208 distutils.log.warn( |
| 209 "WARNING: %r not a valid package name; please use only " |
| 210 ".-separated package names in setup.py", pkgname |
| 211 ) |
| 212 |
| 213 |
| 214 _Distribution = get_unpatched(distutils.core.Distribution) |
| 215 |
| 216 |
| 217 class Distribution(Distribution_parse_config_files, _Distribution): |
| 218 """Distribution with support for features, tests, and package data |
| 219 |
| 220 This is an enhanced version of 'distutils.dist.Distribution' that |
| 221 effectively adds the following new optional keyword arguments to 'setup()': |
| 222 |
| 223 'install_requires' -- a string or sequence of strings specifying project |
| 224 versions that the distribution requires when installed, in the format |
| 225 used by 'pkg_resources.require()'. They will be installed |
| 226 automatically when the package is installed. If you wish to use |
| 227 packages that are not available in PyPI, or want to give your users an |
| 228 alternate download location, you can add a 'find_links' option to the |
| 229 '[easy_install]' section of your project's 'setup.cfg' file, and then |
| 230 setuptools will scan the listed web pages for links that satisfy the |
| 231 requirements. |
| 232 |
| 233 'extras_require' -- a dictionary mapping names of optional "extras" to the |
| 234 additional requirement(s) that using those extras incurs. For example, |
| 235 this:: |
| 236 |
| 237 extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) |
| 238 |
| 239 indicates that the distribution can optionally provide an extra |
| 240 capability called "reST", but it can only be used if docutils and |
| 241 reSTedit are installed. If the user installs your package using |
| 242 EasyInstall and requests one of your extras, the corresponding |
| 243 additional requirements will be installed if needed. |
| 244 |
| 245 'features' **deprecated** -- a dictionary mapping option names to |
| 246 'setuptools.Feature' |
| 247 objects. Features are a portion of the distribution that can be |
| 248 included or excluded based on user options, inter-feature dependencies, |
| 249 and availability on the current system. Excluded features are omitted |
| 250 from all setup commands, including source and binary distributions, so |
| 251 you can create multiple distributions from the same source tree. |
| 252 Feature names should be valid Python identifiers, except that they may |
| 253 contain the '-' (minus) sign. Features can be included or excluded |
| 254 via the command line options '--with-X' and '--without-X', where 'X' is |
| 255 the name of the feature. Whether a feature is included by default, and |
| 256 whether you are allowed to control this from the command line, is |
| 257 determined by the Feature object. See the 'Feature' class for more |
| 258 information. |
| 259 |
| 260 'test_suite' -- the name of a test suite to run for the 'test' command. |
| 261 If the user runs 'python setup.py test', the package will be installed, |
| 262 and the named test suite will be run. The format is the same as |
| 263 would be used on a 'unittest.py' command line. That is, it is the |
| 264 dotted name of an object to import and call to generate a test suite. |
| 265 |
| 266 'package_data' -- a dictionary mapping package names to lists of filenames |
| 267 or globs to use to find data files contained in the named packages. |
| 268 If the dictionary has filenames or globs listed under '""' (the empty |
| 269 string), those names will be searched for in every package, in addition |
| 270 to any names for the specific package. Data files found using these |
| 271 names/globs will be installed along with the package, in the same |
| 272 location as the package. Note that globs are allowed to reference |
| 273 the contents of non-package subdirectories, as long as you use '/' as |
| 274 a path separator. (Globs are automatically converted to |
| 275 platform-specific paths at runtime.) |
| 276 |
| 277 In addition to these new keywords, this class also has several new methods |
| 278 for manipulating the distribution's contents. For example, the 'include()' |
| 279 and 'exclude()' methods can be thought of as in-place add and subtract |
| 280 commands that add or remove packages, modules, extensions, and so on from |
| 281 the distribution. They are used by the feature subsystem to configure the |
| 282 distribution for the included and excluded features. |
| 283 """ |
| 284 |
| 285 _patched_dist = None |
| 286 |
| 287 def patch_missing_pkg_info(self, attrs): |
| 288 # Fake up a replacement for the data that would normally come from |
| 289 # PKG-INFO, but which might not yet be built if this is a fresh |
| 290 # checkout. |
| 291 # |
| 292 if not attrs or 'name' not in attrs or 'version' not in attrs: |
| 293 return |
| 294 key = pkg_resources.safe_name(str(attrs['name'])).lower() |
| 295 dist = pkg_resources.working_set.by_key.get(key) |
| 296 if dist is not None and not dist.has_metadata('PKG-INFO'): |
| 297 dist._version = pkg_resources.safe_version(str(attrs['version'])) |
| 298 self._patched_dist = dist |
| 299 |
| 300 def __init__(self, attrs=None): |
| 301 have_package_data = hasattr(self, "package_data") |
| 302 if not have_package_data: |
| 303 self.package_data = {} |
| 304 _attrs_dict = attrs or {} |
| 305 if 'features' in _attrs_dict or 'require_features' in _attrs_dict: |
| 306 Feature.warn_deprecated() |
| 307 self.require_features = [] |
| 308 self.features = {} |
| 309 self.dist_files = [] |
| 310 self.src_root = attrs and attrs.pop("src_root", None) |
| 311 self.patch_missing_pkg_info(attrs) |
| 312 # Make sure we have any eggs needed to interpret 'attrs' |
| 313 if attrs is not None: |
| 314 self.dependency_links = attrs.pop('dependency_links', []) |
| 315 assert_string_list(self, 'dependency_links', self.dependency_links) |
| 316 if attrs and 'setup_requires' in attrs: |
| 317 self.fetch_build_eggs(attrs['setup_requires']) |
| 318 for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): |
| 319 vars(self).setdefault(ep.name, None) |
| 320 _Distribution.__init__(self, attrs) |
| 321 if isinstance(self.metadata.version, numbers.Number): |
| 322 # Some people apparently take "version number" too literally :) |
| 323 self.metadata.version = str(self.metadata.version) |
| 324 |
| 325 if self.metadata.version is not None: |
| 326 try: |
| 327 ver = packaging.version.Version(self.metadata.version) |
| 328 normalized_version = str(ver) |
| 329 if self.metadata.version != normalized_version: |
| 330 warnings.warn( |
| 331 "Normalizing '%s' to '%s'" % ( |
| 332 self.metadata.version, |
| 333 normalized_version, |
| 334 ) |
| 335 ) |
| 336 self.metadata.version = normalized_version |
| 337 except (packaging.version.InvalidVersion, TypeError): |
| 338 warnings.warn( |
| 339 "The version specified (%r) is an invalid version, this " |
| 340 "may not work as expected with newer versions of " |
| 341 "setuptools, pip, and PyPI. Please see PEP 440 for more " |
| 342 "details." % self.metadata.version |
| 343 ) |
| 344 if getattr(self, 'python_requires', None): |
| 345 self.metadata.python_requires = self.python_requires |
| 346 |
| 347 def parse_config_files(self, filenames=None): |
| 348 """Parses configuration files from various levels |
| 349 and loads configuration. |
| 350 |
| 351 """ |
| 352 _Distribution.parse_config_files(self, filenames=filenames) |
| 353 |
| 354 parse_configuration(self, self.command_options) |
| 355 |
| 356 def parse_command_line(self): |
| 357 """Process features after parsing command line options""" |
| 358 result = _Distribution.parse_command_line(self) |
| 359 if self.features: |
| 360 self._finalize_features() |
| 361 return result |
| 362 |
| 363 def _feature_attrname(self, name): |
| 364 """Convert feature name to corresponding option attribute name""" |
| 365 return 'with_' + name.replace('-', '_') |
| 366 |
| 367 def fetch_build_eggs(self, requires): |
| 368 """Resolve pre-setup requirements""" |
| 369 resolved_dists = pkg_resources.working_set.resolve( |
| 370 pkg_resources.parse_requirements(requires), |
| 371 installer=self.fetch_build_egg, |
| 372 replace_conflicting=True, |
| 373 ) |
| 374 for dist in resolved_dists: |
| 375 pkg_resources.working_set.add(dist, replace=True) |
| 376 return resolved_dists |
| 377 |
| 378 def finalize_options(self): |
| 379 _Distribution.finalize_options(self) |
| 380 if self.features: |
| 381 self._set_global_opts_from_features() |
| 382 |
| 383 for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): |
| 384 value = getattr(self, ep.name, None) |
| 385 if value is not None: |
| 386 ep.require(installer=self.fetch_build_egg) |
| 387 ep.load()(self, ep.name, value) |
| 388 if getattr(self, 'convert_2to3_doctests', None): |
| 389 # XXX may convert to set here when we can rely on set being builtin |
| 390 self.convert_2to3_doctests = [os.path.abspath(p) for p in self.conve
rt_2to3_doctests] |
| 391 else: |
| 392 self.convert_2to3_doctests = [] |
| 393 |
| 394 def get_egg_cache_dir(self): |
| 395 egg_cache_dir = os.path.join(os.curdir, '.eggs') |
| 396 if not os.path.exists(egg_cache_dir): |
| 397 os.mkdir(egg_cache_dir) |
| 398 windows_support.hide_file(egg_cache_dir) |
| 399 readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') |
| 400 with open(readme_txt_filename, 'w') as f: |
| 401 f.write('This directory contains eggs that were downloaded ' |
| 402 'by setuptools to build, test, and run plug-ins.\n\n') |
| 403 f.write('This directory caches those eggs to prevent ' |
| 404 'repeated downloads.\n\n') |
| 405 f.write('However, it is safe to delete this directory.\n\n') |
| 406 |
| 407 return egg_cache_dir |
| 408 |
| 409 def fetch_build_egg(self, req): |
| 410 """Fetch an egg needed for building""" |
| 411 |
| 412 try: |
| 413 cmd = self._egg_fetcher |
| 414 cmd.package_index.to_scan = [] |
| 415 except AttributeError: |
| 416 from setuptools.command.easy_install import easy_install |
| 417 dist = self.__class__({'script_args': ['easy_install']}) |
| 418 dist.parse_config_files() |
| 419 opts = dist.get_option_dict('easy_install') |
| 420 keep = ( |
| 421 'find_links', 'site_dirs', 'index_url', 'optimize', |
| 422 'site_dirs', 'allow_hosts' |
| 423 ) |
| 424 for key in list(opts): |
| 425 if key not in keep: |
| 426 del opts[key] # don't use any other settings |
| 427 if self.dependency_links: |
| 428 links = self.dependency_links[:] |
| 429 if 'find_links' in opts: |
| 430 links = opts['find_links'][1].split() + links |
| 431 opts['find_links'] = ('setup', links) |
| 432 install_dir = self.get_egg_cache_dir() |
| 433 cmd = easy_install( |
| 434 dist, args=["x"], install_dir=install_dir, exclude_scripts=True, |
| 435 always_copy=False, build_directory=None, editable=False, |
| 436 upgrade=False, multi_version=True, no_report=True, user=False |
| 437 ) |
| 438 cmd.ensure_finalized() |
| 439 self._egg_fetcher = cmd |
| 440 return cmd.easy_install(req) |
| 441 |
| 442 def _set_global_opts_from_features(self): |
| 443 """Add --with-X/--without-X options based on optional features""" |
| 444 |
| 445 go = [] |
| 446 no = self.negative_opt.copy() |
| 447 |
| 448 for name, feature in self.features.items(): |
| 449 self._set_feature(name, None) |
| 450 feature.validate(self) |
| 451 |
| 452 if feature.optional: |
| 453 descr = feature.description |
| 454 incdef = ' (default)' |
| 455 excdef = '' |
| 456 if not feature.include_by_default(): |
| 457 excdef, incdef = incdef, excdef |
| 458 |
| 459 go.append(('with-' + name, None, 'include ' + descr + incdef)) |
| 460 go.append(('without-' + name, None, 'exclude ' + descr + excdef)
) |
| 461 no['without-' + name] = 'with-' + name |
| 462 |
| 463 self.global_options = self.feature_options = go + self.global_options |
| 464 self.negative_opt = self.feature_negopt = no |
| 465 |
| 466 def _finalize_features(self): |
| 467 """Add/remove features and resolve dependencies between them""" |
| 468 |
| 469 # First, flag all the enabled items (and thus their dependencies) |
| 470 for name, feature in self.features.items(): |
| 471 enabled = self.feature_is_included(name) |
| 472 if enabled or (enabled is None and feature.include_by_default()): |
| 473 feature.include_in(self) |
| 474 self._set_feature(name, 1) |
| 475 |
| 476 # Then disable the rest, so that off-by-default features don't |
| 477 # get flagged as errors when they're required by an enabled feature |
| 478 for name, feature in self.features.items(): |
| 479 if not self.feature_is_included(name): |
| 480 feature.exclude_from(self) |
| 481 self._set_feature(name, 0) |
| 482 |
| 483 def get_command_class(self, command): |
| 484 """Pluggable version of get_command_class()""" |
| 485 if command in self.cmdclass: |
| 486 return self.cmdclass[command] |
| 487 |
| 488 for ep in pkg_resources.iter_entry_points('distutils.commands', command)
: |
| 489 ep.require(installer=self.fetch_build_egg) |
| 490 self.cmdclass[command] = cmdclass = ep.load() |
| 491 return cmdclass |
| 492 else: |
| 493 return _Distribution.get_command_class(self, command) |
| 494 |
| 495 def print_commands(self): |
| 496 for ep in pkg_resources.iter_entry_points('distutils.commands'): |
| 497 if ep.name not in self.cmdclass: |
| 498 # don't require extras as the commands won't be invoked |
| 499 cmdclass = ep.resolve() |
| 500 self.cmdclass[ep.name] = cmdclass |
| 501 return _Distribution.print_commands(self) |
| 502 |
| 503 def get_command_list(self): |
| 504 for ep in pkg_resources.iter_entry_points('distutils.commands'): |
| 505 if ep.name not in self.cmdclass: |
| 506 # don't require extras as the commands won't be invoked |
| 507 cmdclass = ep.resolve() |
| 508 self.cmdclass[ep.name] = cmdclass |
| 509 return _Distribution.get_command_list(self) |
| 510 |
| 511 def _set_feature(self, name, status): |
| 512 """Set feature's inclusion status""" |
| 513 setattr(self, self._feature_attrname(name), status) |
| 514 |
| 515 def feature_is_included(self, name): |
| 516 """Return 1 if feature is included, 0 if excluded, 'None' if unknown""" |
| 517 return getattr(self, self._feature_attrname(name)) |
| 518 |
| 519 def include_feature(self, name): |
| 520 """Request inclusion of feature named 'name'""" |
| 521 |
| 522 if self.feature_is_included(name) == 0: |
| 523 descr = self.features[name].description |
| 524 raise DistutilsOptionError( |
| 525 descr + " is required, but was excluded or is not available" |
| 526 ) |
| 527 self.features[name].include_in(self) |
| 528 self._set_feature(name, 1) |
| 529 |
| 530 def include(self, **attrs): |
| 531 """Add items to distribution that are named in keyword arguments |
| 532 |
| 533 For example, 'dist.exclude(py_modules=["x"])' would add 'x' to |
| 534 the distribution's 'py_modules' attribute, if it was not already |
| 535 there. |
| 536 |
| 537 Currently, this method only supports inclusion for attributes that are |
| 538 lists or tuples. If you need to add support for adding to other |
| 539 attributes in this or a subclass, you can add an '_include_X' method, |
| 540 where 'X' is the name of the attribute. The method will be called with |
| 541 the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' |
| 542 will try to call 'dist._include_foo({"bar":"baz"})', which can then |
| 543 handle whatever special inclusion logic is needed. |
| 544 """ |
| 545 for k, v in attrs.items(): |
| 546 include = getattr(self, '_include_' + k, None) |
| 547 if include: |
| 548 include(v) |
| 549 else: |
| 550 self._include_misc(k, v) |
| 551 |
| 552 def exclude_package(self, package): |
| 553 """Remove packages, modules, and extensions in named package""" |
| 554 |
| 555 pfx = package + '.' |
| 556 if self.packages: |
| 557 self.packages = [ |
| 558 p for p in self.packages |
| 559 if p != package and not p.startswith(pfx) |
| 560 ] |
| 561 |
| 562 if self.py_modules: |
| 563 self.py_modules = [ |
| 564 p for p in self.py_modules |
| 565 if p != package and not p.startswith(pfx) |
| 566 ] |
| 567 |
| 568 if self.ext_modules: |
| 569 self.ext_modules = [ |
| 570 p for p in self.ext_modules |
| 571 if p.name != package and not p.name.startswith(pfx) |
| 572 ] |
| 573 |
| 574 def has_contents_for(self, package): |
| 575 """Return true if 'exclude_package(package)' would do something""" |
| 576 |
| 577 pfx = package + '.' |
| 578 |
| 579 for p in self.iter_distribution_names(): |
| 580 if p == package or p.startswith(pfx): |
| 581 return True |
| 582 |
| 583 def _exclude_misc(self, name, value): |
| 584 """Handle 'exclude()' for list/tuple attrs without a special handler""" |
| 585 if not isinstance(value, sequence): |
| 586 raise DistutilsSetupError( |
| 587 "%s: setting must be a list or tuple (%r)" % (name, value) |
| 588 ) |
| 589 try: |
| 590 old = getattr(self, name) |
| 591 except AttributeError: |
| 592 raise DistutilsSetupError( |
| 593 "%s: No such distribution setting" % name |
| 594 ) |
| 595 if old is not None and not isinstance(old, sequence): |
| 596 raise DistutilsSetupError( |
| 597 name + ": this setting cannot be changed via include/exclude" |
| 598 ) |
| 599 elif old: |
| 600 setattr(self, name, [item for item in old if item not in value]) |
| 601 |
| 602 def _include_misc(self, name, value): |
| 603 """Handle 'include()' for list/tuple attrs without a special handler""" |
| 604 |
| 605 if not isinstance(value, sequence): |
| 606 raise DistutilsSetupError( |
| 607 "%s: setting must be a list (%r)" % (name, value) |
| 608 ) |
| 609 try: |
| 610 old = getattr(self, name) |
| 611 except AttributeError: |
| 612 raise DistutilsSetupError( |
| 613 "%s: No such distribution setting" % name |
| 614 ) |
| 615 if old is None: |
| 616 setattr(self, name, value) |
| 617 elif not isinstance(old, sequence): |
| 618 raise DistutilsSetupError( |
| 619 name + ": this setting cannot be changed via include/exclude" |
| 620 ) |
| 621 else: |
| 622 setattr(self, name, old + [item for item in value if item not in old
]) |
| 623 |
| 624 def exclude(self, **attrs): |
| 625 """Remove items from distribution that are named in keyword arguments |
| 626 |
| 627 For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from |
| 628 the distribution's 'py_modules' attribute. Excluding packages uses |
| 629 the 'exclude_package()' method, so all of the package's contained |
| 630 packages, modules, and extensions are also excluded. |
| 631 |
| 632 Currently, this method only supports exclusion from attributes that are |
| 633 lists or tuples. If you need to add support for excluding from other |
| 634 attributes in this or a subclass, you can add an '_exclude_X' method, |
| 635 where 'X' is the name of the attribute. The method will be called with |
| 636 the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' |
| 637 will try to call 'dist._exclude_foo({"bar":"baz"})', which can then |
| 638 handle whatever special exclusion logic is needed. |
| 639 """ |
| 640 for k, v in attrs.items(): |
| 641 exclude = getattr(self, '_exclude_' + k, None) |
| 642 if exclude: |
| 643 exclude(v) |
| 644 else: |
| 645 self._exclude_misc(k, v) |
| 646 |
| 647 def _exclude_packages(self, packages): |
| 648 if not isinstance(packages, sequence): |
| 649 raise DistutilsSetupError( |
| 650 "packages: setting must be a list or tuple (%r)" % (packages,) |
| 651 ) |
| 652 list(map(self.exclude_package, packages)) |
| 653 |
| 654 def _parse_command_opts(self, parser, args): |
| 655 # Remove --with-X/--without-X options when processing command args |
| 656 self.global_options = self.__class__.global_options |
| 657 self.negative_opt = self.__class__.negative_opt |
| 658 |
| 659 # First, expand any aliases |
| 660 command = args[0] |
| 661 aliases = self.get_option_dict('aliases') |
| 662 while command in aliases: |
| 663 src, alias = aliases[command] |
| 664 del aliases[command] # ensure each alias can expand only once! |
| 665 import shlex |
| 666 args[:1] = shlex.split(alias, True) |
| 667 command = args[0] |
| 668 |
| 669 nargs = _Distribution._parse_command_opts(self, parser, args) |
| 670 |
| 671 # Handle commands that want to consume all remaining arguments |
| 672 cmd_class = self.get_command_class(command) |
| 673 if getattr(cmd_class, 'command_consumes_arguments', None): |
| 674 self.get_option_dict(command)['args'] = ("command line", nargs) |
| 675 if nargs is not None: |
| 676 return [] |
| 677 |
| 678 return nargs |
| 679 |
| 680 def get_cmdline_options(self): |
| 681 """Return a '{cmd: {opt:val}}' map of all command-line options |
| 682 |
| 683 Option names are all long, but do not include the leading '--', and |
| 684 contain dashes rather than underscores. If the option doesn't take |
| 685 an argument (e.g. '--quiet'), the 'val' is 'None'. |
| 686 |
| 687 Note that options provided by config files are intentionally excluded. |
| 688 """ |
| 689 |
| 690 d = {} |
| 691 |
| 692 for cmd, opts in self.command_options.items(): |
| 693 |
| 694 for opt, (src, val) in opts.items(): |
| 695 |
| 696 if src != "command line": |
| 697 continue |
| 698 |
| 699 opt = opt.replace('_', '-') |
| 700 |
| 701 if val == 0: |
| 702 cmdobj = self.get_command_obj(cmd) |
| 703 neg_opt = self.negative_opt.copy() |
| 704 neg_opt.update(getattr(cmdobj, 'negative_opt', {})) |
| 705 for neg, pos in neg_opt.items(): |
| 706 if pos == opt: |
| 707 opt = neg |
| 708 val = None |
| 709 break |
| 710 else: |
| 711 raise AssertionError("Shouldn't be able to get here") |
| 712 |
| 713 elif val == 1: |
| 714 val = None |
| 715 |
| 716 d.setdefault(cmd, {})[opt] = val |
| 717 |
| 718 return d |
| 719 |
| 720 def iter_distribution_names(self): |
| 721 """Yield all packages, modules, and extension names in distribution""" |
| 722 |
| 723 for pkg in self.packages or (): |
| 724 yield pkg |
| 725 |
| 726 for module in self.py_modules or (): |
| 727 yield module |
| 728 |
| 729 for ext in self.ext_modules or (): |
| 730 if isinstance(ext, tuple): |
| 731 name, buildinfo = ext |
| 732 else: |
| 733 name = ext.name |
| 734 if name.endswith('module'): |
| 735 name = name[:-6] |
| 736 yield name |
| 737 |
| 738 def handle_display_options(self, option_order): |
| 739 """If there were any non-global "display-only" options |
| 740 (--help-commands or the metadata display options) on the command |
| 741 line, display the requested info and return true; else return |
| 742 false. |
| 743 """ |
| 744 import sys |
| 745 |
| 746 if six.PY2 or self.help_commands: |
| 747 return _Distribution.handle_display_options(self, option_order) |
| 748 |
| 749 # Stdout may be StringIO (e.g. in tests) |
| 750 import io |
| 751 if not isinstance(sys.stdout, io.TextIOWrapper): |
| 752 return _Distribution.handle_display_options(self, option_order) |
| 753 |
| 754 # Don't wrap stdout if utf-8 is already the encoding. Provides |
| 755 # workaround for #334. |
| 756 if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): |
| 757 return _Distribution.handle_display_options(self, option_order) |
| 758 |
| 759 # Print metadata in UTF-8 no matter the platform |
| 760 encoding = sys.stdout.encoding |
| 761 errors = sys.stdout.errors |
| 762 newline = sys.platform != 'win32' and '\n' or None |
| 763 line_buffering = sys.stdout.line_buffering |
| 764 |
| 765 sys.stdout = io.TextIOWrapper( |
| 766 sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) |
| 767 try: |
| 768 return _Distribution.handle_display_options(self, option_order) |
| 769 finally: |
| 770 sys.stdout = io.TextIOWrapper( |
| 771 sys.stdout.detach(), encoding, errors, newline, line_buffering) |
| 772 |
| 773 |
| 774 class Feature: |
| 775 """ |
| 776 **deprecated** -- The `Feature` facility was never completely implemented |
| 777 or supported, `has reported issues |
| 778 <https://github.com/pypa/setuptools/issues/58>`_ and will be removed in |
| 779 a future version. |
| 780 |
| 781 A subset of the distribution that can be excluded if unneeded/wanted |
| 782 |
| 783 Features are created using these keyword arguments: |
| 784 |
| 785 'description' -- a short, human readable description of the feature, to |
| 786 be used in error messages, and option help messages. |
| 787 |
| 788 'standard' -- if true, the feature is included by default if it is |
| 789 available on the current system. Otherwise, the feature is only |
| 790 included if requested via a command line '--with-X' option, or if |
| 791 another included feature requires it. The default setting is 'False'. |
| 792 |
| 793 'available' -- if true, the feature is available for installation on the |
| 794 current system. The default setting is 'True'. |
| 795 |
| 796 'optional' -- if true, the feature's inclusion can be controlled from the |
| 797 command line, using the '--with-X' or '--without-X' options. If |
| 798 false, the feature's inclusion status is determined automatically, |
| 799 based on 'availabile', 'standard', and whether any other feature |
| 800 requires it. The default setting is 'True'. |
| 801 |
| 802 'require_features' -- a string or sequence of strings naming features |
| 803 that should also be included if this feature is included. Defaults to |
| 804 empty list. May also contain 'Require' objects that should be |
| 805 added/removed from the distribution. |
| 806 |
| 807 'remove' -- a string or list of strings naming packages to be removed |
| 808 from the distribution if this feature is *not* included. If the |
| 809 feature *is* included, this argument is ignored. This argument exists |
| 810 to support removing features that "crosscut" a distribution, such as |
| 811 defining a 'tests' feature that removes all the 'tests' subpackages |
| 812 provided by other features. The default for this argument is an empty |
| 813 list. (Note: the named package(s) or modules must exist in the base |
| 814 distribution when the 'setup()' function is initially called.) |
| 815 |
| 816 other keywords -- any other keyword arguments are saved, and passed to |
| 817 the distribution's 'include()' and 'exclude()' methods when the |
| 818 feature is included or excluded, respectively. So, for example, you |
| 819 could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be |
| 820 added or removed from the distribution as appropriate. |
| 821 |
| 822 A feature must include at least one 'requires', 'remove', or other |
| 823 keyword argument. Otherwise, it can't affect the distribution in any way. |
| 824 Note also that you can subclass 'Feature' to create your own specialized |
| 825 feature types that modify the distribution in other ways when included or |
| 826 excluded. See the docstrings for the various methods here for more detail. |
| 827 Aside from the methods, the only feature attributes that distributions look |
| 828 at are 'description' and 'optional'. |
| 829 """ |
| 830 |
| 831 @staticmethod |
| 832 def warn_deprecated(): |
| 833 warnings.warn( |
| 834 "Features are deprecated and will be removed in a future " |
| 835 "version. See https://github.com/pypa/setuptools/issues/65.", |
| 836 DeprecationWarning, |
| 837 stacklevel=3, |
| 838 ) |
| 839 |
| 840 def __init__(self, description, standard=False, available=True, |
| 841 optional=True, require_features=(), remove=(), **extras): |
| 842 self.warn_deprecated() |
| 843 |
| 844 self.description = description |
| 845 self.standard = standard |
| 846 self.available = available |
| 847 self.optional = optional |
| 848 if isinstance(require_features, (str, Require)): |
| 849 require_features = require_features, |
| 850 |
| 851 self.require_features = [ |
| 852 r for r in require_features if isinstance(r, str) |
| 853 ] |
| 854 er = [r for r in require_features if not isinstance(r, str)] |
| 855 if er: |
| 856 extras['require_features'] = er |
| 857 |
| 858 if isinstance(remove, str): |
| 859 remove = remove, |
| 860 self.remove = remove |
| 861 self.extras = extras |
| 862 |
| 863 if not remove and not require_features and not extras: |
| 864 raise DistutilsSetupError( |
| 865 "Feature %s: must define 'require_features', 'remove', or at lea
st one" |
| 866 " of 'packages', 'py_modules', etc." |
| 867 ) |
| 868 |
| 869 def include_by_default(self): |
| 870 """Should this feature be included by default?""" |
| 871 return self.available and self.standard |
| 872 |
| 873 def include_in(self, dist): |
| 874 """Ensure feature and its requirements are included in distribution |
| 875 |
| 876 You may override this in a subclass to perform additional operations on |
| 877 the distribution. Note that this method may be called more than once |
| 878 per feature, and so should be idempotent. |
| 879 |
| 880 """ |
| 881 |
| 882 if not self.available: |
| 883 raise DistutilsPlatformError( |
| 884 self.description + " is required, " |
| 885 "but is not available on this platform" |
| 886 ) |
| 887 |
| 888 dist.include(**self.extras) |
| 889 |
| 890 for f in self.require_features: |
| 891 dist.include_feature(f) |
| 892 |
| 893 def exclude_from(self, dist): |
| 894 """Ensure feature is excluded from distribution |
| 895 |
| 896 You may override this in a subclass to perform additional operations on |
| 897 the distribution. This method will be called at most once per |
| 898 feature, and only after all included features have been asked to |
| 899 include themselves. |
| 900 """ |
| 901 |
| 902 dist.exclude(**self.extras) |
| 903 |
| 904 if self.remove: |
| 905 for item in self.remove: |
| 906 dist.exclude_package(item) |
| 907 |
| 908 def validate(self, dist): |
| 909 """Verify that feature makes sense in context of distribution |
| 910 |
| 911 This method is called by the distribution just before it parses its |
| 912 command line. It checks to ensure that the 'remove' attribute, if any, |
| 913 contains only valid package/module names that are present in the base |
| 914 distribution when 'setup()' is called. You may override it in a |
| 915 subclass to perform any other required validation of the feature |
| 916 against a target distribution. |
| 917 """ |
| 918 |
| 919 for item in self.remove: |
| 920 if not dist.has_contents_for(item): |
| 921 raise DistutilsSetupError( |
| 922 "%s wants to be able to remove %s, but the distribution" |
| 923 " doesn't contain any packages or modules under %s" |
| 924 % (self.description, item, item) |
| 925 ) |
OLD | NEW |