Index: recipe_engine/third_party/setuptools/dist.py |
diff --git a/recipe_engine/third_party/setuptools/dist.py b/recipe_engine/third_party/setuptools/dist.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e44796fd9560c4c0dfd887d29f54fe2a3cd9c9ba |
--- /dev/null |
+++ b/recipe_engine/third_party/setuptools/dist.py |
@@ -0,0 +1,867 @@ |
+__all__ = ['Distribution'] |
+ |
+import re |
+import os |
+import sys |
+import warnings |
+import numbers |
+import distutils.log |
+import distutils.core |
+import distutils.cmd |
+import distutils.dist |
+from distutils.core import Distribution as _Distribution |
+from distutils.errors import (DistutilsOptionError, DistutilsPlatformError, |
+ DistutilsSetupError) |
+ |
+try: |
+ import packaging.version |
+except ImportError: |
+ # fallback to vendored version |
+ import setuptools._vendor.packaging.version |
+ packaging = setuptools._vendor.packaging |
+ |
+from setuptools.depends import Require |
+from setuptools.compat import basestring, PY2 |
+from setuptools import windows_support |
+import pkg_resources |
+ |
+ |
+def _get_unpatched(cls): |
+ """Protect against re-patching the distutils if reloaded |
+ |
+ Also ensures that no other distutils extension monkeypatched the distutils |
+ first. |
+ """ |
+ while cls.__module__.startswith('setuptools'): |
+ cls, = cls.__bases__ |
+ if not cls.__module__.startswith('distutils'): |
+ raise AssertionError( |
+ "distutils has already been patched by %r" % cls |
+ ) |
+ return cls |
+ |
+_Distribution = _get_unpatched(_Distribution) |
+ |
+def _patch_distribution_metadata_write_pkg_info(): |
+ """ |
+ Workaround issue #197 - Python 3 prior to 3.2.2 uses an environment-local |
+ encoding to save the pkg_info. Monkey-patch its write_pkg_info method to |
+ correct this undesirable behavior. |
+ """ |
+ environment_local = (3,) <= sys.version_info[:3] < (3, 2, 2) |
+ if not environment_local: |
+ return |
+ |
+ # from Python 3.4 |
+ def write_pkg_info(self, base_dir): |
+ """Write the PKG-INFO file into the release tree. |
+ """ |
+ with open(os.path.join(base_dir, 'PKG-INFO'), 'w', |
+ encoding='UTF-8') as pkg_info: |
+ self.write_pkg_file(pkg_info) |
+ |
+ distutils.dist.DistributionMetadata.write_pkg_info = write_pkg_info |
+_patch_distribution_metadata_write_pkg_info() |
+ |
+sequence = tuple, list |
+ |
+def check_importable(dist, attr, value): |
+ try: |
+ ep = pkg_resources.EntryPoint.parse('x='+value) |
+ assert not ep.extras |
+ except (TypeError,ValueError,AttributeError,AssertionError): |
+ raise DistutilsSetupError( |
+ "%r must be importable 'module:attrs' string (got %r)" |
+ % (attr,value) |
+ ) |
+ |
+ |
+def assert_string_list(dist, attr, value): |
+ """Verify that value is a string list or None""" |
+ try: |
+ assert ''.join(value)!=value |
+ except (TypeError,ValueError,AttributeError,AssertionError): |
+ raise DistutilsSetupError( |
+ "%r must be a list of strings (got %r)" % (attr,value) |
+ ) |
+def check_nsp(dist, attr, value): |
+ """Verify that namespace packages are valid""" |
+ assert_string_list(dist,attr,value) |
+ for nsp in value: |
+ if not dist.has_contents_for(nsp): |
+ raise DistutilsSetupError( |
+ "Distribution contains no modules or packages for " + |
+ "namespace package %r" % nsp |
+ ) |
+ if '.' in nsp: |
+ parent = '.'.join(nsp.split('.')[:-1]) |
+ if parent not in value: |
+ distutils.log.warn( |
+ "WARNING: %r is declared as a package namespace, but %r" |
+ " is not: please correct this in setup.py", nsp, parent |
+ ) |
+ |
+def check_extras(dist, attr, value): |
+ """Verify that extras_require mapping is valid""" |
+ try: |
+ for k,v in value.items(): |
+ if ':' in k: |
+ k,m = k.split(':',1) |
+ if pkg_resources.invalid_marker(m): |
+ raise DistutilsSetupError("Invalid environment marker: "+m) |
+ list(pkg_resources.parse_requirements(v)) |
+ except (TypeError,ValueError,AttributeError): |
+ raise DistutilsSetupError( |
+ "'extras_require' must be a dictionary whose values are " |
+ "strings or lists of strings containing valid project/version " |
+ "requirement specifiers." |
+ ) |
+ |
+def assert_bool(dist, attr, value): |
+ """Verify that value is True, False, 0, or 1""" |
+ if bool(value) != value: |
+ raise DistutilsSetupError( |
+ "%r must be a boolean value (got %r)" % (attr,value) |
+ ) |
+def check_requirements(dist, attr, value): |
+ """Verify that install_requires is a valid requirements list""" |
+ try: |
+ list(pkg_resources.parse_requirements(value)) |
+ except (TypeError,ValueError): |
+ raise DistutilsSetupError( |
+ "%r must be a string or list of strings " |
+ "containing valid project/version requirement specifiers" % (attr,) |
+ ) |
+def check_entry_points(dist, attr, value): |
+ """Verify that entry_points map is parseable""" |
+ try: |
+ pkg_resources.EntryPoint.parse_map(value) |
+ except ValueError: |
+ e = sys.exc_info()[1] |
+ raise DistutilsSetupError(e) |
+ |
+def check_test_suite(dist, attr, value): |
+ if not isinstance(value,basestring): |
+ raise DistutilsSetupError("test_suite must be a string") |
+ |
+def check_package_data(dist, attr, value): |
+ """Verify that value is a dictionary of package names to glob lists""" |
+ if isinstance(value,dict): |
+ for k,v in value.items(): |
+ if not isinstance(k,str): break |
+ try: iter(v) |
+ except TypeError: |
+ break |
+ else: |
+ return |
+ raise DistutilsSetupError( |
+ attr+" must be a dictionary mapping package names to lists of " |
+ "wildcard patterns" |
+ ) |
+ |
+def check_packages(dist, attr, value): |
+ for pkgname in value: |
+ if not re.match(r'\w+(\.\w+)*', pkgname): |
+ distutils.log.warn( |
+ "WARNING: %r not a valid package name; please use only" |
+ ".-separated package names in setup.py", pkgname |
+ ) |
+ |
+ |
+class Distribution(_Distribution): |
+ """Distribution with support for features, tests, and package data |
+ |
+ This is an enhanced version of 'distutils.dist.Distribution' that |
+ effectively adds the following new optional keyword arguments to 'setup()': |
+ |
+ 'install_requires' -- a string or sequence of strings specifying project |
+ versions that the distribution requires when installed, in the format |
+ used by 'pkg_resources.require()'. They will be installed |
+ automatically when the package is installed. If you wish to use |
+ packages that are not available in PyPI, or want to give your users an |
+ alternate download location, you can add a 'find_links' option to the |
+ '[easy_install]' section of your project's 'setup.cfg' file, and then |
+ setuptools will scan the listed web pages for links that satisfy the |
+ requirements. |
+ |
+ 'extras_require' -- a dictionary mapping names of optional "extras" to the |
+ additional requirement(s) that using those extras incurs. For example, |
+ this:: |
+ |
+ extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) |
+ |
+ indicates that the distribution can optionally provide an extra |
+ capability called "reST", but it can only be used if docutils and |
+ reSTedit are installed. If the user installs your package using |
+ EasyInstall and requests one of your extras, the corresponding |
+ additional requirements will be installed if needed. |
+ |
+ 'features' **deprecated** -- a dictionary mapping option names to |
+ 'setuptools.Feature' |
+ objects. Features are a portion of the distribution that can be |
+ included or excluded based on user options, inter-feature dependencies, |
+ and availability on the current system. Excluded features are omitted |
+ from all setup commands, including source and binary distributions, so |
+ you can create multiple distributions from the same source tree. |
+ Feature names should be valid Python identifiers, except that they may |
+ contain the '-' (minus) sign. Features can be included or excluded |
+ via the command line options '--with-X' and '--without-X', where 'X' is |
+ the name of the feature. Whether a feature is included by default, and |
+ whether you are allowed to control this from the command line, is |
+ determined by the Feature object. See the 'Feature' class for more |
+ information. |
+ |
+ 'test_suite' -- the name of a test suite to run for the 'test' command. |
+ If the user runs 'python setup.py test', the package will be installed, |
+ and the named test suite will be run. The format is the same as |
+ would be used on a 'unittest.py' command line. That is, it is the |
+ dotted name of an object to import and call to generate a test suite. |
+ |
+ 'package_data' -- a dictionary mapping package names to lists of filenames |
+ or globs to use to find data files contained in the named packages. |
+ If the dictionary has filenames or globs listed under '""' (the empty |
+ string), those names will be searched for in every package, in addition |
+ to any names for the specific package. Data files found using these |
+ names/globs will be installed along with the package, in the same |
+ location as the package. Note that globs are allowed to reference |
+ the contents of non-package subdirectories, as long as you use '/' as |
+ a path separator. (Globs are automatically converted to |
+ platform-specific paths at runtime.) |
+ |
+ In addition to these new keywords, this class also has several new methods |
+ for manipulating the distribution's contents. For example, the 'include()' |
+ and 'exclude()' methods can be thought of as in-place add and subtract |
+ commands that add or remove packages, modules, extensions, and so on from |
+ the distribution. They are used by the feature subsystem to configure the |
+ distribution for the included and excluded features. |
+ """ |
+ |
+ _patched_dist = None |
+ |
+ def patch_missing_pkg_info(self, attrs): |
+ # Fake up a replacement for the data that would normally come from |
+ # PKG-INFO, but which might not yet be built if this is a fresh |
+ # checkout. |
+ # |
+ if not attrs or 'name' not in attrs or 'version' not in attrs: |
+ return |
+ key = pkg_resources.safe_name(str(attrs['name'])).lower() |
+ dist = pkg_resources.working_set.by_key.get(key) |
+ if dist is not None and not dist.has_metadata('PKG-INFO'): |
+ dist._version = pkg_resources.safe_version(str(attrs['version'])) |
+ self._patched_dist = dist |
+ |
+ def __init__(self, attrs=None): |
+ have_package_data = hasattr(self, "package_data") |
+ if not have_package_data: |
+ self.package_data = {} |
+ _attrs_dict = attrs or {} |
+ if 'features' in _attrs_dict or 'require_features' in _attrs_dict: |
+ Feature.warn_deprecated() |
+ self.require_features = [] |
+ self.features = {} |
+ self.dist_files = [] |
+ self.src_root = attrs and attrs.pop("src_root", None) |
+ self.patch_missing_pkg_info(attrs) |
+ # Make sure we have any eggs needed to interpret 'attrs' |
+ if attrs is not None: |
+ self.dependency_links = attrs.pop('dependency_links', []) |
+ assert_string_list(self,'dependency_links',self.dependency_links) |
+ if attrs and 'setup_requires' in attrs: |
+ self.fetch_build_eggs(attrs['setup_requires']) |
+ for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): |
+ if not hasattr(self,ep.name): |
+ setattr(self,ep.name,None) |
+ _Distribution.__init__(self,attrs) |
+ if isinstance(self.metadata.version, numbers.Number): |
+ # Some people apparently take "version number" too literally :) |
+ self.metadata.version = str(self.metadata.version) |
+ |
+ if self.metadata.version is not None: |
+ try: |
+ ver = packaging.version.Version(self.metadata.version) |
+ normalized_version = str(ver) |
+ if self.metadata.version != normalized_version: |
+ warnings.warn( |
+ "The version specified requires normalization, " |
+ "consider using '%s' instead of '%s'." % ( |
+ normalized_version, |
+ self.metadata.version, |
+ ) |
+ ) |
+ self.metadata.version = normalized_version |
+ except (packaging.version.InvalidVersion, TypeError): |
+ warnings.warn( |
+ "The version specified (%r) is an invalid version, this " |
+ "may not work as expected with newer versions of " |
+ "setuptools, pip, and PyPI. Please see PEP 440 for more " |
+ "details." % self.metadata.version |
+ ) |
+ |
+ def parse_command_line(self): |
+ """Process features after parsing command line options""" |
+ result = _Distribution.parse_command_line(self) |
+ if self.features: |
+ self._finalize_features() |
+ return result |
+ |
+ def _feature_attrname(self,name): |
+ """Convert feature name to corresponding option attribute name""" |
+ return 'with_'+name.replace('-','_') |
+ |
+ def fetch_build_eggs(self, requires): |
+ """Resolve pre-setup requirements""" |
+ resolved_dists = pkg_resources.working_set.resolve( |
+ pkg_resources.parse_requirements(requires), |
+ installer=self.fetch_build_egg, |
+ replace_conflicting=True, |
+ ) |
+ for dist in resolved_dists: |
+ pkg_resources.working_set.add(dist, replace=True) |
+ |
+ def finalize_options(self): |
+ _Distribution.finalize_options(self) |
+ if self.features: |
+ self._set_global_opts_from_features() |
+ |
+ for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): |
+ value = getattr(self,ep.name,None) |
+ if value is not None: |
+ ep.require(installer=self.fetch_build_egg) |
+ ep.load()(self, ep.name, value) |
+ if getattr(self, 'convert_2to3_doctests', None): |
+ # XXX may convert to set here when we can rely on set being builtin |
+ self.convert_2to3_doctests = [os.path.abspath(p) for p in self.convert_2to3_doctests] |
+ else: |
+ self.convert_2to3_doctests = [] |
+ |
+ def get_egg_cache_dir(self): |
+ egg_cache_dir = os.path.join(os.curdir, '.eggs') |
+ if not os.path.exists(egg_cache_dir): |
+ os.mkdir(egg_cache_dir) |
+ windows_support.hide_file(egg_cache_dir) |
+ readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') |
+ with open(readme_txt_filename, 'w') as f: |
+ f.write('This directory contains eggs that were downloaded ' |
+ 'by setuptools to build, test, and run plug-ins.\n\n') |
+ f.write('This directory caches those eggs to prevent ' |
+ 'repeated downloads.\n\n') |
+ f.write('However, it is safe to delete this directory.\n\n') |
+ |
+ return egg_cache_dir |
+ |
+ def fetch_build_egg(self, req): |
+ """Fetch an egg needed for building""" |
+ |
+ try: |
+ cmd = self._egg_fetcher |
+ cmd.package_index.to_scan = [] |
+ except AttributeError: |
+ from setuptools.command.easy_install import easy_install |
+ dist = self.__class__({'script_args':['easy_install']}) |
+ dist.parse_config_files() |
+ opts = dist.get_option_dict('easy_install') |
+ keep = ( |
+ 'find_links', 'site_dirs', 'index_url', 'optimize', |
+ 'site_dirs', 'allow_hosts' |
+ ) |
+ for key in list(opts): |
+ if key not in keep: |
+ del opts[key] # don't use any other settings |
+ if self.dependency_links: |
+ links = self.dependency_links[:] |
+ if 'find_links' in opts: |
+ links = opts['find_links'][1].split() + links |
+ opts['find_links'] = ('setup', links) |
+ install_dir = self.get_egg_cache_dir() |
+ cmd = easy_install( |
+ dist, args=["x"], install_dir=install_dir, exclude_scripts=True, |
+ always_copy=False, build_directory=None, editable=False, |
+ upgrade=False, multi_version=True, no_report=True, user=False |
+ ) |
+ cmd.ensure_finalized() |
+ self._egg_fetcher = cmd |
+ return cmd.easy_install(req) |
+ |
+ def _set_global_opts_from_features(self): |
+ """Add --with-X/--without-X options based on optional features""" |
+ |
+ go = [] |
+ no = self.negative_opt.copy() |
+ |
+ for name,feature in self.features.items(): |
+ self._set_feature(name,None) |
+ feature.validate(self) |
+ |
+ if feature.optional: |
+ descr = feature.description |
+ incdef = ' (default)' |
+ excdef='' |
+ if not feature.include_by_default(): |
+ excdef, incdef = incdef, excdef |
+ |
+ go.append(('with-'+name, None, 'include '+descr+incdef)) |
+ go.append(('without-'+name, None, 'exclude '+descr+excdef)) |
+ no['without-'+name] = 'with-'+name |
+ |
+ self.global_options = self.feature_options = go + self.global_options |
+ self.negative_opt = self.feature_negopt = no |
+ |
+ def _finalize_features(self): |
+ """Add/remove features and resolve dependencies between them""" |
+ |
+ # First, flag all the enabled items (and thus their dependencies) |
+ for name,feature in self.features.items(): |
+ enabled = self.feature_is_included(name) |
+ if enabled or (enabled is None and feature.include_by_default()): |
+ feature.include_in(self) |
+ self._set_feature(name,1) |
+ |
+ # Then disable the rest, so that off-by-default features don't |
+ # get flagged as errors when they're required by an enabled feature |
+ for name,feature in self.features.items(): |
+ if not self.feature_is_included(name): |
+ feature.exclude_from(self) |
+ self._set_feature(name,0) |
+ |
+ def get_command_class(self, command): |
+ """Pluggable version of get_command_class()""" |
+ if command in self.cmdclass: |
+ return self.cmdclass[command] |
+ |
+ for ep in pkg_resources.iter_entry_points('distutils.commands',command): |
+ ep.require(installer=self.fetch_build_egg) |
+ self.cmdclass[command] = cmdclass = ep.load() |
+ return cmdclass |
+ else: |
+ return _Distribution.get_command_class(self, command) |
+ |
+ def print_commands(self): |
+ for ep in pkg_resources.iter_entry_points('distutils.commands'): |
+ if ep.name not in self.cmdclass: |
+ cmdclass = ep.load(False) # don't require extras, we're not running |
+ self.cmdclass[ep.name] = cmdclass |
+ return _Distribution.print_commands(self) |
+ |
+ def _set_feature(self,name,status): |
+ """Set feature's inclusion status""" |
+ setattr(self,self._feature_attrname(name),status) |
+ |
+ def feature_is_included(self,name): |
+ """Return 1 if feature is included, 0 if excluded, 'None' if unknown""" |
+ return getattr(self,self._feature_attrname(name)) |
+ |
+ def include_feature(self,name): |
+ """Request inclusion of feature named 'name'""" |
+ |
+ if self.feature_is_included(name)==0: |
+ descr = self.features[name].description |
+ raise DistutilsOptionError( |
+ descr + " is required, but was excluded or is not available" |
+ ) |
+ self.features[name].include_in(self) |
+ self._set_feature(name,1) |
+ |
+ def include(self,**attrs): |
+ """Add items to distribution that are named in keyword arguments |
+ |
+ For example, 'dist.exclude(py_modules=["x"])' would add 'x' to |
+ the distribution's 'py_modules' attribute, if it was not already |
+ there. |
+ |
+ Currently, this method only supports inclusion for attributes that are |
+ lists or tuples. If you need to add support for adding to other |
+ attributes in this or a subclass, you can add an '_include_X' method, |
+ where 'X' is the name of the attribute. The method will be called with |
+ the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' |
+ will try to call 'dist._include_foo({"bar":"baz"})', which can then |
+ handle whatever special inclusion logic is needed. |
+ """ |
+ for k,v in attrs.items(): |
+ include = getattr(self, '_include_'+k, None) |
+ if include: |
+ include(v) |
+ else: |
+ self._include_misc(k,v) |
+ |
+ def exclude_package(self,package): |
+ """Remove packages, modules, and extensions in named package""" |
+ |
+ pfx = package+'.' |
+ if self.packages: |
+ self.packages = [ |
+ p for p in self.packages |
+ if p != package and not p.startswith(pfx) |
+ ] |
+ |
+ if self.py_modules: |
+ self.py_modules = [ |
+ p for p in self.py_modules |
+ if p != package and not p.startswith(pfx) |
+ ] |
+ |
+ if self.ext_modules: |
+ self.ext_modules = [ |
+ p for p in self.ext_modules |
+ if p.name != package and not p.name.startswith(pfx) |
+ ] |
+ |
+ def has_contents_for(self,package): |
+ """Return true if 'exclude_package(package)' would do something""" |
+ |
+ pfx = package+'.' |
+ |
+ for p in self.iter_distribution_names(): |
+ if p==package or p.startswith(pfx): |
+ return True |
+ |
+ def _exclude_misc(self,name,value): |
+ """Handle 'exclude()' for list/tuple attrs without a special handler""" |
+ if not isinstance(value,sequence): |
+ raise DistutilsSetupError( |
+ "%s: setting must be a list or tuple (%r)" % (name, value) |
+ ) |
+ try: |
+ old = getattr(self,name) |
+ except AttributeError: |
+ raise DistutilsSetupError( |
+ "%s: No such distribution setting" % name |
+ ) |
+ if old is not None and not isinstance(old,sequence): |
+ raise DistutilsSetupError( |
+ name+": this setting cannot be changed via include/exclude" |
+ ) |
+ elif old: |
+ setattr(self,name,[item for item in old if item not in value]) |
+ |
+ def _include_misc(self,name,value): |
+ """Handle 'include()' for list/tuple attrs without a special handler""" |
+ |
+ if not isinstance(value,sequence): |
+ raise DistutilsSetupError( |
+ "%s: setting must be a list (%r)" % (name, value) |
+ ) |
+ try: |
+ old = getattr(self,name) |
+ except AttributeError: |
+ raise DistutilsSetupError( |
+ "%s: No such distribution setting" % name |
+ ) |
+ if old is None: |
+ setattr(self,name,value) |
+ elif not isinstance(old,sequence): |
+ raise DistutilsSetupError( |
+ name+": this setting cannot be changed via include/exclude" |
+ ) |
+ else: |
+ setattr(self,name,old+[item for item in value if item not in old]) |
+ |
+ def exclude(self,**attrs): |
+ """Remove items from distribution that are named in keyword arguments |
+ |
+ For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from |
+ the distribution's 'py_modules' attribute. Excluding packages uses |
+ the 'exclude_package()' method, so all of the package's contained |
+ packages, modules, and extensions are also excluded. |
+ |
+ Currently, this method only supports exclusion from attributes that are |
+ lists or tuples. If you need to add support for excluding from other |
+ attributes in this or a subclass, you can add an '_exclude_X' method, |
+ where 'X' is the name of the attribute. The method will be called with |
+ the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' |
+ will try to call 'dist._exclude_foo({"bar":"baz"})', which can then |
+ handle whatever special exclusion logic is needed. |
+ """ |
+ for k,v in attrs.items(): |
+ exclude = getattr(self, '_exclude_'+k, None) |
+ if exclude: |
+ exclude(v) |
+ else: |
+ self._exclude_misc(k,v) |
+ |
+ def _exclude_packages(self,packages): |
+ if not isinstance(packages,sequence): |
+ raise DistutilsSetupError( |
+ "packages: setting must be a list or tuple (%r)" % (packages,) |
+ ) |
+ list(map(self.exclude_package, packages)) |
+ |
+ def _parse_command_opts(self, parser, args): |
+ # Remove --with-X/--without-X options when processing command args |
+ self.global_options = self.__class__.global_options |
+ self.negative_opt = self.__class__.negative_opt |
+ |
+ # First, expand any aliases |
+ command = args[0] |
+ aliases = self.get_option_dict('aliases') |
+ while command in aliases: |
+ src,alias = aliases[command] |
+ del aliases[command] # ensure each alias can expand only once! |
+ import shlex |
+ args[:1] = shlex.split(alias,True) |
+ command = args[0] |
+ |
+ nargs = _Distribution._parse_command_opts(self, parser, args) |
+ |
+ # Handle commands that want to consume all remaining arguments |
+ cmd_class = self.get_command_class(command) |
+ if getattr(cmd_class,'command_consumes_arguments',None): |
+ self.get_option_dict(command)['args'] = ("command line", nargs) |
+ if nargs is not None: |
+ return [] |
+ |
+ return nargs |
+ |
+ def get_cmdline_options(self): |
+ """Return a '{cmd: {opt:val}}' map of all command-line options |
+ |
+ Option names are all long, but do not include the leading '--', and |
+ contain dashes rather than underscores. If the option doesn't take |
+ an argument (e.g. '--quiet'), the 'val' is 'None'. |
+ |
+ Note that options provided by config files are intentionally excluded. |
+ """ |
+ |
+ d = {} |
+ |
+ for cmd,opts in self.command_options.items(): |
+ |
+ for opt,(src,val) in opts.items(): |
+ |
+ if src != "command line": |
+ continue |
+ |
+ opt = opt.replace('_','-') |
+ |
+ if val==0: |
+ cmdobj = self.get_command_obj(cmd) |
+ neg_opt = self.negative_opt.copy() |
+ neg_opt.update(getattr(cmdobj,'negative_opt',{})) |
+ for neg,pos in neg_opt.items(): |
+ if pos==opt: |
+ opt=neg |
+ val=None |
+ break |
+ else: |
+ raise AssertionError("Shouldn't be able to get here") |
+ |
+ elif val==1: |
+ val = None |
+ |
+ d.setdefault(cmd,{})[opt] = val |
+ |
+ return d |
+ |
+ def iter_distribution_names(self): |
+ """Yield all packages, modules, and extension names in distribution""" |
+ |
+ for pkg in self.packages or (): |
+ yield pkg |
+ |
+ for module in self.py_modules or (): |
+ yield module |
+ |
+ for ext in self.ext_modules or (): |
+ if isinstance(ext,tuple): |
+ name, buildinfo = ext |
+ else: |
+ name = ext.name |
+ if name.endswith('module'): |
+ name = name[:-6] |
+ yield name |
+ |
+ def handle_display_options(self, option_order): |
+ """If there were any non-global "display-only" options |
+ (--help-commands or the metadata display options) on the command |
+ line, display the requested info and return true; else return |
+ false. |
+ """ |
+ import sys |
+ |
+ if PY2 or self.help_commands: |
+ return _Distribution.handle_display_options(self, option_order) |
+ |
+ # Stdout may be StringIO (e.g. in tests) |
+ import io |
+ if not isinstance(sys.stdout, io.TextIOWrapper): |
+ return _Distribution.handle_display_options(self, option_order) |
+ |
+ # Don't wrap stdout if utf-8 is already the encoding. Provides |
+ # workaround for #334. |
+ if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): |
+ return _Distribution.handle_display_options(self, option_order) |
+ |
+ # Print metadata in UTF-8 no matter the platform |
+ encoding = sys.stdout.encoding |
+ errors = sys.stdout.errors |
+ newline = sys.platform != 'win32' and '\n' or None |
+ line_buffering = sys.stdout.line_buffering |
+ |
+ sys.stdout = io.TextIOWrapper( |
+ sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) |
+ try: |
+ return _Distribution.handle_display_options(self, option_order) |
+ finally: |
+ sys.stdout = io.TextIOWrapper( |
+ sys.stdout.detach(), encoding, errors, newline, line_buffering) |
+ |
+ |
+# Install it throughout the distutils |
+for module in distutils.dist, distutils.core, distutils.cmd: |
+ module.Distribution = Distribution |
+ |
+ |
+class Feature: |
+ """ |
+ **deprecated** -- The `Feature` facility was never completely implemented |
+ or supported, `has reported issues |
+ <https://bitbucket.org/pypa/setuptools/issue/58>`_ and will be removed in |
+ a future version. |
+ |
+ A subset of the distribution that can be excluded if unneeded/wanted |
+ |
+ Features are created using these keyword arguments: |
+ |
+ 'description' -- a short, human readable description of the feature, to |
+ be used in error messages, and option help messages. |
+ |
+ 'standard' -- if true, the feature is included by default if it is |
+ available on the current system. Otherwise, the feature is only |
+ included if requested via a command line '--with-X' option, or if |
+ another included feature requires it. The default setting is 'False'. |
+ |
+ 'available' -- if true, the feature is available for installation on the |
+ current system. The default setting is 'True'. |
+ |
+ 'optional' -- if true, the feature's inclusion can be controlled from the |
+ command line, using the '--with-X' or '--without-X' options. If |
+ false, the feature's inclusion status is determined automatically, |
+ based on 'availabile', 'standard', and whether any other feature |
+ requires it. The default setting is 'True'. |
+ |
+ 'require_features' -- a string or sequence of strings naming features |
+ that should also be included if this feature is included. Defaults to |
+ empty list. May also contain 'Require' objects that should be |
+ added/removed from the distribution. |
+ |
+ 'remove' -- a string or list of strings naming packages to be removed |
+ from the distribution if this feature is *not* included. If the |
+ feature *is* included, this argument is ignored. This argument exists |
+ to support removing features that "crosscut" a distribution, such as |
+ defining a 'tests' feature that removes all the 'tests' subpackages |
+ provided by other features. The default for this argument is an empty |
+ list. (Note: the named package(s) or modules must exist in the base |
+ distribution when the 'setup()' function is initially called.) |
+ |
+ other keywords -- any other keyword arguments are saved, and passed to |
+ the distribution's 'include()' and 'exclude()' methods when the |
+ feature is included or excluded, respectively. So, for example, you |
+ could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be |
+ added or removed from the distribution as appropriate. |
+ |
+ A feature must include at least one 'requires', 'remove', or other |
+ keyword argument. Otherwise, it can't affect the distribution in any way. |
+ Note also that you can subclass 'Feature' to create your own specialized |
+ feature types that modify the distribution in other ways when included or |
+ excluded. See the docstrings for the various methods here for more detail. |
+ Aside from the methods, the only feature attributes that distributions look |
+ at are 'description' and 'optional'. |
+ """ |
+ |
+ @staticmethod |
+ def warn_deprecated(): |
+ warnings.warn( |
+ "Features are deprecated and will be removed in a future " |
+ "version. See http://bitbucket.org/pypa/setuptools/65.", |
+ DeprecationWarning, |
+ stacklevel=3, |
+ ) |
+ |
+ def __init__(self, description, standard=False, available=True, |
+ optional=True, require_features=(), remove=(), **extras): |
+ self.warn_deprecated() |
+ |
+ self.description = description |
+ self.standard = standard |
+ self.available = available |
+ self.optional = optional |
+ if isinstance(require_features,(str,Require)): |
+ require_features = require_features, |
+ |
+ self.require_features = [ |
+ r for r in require_features if isinstance(r,str) |
+ ] |
+ er = [r for r in require_features if not isinstance(r,str)] |
+ if er: extras['require_features'] = er |
+ |
+ if isinstance(remove,str): |
+ remove = remove, |
+ self.remove = remove |
+ self.extras = extras |
+ |
+ if not remove and not require_features and not extras: |
+ raise DistutilsSetupError( |
+ "Feature %s: must define 'require_features', 'remove', or at least one" |
+ " of 'packages', 'py_modules', etc." |
+ ) |
+ |
+ def include_by_default(self): |
+ """Should this feature be included by default?""" |
+ return self.available and self.standard |
+ |
+ def include_in(self,dist): |
+ |
+ """Ensure feature and its requirements are included in distribution |
+ |
+ You may override this in a subclass to perform additional operations on |
+ the distribution. Note that this method may be called more than once |
+ per feature, and so should be idempotent. |
+ |
+ """ |
+ |
+ if not self.available: |
+ raise DistutilsPlatformError( |
+ self.description+" is required," |
+ "but is not available on this platform" |
+ ) |
+ |
+ dist.include(**self.extras) |
+ |
+ for f in self.require_features: |
+ dist.include_feature(f) |
+ |
+ def exclude_from(self,dist): |
+ |
+ """Ensure feature is excluded from distribution |
+ |
+ You may override this in a subclass to perform additional operations on |
+ the distribution. This method will be called at most once per |
+ feature, and only after all included features have been asked to |
+ include themselves. |
+ """ |
+ |
+ dist.exclude(**self.extras) |
+ |
+ if self.remove: |
+ for item in self.remove: |
+ dist.exclude_package(item) |
+ |
+ def validate(self,dist): |
+ |
+ """Verify that feature makes sense in context of distribution |
+ |
+ This method is called by the distribution just before it parses its |
+ command line. It checks to ensure that the 'remove' attribute, if any, |
+ contains only valid package/module names that are present in the base |
+ distribution when 'setup()' is called. You may override it in a |
+ subclass to perform any other required validation of the feature |
+ against a target distribution. |
+ """ |
+ |
+ for item in self.remove: |
+ if not dist.has_contents_for(item): |
+ raise DistutilsSetupError( |
+ "%s wants to be able to remove %s, but the distribution" |
+ " doesn't contain any packages or modules under %s" |
+ % (self.description, item, item) |
+ ) |