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