Project

General

Profile

Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / distribute-0.6.19-py2.7.egg / setuptools / dist.py @ 1a305335

History | View | Annotate | Download (29.6 KB)

1 1a305335 officers
__all__ = ['Distribution']
2
3
import re
4
from distutils.core import Distribution as _Distribution
5
from setuptools.depends import Require
6
from setuptools.command.install import install
7
from setuptools.command.sdist import sdist
8
from setuptools.command.install_lib import install_lib
9
from distutils.errors import DistutilsOptionError, DistutilsPlatformError
10
from distutils.errors import DistutilsSetupError
11
import setuptools, pkg_resources, distutils.core, distutils.dist, distutils.cmd
12
import os, distutils.log
13
14
def _get_unpatched(cls):
15
    """Protect against re-patching the distutils if reloaded
16

17
    Also ensures that no other distutils extension monkeypatched the distutils
18
    first.
19
    """
20
    while cls.__module__.startswith('setuptools'):
21
        cls, = cls.__bases__
22
    if not cls.__module__.startswith('distutils'):
23
        raise AssertionError(
24
            "distutils has already been patched by %r" % cls
25
        )
26
    return cls
27
28
_Distribution = _get_unpatched(_Distribution)
29
30
sequence = tuple, list
31
32
def check_importable(dist, attr, value):
33
    try:
34
        ep = pkg_resources.EntryPoint.parse('x='+value)
35
        assert not ep.extras
36
    except (TypeError,ValueError,AttributeError,AssertionError):
37
        raise DistutilsSetupError(
38
            "%r must be importable 'module:attrs' string (got %r)"
39
            % (attr,value)
40
        )
41
42
43
def assert_string_list(dist, attr, value):
44
    """Verify that value is a string list or None"""
45
    try:
46
        assert ''.join(value)!=value
47
    except (TypeError,ValueError,AttributeError,AssertionError):
48
        raise DistutilsSetupError(
49
            "%r must be a list of strings (got %r)" % (attr,value)
50
        )
51
52
def check_nsp(dist, attr, value):
53
    """Verify that namespace packages are valid"""
54
    assert_string_list(dist,attr,value)
55
    for nsp in value:
56
        if not dist.has_contents_for(nsp):
57
            raise DistutilsSetupError(
58
                "Distribution contains no modules or packages for " +
59
                "namespace package %r" % nsp
60
            )
61
        if '.' in nsp:
62
            parent = '.'.join(nsp.split('.')[:-1])
63
            if parent not in value:
64
                distutils.log.warn(
65
                    "%r is declared as a package namespace, but %r is not:"
66
                    " please correct this in setup.py", nsp, parent
67
                )
68
69
def check_extras(dist, attr, value):
70
    """Verify that extras_require mapping is valid"""
71
    try:
72
        for k,v in value.items():
73
            list(pkg_resources.parse_requirements(v))
74
    except (TypeError,ValueError,AttributeError):
75
        raise DistutilsSetupError(
76
            "'extras_require' must be a dictionary whose values are "
77
            "strings or lists of strings containing valid project/version "
78
            "requirement specifiers."
79
        )
80
81
82
83
84
def assert_bool(dist, attr, value):
85
    """Verify that value is True, False, 0, or 1"""
86
    if bool(value) != value:
87
        raise DistutilsSetupError(
88
            "%r must be a boolean value (got %r)" % (attr,value)
89
        )
90
def check_requirements(dist, attr, value):
91
    """Verify that install_requires is a valid requirements list"""
92
    try:
93
        list(pkg_resources.parse_requirements(value))
94
    except (TypeError,ValueError):
95
        raise DistutilsSetupError(
96
            "%r must be a string or list of strings "
97
            "containing valid project/version requirement specifiers" % (attr,)
98
        )
99
def check_entry_points(dist, attr, value):
100
    """Verify that entry_points map is parseable"""
101
    try:
102
        pkg_resources.EntryPoint.parse_map(value)
103
    except ValueError, e:
104
        raise DistutilsSetupError(e)
105
106
def check_test_suite(dist, attr, value):
107
    if not isinstance(value,basestring):
108
        raise DistutilsSetupError("test_suite must be a string")
109
110
def check_package_data(dist, attr, value):
111
    """Verify that value is a dictionary of package names to glob lists"""
112
    if isinstance(value,dict):
113
        for k,v in value.items():
114
            if not isinstance(k,str): break
115
            try: iter(v)
116
            except TypeError:
117
                break
118
        else:
119
            return
120
    raise DistutilsSetupError(
121
        attr+" must be a dictionary mapping package names to lists of "
122
        "wildcard patterns"
123
    )
124
125
class Distribution(_Distribution):
126
    """Distribution with support for features, tests, and package data
127

128
    This is an enhanced version of 'distutils.dist.Distribution' that
129
    effectively adds the following new optional keyword arguments to 'setup()':
130

131
     'install_requires' -- a string or sequence of strings specifying project
132
        versions that the distribution requires when installed, in the format
133
        used by 'pkg_resources.require()'.  They will be installed
134
        automatically when the package is installed.  If you wish to use
135
        packages that are not available in PyPI, or want to give your users an
136
        alternate download location, you can add a 'find_links' option to the
137
        '[easy_install]' section of your project's 'setup.cfg' file, and then
138
        setuptools will scan the listed web pages for links that satisfy the
139
        requirements.
140

141
     'extras_require' -- a dictionary mapping names of optional "extras" to the
142
        additional requirement(s) that using those extras incurs. For example,
143
        this::
144

145
            extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
146

147
        indicates that the distribution can optionally provide an extra
148
        capability called "reST", but it can only be used if docutils and
149
        reSTedit are installed.  If the user installs your package using
150
        EasyInstall and requests one of your extras, the corresponding
151
        additional requirements will be installed if needed.
152

153
     'features' -- a dictionary mapping option names to 'setuptools.Feature'
154
        objects.  Features are a portion of the distribution that can be
155
        included or excluded based on user options, inter-feature dependencies,
156
        and availability on the current system.  Excluded features are omitted
157
        from all setup commands, including source and binary distributions, so
158
        you can create multiple distributions from the same source tree.
159
        Feature names should be valid Python identifiers, except that they may
160
        contain the '-' (minus) sign.  Features can be included or excluded
161
        via the command line options '--with-X' and '--without-X', where 'X' is
162
        the name of the feature.  Whether a feature is included by default, and
163
        whether you are allowed to control this from the command line, is
164
        determined by the Feature object.  See the 'Feature' class for more
165
        information.
166

167
     'test_suite' -- the name of a test suite to run for the 'test' command.
168
        If the user runs 'python setup.py test', the package will be installed,
169
        and the named test suite will be run.  The format is the same as
170
        would be used on a 'unittest.py' command line.  That is, it is the
171
        dotted name of an object to import and call to generate a test suite.
172

173
     'package_data' -- a dictionary mapping package names to lists of filenames
174
        or globs to use to find data files contained in the named packages.
175
        If the dictionary has filenames or globs listed under '""' (the empty
176
        string), those names will be searched for in every package, in addition
177
        to any names for the specific package.  Data files found using these
178
        names/globs will be installed along with the package, in the same
179
        location as the package.  Note that globs are allowed to reference
180
        the contents of non-package subdirectories, as long as you use '/' as
181
        a path separator.  (Globs are automatically converted to
182
        platform-specific paths at runtime.)
183

184
    In addition to these new keywords, this class also has several new methods
185
    for manipulating the distribution's contents.  For example, the 'include()'
186
    and 'exclude()' methods can be thought of as in-place add and subtract
187
    commands that add or remove packages, modules, extensions, and so on from
188
    the distribution.  They are used by the feature subsystem to configure the
189
    distribution for the included and excluded features.
190
    """
191
192
    _patched_dist = None
193
194
    def patch_missing_pkg_info(self, attrs):
195
        # Fake up a replacement for the data that would normally come from
196
        # PKG-INFO, but which might not yet be built if this is a fresh
197
        # checkout.
198
        #
199
        if not attrs or 'name' not in attrs or 'version' not in attrs:
200
            return
201
        key = pkg_resources.safe_name(str(attrs['name'])).lower()
202
        dist = pkg_resources.working_set.by_key.get(key)
203
        if dist is not None and not dist.has_metadata('PKG-INFO'):
204
            dist._version = pkg_resources.safe_version(str(attrs['version']))
205
            self._patched_dist = dist
206
207
    def __init__ (self, attrs=None):
208
        have_package_data = hasattr(self, "package_data")
209
        if not have_package_data:
210
            self.package_data = {}
211
        self.require_features = []
212
        self.features = {}
213
        self.dist_files = []
214
        self.src_root = attrs and attrs.pop("src_root", None)
215
        self.patch_missing_pkg_info(attrs)
216
        # Make sure we have any eggs needed to interpret 'attrs'
217
        if attrs is not None:
218
            self.dependency_links = attrs.pop('dependency_links', [])
219
            assert_string_list(self,'dependency_links',self.dependency_links)
220
        if attrs and 'setup_requires' in attrs:
221
            self.fetch_build_eggs(attrs.pop('setup_requires'))
222
        for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
223
            if not hasattr(self,ep.name):
224
                setattr(self,ep.name,None)
225
        _Distribution.__init__(self,attrs)
226
        if isinstance(self.metadata.version, (int,long,float)):
227
            # Some people apparently take "version number" too literally :)
228
            self.metadata.version = str(self.metadata.version)
229
230
    def parse_command_line(self):
231
        """Process features after parsing command line options"""
232
        result = _Distribution.parse_command_line(self)
233
        if self.features:
234
            self._finalize_features()
235
        return result
236
237
    def _feature_attrname(self,name):
238
        """Convert feature name to corresponding option attribute name"""
239
        return 'with_'+name.replace('-','_')
240
241
    def fetch_build_eggs(self, requires):
242
        """Resolve pre-setup requirements"""
243
        from pkg_resources import working_set, parse_requirements
244
        for dist in working_set.resolve(
245
            parse_requirements(requires), installer=self.fetch_build_egg
246
        ):
247
            working_set.add(dist)
248
249
    def finalize_options(self):
250
        _Distribution.finalize_options(self)
251
        if self.features:
252
            self._set_global_opts_from_features()
253
254
        for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
255
            value = getattr(self,ep.name,None)
256
            if value is not None:
257
                ep.require(installer=self.fetch_build_egg)
258
                ep.load()(self, ep.name, value)
259
        if getattr(self, 'convert_2to3_doctests', None):
260
            # XXX may convert to set here when we can rely on set being builtin
261
            self.convert_2to3_doctests = [os.path.abspath(p) for p in self.convert_2to3_doctests]
262
        else:
263
            self.convert_2to3_doctests = []
264
265
    def fetch_build_egg(self, req):
266
        """Fetch an egg needed for building"""
267
        try:
268
            cmd = self._egg_fetcher
269
            cmd.package_index.to_scan = []
270
        except AttributeError:
271
            from setuptools.command.easy_install import easy_install
272
            dist = self.__class__({'script_args':['easy_install']})
273
            dist.parse_config_files()
274
            opts = dist.get_option_dict('easy_install')
275
            keep = (
276
                'find_links', 'site_dirs', 'index_url', 'optimize',
277
                'site_dirs', 'allow_hosts'
278
            )
279
            for key in opts.keys():
280
                if key not in keep:
281
                    del opts[key]   # don't use any other settings
282
            if self.dependency_links:
283
                links = self.dependency_links[:]
284
                if 'find_links' in opts:
285
                    links = opts['find_links'][1].split() + links
286
                opts['find_links'] = ('setup', links)
287
            cmd = easy_install(
288
                dist, args=["x"], install_dir=os.curdir, exclude_scripts=True,
289
                always_copy=False, build_directory=None, editable=False,
290
                upgrade=False, multi_version=True, no_report = True
291
            )
292
            cmd.ensure_finalized()
293
            self._egg_fetcher = cmd
294
        return cmd.easy_install(req)
295
296
    def _set_global_opts_from_features(self):
297
        """Add --with-X/--without-X options based on optional features"""
298
299
        go = []
300
        no = self.negative_opt.copy()
301
302
        for name,feature in self.features.items():
303
            self._set_feature(name,None)
304
            feature.validate(self)
305
306
            if feature.optional:
307
                descr = feature.description
308
                incdef = ' (default)'
309
                excdef=''
310
                if not feature.include_by_default():
311
                    excdef, incdef = incdef, excdef
312
313
                go.append(('with-'+name, None, 'include '+descr+incdef))
314
                go.append(('without-'+name, None, 'exclude '+descr+excdef))
315
                no['without-'+name] = 'with-'+name
316
317
        self.global_options = self.feature_options = go + self.global_options
318
        self.negative_opt = self.feature_negopt = no
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
    def _finalize_features(self):
338
        """Add/remove features and resolve dependencies between them"""
339
340
        # First, flag all the enabled items (and thus their dependencies)
341
        for name,feature in self.features.items():
342
            enabled = self.feature_is_included(name)
343
            if enabled or (enabled is None and feature.include_by_default()):
344
                feature.include_in(self)
345
                self._set_feature(name,1)
346
347
        # Then disable the rest, so that off-by-default features don't
348
        # get flagged as errors when they're required by an enabled feature
349
        for name,feature in self.features.items():
350
            if not self.feature_is_included(name):
351
                feature.exclude_from(self)
352
                self._set_feature(name,0)
353
354
355
    def get_command_class(self, command):
356
        """Pluggable version of get_command_class()"""
357
        if command in self.cmdclass:
358
            return self.cmdclass[command]
359
360
        for ep in pkg_resources.iter_entry_points('distutils.commands',command):
361
            ep.require(installer=self.fetch_build_egg)
362
            self.cmdclass[command] = cmdclass = ep.load()
363
            return cmdclass
364
        else:
365
            return _Distribution.get_command_class(self, command)
366
367
    def print_commands(self):
368
        for ep in pkg_resources.iter_entry_points('distutils.commands'):
369
            if ep.name not in self.cmdclass:
370
                cmdclass = ep.load(False) # don't require extras, we're not running
371
                self.cmdclass[ep.name] = cmdclass
372
        return _Distribution.print_commands(self)
373
374
375
376
377
378
    def _set_feature(self,name,status):
379
        """Set feature's inclusion status"""
380
        setattr(self,self._feature_attrname(name),status)
381
382
    def feature_is_included(self,name):
383
        """Return 1 if feature is included, 0 if excluded, 'None' if unknown"""
384
        return getattr(self,self._feature_attrname(name))
385
386
    def include_feature(self,name):
387
        """Request inclusion of feature named 'name'"""
388
389
        if self.feature_is_included(name)==0:
390
            descr = self.features[name].description
391
            raise DistutilsOptionError(
392
               descr + " is required, but was excluded or is not available"
393
           )
394
        self.features[name].include_in(self)
395
        self._set_feature(name,1)
396
397
    def include(self,**attrs):
398
        """Add items to distribution that are named in keyword arguments
399

400
        For example, 'dist.exclude(py_modules=["x"])' would add 'x' to
401
        the distribution's 'py_modules' attribute, if it was not already
402
        there.
403

404
        Currently, this method only supports inclusion for attributes that are
405
        lists or tuples.  If you need to add support for adding to other
406
        attributes in this or a subclass, you can add an '_include_X' method,
407
        where 'X' is the name of the attribute.  The method will be called with
408
        the value passed to 'include()'.  So, 'dist.include(foo={"bar":"baz"})'
409
        will try to call 'dist._include_foo({"bar":"baz"})', which can then
410
        handle whatever special inclusion logic is needed.
411
        """
412
        for k,v in attrs.items():
413
            include = getattr(self, '_include_'+k, None)
414
            if include:
415
                include(v)
416
            else:
417
                self._include_misc(k,v)
418
419
    def exclude_package(self,package):
420
        """Remove packages, modules, and extensions in named package"""
421
422
        pfx = package+'.'
423
        if self.packages:
424
            self.packages = [
425
                p for p in self.packages
426
                    if p != package and not p.startswith(pfx)
427
            ]
428
429
        if self.py_modules:
430
            self.py_modules = [
431
                p for p in self.py_modules
432
                    if p != package and not p.startswith(pfx)
433
            ]
434
435
        if self.ext_modules:
436
            self.ext_modules = [
437
                p for p in self.ext_modules
438
                    if p.name != package and not p.name.startswith(pfx)
439
            ]
440
441
442
    def has_contents_for(self,package):
443
        """Return true if 'exclude_package(package)' would do something"""
444
445
        pfx = package+'.'
446
447
        for p in self.iter_distribution_names():
448
            if p==package or p.startswith(pfx):
449
                return True
450
451
452
453
454
455
456
457
458
459
460
    def _exclude_misc(self,name,value):
461
        """Handle 'exclude()' for list/tuple attrs without a special handler"""
462
        if not isinstance(value,sequence):
463
            raise DistutilsSetupError(
464
                "%s: setting must be a list or tuple (%r)" % (name, value)
465
            )
466
        try:
467
            old = getattr(self,name)
468
        except AttributeError:
469
            raise DistutilsSetupError(
470
                "%s: No such distribution setting" % name
471
            )
472
        if old is not None and not isinstance(old,sequence):
473
            raise DistutilsSetupError(
474
                name+": this setting cannot be changed via include/exclude"
475
            )
476
        elif old:
477
            setattr(self,name,[item for item in old if item not in value])
478
479
    def _include_misc(self,name,value):
480
        """Handle 'include()' for list/tuple attrs without a special handler"""
481
482
        if not isinstance(value,sequence):
483
            raise DistutilsSetupError(
484
                "%s: setting must be a list (%r)" % (name, value)
485
            )
486
        try:
487
            old = getattr(self,name)
488
        except AttributeError:
489
            raise DistutilsSetupError(
490
                "%s: No such distribution setting" % name
491
            )
492
        if old is None:
493
            setattr(self,name,value)
494
        elif not isinstance(old,sequence):
495
            raise DistutilsSetupError(
496
                name+": this setting cannot be changed via include/exclude"
497
            )
498
        else:
499
            setattr(self,name,old+[item for item in value if item not in old])
500
501
    def exclude(self,**attrs):
502
        """Remove items from distribution that are named in keyword arguments
503

504
        For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from
505
        the distribution's 'py_modules' attribute.  Excluding packages uses
506
        the 'exclude_package()' method, so all of the package's contained
507
        packages, modules, and extensions are also excluded.
508

509
        Currently, this method only supports exclusion from attributes that are
510
        lists or tuples.  If you need to add support for excluding from other
511
        attributes in this or a subclass, you can add an '_exclude_X' method,
512
        where 'X' is the name of the attribute.  The method will be called with
513
        the value passed to 'exclude()'.  So, 'dist.exclude(foo={"bar":"baz"})'
514
        will try to call 'dist._exclude_foo({"bar":"baz"})', which can then
515
        handle whatever special exclusion logic is needed.
516
        """
517
        for k,v in attrs.items():
518
            exclude = getattr(self, '_exclude_'+k, None)
519
            if exclude:
520
                exclude(v)
521
            else:
522
                self._exclude_misc(k,v)
523
524
    def _exclude_packages(self,packages):
525
        if not isinstance(packages,sequence):
526
            raise DistutilsSetupError(
527
                "packages: setting must be a list or tuple (%r)" % (packages,)
528
            )
529
        map(self.exclude_package, packages)
530
531
532
533
534
535
536
537
538
539
540
541
542
    def _parse_command_opts(self, parser, args):
543
        # Remove --with-X/--without-X options when processing command args
544
        self.global_options = self.__class__.global_options
545
        self.negative_opt = self.__class__.negative_opt
546
547
        # First, expand any aliases
548
        command = args[0]
549
        aliases = self.get_option_dict('aliases')
550
        while command in aliases:
551
            src,alias = aliases[command]
552
            del aliases[command]    # ensure each alias can expand only once!
553
            import shlex
554
            args[:1] = shlex.split(alias,True)
555
            command = args[0]
556
557
        nargs = _Distribution._parse_command_opts(self, parser, args)
558
559
        # Handle commands that want to consume all remaining arguments
560
        cmd_class = self.get_command_class(command)
561
        if getattr(cmd_class,'command_consumes_arguments',None):
562
            self.get_option_dict(command)['args'] = ("command line", nargs)
563
            if nargs is not None:
564
                return []
565
566
        return nargs
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
    def get_cmdline_options(self):
584
        """Return a '{cmd: {opt:val}}' map of all command-line options
585

586
        Option names are all long, but do not include the leading '--', and
587
        contain dashes rather than underscores.  If the option doesn't take
588
        an argument (e.g. '--quiet'), the 'val' is 'None'.
589

590
        Note that options provided by config files are intentionally excluded.
591
        """
592
593
        d = {}
594
595
        for cmd,opts in self.command_options.items():
596
597
            for opt,(src,val) in opts.items():
598
599
                if src != "command line":
600
                    continue
601
602
                opt = opt.replace('_','-')
603
604
                if val==0:
605
                    cmdobj = self.get_command_obj(cmd)
606
                    neg_opt = self.negative_opt.copy()
607
                    neg_opt.update(getattr(cmdobj,'negative_opt',{}))
608
                    for neg,pos in neg_opt.items():
609
                        if pos==opt:
610
                            opt=neg
611
                            val=None
612
                            break
613
                    else:
614
                        raise AssertionError("Shouldn't be able to get here")
615
616
                elif val==1:
617
                    val = None
618
619
                d.setdefault(cmd,{})[opt] = val
620
621
        return d
622
623
624
    def iter_distribution_names(self):
625
        """Yield all packages, modules, and extension names in distribution"""
626
627
        for pkg in self.packages or ():
628
            yield pkg
629
630
        for module in self.py_modules or ():
631
            yield module
632
633
        for ext in self.ext_modules or ():
634
            if isinstance(ext,tuple):
635
                name, buildinfo = ext
636
            else:
637
                name = ext.name
638
            if name.endswith('module'):
639
                name = name[:-6]
640
            yield name
641
642
# Install it throughout the distutils
643
for module in distutils.dist, distutils.core, distutils.cmd:
644
    module.Distribution = Distribution
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
class Feature:
666
    """A subset of the distribution that can be excluded if unneeded/wanted
667

668
    Features are created using these keyword arguments:
669

670
      'description' -- a short, human readable description of the feature, to
671
         be used in error messages, and option help messages.
672

673
      'standard' -- if true, the feature is included by default if it is
674
         available on the current system.  Otherwise, the feature is only
675
         included if requested via a command line '--with-X' option, or if
676
         another included feature requires it.  The default setting is 'False'.
677

678
      'available' -- if true, the feature is available for installation on the
679
         current system.  The default setting is 'True'.
680

681
      'optional' -- if true, the feature's inclusion can be controlled from the
682
         command line, using the '--with-X' or '--without-X' options.  If
683
         false, the feature's inclusion status is determined automatically,
684
         based on 'availabile', 'standard', and whether any other feature
685
         requires it.  The default setting is 'True'.
686

687
      'require_features' -- a string or sequence of strings naming features
688
         that should also be included if this feature is included.  Defaults to
689
         empty list.  May also contain 'Require' objects that should be
690
         added/removed from the distribution.
691

692
      'remove' -- a string or list of strings naming packages to be removed
693
         from the distribution if this feature is *not* included.  If the
694
         feature *is* included, this argument is ignored.  This argument exists
695
         to support removing features that "crosscut" a distribution, such as
696
         defining a 'tests' feature that removes all the 'tests' subpackages
697
         provided by other features.  The default for this argument is an empty
698
         list.  (Note: the named package(s) or modules must exist in the base
699
         distribution when the 'setup()' function is initially called.)
700

701
      other keywords -- any other keyword arguments are saved, and passed to
702
         the distribution's 'include()' and 'exclude()' methods when the
703
         feature is included or excluded, respectively.  So, for example, you
704
         could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be
705
         added or removed from the distribution as appropriate.
706

707
    A feature must include at least one 'requires', 'remove', or other
708
    keyword argument.  Otherwise, it can't affect the distribution in any way.
709
    Note also that you can subclass 'Feature' to create your own specialized
710
    feature types that modify the distribution in other ways when included or
711
    excluded.  See the docstrings for the various methods here for more detail.
712
    Aside from the methods, the only feature attributes that distributions look
713
    at are 'description' and 'optional'.
714
    """
715
    def __init__(self, description, standard=False, available=True,
716
        optional=True, require_features=(), remove=(), **extras
717
    ):
718
719
        self.description = description
720
        self.standard = standard
721
        self.available = available
722
        self.optional = optional
723
        if isinstance(require_features,(str,Require)):
724
            require_features = require_features,
725
726
        self.require_features = [
727
            r for r in require_features if isinstance(r,str)
728
        ]
729
        er = [r for r in require_features if not isinstance(r,str)]
730
        if er: extras['require_features'] = er
731
732
        if isinstance(remove,str):
733
            remove = remove,
734
        self.remove = remove
735
        self.extras = extras
736
737
        if not remove and not require_features and not extras:
738
            raise DistutilsSetupError(
739
                "Feature %s: must define 'require_features', 'remove', or at least one"
740
                " of 'packages', 'py_modules', etc."
741
            )
742
743
    def include_by_default(self):
744
        """Should this feature be included by default?"""
745
        return self.available and self.standard
746
747
    def include_in(self,dist):
748
749
        """Ensure feature and its requirements are included in distribution
750

751
        You may override this in a subclass to perform additional operations on
752
        the distribution.  Note that this method may be called more than once
753
        per feature, and so should be idempotent.
754

755
        """
756
757
        if not self.available:
758
            raise DistutilsPlatformError(
759
                self.description+" is required,"
760
                "but is not available on this platform"
761
            )
762
763
        dist.include(**self.extras)
764
765
        for f in self.require_features:
766
            dist.include_feature(f)
767
768
769
770
    def exclude_from(self,dist):
771
772
        """Ensure feature is excluded from distribution
773

774
        You may override this in a subclass to perform additional operations on
775
        the distribution.  This method will be called at most once per
776
        feature, and only after all included features have been asked to
777
        include themselves.
778
        """
779
780
        dist.exclude(**self.extras)
781
782
        if self.remove:
783
            for item in self.remove:
784
                dist.exclude_package(item)
785
786
787
788
    def validate(self,dist):
789
790
        """Verify that feature makes sense in context of distribution
791

792
        This method is called by the distribution just before it parses its
793
        command line.  It checks to ensure that the 'remove' attribute, if any,
794
        contains only valid package/module names that are present in the base
795
        distribution when 'setup()' is called.  You may override it in a
796
        subclass to perform any other required validation of the feature
797
        against a target distribution.
798
        """
799
800
        for item in self.remove:
801
            if not dist.has_contents_for(item):
802
                raise DistutilsSetupError(
803
                    "%s wants to be able to remove %s, but the distribution"
804
                    " doesn't contain any packages or modules under %s"
805
                    % (self.description, item, item)
806
                )
807
808
809
810
def check_packages(dist, attr, value):
811
    for pkgname in value:
812
        if not re.match(r'\w+(\.\w+)*', pkgname):
813
            distutils.log.warn(
814
                "WARNING: %r not a valid package name; please use only"
815
                ".-separated package names in setup.py", pkgname
816
            )