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