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
__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
            )
817