aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO.txt58
-rwxr-xr-xsetup.py21
-rw-r--r--setuptools/__init__.py82
-rw-r--r--setuptools/command/__init__.py11
-rw-r--r--setuptools/command/build_ext.py7
-rw-r--r--setuptools/command/build_py.py123
-rw-r--r--setuptools/command/depends.py27
-rw-r--r--setuptools/command/install.py11
-rw-r--r--setuptools/command/install_lib.py17
-rw-r--r--setuptools/command/test.py82
-rw-r--r--setuptools/depends.py246
-rw-r--r--setuptools/dist.py453
-rw-r--r--setuptools/extension.py27
-rw-r--r--setuptools/tests/__init__.py410
-rw-r--r--setuptools_boot.py123
15 files changed, 1698 insertions, 0 deletions
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 00000000..de619c04
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,58 @@
+To-Do
+
+* Automatic download and installation of dependencies
+
+ * install_deps command (install runtime dependencies)
+
+ * compute child command line, abort if user specified incompatible options
+
+ * OPEN ISSUE: should parent install command include child install's files?
+
+ * Dependency class
+
+ * Check for presence/version via file existence, regular expression match,
+ version comparison (using 'distutils.version' classes), installed on
+ sys.path, or require just installation directory
+
+ * Find appropriate release, or explain why not
+
+ * Base URL(s) and distribution name
+
+ * Release class
+
+ * Distro type - source v. binary (determine via extension?)
+
+ * Platform requirements, whether compiler needed (how can we check?)
+
+ * Download URL, default from extension + dependency
+
+ * Download + extract to target dir
+
+ * run child install
+
+ * build_deps command (install build-time dependencies)
+
+* Build and install documentation sets
+
+* Installation database similar to PEP 262
+
+ * Needs to write file *before* installing anything, so an aborted install
+ can be uninstalled. Possibly should use 'unknown' for all metadata, then
+ replace with real metadata once it's known.
+
+ * REQUIRES should probably just be list of dependencies
+
+* Bootstrap module
+
+ The idea here is that you include the "bootstrap module" in your
+ distribution, and it downloads the right version of setuptools automatically
+ if a good-enough version isn't on sys.path. This would let you use
+ setuptools for your installer, without having to distribute the full
+ setuptools package. This would might look something like::
+
+ from boot_setuptools import require_version
+ require_version("0.6", "http://somewhere/setuptools-0.6.tar.gz")
+
+ from setuptools import setup, Feature, findPackages
+ # ...etc
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 00000000..1aadf759
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+"""Distutils setup file, used to install or test 'setuptools'"""
+
+from setuptools import setup, find_packages, Require
+
+setup(
+ name="setuptools",
+ version="0.0.1",
+
+ description="Distutils enhancements",
+ author="Phillip J. Eby",
+ author_email="peak@eby-sarna.com",
+ license="PSF or ZPL",
+
+ test_suite = 'setuptools.tests.test_suite',
+ requires = [Require('Distutils','1.0.3','distutils')],
+ packages = find_packages(),
+ py_modules = ['setuptools_boot'],
+)
+
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
new file mode 100644
index 00000000..12b6b57c
--- /dev/null
+++ b/setuptools/__init__.py
@@ -0,0 +1,82 @@
+"""Extensions to the 'distutils' for large or complex distributions"""
+
+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
+from distutils.util import convert_path
+import os.path
+
+__version__ = '0.0.1'
+
+__all__ = [
+ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
+ 'find_packages'
+]
+
+
+def find_packages(where='.'):
+ """Return a list all Python packages found within directory 'where'
+
+ 'where' should be supplied as a "cross-platform" (i.e. URL-style) path; it
+ will be converted to the appropriate local path syntax.
+ """
+
+ out = []
+ stack=[(convert_path(where), '')]
+
+ while stack:
+ where,prefix = stack.pop(0)
+ for name in os.listdir(where):
+ fn = os.path.join(where,name)
+ if (os.path.isdir(fn) and
+ os.path.isfile(os.path.join(fn,'__init__.py'))
+ ):
+ out.append(prefix+name); stack.append((fn,prefix+name+'.'))
+ 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)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
new file mode 100644
index 00000000..3429634c
--- /dev/null
+++ b/setuptools/command/__init__.py
@@ -0,0 +1,11 @@
+import distutils.command
+
+__all__ = ['test', 'depends']
+
+
+# Make our commands available as though they were part of the distutils
+
+distutils.command.__path__.extend(__path__)
+distutils.command.__all__.extend(
+ [cmd for cmd in __all__ if cmd not in distutils.command.__all__]
+)
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
new file mode 100644
index 00000000..86ac13a9
--- /dev/null
+++ b/setuptools/command/build_ext.py
@@ -0,0 +1,7 @@
+# Attempt to use Pyrex for building extensions, if available
+
+try:
+ from Pyrex.Distutils.build_ext import build_ext
+except ImportError:
+ from distutils.command.build_ext import build_ext
+
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
new file mode 100644
index 00000000..7d5b6ffd
--- /dev/null
+++ b/setuptools/command/build_py.py
@@ -0,0 +1,123 @@
+from distutils.command.build_py import build_py as _build_py
+from distutils.util import convert_path
+from glob import glob
+import os.path
+
+class build_py(_build_py):
+
+ """Enhanced 'build_py' command that includes data files with packages
+
+ The data files are specified via a 'package_data' argument to 'setup()'.
+ See 'setuptools.dist.Distribution' for more details.
+
+ Also, this version of the 'build_py' command allows you to specify both
+ 'py_modules' and 'packages' in the same setup operation.
+ """
+
+ def finalize_options(self):
+ _build_py.finalize_options(self)
+ self.package_data = self.distribution.package_data
+ self.data_files = self.get_data_files()
+
+
+ def run(self):
+
+ """Build modules, packages, and copy data files to build directory"""
+
+ if not self.py_modules and not self.packages:
+ return
+
+ if self.py_modules:
+ self.build_modules()
+
+ if self.packages:
+ self.build_packages()
+ self.build_package_data()
+
+ # Only compile actual .py files, using our base class' idea of what our
+ # output files are.
+ self.byte_compile(_build_py.get_outputs(self,include_bytecode=0))
+
+
+ def get_data_files(self):
+
+ """Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
+
+ data = []
+
+ for package in self.packages:
+ # Locate package source directory
+ src_dir = self.get_package_dir(package)
+
+ # Compute package build directory
+ build_dir = os.path.join(*([self.build_lib]+package.split('.')))
+
+ # Length of path to strip from found files
+ plen = len(src_dir)+1
+
+ # Strip directory from globbed filenames
+ filenames = [
+ file[plen:] for file in self.find_data_files(package, src_dir)
+ ]
+
+ data.append( (package, src_dir, build_dir, filenames) )
+
+ return data
+
+
+ def find_data_files(self, package, src_dir):
+
+ """Return filenames for package's data files in 'src_dir'"""
+
+ globs = self.package_data.get('',[])+self.package_data.get(package,[])
+ files = []
+
+ for pattern in globs:
+ # Each pattern has to be converted to a platform-specific path
+ files.extend(glob(os.path.join(src_dir, convert_path(pattern))))
+
+ return files
+
+
+
+ def build_package_data(self):
+
+ """Copy data files into build directory"""
+
+ lastdir = None
+
+ for package, src_dir, build_dir, filenames in self.data_files:
+
+ for filename in filenames:
+ target = os.path.join(build_dir,filename)
+ self.mkpath(os.path.dirname(target))
+ self.copy_file(os.path.join(src_dir,filename), target)
+
+
+ def get_outputs(self, include_bytecode=1):
+
+ """Return complete list of files copied to the build directory
+
+ This includes both '.py' files and data files, as well as '.pyc' and
+ '.pyo' files if 'include_bytecode' is true. (This method is needed for
+ the 'install_lib' command to do its job properly, and to generate a
+ correct installation manifest.)
+ """
+
+ return _build_py.get_outputs(self,include_bytecode) + [
+ os.path.join(build_dir,filename)
+ for package,src_dir,build_dir,filenames in self.data_files
+ for filename in filenames
+ ]
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools/command/depends.py b/setuptools/command/depends.py
new file mode 100644
index 00000000..e149faca
--- /dev/null
+++ b/setuptools/command/depends.py
@@ -0,0 +1,27 @@
+from distutils.cmd import Command
+import os
+
+class depends(Command):
+ """Download and install dependencies, if needed"""
+
+ description = "download and install dependencies, if needed"
+
+ user_options = [
+ ('temp=', 't',
+ "directory where dependencies will be downloaded and built"),
+ ('ignore-extra-args', 'i',
+ "ignore options that won't be passed to child setup scripts"),
+ ]
+
+ def initialize_options(self):
+ self.temp = None
+ self.install_purelib = self.install_platlib = None
+ self.install_lib = self.install_libbase = None
+ self.install_scripts = self.install_data = self.install_headers = None
+ self.compiler = self.debug = self.force = None
+
+ def finalize_options(self):
+ self.set_undefined_options('build',('build_temp', 'temp'))
+
+ def run(self):
+ self.announce("downloading and building here")
diff --git a/setuptools/command/install.py b/setuptools/command/install.py
new file mode 100644
index 00000000..82e7ebe8
--- /dev/null
+++ b/setuptools/command/install.py
@@ -0,0 +1,11 @@
+from distutils.command.install import install as _install
+
+class install(_install):
+ """Build dependencies before installation"""
+
+ def has_dependencies(self):
+ return self.distribution.has_dependencies()
+
+ sub_commands = [('depends',has_dependencies)] + _install.sub_commands
+
+
diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py
new file mode 100644
index 00000000..ec406f7e
--- /dev/null
+++ b/setuptools/command/install_lib.py
@@ -0,0 +1,17 @@
+from distutils.command.install_lib import install_lib as _install_lib
+
+class install_lib(_install_lib):
+ """Don't add compiled flags to filenames of non-Python files"""
+
+ def _bytecode_filenames (self, py_filenames):
+ bytecode_files = []
+ for py_file in py_filenames:
+ if not py_file.endswith('.py'):
+ continue
+ if self.compile:
+ bytecode_files.append(py_file + "c")
+ if self.optimize > 0:
+ bytecode_files.append(py_file + "o")
+
+ return bytecode_files
+
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
new file mode 100644
index 00000000..6b37a9fd
--- /dev/null
+++ b/setuptools/command/test.py
@@ -0,0 +1,82 @@
+from distutils.cmd import Command
+from distutils.errors import DistutilsOptionError
+import sys
+
+class test(Command):
+
+ """Command to run unit tests after installation"""
+
+ description = "run unit tests after installation"
+
+ user_options = [
+ ('test-module=','m', "Run 'test_suite' in specified module"),
+ ('test-suite=','s',
+ "Test suite to run (e.g. 'some_module.test_suite')"),
+ ]
+
+ test_suite = None
+ test_module = None
+
+ def initialize_options(self):
+ pass
+
+
+ def finalize_options(self):
+
+ if self.test_suite is None:
+ if self.test_module is None:
+ self.test_suite = self.distribution.test_suite
+ else:
+ self.test_suite = self.test_module+".test_suite"
+ elif self.test_module:
+ raise DistutilsOptionError(
+ "You may specify a module or a suite, but not both"
+ )
+
+ self.test_args = [self.test_suite]
+
+ if self.verbose:
+ self.test_args.insert(0,'--verbose')
+
+
+ def run(self):
+
+ # Install before testing
+ self.run_command('install')
+
+ if self.test_suite:
+ cmd = ' '.join(self.test_args)
+
+ if self.dry_run:
+ self.announce('skipping "unittest %s" (dry run)' % cmd)
+ else:
+ self.announce('running "unittest %s"' % cmd)
+ import unittest
+ unittest.main(None, None, [unittest.__file__]+self.test_args)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools/depends.py b/setuptools/depends.py
new file mode 100644
index 00000000..c3bc3334
--- /dev/null
+++ b/setuptools/depends.py
@@ -0,0 +1,246 @@
+from __future__ import generators
+import sys, imp, marshal
+from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
+from distutils.version import StrictVersion, LooseVersion
+
+__all__ = [
+ 'Require', 'find_module', 'get_module_constant', 'extract_constant'
+]
+
+class Require:
+ """A prerequisite to building or installing a distribution"""
+
+ def __init__(self,name,requested_version,module,attribute=None,format=None):
+
+ if format is None and requested_version is not None:
+ format = StrictVersion
+
+ if format is not None:
+ requested_version = format(requested_version)
+ if attribute is None:
+ attribute = '__version__'
+
+ self.name = name
+ self.requested_version = requested_version
+ self.module = module
+ self.attribute = attribute
+ self.format = format
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ def get_version(self, paths=None, default="unknown"):
+
+ """Get version number of installed module, 'None', or 'default'
+
+ Search 'paths' for module. If not found, return 'None'. If found,
+ return the extracted version attribute, or 'default' if no version
+ attribute was specified, or the value cannot be determined without
+ importing the module. The version is formatted according to the
+ requirement's version format (if any), unless it is 'None' or the
+ supplied 'default'.
+ """
+
+ if self.attribute is None:
+ try:
+ f,p,i = find_module(self.module,paths)
+ if f: f.close()
+ return default
+ except ImportError:
+ return None
+
+ v = get_module_constant(self.module,self.attribute,default,paths)
+
+ if v is not None and v is not default and self.format is not None:
+ return self.format(v)
+
+ return v
+
+ def is_present(self,paths=None):
+ """Return true if dependency is present on 'paths'"""
+ return self.get_version(paths) is not None
+
+ def is_current(self,paths=None):
+ """Return true if dependency is present and up-to-date on 'paths'"""
+ version = self.get_version(paths)
+ if version is None:
+ return False
+ return self.attribute is None or self.format is None or \
+ version >= self.requested_version
+
+
+
+def _iter_code(code):
+
+ """Yield '(op,arg)' pair for each operation in code object 'code'"""
+
+ from array import array
+ from dis import HAVE_ARGUMENT, EXTENDED_ARG
+
+ bytes = array('b',code.co_code)
+ eof = len(code.co_code)
+
+ ptr = 0
+ extended_arg = 0
+
+ while ptr<eof:
+
+ op = bytes[ptr]
+
+ if op>=HAVE_ARGUMENT:
+
+ arg = bytes[ptr+1] + bytes[ptr+2]*256 + extended_arg
+ ptr += 3
+
+ if op==EXTENDED_ARG:
+ extended_arg = arg * 65536L
+ continue
+
+ else:
+ arg = None
+ ptr += 1
+
+ yield op,arg
+
+
+
+
+
+
+
+
+
+
+def find_module(module, paths=None):
+ """Just like 'imp.find_module()', but with package support"""
+
+ parts = module.split('.')
+
+ while parts:
+ part = parts.pop(0)
+ f, path, (suffix,mode,kind) = info = imp.find_module(part, paths)
+
+ if kind==PKG_DIRECTORY:
+ parts = parts or ['__init__']
+ paths = [path]
+
+ elif parts:
+ raise ImportError("Can't find %r in %s" % (parts,module))
+
+ return info
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def get_module_constant(module, symbol, default=-1, paths=None):
+
+ """Find 'module' by searching 'paths', and extract 'symbol'
+
+ Return 'None' if 'module' does not exist on 'paths', or it does not define
+ 'symbol'. If the module defines 'symbol' as a constant, return the
+ constant. Otherwise, return 'default'."""
+
+ try:
+ f, path, (suffix,mode,kind) = find_module(module,paths)
+ except ImportError:
+ # Module doesn't exist
+ return None
+
+ try:
+ if kind==PY_COMPILED:
+ f.read(8) # skip magic & date
+ code = marshal.load(f)
+ elif kind==PY_FROZEN:
+ code = imp.get_frozen_object(module)
+ elif kind==PY_SOURCE:
+ code = compile(f.read(), path, 'exec')
+ else:
+ # Not something we can parse; we'll have to import it. :(
+ if module not in sys.modules:
+ imp.load_module(module,f,path,(suffix,mode,kind))
+ return getattr(sys.modules[module],symbol,None)
+
+ finally:
+ if f:
+ f.close()
+
+ return extract_constant(code,symbol,default)
+
+
+
+
+
+
+
+
+def extract_constant(code,symbol,default=-1):
+
+ """Extract the constant value of 'symbol' from 'code'
+
+ If the name 'symbol' is bound to a constant value by the Python code
+ object 'code', return that value. If 'symbol' is bound to an expression,
+ return 'default'. Otherwise, return 'None'.
+
+ Return value is based on the first assignment to 'symbol'. 'symbol' must
+ be a global, or at least a non-"fast" local in the code block. That is,
+ only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol'
+ must be present in 'code.co_names'.
+ """
+
+ if symbol not in code.co_names:
+ # name's not there, can't possibly be an assigment
+ return None
+
+ name_idx = list(code.co_names).index(symbol)
+
+ STORE_NAME = 90
+ STORE_GLOBAL = 97
+ LOAD_CONST = 100
+
+ const = default
+
+ for op, arg in _iter_code(code):
+
+ if op==LOAD_CONST:
+ const = code.co_consts[arg]
+ elif arg==name_idx and (op==STORE_NAME or op==STORE_GLOBAL):
+ return const
+ else:
+ const = default
+
+
+
+
+
+
+
diff --git a/setuptools/dist.py b/setuptools/dist.py
new file mode 100644
index 00000000..2941f26e
--- /dev/null
+++ b/setuptools/dist.py
@@ -0,0 +1,453 @@
+__all__ = ['Distribution', 'Feature']
+
+from distutils.core import Distribution as _Distribution
+from distutils.core import Extension
+from setuptools.command.build_py import build_py
+from setuptools.command.build_ext import build_ext
+from setuptools.command.install import install
+from setuptools.command.install_lib import install_lib
+from distutils.errors import DistutilsOptionError, DistutilsPlatformError
+from distutils.errors import DistutilsSetupError
+sequence = tuple, list
+
+class Distribution(_Distribution):
+
+ """Distribution with support for features, tests, and package data
+
+ This is an enhanced version of 'distutils.dist.Distribution' that
+ effectively adds the following new optional keyword arguments to 'setup()':
+
+ 'features' -- a dictionary mapping option names to 'setuptools.Feature'
+ objects. Features are a portion of the distribution that can be
+ included or excluded based on user options, inter-feature dependencies,
+ and availability on the current system. Excluded features are omitted
+ from all setup commands, including source and binary distributions, so
+ you can create multiple distributions from the same source tree.
+
+ Feature names should be valid Python identifiers, except that they may
+ contain the '-' (minus) sign. Features can be included or excluded
+ via the command line options '--with-X' and '--without-X', where 'X' is
+ the name of the feature. Whether a feature is included by default, and
+ whether you are allowed to control this from the command line, is
+ determined by the Feature object. See the 'Feature' class for more
+ information.
+
+ 'test_suite' -- the name of a test suite to run for the 'test' command.
+ If the user runs 'python setup.py test', the package will be installed,
+ and the named test suite will be run. The format is the same as
+ would be used on a 'unittest.py' command line. That is, it is the
+ dotted name of an object to import and call to generate a test suite.
+
+ 'package_data' -- a dictionary mapping package names to lists of filenames
+ or globs to use to find data files contained in the named packages.
+ If the dictionary has filenames or globs listed under '""' (the empty
+ string), those names will be searched for in every package, in addition
+ to any names for the specific package. Data files found using these
+ names/globs will be installed along with the package, in the same
+ location as the package. Note that globs are allowed to reference
+ the contents of non-package subdirectories, as long as you use '/' as
+ a path separator. (Globs are automatically converted to
+ platform-specific paths at runtime.)
+
+ In addition to these new keywords, this class also has several new methods
+ for manipulating the distribution's contents. For example, the 'include()'
+ and 'exclude()' methods can be thought of as in-place add and subtract
+ commands that add or remove packages, modules, extensions, and so on from
+ the distribution. They are used by the feature subsystem to configure the
+ distribution for the included and excluded features.
+ """
+
+ def __init__ (self, attrs=None):
+ self.features = {}
+ self.package_data = {}
+ self.test_suite = None
+ self.requires = []
+ _Distribution.__init__(self,attrs)
+ self.cmdclass.setdefault('build_py',build_py)
+ self.cmdclass.setdefault('build_ext',build_ext)
+ self.cmdclass.setdefault('install',install)
+ self.cmdclass.setdefault('install_lib',install_lib)
+
+ if self.features:
+ self._set_global_opts_from_features()
+
+ def parse_command_line(self):
+ """Process features after parsing command line options"""
+ result = _Distribution.parse_command_line(self)
+ if self.features:
+ self._finalize_features()
+ return result
+
+ def _feature_attrname(self,name):
+ """Convert feature name to corresponding option attribute name"""
+ return 'with_'+name.replace('-','_')
+
+ def _set_global_opts_from_features(self):
+ """Add --with-X/--without-X options based on optional features"""
+
+ go = []
+ no = self.negative_opt.copy()
+
+ for name,feature in self.features.items():
+ self._set_feature(name,None)
+ feature.validate(self)
+
+ if feature.optional:
+ descr = feature.description
+ incdef = ' (default)'
+ 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
+
+ self.global_options = self.feature_options = go + self.global_options
+ self.negative_opt = self.feature_negopt = no
+
+ 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 _set_feature(self,name,status):
+ """Set feature's inclusion status"""
+ setattr(self,self._feature_attrname(name),status)
+
+ 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))
+
+ def include_feature(self,name):
+ """Request inclusion of feature named 'name'"""
+
+ 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)
+
+ 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
+ the distribution's 'py_modules' attribute, if it was not already
+ there.
+
+ Currently, this method only supports inclusion for attributes that are
+ lists or tuples. If you need to add support for adding to other
+ attributes in this or a subclass, you can add an '_include_X' method,
+ where 'X' is the name of the attribute. The method will be called with
+ the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})'
+ 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)
+ if include:
+ include(v)
+ else:
+ self._include_misc(k,v)
+
+ def exclude_package(self,package):
+ """Remove packages, modules, and extensions in named package"""
+
+ pfx = package+'.'
+ if self.packages:
+ self.packages = [
+ p for p in self.packages
+ 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 self.ext_modules:
+ self.ext_modules = [
+ p for p in self.ext_modules
+ if p.name<>package and not p.name.startswith(pfx)
+ ]
+
+
+ def has_contents_for(self,package):
+ """Return true if 'exclude_package(package)' would do something"""
+
+ pfx = package+'.'
+
+ for p in self.packages or ():
+ if p==package or p.startswith(pfx):
+ return True
+
+ for p in self.py_modules or ():
+ if p==package or p.startswith(pfx):
+ return True
+
+ for p in self.ext_modules or ():
+ if p.name==package or p.name.startswith(pfx):
+ return True
+
+
+ def _exclude_misc(self,name,value):
+ """Handle 'exclude()' for list/tuple attrs without a special handler"""
+ if not isinstance(value,sequence):
+ raise DistutilsSetupError(
+ "%s: setting must be a list or tuple (%r)" % (name, value)
+ )
+ try:
+ old = getattr(self,name)
+ except AttributeError:
+ raise DistutilsSetupError(
+ "%s: No such distribution setting" % name
+ )
+ if old is not None and not isinstance(old,sequence):
+ raise DistutilsSetupError(
+ name+": this setting cannot be changed via include/exclude"
+ )
+ elif old:
+ setattr(self,name,[item for item in old if item not in value])
+
+ def _include_misc(self,name,value):
+ """Handle 'include()' for list/tuple attrs without a special handler"""
+
+ if not isinstance(value,sequence):
+ raise DistutilsSetupError(
+ "%s: setting must be a list (%r)" % (name, value)
+ )
+ try:
+ 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):
+ raise DistutilsSetupError(
+ name+": this setting cannot be changed via include/exclude"
+ )
+ else:
+ setattr(self,name,old+[item for item in value if item not in old])
+
+ 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
+ the distribution's 'py_modules' attribute. Excluding packages uses
+ the 'exclude_package()' method, so all of the package's contained
+ packages, modules, and extensions are also excluded.
+
+ Currently, this method only supports exclusion from attributes that are
+ lists or tuples. If you need to add support for excluding from other
+ attributes in this or a subclass, you can add an '_exclude_X' method,
+ where 'X' is the name of the attribute. The method will be called with
+ the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})'
+ 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)
+ if exclude:
+ exclude(v)
+ else:
+ self._exclude_misc(k,v)
+
+ def _exclude_packages(self,packages):
+ if not isinstance(packages,sequence):
+ raise DistutilsSetupError(
+ "packages: setting must be a list or tuple (%r)" % (packages,)
+ )
+ map(self.exclude_package, packages)
+
+ def _parse_command_opts(self, parser, args):
+ # Remove --with-X/--without-X options when processing command args
+ self.global_options = self.__class__.global_options
+ self.negative_opt = self.__class__.negative_opt
+ return _Distribution._parse_command_opts(self, parser, args)
+
+ def has_dependencies(self):
+ return not not self.requires
+
+
+
+class Feature:
+
+ """A subset of the distribution that can be excluded if unneeded/wanted
+
+ Features are created using these keyword arguments:
+
+ 'description' -- a short, human readable description of the feature, to
+ be used in error messages, and option help messages.
+
+ 'standard' -- if true, the feature is included by default if it is
+ available on the current system. Otherwise, the feature is only
+ included if requested via a command line '--with-X' option, or if
+ another included feature requires it. The default setting is 'False'.
+
+ 'available' -- if true, the feature is available for installation on the
+ current system. The default setting is 'True'.
+
+ 'optional' -- if true, the feature's inclusion can be controlled from the
+ command line, using the '--with-X' or '--without-X' options. If
+ false, the feature's inclusion status is determined automatically,
+ based on 'availabile', 'standard', and whether any other feature
+ requires it. The default setting is 'True'.
+
+ 'requires' -- a string or sequence of strings naming features that should
+ also be included if this feature is included. Defaults to empty list.
+
+ 'remove' -- a string or list of strings naming packages to be removed
+ from the distribution if this feature is *not* included. If the
+ feature *is* included, this argument is ignored. This argument exists
+ to support removing features that "crosscut" a distribution, such as
+ defining a 'tests' feature that removes all the 'tests' subpackages
+ provided by other features. The default for this argument is an empty
+ list. (Note: the named package(s) or modules must exist in the base
+ distribution when the 'setup()' function is initially called.)
+
+ other keywords -- any other keyword arguments are saved, and passed to
+ the distribution's 'include()' and 'exclude()' methods when the
+ feature is included or excluded, respectively. So, for example, you
+ could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be
+ added or removed from the distribution as appropriate.
+
+ A feature must include at least one 'requires', 'remove', or other
+ keyword argument. Otherwise, it can't affect the distribution in any way.
+ Note also that you can subclass 'Feature' to create your own specialized
+ feature types that modify the distribution in other ways when included or
+ excluded. See the docstrings for the various methods here for more detail.
+ Aside from the methods, the only feature attributes that distributions look
+ at are 'description' and 'optional'.
+ """
+
+ def __init__(self, description, standard=False, available=True,
+ optional=True, requires=(), remove=(), **extras
+ ):
+
+ self.description = description
+ self.standard = standard
+ self.available = available
+ self.optional = optional
+
+ if isinstance(requires,str):
+ requires = requires,
+
+ self.requires = requires
+
+ if isinstance(remove,str):
+ remove = remove,
+
+ self.remove = remove
+ self.extras = extras
+
+ if not remove and not requires and not extras:
+ raise DistutilsSetupError(
+ "Feature %s: must define 'requires', 'remove', or at least one"
+ " of 'packages', 'py_modules', etc."
+ )
+
+
+ def include_by_default(self):
+ """Should this feature be included by default?"""
+ return self.available and self.standard
+
+
+ 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
+ the distribution. Note that this method may be called more than once
+ per feature, and so should be idempotent.
+
+ """
+
+ if not self.available:
+ raise DistutilsPlatformError(
+ self.description+" is required,"
+ "but is not available on this platform"
+ )
+
+ dist.include(**self.extras)
+
+ for f in self.requires:
+ dist.include_feature(f)
+
+
+
+ def exclude_from(self,dist):
+
+ """Ensure feature is excluded from distribution
+
+ You may override this in a subclass to perform additional operations on
+ the distribution. This method will be called at most once per
+ feature, and only after all included features have been asked to
+ include themselves.
+ """
+
+ dist.exclude(**self.extras)
+
+ if self.remove:
+ for item in self.remove:
+ dist.exclude_package(item)
+
+
+
+ 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
+ command line. It checks to ensure that the 'remove' attribute, if any,
+ contains only valid package/module names that are present in the base
+ distribution when 'setup()' is called. You may override it in a
+ subclass to perform any other required validation of the feature
+ against a target distribution.
+ """
+
+ for item in self.remove:
+ if not dist.has_contents_for(item):
+ raise DistutilsSetupError(
+ "%s wants to be able to remove %s, but the distribution"
+ " doesn't contain any packages or modules under %s"
+ % (self.description, item, item)
+ )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools/extension.py b/setuptools/extension.py
new file mode 100644
index 00000000..55a4d946
--- /dev/null
+++ b/setuptools/extension.py
@@ -0,0 +1,27 @@
+from distutils.core import Extension as _Extension
+
+try:
+ from Pyrex.Distutils.build_ext import build_ext
+
+except ImportError:
+
+ # Pyrex isn't around, so fix up the sources
+
+ class Extension(_Extension):
+
+ """Extension that uses '.c' files in place of '.pyx' files"""
+
+ def __init__(self,*args,**kw):
+ _Extension.__init__(self,*args,**kw)
+ sources = []
+ for s in self.sources:
+ if s.endswith('.pyx'):
+ sources.append(s[:-3]+'c')
+ else:
+ sources.append(s)
+ self.sources = sources
+
+else:
+
+ # Pyrex is here, just use regular extension type
+ Extension = _Extension
diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py
new file mode 100644
index 00000000..662d4ec1
--- /dev/null
+++ b/setuptools/tests/__init__.py
@@ -0,0 +1,410 @@
+"""Tests for the 'setuptools' package"""
+
+from unittest import TestSuite, TestCase, makeSuite
+import distutils.core, distutils.cmd
+from distutils.errors import DistutilsOptionError, DistutilsPlatformError
+from distutils.errors import DistutilsSetupError
+import setuptools, setuptools.dist
+from setuptools import Feature
+from distutils.core import Extension
+from setuptools.depends import extract_constant, get_module_constant
+from setuptools.depends import find_module, Require
+from distutils.version import StrictVersion, LooseVersion
+
+import sys, os.path
+
+def makeSetup(**args):
+ """Return distribution from 'setup(**args)', without executing commands"""
+
+ distutils.core._setup_stop_after = "commandline"
+
+ # Don't let system command line leak into tests!
+ args.setdefault('script_args',['install'])
+
+ try:
+ return setuptools.setup(**args)
+ finally:
+ distutils.core_setup_stop_after = None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+class DependsTests(TestCase):
+
+ def testExtractConst(self):
+
+ from setuptools.depends import extract_constant
+
+ def f1():
+ global x,y,z
+ x = "test"
+ y = z
+
+ # unrecognized name
+ self.assertEqual(extract_constant(f1.func_code,'q', -1), None)
+
+ # constant assigned
+ self.assertEqual(extract_constant(f1.func_code,'x', -1), "test")
+
+ # expression assigned
+ self.assertEqual(extract_constant(f1.func_code,'y', -1), -1)
+
+ # recognized name, not assigned
+ self.assertEqual(extract_constant(f1.func_code,'z', -1), None)
+
+
+ def testFindModule(self):
+ self.assertRaises(ImportError, find_module, 'no-such.-thing')
+ self.assertRaises(ImportError, find_module, 'setuptools.non-existent')
+ f,p,i = find_module('setuptools.tests'); f.close()
+
+ def testModuleExtract(self):
+ from distutils import __version__
+ self.assertEqual(
+ get_module_constant('distutils','__version__'), __version__
+ )
+ self.assertEqual(
+ get_module_constant('sys','version'), sys.version
+ )
+ self.assertEqual(
+ get_module_constant('setuptools.tests','__doc__'),__doc__
+ )
+
+ def testDependsCmd(self):
+ dist = makeSetup()
+ cmd = dist.get_command_obj('depends')
+ cmd.ensure_finalized()
+ self.assertEqual(cmd.temp, dist.get_command_obj('build').build_temp)
+ self.assertEqual(cmd.install_lib, dist.get_command_obj('install').install_lib)
+
+
+ def testRequire(self):
+ req = Require('Distutils','1.0.3','distutils')
+
+ self.assertEqual(req.name, 'Distutils')
+ self.assertEqual(req.module, 'distutils')
+ self.assertEqual(req.requested_version, '1.0.3')
+ self.assertEqual(req.attribute, '__version__')
+
+ from distutils import __version__
+ self.assertEqual(req.get_version(), __version__)
+
+ self.failUnless(req.is_present())
+ self.failUnless(req.is_current())
+
+ req = Require('Distutils 3000','03000','distutils',format=LooseVersion)
+ self.failUnless(req.is_present())
+ self.failIf(req.is_current())
+
+ req = Require('Do-what-I-mean','1.0','d-w-i-m')
+ self.failIf(req.is_present())
+ self.failIf(req.is_current())
+
+ req = Require('Tests', None, 'tests')
+ self.assertEqual(req.format, None)
+ self.assertEqual(req.attribute, None)
+ self.assertEqual(req.requested_version, None)
+
+ paths = [os.path.dirname(p) for p in __path__]
+ self.failUnless(req.is_present(paths))
+ self.failUnless(req.is_current(paths))
+
+
+
+class DistroTests(TestCase):
+
+ def setUp(self):
+ self.e1 = Extension('bar.ext',['bar.c'])
+ self.e2 = Extension('c.y', ['y.c'])
+
+ self.dist = makeSetup(
+ packages=['a', 'a.b', 'a.b.c', 'b', 'c'],
+ py_modules=['b.d','x'],
+ ext_modules = (self.e1, self.e2),
+ package_dir = {},
+ )
+
+
+ def testDistroType(self):
+ self.failUnless(isinstance(self.dist,setuptools.dist.Distribution))
+
+
+ def testExcludePackage(self):
+ self.dist.exclude_package('a')
+ self.assertEqual(self.dist.packages, ['b','c'])
+
+ self.dist.exclude_package('b')
+ self.assertEqual(self.dist.packages, ['c'])
+ self.assertEqual(self.dist.py_modules, ['x'])
+ self.assertEqual(self.dist.ext_modules, [self.e1, self.e2])
+
+ self.dist.exclude_package('c')
+ self.assertEqual(self.dist.packages, [])
+ self.assertEqual(self.dist.py_modules, ['x'])
+ self.assertEqual(self.dist.ext_modules, [self.e1])
+
+ # test removals from unspecified options
+ makeSetup().exclude_package('x')
+
+
+
+
+
+
+
+ def testIncludeExclude(self):
+ # remove an extension
+ self.dist.exclude(ext_modules=[self.e1])
+ self.assertEqual(self.dist.ext_modules, [self.e2])
+
+ # add it back in
+ self.dist.include(ext_modules=[self.e1])
+ self.assertEqual(self.dist.ext_modules, [self.e2, self.e1])
+
+ # should not add duplicate
+ self.dist.include(ext_modules=[self.e1])
+ self.assertEqual(self.dist.ext_modules, [self.e2, self.e1])
+
+ def testExcludePackages(self):
+ self.dist.exclude(packages=['c','b','a'])
+ self.assertEqual(self.dist.packages, [])
+ self.assertEqual(self.dist.py_modules, ['x'])
+ self.assertEqual(self.dist.ext_modules, [self.e1])
+
+ def testEmpty(self):
+ dist = makeSetup()
+ dist.include(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
+ dist = makeSetup()
+ dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
+
+ def testContents(self):
+ self.failUnless(self.dist.has_contents_for('a'))
+ self.dist.exclude_package('a')
+ self.failIf(self.dist.has_contents_for('a'))
+
+ self.failUnless(self.dist.has_contents_for('b'))
+ self.dist.exclude_package('b')
+ self.failIf(self.dist.has_contents_for('b'))
+
+ self.failUnless(self.dist.has_contents_for('c'))
+ self.dist.exclude_package('c')
+ self.failIf(self.dist.has_contents_for('c'))
+
+
+
+
+ def testInvalidIncludeExclude(self):
+ self.assertRaises(DistutilsSetupError,
+ self.dist.include, nonexistent_option='x'
+ )
+ self.assertRaises(DistutilsSetupError,
+ self.dist.exclude, nonexistent_option='x'
+ )
+ self.assertRaises(DistutilsSetupError,
+ self.dist.include, packages={'x':'y'}
+ )
+ self.assertRaises(DistutilsSetupError,
+ self.dist.exclude, packages={'x':'y'}
+ )
+ self.assertRaises(DistutilsSetupError,
+ self.dist.include, ext_modules={'x':'y'}
+ )
+ self.assertRaises(DistutilsSetupError,
+ self.dist.exclude, ext_modules={'x':'y'}
+ )
+
+ self.assertRaises(DistutilsSetupError,
+ self.dist.include, package_dir=['q']
+ )
+ self.assertRaises(DistutilsSetupError,
+ self.dist.exclude, package_dir=['q']
+ )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+class FeatureTests(TestCase):
+
+ def setUp(self):
+ self.dist = makeSetup(
+ features={
+ 'foo': Feature("foo",standard=True,requires='baz'),
+ 'bar': Feature("bar", standard=True, packages=['pkg.bar'],
+ py_modules=['bar_et'], remove=['bar.ext'],
+ ),
+ 'baz': Feature(
+ "baz", optional=False, packages=['pkg.baz'],
+ scripts = ['scripts/baz_it'],
+ libraries=[('libfoo','foo/foofoo.c')]
+ ),
+ 'dwim': Feature("DWIM", available=False, remove='bazish'),
+ },
+ script_args=['--without-bar', 'install'],
+ packages = ['pkg.bar', 'pkg.foo'],
+ py_modules = ['bar_et', 'bazish'],
+ ext_modules = [Extension('bar.ext',['bar.c'])]
+ )
+
+ def testDefaults(self):
+ self.failIf(
+ Feature(
+ "test",standard=True,remove='x',available=False
+ ).include_by_default()
+ )
+ self.failUnless(
+ Feature("test",standard=True,remove='x').include_by_default()
+ )
+ # Feature must have either kwargs, removes, or requires
+ self.assertRaises(DistutilsSetupError, Feature, "test")
+
+ def testAvailability(self):
+ self.assertRaises(
+ DistutilsPlatformError,
+ self.dist.features['dwim'].include_in, self.dist
+ )
+
+
+ def testFeatureOptions(self):
+ dist = self.dist
+ self.failUnless(
+ ('with-dwim',None,'include DWIM') in dist.feature_options
+ )
+ self.failUnless(
+ ('without-dwim',None,'exclude DWIM (default)') in dist.feature_options
+ )
+ self.failUnless(
+ ('with-bar',None,'include bar (default)') in dist.feature_options
+ )
+ self.failUnless(
+ ('without-bar',None,'exclude bar') in dist.feature_options
+ )
+ self.assertEqual(dist.feature_negopt['without-foo'],'with-foo')
+ self.assertEqual(dist.feature_negopt['without-bar'],'with-bar')
+ self.assertEqual(dist.feature_negopt['without-dwim'],'with-dwim')
+ self.failIf('without-baz' in dist.feature_negopt)
+
+ def testUseFeatures(self):
+ dist = self.dist
+ self.assertEqual(dist.with_foo,1)
+ self.assertEqual(dist.with_bar,0)
+ self.assertEqual(dist.with_baz,1)
+
+ self.failIf('bar_et' in dist.py_modules)
+ self.failIf('pkg.bar' in dist.packages)
+ self.failUnless('pkg.baz' in dist.packages)
+ self.failUnless('scripts/baz_it' in dist.scripts)
+ self.failUnless(('libfoo','foo/foofoo.c') in dist.libraries)
+ self.assertEqual(dist.ext_modules,[])
+
+ # If we ask for bar, it should fail because we explicitly disabled
+ # it on the command line
+ self.assertRaises(DistutilsOptionError, dist.include_feature, 'bar')
+
+ def testFeatureWithInvalidRemove(self):
+ self.assertRaises(
+ SystemExit, makeSetup, features = {'x':Feature('x', remove='y')}
+ )
+
+class TestCommandTests(TestCase):
+
+ def testTestIsCommand(self):
+ test_cmd = makeSetup().get_command_obj('test')
+ self.failUnless(isinstance(test_cmd, distutils.cmd.Command))
+
+ def testLongOptSuiteWNoDefault(self):
+ ts1 = makeSetup(script_args=['test','--test-suite=foo.tests.suite'])
+ ts1 = ts1.get_command_obj('test')
+ ts1.ensure_finalized()
+ self.assertEqual(ts1.test_suite, 'foo.tests.suite')
+
+ def testDefaultSuite(self):
+ ts2 = makeSetup(test_suite='bar.tests.suite').get_command_obj('test')
+ ts2.ensure_finalized()
+ self.assertEqual(ts2.test_suite, 'bar.tests.suite')
+
+ def testDefaultWModuleOnCmdLine(self):
+ ts3 = makeSetup(
+ test_suite='bar.tests',
+ script_args=['test','-m','foo.tests']
+ ).get_command_obj('test')
+ ts3.ensure_finalized()
+ self.assertEqual(ts3.test_module, 'foo.tests')
+ self.assertEqual(ts3.test_suite, 'foo.tests.test_suite')
+
+ def testConflictingOptions(self):
+ ts4 = makeSetup(
+ script_args=['test','-m','bar.tests', '-s','foo.tests.suite']
+ ).get_command_obj('test')
+ self.assertRaises(DistutilsOptionError, ts4.ensure_finalized)
+
+ def testNoSuite(self):
+ ts5 = makeSetup().get_command_obj('test')
+ ts5.ensure_finalized()
+ self.assertEqual(ts5.test_suite, None)
+
+
+
+
+
+testClasses = (DependsTests, DistroTests, FeatureTests, TestCommandTests)
+
+def test_suite():
+ return TestSuite([makeSuite(t,'test') for t in testClasses])
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools_boot.py b/setuptools_boot.py
new file mode 100644
index 00000000..c13b6b3f
--- /dev/null
+++ b/setuptools_boot.py
@@ -0,0 +1,123 @@
+"""Bootstrap module to download/quasi-install 'setuptools' package
+
+Usage::
+
+ from setuptools_boot import require_version
+ require_version('0.0.1')
+
+ from setuptools import setup, Extension, ...
+
+Note that if a suitable version of 'setuptools' is not found on 'sys.path',
+it will be downloaded and installed to the current directory. This means
+that if you are using 'setuptools.find_packages()' in the same directory, you
+will need to exclude the setuptools package from the distribution (unless you
+want setuptools to be installed as part of your distribution). To do this,
+you can simply use:
+
+ setup(
+ # ...
+ packages = [pkg for pkg in find_packages()
+ if not pkg.startswith('setuptools')
+ ],
+ # ...
+ )
+
+to eliminate the setuptools packages from consideration. However, if you are
+using a 'lib' or 'src' directory to contain your distribution's packages, this
+will not be an issue.
+"""
+
+from distutils.version import StrictVersion
+from distutils.util import convert_path
+import os.path
+
+__all__ = ['require_version']
+
+
+
+
+
+
+
+def require_version(version='0.0.1', dlbase='file:../../setuptools/dist'):
+ """Request to use setuptools of specified version
+
+ 'dlbase', if provided, is the base URL that should be used to download
+ a particular version of setuptools. '/setuptools-VERSION.zip' will be
+ added to 'dlbase' to construct the download URL, if a download is needed.
+
+ XXX current dlbase works for local testing only
+ """
+
+ if StrictVersion(version) > get_installed_version():
+ unload_setuptools()
+ download_setuptools(version,dlbase)
+
+ if StrictVersion(version) > get_installed_version():
+ # Should never get here
+ raise SystemExit(
+ "Downloaded new version of setuptools, but it's not on sys.path"
+ )
+
+
+def get_installed_version():
+ """Return version of currently-installed setuptools, or '"0.0.0"'"""
+ try:
+ from setuptools import __version__
+ return __version__
+ except ImportError:
+ return '0.0.0'
+
+
+def download_setuptools(version,dlbase):
+ """Download setuptools-VERSION.zip from dlbase and extract in local dir"""
+ basename = 'setuptools-%s' % version
+ filename = basename+'.zip'
+ url = '%s/%s' % (dlbase,filename)
+ download_file(url,filename)
+ extract_zipdir(filename,basename+'/setuptools','setuptools')
+
+
+
+
+def unload_setuptools():
+ """Unload the current (outdated) 'setuptools' version from memory"""
+ import sys
+ for k in sys.modules.keys():
+ if k.startswith('setuptools.') or k=='setuptools':
+ del sys.modules[k]
+
+
+def download_file(url,filename):
+ """Download 'url', saving to 'filename'"""
+ from urllib2 import urlopen
+ f = urlopen(url); bytes = f.read(); f.close()
+ f = open(filename,'wb'); f.write(bytes); f.close()
+
+
+def extract_zipdir(filename,zipdir,targetdir):
+ """Unpack zipfile 'filename', extracting 'zipdir' to 'targetdir'"""
+
+ from zipfile import ZipFile
+
+ f = ZipFile(filename)
+ if zipdir and not zipdir.endswith('/'):
+ zipdir+='/'
+ plen = len(zipdir)
+ paths = [
+ path for path in f.namelist()
+ if path.startswith(zipdir) and not path.endswith('/')
+ ]
+ paths.sort()
+ paths.reverse() # unpack in reverse order so __init__ goes last!
+
+ for path in paths:
+ out = os.path.join(targetdir,convert_path(path[plen:]))
+ dir = os.path.dirname(out)
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+ out=open(out,'wb'); out.write(f.read(path)); out.close()
+
+ f.close()
+
+