diff options
| author | PJ Eby <distutils-sig@python.org> | 2005-08-06 18:46:28 +0000 |
|---|---|---|
| committer | PJ Eby <distutils-sig@python.org> | 2005-08-06 18:46:28 +0000 |
| commit | 8a29467d941a7983d5f6eadc5c0e1624417944b6 (patch) | |
| tree | b270afe3a01c9bead94060de3c3adfa590bd933f /setuptools | |
| parent | a762d97ea517f64a405d82ad7acaa85d3eb30c39 (diff) | |
| download | python-setuptools-git-8a29467d941a7983d5f6eadc5c0e1624417944b6.tar.gz | |
Enhanced setuptools infrastructure to support distutils extensions that
can be plugged in at setup() time to define new setup() arguments or
distutils commands. This allows modularization and reuse of distutils
extensions in a way that was previously not possible.
--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041180
Diffstat (limited to 'setuptools')
| -rw-r--r-- | setuptools/__init__.py | 22 | ||||
| -rwxr-xr-x | setuptools/command/egg_info.py | 14 | ||||
| -rw-r--r-- | setuptools/dist.py | 296 | ||||
| -rw-r--r-- | setuptools/extension.py | 10 |
4 files changed, 217 insertions, 125 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 05c4a73e..eeb2975b 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,6 +1,6 @@ """Extensions to the 'distutils' for large or complex distributions""" +from setuptools.dist import Distribution, Feature, _get_unpatched import distutils.core, setuptools.command -from setuptools.dist import Distribution, Feature from setuptools.extension import Extension from setuptools.depends import Require from distutils.core import Command as _Command @@ -39,17 +39,9 @@ def find_packages(where='.', exclude=()): out = [item for item in out if not fnmatchcase(item,pat)] return out -def setup(**attrs): - """Do package setup - - This function takes the same arguments as 'distutils.core.setup()', except - that the default distribution class is 'setuptools.dist.Distribution'. See - that class' documentation for details on the new keyword arguments that it - makes available via this function. - """ - attrs.setdefault("distclass",Distribution) - return distutils.core.setup(**attrs) +setup = distutils.core.setup +_Command = _get_unpatched(_Command) class Command(_Command): __doc__ = _Command.__doc__ @@ -68,6 +60,14 @@ class Command(_Command): setattr(cmd,k,v) # update command with keywords return cmd +import distutils.core +distutils.core.Command = Command # we can't patch distutils.cmd, alas + + + + + + diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 8577230f..7d0a1473 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -9,7 +9,6 @@ from distutils.errors import * from distutils import log from pkg_resources import parse_requirements, safe_name, \ safe_version, yield_lines, EntryPoint -from setuptools.dist import iter_distribution_names class egg_info(Command): @@ -39,6 +38,7 @@ class egg_info(Command): + def finalize_options (self): self.egg_name = safe_name(self.distribution.get_name()) self.egg_version = self.tagged_version() @@ -149,7 +149,7 @@ class egg_info(Command): def write_toplevel_names(self): pkgs = dict.fromkeys( [k.split('.',1)[0] - for k in iter_distribution_names(self.distribution) + for k in self.distribution.iter_distribution_names() ] ) toplevel = os.path.join(self.egg_info, "top_level.txt") @@ -164,12 +164,8 @@ class egg_info(Command): def write_or_delete_dist_arg(self, argname, filename=None): value = getattr(self.distribution, argname, None) - if value is None: - return - filename = filename or argname+'.txt' filename = os.path.join(self.egg_info,filename) - if value: log.info("writing %s", filename) if not self.dry_run: @@ -177,8 +173,12 @@ class egg_info(Command): f.write('\n'.join(value)) f.write('\n') f.close() - elif os.path.exists(filename): + if value is None: + log.warn( + "%s not set in setup(), but %s exists", argname, filename + ) + return log.info("deleting %s", filename) if not self.dry_run: os.unlink(filename) diff --git a/setuptools/dist.py b/setuptools/dist.py index 40234b4e..6d226d68 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -9,36 +9,118 @@ from setuptools.command.sdist import sdist from setuptools.command.install_lib import install_lib from distutils.errors import DistutilsOptionError, DistutilsPlatformError from distutils.errors import DistutilsSetupError -import setuptools, pkg_resources - -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): - self.cmdclass[command] = cmdclass = ep.load() - return cmdclass - else: - return _old_get_command_class(self, command) - -def print_commands(self): - for ep in pkg_resources.iter_entry_points('distutils.commands'): - if ep.name not in self.cmdclass: - cmdclass = ep.load(False) # don't require extras, we're not running - self.cmdclass[ep.name] = cmdclass - return _old_print_commands(self) - -for meth in 'print_commands', 'get_command_class': - if getattr(_Distribution,meth).im_func.func_globals is not globals(): - globals()['_old_'+meth] = getattr(_Distribution,meth) - setattr(_Distribution, meth, globals()[meth]) +import setuptools, pkg_resources, distutils.core, distutils.dist, distutils.cmd +import os + +def _get_unpatched(cls): + """Protect against re-patching the distutils if reloaded + + 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) sequence = tuple, list + + + + + + +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): + raise DistutilsSetupError( + "%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) + + for nsp in value: + for name in dist.iter_distribution_names(): + if name.startswith(nsp+'.'): break + else: + raise DistutilsSetupError( + "Distribution contains no modules or packages for " + + "namespace package %r" % nsp + ) + +def check_extras(dist, attr, value): + """Verify that extras_require mapping is valid""" + try: + for k,v in value.items(): + list(pkg_resources.parse_requirements(v)) + 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: + raise DistutilsSetupError( + "%r must be a boolean value (got %r)" % (attr,value) + ) + +def check_install_requires(dist, attr, value): + """Verify that install_requires is a valid requirements list""" + try: + list(pkg_resources.parse_requirements(value)) + except (TypeError,ValueError): + raise DistutilsSetupError( + "'install_requires' must be a string or list of strings " + "containing valid project/version requirement specifiers" + ) + +def check_entry_points(dist, attr, value): + """Verify that entry_points map is parseable""" + try: + pkg_resources.EntryPoint.parse_map(value) + except ValueError, e: + raise DistutilsSetupError(e) + + +def check_test_suite(dist, attr, value): + if not isinstance(value,basestring): + raise DistutilsSetupError("test_suite must be a string") + + + + + + + + + + + + + + + + + + + + class Distribution(_Distribution): """Distribution with support for features, tests, and package data @@ -125,16 +207,19 @@ class Distribution(_Distribution): have_package_data = hasattr(self, "package_data") if not have_package_data: self.package_data = {} + self.features = {} - self.test_suite = None self.requires = [] - self.install_requires = [] - self.extras_require = {} self.dist_files = [] - self.zip_safe = None - self.namespace_packages = None - self.eager_resources = None - self.entry_points = None + + if attrs and 'setup_requires' in attrs: + # Make sure we have any eggs needed to interpret 'attrs' + self.fetch_build_eggs(attrs.pop('setup_requires')) + + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + if not hasattr(self,ep.name): + setattr(self,ep.name,None) + _Distribution.__init__(self,attrs) @@ -145,20 +230,17 @@ class Distribution(_Distribution): self._finalize_features() return result - def _feature_attrname(self,name): """Convert feature name to corresponding option attribute name""" return 'with_'+name.replace('-','_') - - - - - - - - - + def fetch_build_eggs(self, requires): + """Resolve pre-setup requirements""" + from pkg_resources import working_set, parse_requirements + for dist in working_set.resolve( + parse_requirements(requires), installer=self.fetch_build_egg + ): + working_set.add(dist) @@ -174,49 +256,34 @@ class Distribution(_Distribution): "setuptools. Please remove it from your setup script." ) - try: - list(pkg_resources.parse_requirements(self.install_requires)) - except (TypeError,ValueError): - raise DistutilsSetupError( - "'install_requires' must be a string or list of strings " - "containing valid project/version requirement specifiers" - ) + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + value = getattr(self,ep.name,None) + if value is not None: + ep.require(installer=self.fetch_build_egg) + ep.load()(self, ep.name, value) + + def fetch_build_egg(self, req): + """Fetch an egg needed for building""" try: - for k,v in self.extras_require.items(): - list(pkg_resources.parse_requirements(v)) - 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." + cmd = self._egg_fetcher + except AttributeError: + from setuptools.command.easy_install import easy_install + cmd = easy_install( + self.__class__({'script_args':['easy_install']}), + args="x", install_dir=os.curdir, exclude_scripts=True, + always_copy=False, build_directory=None, editable=False, + upgrade=False ) + cmd.ensure_finalized() + cmd.zip_ok = None # override any setup.cfg setting for these + cmd.build_directory = None + self._egg_fetcher = cmd - for attr in 'namespace_packages','eager_resources': - value = getattr(self,attr,None) - if value is not None: - try: - assert ''.join(value)!=value - except (TypeError,ValueError,AttributeError,AssertionError): - raise DistutilsSetupError( - "%r must be a list of strings (got %r)" % (attr,value) - ) + return cmd.easy_install(req) - for nsp in self.namespace_packages or (): - for name in iter_distribution_names(self): - if name.startswith(nsp+'.'): break - else: - raise DistutilsSetupError( - "Distribution contains no modules or packages for " + - "namespace package %r" % nsp - ) - if self.entry_points is not None: - try: - pkg_resources.EntryPoint.parse_map(self.entry_points) - except ValueError, e: - raise DistutilsSetupError(e) def _set_global_opts_from_features(self): """Add --with-X/--without-X options based on optional features""" @@ -244,22 +311,7 @@ class Distribution(_Distribution): - def _finalize_features(self): - """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(): - 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) - # 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(): - if not self.feature_is_included(name): - feature.exclude_from(self) - self._set_feature(name,0) @@ -274,12 +326,42 @@ class Distribution(_Distribution): + def _finalize_features(self): + """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(): + 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) + # 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(): + if not self.feature_is_included(name): + feature.exclude_from(self) + 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): + ep.require(installer=self.fetch_build_egg) + self.cmdclass[command] = cmdclass = ep.load() + return cmdclass + else: + return _Distribution.get_command_class(self, command) + def print_commands(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + cmdclass = ep.load(False) # don't require extras, we're not running + self.cmdclass[ep.name] = cmdclass + return _Distribution.print_commands(self) @@ -572,25 +654,25 @@ class Distribution(_Distribution): return d -def iter_distribution_names(distribution): - """Yield all packages, modules, and extensions declared by distribution""" - - for pkg in distribution.packages or (): - yield pkg - - for module in distribution.py_modules or (): - yield module - - for ext in distribution.ext_modules or (): - if isinstance(ext,tuple): - name,buildinfo = ext - yield name - else: - yield ext.name + def iter_distribution_names(self): + """Yield all packages, modules, and extension names in distribution""" + for pkg in self.packages or (): + yield pkg + for module in self.py_modules or (): + yield module + for ext in self.ext_modules or (): + if isinstance(ext,tuple): + name,buildinfo = ext + yield name + else: + yield ext.name +# Install it throughout the distutils +for module in distutils.dist, distutils.core, distutils.cmd: + module.Distribution = Distribution diff --git a/setuptools/extension.py b/setuptools/extension.py index 55a4d946..37b62576 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -7,6 +7,9 @@ except ImportError: # Pyrex isn't around, so fix up the sources + from dist import _get_unpatched + _Extension = _get_unpatched(_Extension) + class Extension(_Extension): """Extension that uses '.c' files in place of '.pyx' files""" @@ -21,7 +24,14 @@ except ImportError: sources.append(s) self.sources = sources + import sys, distutils.core, distutils.extension + distutils.core.Extension = Extension + distutils.extension.Extension = Extension + if 'distutils.command.build_ext' in sys.modules: + sys.modules['distutils.command.build_ext'].Extension = Extension + else: # Pyrex is here, just use regular extension type Extension = _Extension + |
