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