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