summaryrefslogtreecommitdiff
path: root/setuptools/dist.py
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2016-09-27 14:24:22 -0500
committerJason R. Coombs <jaraco@jaraco.com>2016-09-27 14:24:22 -0500
commit66a6724da8eda3336643dee086da2a3495e6422a (patch)
tree64043e9782491bde3a3a9ae2314cc59451a6c9c0 /setuptools/dist.py
parentdf3905616933c90af95e99f705b800a2f5c1c921 (diff)
parent35ea365b50bd1a64375fdbcce187affab22af3b7 (diff)
downloadpython-setuptools-git-setuptools-scm.tar.gz
Merge with mastersetuptools-scm
Diffstat (limited to 'setuptools/dist.py')
-rw-r--r--setuptools/dist.py312
1 files changed, 177 insertions, 135 deletions
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 086e0a58..364f2b4d 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -2,16 +2,15 @@ __all__ = ['Distribution']
import re
import os
-import sys
import warnings
import numbers
import distutils.log
import distutils.core
import distutils.cmd
import distutils.dist
-from distutils.core import Distribution as _Distribution
from distutils.errors import (DistutilsOptionError, DistutilsPlatformError,
DistutilsSetupError)
+from distutils.util import rfc822_escape
from setuptools.extern import six
from setuptools.extern.six.moves import map
@@ -19,70 +18,94 @@ from pkg_resources.extern import packaging
from setuptools.depends import Require
from setuptools import windows_support
+from setuptools.monkey import get_unpatched
import pkg_resources
def _get_unpatched(cls):
- """Protect against re-patching the distutils if reloaded
+ warnings.warn("Do not call this function", DeprecationWarning)
+ return get_unpatched(cls)
- Also ensures that no other distutils extension monkeypatched the distutils
- first.
- """
- while cls.__module__.startswith('setuptools'):
- cls, = cls.__bases__
- if not cls.__module__.startswith('distutils'):
- raise AssertionError(
- "distutils has already been patched by %r" % cls
- )
- return cls
-_Distribution = _get_unpatched(_Distribution)
-
-def _patch_distribution_metadata_write_pkg_info():
+# Based on Python 3.5 version
+def write_pkg_file(self, file):
+ """Write the PKG-INFO format data to a file object.
"""
- Workaround issue #197 - Python 3 prior to 3.2.2 uses an environment-local
- encoding to save the pkg_info. Monkey-patch its write_pkg_info method to
- correct this undesirable behavior.
+ version = '1.0'
+ if (self.provides or self.requires or self.obsoletes or
+ self.classifiers or self.download_url):
+ version = '1.1'
+ # Setuptools specific for PEP 345
+ if hasattr(self, 'python_requires'):
+ version = '1.2'
+
+ file.write('Metadata-Version: %s\n' % version)
+ file.write('Name: %s\n' % self.get_name())
+ file.write('Version: %s\n' % self.get_version())
+ file.write('Summary: %s\n' % self.get_description())
+ file.write('Home-page: %s\n' % self.get_url())
+ file.write('Author: %s\n' % self.get_contact())
+ file.write('Author-email: %s\n' % self.get_contact_email())
+ file.write('License: %s\n' % self.get_license())
+ if self.download_url:
+ file.write('Download-URL: %s\n' % self.download_url)
+
+ long_desc = rfc822_escape(self.get_long_description())
+ file.write('Description: %s\n' % long_desc)
+
+ keywords = ','.join(self.get_keywords())
+ if keywords:
+ file.write('Keywords: %s\n' % keywords)
+
+ self._write_list(file, 'Platform', self.get_platforms())
+ self._write_list(file, 'Classifier', self.get_classifiers())
+
+ # PEP 314
+ self._write_list(file, 'Requires', self.get_requires())
+ self._write_list(file, 'Provides', self.get_provides())
+ self._write_list(file, 'Obsoletes', self.get_obsoletes())
+
+ # Setuptools specific for PEP 345
+ if hasattr(self, 'python_requires'):
+ file.write('Requires-Python: %s\n' % self.python_requires)
+
+
+# from Python 3.4
+def write_pkg_info(self, base_dir):
+ """Write the PKG-INFO file into the release tree.
"""
- environment_local = (3,) <= sys.version_info[:3] < (3, 2, 2)
- if not environment_local:
- return
-
- # from Python 3.4
- def write_pkg_info(self, base_dir):
- """Write the PKG-INFO file into the release tree.
- """
- with open(os.path.join(base_dir, 'PKG-INFO'), 'w',
- encoding='UTF-8') as pkg_info:
- self.write_pkg_file(pkg_info)
+ with open(os.path.join(base_dir, 'PKG-INFO'), 'w',
+ encoding='UTF-8') as pkg_info:
+ self.write_pkg_file(pkg_info)
- distutils.dist.DistributionMetadata.write_pkg_info = write_pkg_info
-_patch_distribution_metadata_write_pkg_info()
sequence = tuple, list
+
def check_importable(dist, attr, value):
try:
- ep = pkg_resources.EntryPoint.parse('x='+value)
+ ep = pkg_resources.EntryPoint.parse('x=' + value)
assert not ep.extras
- except (TypeError,ValueError,AttributeError,AssertionError):
+ except (TypeError, ValueError, AttributeError, AssertionError):
raise DistutilsSetupError(
"%r must be importable 'module:attrs' string (got %r)"
- % (attr,value)
+ % (attr, value)
)
def assert_string_list(dist, attr, value):
"""Verify that value is a string list or None"""
try:
- assert ''.join(value)!=value
- except (TypeError,ValueError,AttributeError,AssertionError):
+ assert ''.join(value) != value
+ except (TypeError, ValueError, AttributeError, AssertionError):
raise DistutilsSetupError(
- "%r must be a list of strings (got %r)" % (attr,value)
+ "%r must be a list of strings (got %r)" % (attr, value)
)
+
+
def check_nsp(dist, attr, value):
"""Verify that namespace packages are valid"""
- assert_string_list(dist,attr,value)
+ assert_string_list(dist, attr, value)
for nsp in value:
if not dist.has_contents_for(nsp):
raise DistutilsSetupError(
@@ -97,22 +120,24 @@ def check_nsp(dist, attr, value):
" is not: please correct this in setup.py", nsp, parent
)
+
def check_extras(dist, attr, value):
"""Verify that extras_require mapping is valid"""
try:
- for k,v in value.items():
+ for k, v in value.items():
if ':' in k:
- k,m = k.split(':',1)
+ k, m = k.split(':', 1)
if pkg_resources.invalid_marker(m):
- raise DistutilsSetupError("Invalid environment marker: "+m)
+ raise DistutilsSetupError("Invalid environment marker: " + m)
list(pkg_resources.parse_requirements(v))
- except (TypeError,ValueError,AttributeError):
+ except (TypeError, ValueError, AttributeError):
raise DistutilsSetupError(
"'extras_require' must be a dictionary whose values are "
"strings or lists of strings containing valid project/version "
"requirement specifiers."
)
+
def assert_bool(dist, attr, value):
"""Verify that value is True, False, 0, or 1"""
if bool(value) != value:
@@ -131,6 +156,19 @@ def check_requirements(dist, attr, value):
)
raise DistutilsSetupError(tmpl.format(attr=attr, error=error))
+
+def check_specifier(dist, attr, value):
+ """Verify that value is a valid version specifier"""
+ try:
+ packaging.specifiers.SpecifierSet(value)
+ except packaging.specifiers.InvalidSpecifier as error:
+ tmpl = (
+ "{attr!r} must be a string or list of strings "
+ "containing valid version specifiers; {error}"
+ )
+ raise DistutilsSetupError(tmpl.format(attr=attr, error=error))
+
+
def check_entry_points(dist, attr, value):
"""Verify that entry_points map is parseable"""
try:
@@ -138,25 +176,30 @@ def check_entry_points(dist, attr, value):
except ValueError as e:
raise DistutilsSetupError(e)
+
def check_test_suite(dist, attr, value):
if not isinstance(value, six.string_types):
raise DistutilsSetupError("test_suite must be a string")
+
def check_package_data(dist, attr, value):
"""Verify that value is a dictionary of package names to glob lists"""
- if isinstance(value,dict):
- for k,v in value.items():
- if not isinstance(k,str): break
- try: iter(v)
+ if isinstance(value, dict):
+ for k, v in value.items():
+ if not isinstance(k, str):
+ break
+ try:
+ iter(v)
except TypeError:
break
else:
return
raise DistutilsSetupError(
- attr+" must be a dictionary mapping package names to lists of "
+ attr + " must be a dictionary mapping package names to lists of "
"wildcard patterns"
)
+
def check_packages(dist, attr, value):
for pkgname in value:
if not re.match(r'\w+(\.\w+)*', pkgname):
@@ -166,6 +209,9 @@ def check_packages(dist, attr, value):
)
+_Distribution = get_unpatched(distutils.core.Distribution)
+
+
class Distribution(_Distribution):
"""Distribution with support for features, tests, and package data
@@ -264,12 +310,12 @@ class Distribution(_Distribution):
# Make sure we have any eggs needed to interpret 'attrs'
if attrs is not None:
self.dependency_links = attrs.pop('dependency_links', [])
- assert_string_list(self,'dependency_links',self.dependency_links)
+ assert_string_list(self, 'dependency_links', self.dependency_links)
if attrs and 'setup_requires' in attrs:
self.fetch_build_eggs(attrs['setup_requires'])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
vars(self).setdefault(ep.name, None)
- _Distribution.__init__(self,attrs)
+ _Distribution.__init__(self, attrs)
if isinstance(self.metadata.version, numbers.Number):
# Some people apparently take "version number" too literally :)
self.metadata.version = str(self.metadata.version)
@@ -293,6 +339,8 @@ class Distribution(_Distribution):
"setuptools, pip, and PyPI. Please see PEP 440 for more "
"details." % self.metadata.version
)
+ if getattr(self, 'python_requires', None):
+ self.metadata.python_requires = self.python_requires
def parse_command_line(self):
"""Process features after parsing command line options"""
@@ -301,9 +349,9 @@ class Distribution(_Distribution):
self._finalize_features()
return result
- def _feature_attrname(self,name):
+ def _feature_attrname(self, name):
"""Convert feature name to corresponding option attribute name"""
- return 'with_'+name.replace('-','_')
+ return 'with_' + name.replace('-', '_')
def fetch_build_eggs(self, requires):
"""Resolve pre-setup requirements"""
@@ -314,6 +362,7 @@ class Distribution(_Distribution):
)
for dist in resolved_dists:
pkg_resources.working_set.add(dist, replace=True)
+ return resolved_dists
def finalize_options(self):
_Distribution.finalize_options(self)
@@ -321,7 +370,7 @@ class Distribution(_Distribution):
self._set_global_opts_from_features()
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
- value = getattr(self,ep.name,None)
+ value = getattr(self, ep.name, None)
if value is not None:
ep.require(installer=self.fetch_build_egg)
ep.load()(self, ep.name, value)
@@ -354,7 +403,7 @@ class Distribution(_Distribution):
cmd.package_index.to_scan = []
except AttributeError:
from setuptools.command.easy_install import easy_install
- dist = self.__class__({'script_args':['easy_install']})
+ dist = self.__class__({'script_args': ['easy_install']})
dist.parse_config_files()
opts = dist.get_option_dict('easy_install')
keep = (
@@ -385,20 +434,20 @@ class Distribution(_Distribution):
go = []
no = self.negative_opt.copy()
- for name,feature in self.features.items():
- self._set_feature(name,None)
+ for name, feature in self.features.items():
+ self._set_feature(name, None)
feature.validate(self)
if feature.optional:
descr = feature.description
incdef = ' (default)'
- excdef=''
+ excdef = ''
if not feature.include_by_default():
excdef, incdef = incdef, excdef
- go.append(('with-'+name, None, 'include '+descr+incdef))
- go.append(('without-'+name, None, 'exclude '+descr+excdef))
- no['without-'+name] = 'with-'+name
+ go.append(('with-' + name, None, 'include ' + descr + incdef))
+ go.append(('without-' + name, None, 'exclude ' + descr + excdef))
+ no['without-' + name] = 'with-' + name
self.global_options = self.feature_options = go + self.global_options
self.negative_opt = self.feature_negopt = no
@@ -407,25 +456,25 @@ class Distribution(_Distribution):
"""Add/remove features and resolve dependencies between them"""
# First, flag all the enabled items (and thus their dependencies)
- for name,feature in self.features.items():
+ for name, feature in self.features.items():
enabled = self.feature_is_included(name)
if enabled or (enabled is None and feature.include_by_default()):
feature.include_in(self)
- self._set_feature(name,1)
+ self._set_feature(name, 1)
# Then disable the rest, so that off-by-default features don't
# get flagged as errors when they're required by an enabled feature
- for name,feature in self.features.items():
+ for name, feature in self.features.items():
if not self.feature_is_included(name):
feature.exclude_from(self)
- self._set_feature(name,0)
+ self._set_feature(name, 0)
def get_command_class(self, command):
"""Pluggable version of get_command_class()"""
if command in self.cmdclass:
return self.cmdclass[command]
- for ep in pkg_resources.iter_entry_points('distutils.commands',command):
+ for ep in pkg_resources.iter_entry_points('distutils.commands', command):
ep.require(installer=self.fetch_build_egg)
self.cmdclass[command] = cmdclass = ep.load()
return cmdclass
@@ -448,26 +497,26 @@ class Distribution(_Distribution):
self.cmdclass[ep.name] = cmdclass
return _Distribution.get_command_list(self)
- def _set_feature(self,name,status):
+ def _set_feature(self, name, status):
"""Set feature's inclusion status"""
- setattr(self,self._feature_attrname(name),status)
+ setattr(self, self._feature_attrname(name), status)
- def feature_is_included(self,name):
+ def feature_is_included(self, name):
"""Return 1 if feature is included, 0 if excluded, 'None' if unknown"""
- return getattr(self,self._feature_attrname(name))
+ return getattr(self, self._feature_attrname(name))
- def include_feature(self,name):
+ def include_feature(self, name):
"""Request inclusion of feature named 'name'"""
- if self.feature_is_included(name)==0:
+ if self.feature_is_included(name) == 0:
descr = self.features[name].description
raise DistutilsOptionError(
descr + " is required, but was excluded or is not available"
)
self.features[name].include_in(self)
- self._set_feature(name,1)
+ self._set_feature(name, 1)
- def include(self,**attrs):
+ def include(self, **attrs):
"""Add items to distribution that are named in keyword arguments
For example, 'dist.exclude(py_modules=["x"])' would add 'x' to
@@ -482,86 +531,86 @@ class Distribution(_Distribution):
will try to call 'dist._include_foo({"bar":"baz"})', which can then
handle whatever special inclusion logic is needed.
"""
- for k,v in attrs.items():
- include = getattr(self, '_include_'+k, None)
+ for k, v in attrs.items():
+ include = getattr(self, '_include_' + k, None)
if include:
include(v)
else:
- self._include_misc(k,v)
+ self._include_misc(k, v)
- def exclude_package(self,package):
+ def exclude_package(self, package):
"""Remove packages, modules, and extensions in named package"""
- pfx = package+'.'
+ pfx = package + '.'
if self.packages:
self.packages = [
p for p in self.packages
- if p != package and not p.startswith(pfx)
+ if p != package and not p.startswith(pfx)
]
if self.py_modules:
self.py_modules = [
p for p in self.py_modules
- if p != package and not p.startswith(pfx)
+ if p != package and not p.startswith(pfx)
]
if self.ext_modules:
self.ext_modules = [
p for p in self.ext_modules
- if p.name != package and not p.name.startswith(pfx)
+ if p.name != package and not p.name.startswith(pfx)
]
- def has_contents_for(self,package):
+ def has_contents_for(self, package):
"""Return true if 'exclude_package(package)' would do something"""
- pfx = package+'.'
+ pfx = package + '.'
for p in self.iter_distribution_names():
- if p==package or p.startswith(pfx):
+ if p == package or p.startswith(pfx):
return True
- def _exclude_misc(self,name,value):
+ def _exclude_misc(self, name, value):
"""Handle 'exclude()' for list/tuple attrs without a special handler"""
- if not isinstance(value,sequence):
+ if not isinstance(value, sequence):
raise DistutilsSetupError(
"%s: setting must be a list or tuple (%r)" % (name, value)
)
try:
- old = getattr(self,name)
+ old = getattr(self, name)
except AttributeError:
raise DistutilsSetupError(
"%s: No such distribution setting" % name
)
- if old is not None and not isinstance(old,sequence):
+ if old is not None and not isinstance(old, sequence):
raise DistutilsSetupError(
- name+": this setting cannot be changed via include/exclude"
+ name + ": this setting cannot be changed via include/exclude"
)
elif old:
- setattr(self,name,[item for item in old if item not in value])
+ setattr(self, name, [item for item in old if item not in value])
- def _include_misc(self,name,value):
+ def _include_misc(self, name, value):
"""Handle 'include()' for list/tuple attrs without a special handler"""
- if not isinstance(value,sequence):
+ if not isinstance(value, sequence):
raise DistutilsSetupError(
"%s: setting must be a list (%r)" % (name, value)
)
try:
- old = getattr(self,name)
+ old = getattr(self, name)
except AttributeError:
raise DistutilsSetupError(
"%s: No such distribution setting" % name
)
if old is None:
- setattr(self,name,value)
- elif not isinstance(old,sequence):
+ setattr(self, name, value)
+ elif not isinstance(old, sequence):
raise DistutilsSetupError(
- name+": this setting cannot be changed via include/exclude"
+ name + ": this setting cannot be changed via include/exclude"
)
else:
- setattr(self,name,old+[item for item in value if item not in old])
+ setattr(self, name, old + [item for item in value if item not in old])
- def exclude(self,**attrs):
+ def exclude(self, **attrs):
"""Remove items from distribution that are named in keyword arguments
For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from
@@ -577,15 +626,15 @@ class Distribution(_Distribution):
will try to call 'dist._exclude_foo({"bar":"baz"})', which can then
handle whatever special exclusion logic is needed.
"""
- for k,v in attrs.items():
- exclude = getattr(self, '_exclude_'+k, None)
+ for k, v in attrs.items():
+ exclude = getattr(self, '_exclude_' + k, None)
if exclude:
exclude(v)
else:
- self._exclude_misc(k,v)
+ self._exclude_misc(k, v)
- def _exclude_packages(self,packages):
- if not isinstance(packages,sequence):
+ def _exclude_packages(self, packages):
+ if not isinstance(packages, sequence):
raise DistutilsSetupError(
"packages: setting must be a list or tuple (%r)" % (packages,)
)
@@ -600,17 +649,17 @@ class Distribution(_Distribution):
command = args[0]
aliases = self.get_option_dict('aliases')
while command in aliases:
- src,alias = aliases[command]
+ src, alias = aliases[command]
del aliases[command] # ensure each alias can expand only once!
import shlex
- args[:1] = shlex.split(alias,True)
+ args[:1] = shlex.split(alias, True)
command = args[0]
nargs = _Distribution._parse_command_opts(self, parser, args)
# Handle commands that want to consume all remaining arguments
cmd_class = self.get_command_class(command)
- if getattr(cmd_class,'command_consumes_arguments',None):
+ if getattr(cmd_class, 'command_consumes_arguments', None):
self.get_option_dict(command)['args'] = ("command line", nargs)
if nargs is not None:
return []
@@ -629,31 +678,31 @@ class Distribution(_Distribution):
d = {}
- for cmd,opts in self.command_options.items():
+ for cmd, opts in self.command_options.items():
- for opt,(src,val) in opts.items():
+ for opt, (src, val) in opts.items():
if src != "command line":
continue
- opt = opt.replace('_','-')
+ opt = opt.replace('_', '-')
- if val==0:
+ if val == 0:
cmdobj = self.get_command_obj(cmd)
neg_opt = self.negative_opt.copy()
- neg_opt.update(getattr(cmdobj,'negative_opt',{}))
- for neg,pos in neg_opt.items():
- if pos==opt:
- opt=neg
- val=None
+ neg_opt.update(getattr(cmdobj, 'negative_opt', {}))
+ for neg, pos in neg_opt.items():
+ if pos == opt:
+ opt = neg
+ val = None
break
else:
raise AssertionError("Shouldn't be able to get here")
- elif val==1:
+ elif val == 1:
val = None
- d.setdefault(cmd,{})[opt] = val
+ d.setdefault(cmd, {})[opt] = val
return d
@@ -667,7 +716,7 @@ class Distribution(_Distribution):
yield module
for ext in self.ext_modules or ():
- if isinstance(ext,tuple):
+ if isinstance(ext, tuple):
name, buildinfo = ext
else:
name = ext.name
@@ -711,11 +760,6 @@ class Distribution(_Distribution):
sys.stdout.detach(), encoding, errors, newline, line_buffering)
-# Install it throughout the distutils
-for module in distutils.dist, distutils.core, distutils.cmd:
- module.Distribution = Distribution
-
-
class Feature:
"""
**deprecated** -- The `Feature` facility was never completely implemented
@@ -790,16 +834,17 @@ class Feature:
self.standard = standard
self.available = available
self.optional = optional
- if isinstance(require_features,(str,Require)):
+ if isinstance(require_features, (str, Require)):
require_features = require_features,
self.require_features = [
- r for r in require_features if isinstance(r,str)
+ r for r in require_features if isinstance(r, str)
]
- er = [r for r in require_features if not isinstance(r,str)]
- if er: extras['require_features'] = er
+ er = [r for r in require_features if not isinstance(r, str)]
+ if er:
+ extras['require_features'] = er
- if isinstance(remove,str):
+ if isinstance(remove, str):
remove = remove,
self.remove = remove
self.extras = extras
@@ -814,8 +859,7 @@ class Feature:
"""Should this feature be included by default?"""
return self.available and self.standard
- def include_in(self,dist):
-
+ def include_in(self, dist):
"""Ensure feature and its requirements are included in distribution
You may override this in a subclass to perform additional operations on
@@ -826,7 +870,7 @@ class Feature:
if not self.available:
raise DistutilsPlatformError(
- self.description+" is required, "
+ self.description + " is required, "
"but is not available on this platform"
)
@@ -835,8 +879,7 @@ class Feature:
for f in self.require_features:
dist.include_feature(f)
- def exclude_from(self,dist):
-
+ def exclude_from(self, dist):
"""Ensure feature is excluded from distribution
You may override this in a subclass to perform additional operations on
@@ -851,8 +894,7 @@ class Feature:
for item in self.remove:
dist.exclude_package(item)
- def validate(self,dist):
-
+ def validate(self, dist):
"""Verify that feature makes sense in context of distribution
This method is called by the distribution just before it parses its