Commit 643acd6a authored by PJ Eby's avatar PJ Eby

EasyInstall/setuptools 0.5a4: significant new features, including automatic

installation of dependencies, the ability to specify dependencies in a
setup script, and several new options to control EasyInstall's behavior.

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041073
parent 5ed7f988
...@@ -23,7 +23,7 @@ Installing "Easy Install" ...@@ -23,7 +23,7 @@ Installing "Easy Install"
------------------------- -------------------------
Windows users can just download and run the `setuptools binary installer for Windows users can just download and run the `setuptools binary installer for
Windows <http://peak.telecommunity.com/dist/setuptools-0.5a3.win32.exe>`_. Windows <http://peak.telecommunity.com/dist/setuptools-0.5a4.win32.exe>`_.
All others should just download `ez_setup.py All others should just download `ez_setup.py
<http://peak.telecommunity.com/dist/ez_setup.py>`_, and run it; this will <http://peak.telecommunity.com/dist/ez_setup.py>`_, and run it; this will
download and install the correct version of ``setuptools`` for your Python download and install the correct version of ``setuptools`` for your Python
...@@ -62,7 +62,7 @@ version, and automatically downloading, building, and installing it:: ...@@ -62,7 +62,7 @@ version, and automatically downloading, building, and installing it::
**Example 2**. Install or upgrade a package by name and version by finding **Example 2**. Install or upgrade a package by name and version by finding
links on a given "download page":: links on a given "download page"::
easy_install -f http://peak.telecommunity.com/dist "setuptools>=0.5a3" easy_install -f http://peak.telecommunity.com/dist "setuptools>=0.5a4"
**Example 3**. Download a source distribution from a specified URL, **Example 3**. Download a source distribution from a specified URL,
automatically building and installing it:: automatically building and installing it::
...@@ -73,6 +73,11 @@ automatically building and installing it:: ...@@ -73,6 +73,11 @@ automatically building and installing it::
easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg
**Example 5**. Upgrade an already-installed package to the latest version
listed on PyPI:
easy_install --upgrade PyProtocols
Easy Install accepts URLs, filenames, PyPI package names (i.e., ``distutils`` Easy Install accepts URLs, filenames, PyPI package names (i.e., ``distutils``
"distribution" names), and package+version specifiers. In each case, it will "distribution" names), and package+version specifiers. In each case, it will
attempt to locate the latest available version that meets your criteria. attempt to locate the latest available version that meets your criteria.
...@@ -118,23 +123,29 @@ a version greater than the one you have now:: ...@@ -118,23 +123,29 @@ a version greater than the one you have now::
easy_install "SomePackage>2.0" easy_install "SomePackage>2.0"
using the upgrade flag, to find the latest available version on PyPI::
easy_install --upgrade SomePackage
or by using a download page, direct download URL, or package filename:: or by using a download page, direct download URL, or package filename::
easy_install -s http://example.com/downloads ExamplePackage easy_install -f http://example.com/downloads ExamplePackage
easy_install http://example.com/downloads/ExamplePackage-2.0-py2.4.egg easy_install http://example.com/downloads/ExamplePackage-2.0-py2.4.egg
easy_install my_downloads/ExamplePackage-2.0.tgz easy_install my_downloads/ExamplePackage-2.0.tgz
If you're using ``-m`` or ``--multi`` (or installing outside of If you're using ``-m`` or ``--multi`` (or installing outside of
``site-packages``), the ``require()`` function automatically selects the newest ``site-packages``), using the ``require()`` function at runtime automatically
available version of a package that meets your version criteria at runtime, so selects the newest installed version of a package that meets your version
installation is the only step needed. criteria. So, installing a newer version is the only step needed to upgrade
such packages.
If you're installing to ``site-packages`` and not using ``-m``, installing a If you're installing to Python's ``site-packages`` directory (and not
package automatically replaces any previous version in the ``easy-install.pth`` using ``-m``), installing a package automatically replaces any previous version
file, so that Python will import the most-recently installed version by in the ``easy-install.pth`` file, so that Python will import the most-recently
default. installed version by default. So, again, installing the newer version is the
only upgrade step needed.
If you haven't suppressed script installation (using ``--exclude-scripts`` or If you haven't suppressed script installation (using ``--exclude-scripts`` or
``-x``), then the upgraded version's scripts will be installed, and they will ``-x``), then the upgraded version's scripts will be installed, and they will
...@@ -339,6 +350,16 @@ Command-Line Options ...@@ -339,6 +350,16 @@ Command-Line Options
the ``--install-dir`` or ``-d`` option (or they are set via configuration the ``--install-dir`` or ``-d`` option (or they are set via configuration
file(s)) you must also use ``require()`` to enable packages at runtime. file(s)) you must also use ``require()`` to enable packages at runtime.
``--upgrade, -U`` (New in 0.5a4)
By default, EasyInstall only searches the Python Package Index if a
project/version requirement can't be met by distributions already installed
on sys.path or the installation directory. However, if you supply the
``--upgrade`` or ``-U`` flag, EasyInstall will always check the package
index before selecting a version to install. In this way, you can force
EasyInstall to use the latest available version of any package it installs
(subject to any version requirements that might exclude such later
versions).
``--install-dir=DIR, -d DIR`` ``--install-dir=DIR, -d DIR``
Set the installation directory. It is up to you to ensure that this Set the installation directory. It is up to you to ensure that this
directory is on ``sys.path`` at runtime, and to use directory is on ``sys.path`` at runtime, and to use
...@@ -366,6 +387,14 @@ Command-Line Options ...@@ -366,6 +387,14 @@ Command-Line Options
versions of a package, but do not want to reset the version that will be versions of a package, but do not want to reset the version that will be
run by scripts that are already installed. run by scripts that are already installed.
``--always-copy, -a`` (New in 0.5a4)
Copy all needed distributions to the installation directory, even if they
are already present in a directory on sys.path. In older versions of
EasyInstall, this was the default behavior, but now you must explicitly
request it. By default, EasyInstall will no longer copy such distributions
from other sys.path directories to the installation directory, unless you
explicitly gave the distribution's filename on the command line.
``--find-links=URL, -f URL`` (Option renamed in 0.4a2) ``--find-links=URL, -f URL`` (Option renamed in 0.4a2)
Scan the specified "download pages" for direct links to downloadable eggs Scan the specified "download pages" for direct links to downloadable eggs
or source distributions. Any usable packages will be downloaded if they or source distributions. Any usable packages will be downloaded if they
...@@ -434,6 +463,12 @@ Command-Line Options ...@@ -434,6 +463,12 @@ Command-Line Options
the default is 0 (unless it's set under ``install`` or ``install_lib`` in the default is 0 (unless it's set under ``install`` or ``install_lib`` in
one of your distutils configuration files). one of your distutils configuration files).
``--record=FILENAME`` (New in 0.5a4)
Write a record of all installed files to FILENAME. This is basically the
same as the same option for the standard distutils "install" command, and
is included for compatibility with tools that expect to pass this option
to "setup.py install".
Release Notes/Change History Release Notes/Change History
============================ ============================
...@@ -442,6 +477,46 @@ Known Issues ...@@ -442,6 +477,46 @@ Known Issues
* There's no automatic retry for borked Sourceforge mirrors, which can easily * There's no automatic retry for borked Sourceforge mirrors, which can easily
time out or be missing a file. time out or be missing a file.
0.5a4
* Added ``--always-copy/-a`` option to always copy needed packages to the
installation directory, even if they're already present elsewhere on
sys.path. (In previous versions, this was the default behavior, but now
you must request it.)
* Added ``--upgrade/-U`` option to force checking PyPI for latest available
version(s) of all packages requested by name and version, even if a matching
version is available locally.
* Setup scripts using setuptools can now list their dependencies directly in
the setup.py file, without having to manually create a ``depends.txt`` file.
The ``install_requires`` and ``extras_require`` arguments to ``setup()``
are used to create a dependencies file automatically. If you are manually
creating ``depends.txt`` right now, please switch to using these setup
arguments as soon as practical, because ``depends.txt`` support will be
removed in the 0.6 release cycle. For documentation on the new arguments,
see the ``setuptools.dist.Distribution`` class.
* Added automatic installation of dependencies declared by a distribution
being installed. These dependencies must be listed in the distribution's
``EGG-INFO`` directory, so the distribution has to have declared its
dependencies by using setuptools. If a package has requirements it didn't
declare, you'll still have to deal with them yourself. (E.g., by asking
EasyInstall to find and install them.)
* Setup scripts using setuptools now always install using ``easy_install``
internally, for ease of uninstallation and upgrading. Note: you *must*
remove any ``extra_path`` argument from your setup script, as it conflicts
with the proper functioning of the ``easy_install`` command. (Also, added
the ``--record`` option to ``easy_install`` for the benefit of tools that
run ``setup.py install --record=filename`` on behalf of another packaging
system.)
* ``pkg_resources.AvailableDistributions.resolve()`` and related methods now
accept an ``installer`` argument: a callable taking one argument, a
``Requirement`` instance. The callable must return a ``Distribution``
object, or ``None`` if no distribution is found. This feature is used by
EasyInstall to resolve dependencies by recursively invoking itself.
0.5a3 0.5a3
* Fixed not setting script permissions to allow execution. * Fixed not setting script permissions to allow execution.
...@@ -584,10 +659,8 @@ Known Issues ...@@ -584,10 +659,8 @@ Known Issues
Future Plans Future Plans
============ ============
* Process the installed package's dependencies as well as the base package
* Additional utilities to list/remove/verify packages * Additional utilities to list/remove/verify packages
* Signature checking? SSL? Ability to suppress PyPI search? * Signature checking? SSL? Ability to suppress PyPI search?
* Display byte progress meter when downloading distributions and long pages? * Display byte progress meter when downloading distributions and long pages?
* Redirect stdout/stderr to log during run_setup? * Redirect stdout/stderr to log during run_setup?
This diff is collapsed.
...@@ -14,7 +14,7 @@ the appropriate options to ``use_setuptools()``. ...@@ -14,7 +14,7 @@ the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools. This file can also be run as a script to install or upgrade setuptools.
""" """
DEFAULT_VERSION = "0.5a3" DEFAULT_VERSION = "0.5a4"
DEFAULT_URL = "http://peak.telecommunity.com/dist/" DEFAULT_URL = "http://peak.telecommunity.com/dist/"
import sys, os import sys, os
......
...@@ -296,7 +296,7 @@ class AvailableDistributions(object): ...@@ -296,7 +296,7 @@ class AvailableDistributions(object):
"""Remove `dist` from the distribution map""" """Remove `dist` from the distribution map"""
self._distmap[dist.key].remove(dist) self._distmap[dist.key].remove(dist)
def best_match(self,requirement,path=None): def best_match(self, requirement, path=None, installer=None):
"""Find distribution best matching `requirement` and usable on `path` """Find distribution best matching `requirement` and usable on `path`
If a distribution that's already installed on `path` is unsuitable, If a distribution that's already installed on `path` is unsuitable,
...@@ -324,9 +324,9 @@ class AvailableDistributions(object): ...@@ -324,9 +324,9 @@ class AvailableDistributions(object):
for dist in distros: for dist in distros:
if dist in requirement: if dist in requirement:
return dist return dist
return self.obtain(requirement) # try and download return self.obtain(requirement, installer) # try and download/install
def resolve(self, requirements, path=None): def resolve(self, requirements, path=None, installer=None):
"""List all distributions needed to (recursively) meet requirements""" """List all distributions needed to (recursively) meet requirements"""
if path is None: if path is None:
...@@ -344,26 +344,26 @@ class AvailableDistributions(object): ...@@ -344,26 +344,26 @@ class AvailableDistributions(object):
continue continue
dist = best.get(req.key) dist = best.get(req.key)
if dist is None: if dist is None:
# Find the best distribution and add it to the map # Find the best distribution and add it to the map
dist = best[req.key] = self.best_match(req,path) dist = best[req.key] = self.best_match(req, path, installer)
if dist is None: if dist is None:
raise DistributionNotFound(req) # XXX put more info here raise DistributionNotFound(req) # XXX put more info here
to_install.append(dist) to_install.append(dist)
elif dist not in req: elif dist not in req:
# Oops, the "best" so far conflicts with a dependency # Oops, the "best" so far conflicts with a dependency
raise VersionConflict(req,dist) # XXX put more info here raise VersionConflict(dist,req) # XXX put more info here
requirements.extend(dist.depends(req.options)[::-1]) requirements.extend(dist.depends(req.options)[::-1])
processed[req] = True processed[req] = True
return to_install # return list of distros to install return to_install # return list of distros to install
def obtain(self, requirement): def obtain(self, requirement, installer=None):
"""Obtain a distro that matches requirement (e.g. via download)""" """Obtain a distro that matches requirement (e.g. via download)"""
return None # override this in subclasses if installer is not None:
return installer(requirement)
def __len__(self): return len(self._distmap) def __len__(self): return len(self._distmap)
...@@ -1316,10 +1316,9 @@ class Distribution(object): ...@@ -1316,10 +1316,9 @@ class Distribution(object):
return self.__dep_map return self.__dep_map
except AttributeError: except AttributeError:
dm = self.__dep_map = {None: []} dm = self.__dep_map = {None: []}
for section,contents in split_sections( for name in 'requires.txt', 'depends.txt':
self._get_metadata('depends.txt') for extra,reqs in split_sections(self._get_metadata(name)):
): dm.setdefault(extra,[]).extend(parse_requirements(reqs))
dm[section] = list(parse_requirements(contents))
return dm return dm
_dep_map = property(_dep_map) _dep_map = property(_dep_map)
...@@ -1351,6 +1350,7 @@ class Distribution(object): ...@@ -1351,6 +1350,7 @@ class Distribution(object):
fixup_namespace_packages(self.path) fixup_namespace_packages(self.path)
map(declare_namespace, self._get_metadata('namespace_packages.txt')) map(declare_namespace, self._get_metadata('namespace_packages.txt'))
def egg_name(self): def egg_name(self):
"""Return what this distribution's standard .egg filename should be""" """Return what this distribution's standard .egg filename should be"""
filename = "%s-%s-py%s" % ( filename = "%s-%s-py%s" % (
......
#!/usr/bin/env python #!/usr/bin/env python
"""Distutils setup file, used to install or test 'setuptools'""" """Distutils setup file, used to install or test 'setuptools'"""
VERSION = "0.5a3" VERSION = "0.5a4"
from setuptools import setup, find_packages, Require from setuptools import setup, find_packages, Require
setup( setup(
...@@ -42,7 +42,6 @@ setup( ...@@ -42,7 +42,6 @@ setup(
packages = find_packages(), packages = find_packages(),
py_modules = ['pkg_resources', 'easy_install'], py_modules = ['pkg_resources', 'easy_install'],
scripts = ['easy_install.py'], scripts = ['easy_install.py'],
extra_path = ('setuptools', 'setuptools-%s.egg' % VERSION),
classifiers = [f.strip() for f in """ classifiers = [f.strip() for f in """
Development Status :: 3 - Alpha Development Status :: 3 - Alpha
...@@ -78,5 +77,6 @@ setup( ...@@ -78,5 +77,6 @@ setup(
...@@ -8,7 +8,7 @@ from distutils.core import Command as _Command ...@@ -8,7 +8,7 @@ from distutils.core import Command as _Command
from distutils.util import convert_path from distutils.util import convert_path
import os.path import os.path
__version__ = '0.5a3' __version__ = '0.5a4'
__all__ = [ __all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
......
...@@ -11,7 +11,7 @@ from distutils.sysconfig import get_python_version, get_python_lib ...@@ -11,7 +11,7 @@ from distutils.sysconfig import get_python_version, get_python_lib
from distutils.errors import * from distutils.errors import *
from distutils import log from distutils import log
from pkg_resources import parse_requirements, get_platform, safe_name, \ from pkg_resources import parse_requirements, get_platform, safe_name, \
safe_version, Distribution safe_version, Distribution, yield_lines
def write_stub(resource, pyfile): def write_stub(resource, pyfile):
...@@ -78,7 +78,7 @@ class bdist_egg(Command): ...@@ -78,7 +78,7 @@ class bdist_egg(Command):
self.tag_build = None self.tag_build = None
self.tag_svn_revision = 0 self.tag_svn_revision = 0
self.tag_date = 0 self.tag_date = 0
self.egg_output = None
def finalize_options (self): def finalize_options (self):
self.egg_name = safe_name(self.distribution.get_name()) self.egg_name = safe_name(self.distribution.get_name())
...@@ -105,19 +105,19 @@ class bdist_egg(Command): ...@@ -105,19 +105,19 @@ class bdist_egg(Command):
self.bdist_dir = os.path.join(bdist_base, 'egg') self.bdist_dir = os.path.join(bdist_base, 'egg')
if self.plat_name is None: if self.plat_name is None:
self.plat_name = get_platform() self.plat_name = get_platform()
self.set_undefined_options('bdist',('dist_dir', 'dist_dir'))
self.set_undefined_options('bdist',('dist_dir', 'dist_dir'))
if self.egg_output is None:
# Compute filename of the output egg
basename = Distribution(
None, None, self.egg_name, self.egg_version,
get_python_version(),
self.distribution.has_ext_modules() and self.plat_name
).egg_name()
self.egg_output = os.path.join(self.dist_dir, basename+'.egg')
...@@ -146,22 +146,22 @@ class bdist_egg(Command): ...@@ -146,22 +146,22 @@ class bdist_egg(Command):
finally: finally:
self.distribution.data_files = old self.distribution.data_files = old
def get_outputs(self):
return [self.egg_output]
def write_requirements(self):
dist = self.distribution
if not getattr(dist,'install_requires',None) and \
not getattr(dist,'extras_require',None): return
requires = os.path.join(self.egg_info,"requires.txt")
log.info("writing %s", requires)
if not self.dry_run:
f = open(requires, 'wt')
f.write('\n'.join(yield_lines(dist.install_requires)))
for extra,reqs in dist.extras_require.items():
f.write('\n\n[%s]\n%s' % (extra, '\n'.join(yield_lines(reqs))))
f.close()
def run(self): def run(self):
# We run install_lib before install_data, because some data hacks # We run install_lib before install_data, because some data hacks
# pull their data path from the install_lib command. # pull their data path from the install_lib command.
...@@ -189,24 +189,19 @@ class bdist_egg(Command): ...@@ -189,24 +189,19 @@ class bdist_egg(Command):
if self.distribution.data_files: if self.distribution.data_files:
self.do_install_data() self.do_install_data()
# Make the EGG-INFO directory
archive_root = self.bdist_dir
egg_info = os.path.join(archive_root,'EGG-INFO')
self.mkpath(egg_info)
self.mkpath(self.egg_info)
if self.distribution.scripts: if self.distribution.scripts:
script_dir = os.path.join(self.bdist_dir,'EGG-INFO','scripts') script_dir = os.path.join(egg_info, 'scripts')
log.info("installing scripts to %s" % script_dir) log.info("installing scripts to %s" % script_dir)
self.call_command('install_scripts', install_dir=script_dir) self.call_command('install_scripts', install_dir=script_dir)
# And make an archive relative to the root of the self.write_requirements()
# pseudo-installation tree.
archive_basename = Distribution(
None, None, self.egg_name, self.egg_version, get_python_version(),
ext_outputs and self.plat_name
).egg_name()
pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
archive_root = self.bdist_dir
# Make the EGG-INFO directory
egg_info = os.path.join(archive_root,'EGG-INFO')
self.mkpath(egg_info)
self.mkpath(self.egg_info)
log.info("writing %s" % os.path.join(self.egg_info,'PKG-INFO')) log.info("writing %s" % os.path.join(self.egg_info,'PKG-INFO'))
if not self.dry_run: if not self.dry_run:
...@@ -231,15 +226,20 @@ class bdist_egg(Command): ...@@ -231,15 +226,20 @@ class bdist_egg(Command):
if not self.dry_run: if not self.dry_run:
os.unlink(native_libs) os.unlink(native_libs)
if self.egg_info and os.path.exists(self.egg_info): for filename in os.listdir(self.egg_info):
for filename in os.listdir(self.egg_info): path = os.path.join(self.egg_info,filename)
path = os.path.join(self.egg_info,filename) if os.path.isfile(path):
if os.path.isfile(path): self.copy_file(path,os.path.join(egg_info,filename))
self.copy_file(path,os.path.join(egg_info,filename))
if os.path.exists(os.path.join(self.egg_info,'depends.txt')):
log.warn(
"WARNING: 'depends.txt' will not be used by setuptools 0.6!"
)
log.warn(
"Use the install_requires/extras_require setup() args instead."
)
# Make the archive # Make the archive
make_zipfile(pseudoinstall_root+'.egg', make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
archive_root, verbose=self.verbose,
dry_run=self.dry_run) dry_run=self.dry_run)
if not self.keep_temp: if not self.keep_temp:
remove_tree(self.bdist_dir, dry_run=self.dry_run) remove_tree(self.bdist_dir, dry_run=self.dry_run)
......
...@@ -8,50 +8,44 @@ from setuptools.command.install import install ...@@ -8,50 +8,44 @@ from setuptools.command.install import install
from setuptools.command.install_lib import install_lib from setuptools.command.install_lib import install_lib
from distutils.errors import DistutilsOptionError, DistutilsPlatformError from distutils.errors import DistutilsOptionError, DistutilsPlatformError
from distutils.errors import DistutilsSetupError from distutils.errors import DistutilsSetupError
import setuptools import setuptools, pkg_resources
sequence = tuple, list sequence = tuple, list
class Distribution(_Distribution): class Distribution(_Distribution):
"""Distribution with support for features, tests, and package data """Distribution with support for features, tests, and package data
This is an enhanced version of 'distutils.dist.Distribution' that This is an enhanced version of 'distutils.dist.Distribution' that
effectively adds the following new optional keyword arguments to 'setup()': effectively adds the following new optional keyword arguments to 'setup()':
'install_requires' -- a string or sequence of strings specifying project
versions that the distribution requires when installed, in the format
used by 'pkg_resources.require()'. They will be installed
automatically when the package is installed. If you wish to use
packages that are not available in PyPI, or want to give your users an
alternate download location, you can add a 'find_links' option to the
'[easy_install]' section of your project's 'setup.cfg' file, and then
setuptools will scan the listed web pages for links that satisfy the
requirements.
'extras_require' -- a dictionary mapping names of optional "extras" to the
additional requirement(s) that using those extras incurs. For example,
this::
extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])
indicates that the distribution can optionally provide an extra
capability called "reST", but it can only be used if docutils and
reSTedit are installed. If the user installs your package using
EasyInstall and requests one of your extras, the corresponding
additional requirements will be installed if needed.
'features' -- a dictionary mapping option names to 'setuptools.Feature' 'features' -- a dictionary mapping option names to 'setuptools.Feature'
objects. Features are a portion of the distribution that can be objects. Features are a portion of the distribution that can be
included or excluded based on user options, inter-feature dependencies, included or excluded based on user options, inter-feature dependencies,
and availability on the current system. Excluded features are omitted and availability on the current system. Excluded features are omitted
from all setup commands, including source and binary distributions, so from all setup commands, including source and binary distributions, so
you can create multiple distributions from the same source tree. you can create multiple distributions from the same source tree.
Feature names should be valid Python identifiers, except that they may Feature names should be valid Python identifiers, except that they may
contain the '-' (minus) sign. Features can be included or excluded contain the '-' (minus) sign. Features can be included or excluded
via the command line options '--with-X' and '--without-X', where 'X' is via the command line options '--with-X' and '--without-X', where 'X' is
...@@ -84,6 +78,8 @@ class Distribution(_Distribution): ...@@ -84,6 +78,8 @@ class Distribution(_Distribution):
the distribution. They are used by the feature subsystem to configure the the distribution. They are used by the feature subsystem to configure the
distribution for the included and excluded features. distribution for the included and excluded features.
""" """
def __init__ (self, attrs=None): def __init__ (self, attrs=None):
have_package_data = hasattr(self, "package_data") have_package_data = hasattr(self, "package_data")
if not have_package_data: if not have_package_data:
...@@ -91,16 +87,68 @@ class Distribution(_Distribution): ...@@ -91,16 +87,68 @@ class Distribution(_Distribution):
self.features = {} self.features = {}
self.test_suite = None self.test_suite = None
self.requires = [] self.requires = []
self.install_requires = []
self.extras_require = {}
_Distribution.__init__(self,attrs) _Distribution.__init__(self,attrs)
if not have_package_data: if not have_package_data:
from setuptools.command.build_py import build_py from setuptools.command.build_py import build_py
self.cmdclass.setdefault('build_py',build_py) self.cmdclass.setdefault('build_py',build_py)
self.cmdclass.setdefault('build_ext',build_ext) self.cmdclass.setdefault('build_ext',build_ext)
self.cmdclass.setdefault('install',install) self.cmdclass.setdefault('install',install)
self.cmdclass.setdefault('install_lib',install_lib) self.cmdclass.setdefault('install_lib',install_lib)
def finalize_options(self):
_Distribution.finalize_options(self)
if self.features: if self.features:
self._set_global_opts_from_features() self._set_global_opts_from_features()
if self.extra_path:
raise DistutilsSetupError(
"The 'extra_path' parameter is not needed when using "
"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"
)
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."
)
def parse_command_line(self): def parse_command_line(self):
"""Process features after parsing command line options""" """Process features after parsing command line options"""
result = _Distribution.parse_command_line(self) result = _Distribution.parse_command_line(self)
...@@ -108,19 +156,12 @@ class Distribution(_Distribution): ...@@ -108,19 +156,12 @@ class Distribution(_Distribution):
self._finalize_features() self._finalize_features()
return result return result
def _feature_attrname(self,name): def _feature_attrname(self,name):
"""Convert feature name to corresponding option attribute name""" """Convert feature name to corresponding option attribute name"""
return 'with_'+name.replace('-','_') return 'with_'+name.replace('-','_')
def _set_global_opts_from_features(self): def _set_global_opts_from_features(self):
"""Add --with-X/--without-X options based on optional features""" """Add --with-X/--without-X options based on optional features"""
...@@ -343,29 +384,29 @@ class Distribution(_Distribution): ...@@ -343,29 +384,29 @@ class Distribution(_Distribution):
return nargs return nargs
def has_dependencies(self): def has_dependencies(self):
return not not self.requires return not not self.requires
def run_commands(self): def run_commands(self):
if setuptools.bootstrap_install_from and 'install' in self.commands: for cmd in self.commands:
if cmd=='install' and not cmd in self.have_run:
self.install_eggs()
else:
self.run_command(cmd)
def install_eggs(self):
from easy_install import easy_install
cmd = easy_install(self, args="x")
cmd.ensure_finalized() # finalize before bdist_egg munges install cmd
self.run_command('bdist_egg')
args = [self.get_command_obj('bdist_egg').egg_output]
if setuptools.bootstrap_install_from:
# Bootstrap self-installation of setuptools # Bootstrap self-installation of setuptools
from easy_install import easy_install args.insert(0, setuptools.bootstrap_install_from)
cmd = easy_install( cmd.args = args
self, args=[setuptools.bootstrap_install_from], zip_ok=1 cmd.run()
) self.have_run['install'] = 1
cmd.ensure_finalized() setuptools.bootstrap_install_from = None
cmd.run()
setuptools.bootstrap_install_from = None
_Distribution.run_commands(self)
def get_cmdline_options(self): def get_cmdline_options(self):
"""Return a '{cmd: {opt:val}}' map of all command-line options """Return a '{cmd: {opt:val}}' map of all command-line options
......
...@@ -203,7 +203,7 @@ class PackageIndex(AvailableDistributions): ...@@ -203,7 +203,7 @@ class PackageIndex(AvailableDistributions):
def find_packages(self,requirement): def find_packages(self, requirement):
self.scan_url(self.index_url + requirement.distname+'/') self.scan_url(self.index_url + requirement.distname+'/')
if not self.package_pages.get(requirement.key): if not self.package_pages.get(requirement.key):
# We couldn't find the target package, so search the index page too # We couldn't find the target package, so search the index page too
...@@ -221,13 +221,13 @@ class PackageIndex(AvailableDistributions): ...@@ -221,13 +221,13 @@ class PackageIndex(AvailableDistributions):
# scan each page that might be related to the desired package # scan each page that might be related to the desired package
self.scan_url(url) self.scan_url(url)
def obtain(self,requirement): def obtain(self, requirement, installer=None):
self.find_packages(requirement) self.find_packages(requirement)
for dist in self.get(requirement.key, ()): for dist in self.get(requirement.key, ()):
if dist in requirement: if dist in requirement:
return dist return dist
self.debug("%s does not match %s", requirement, dist) self.debug("%s does not match %s", requirement, dist)
return super(PackageIndex, self).obtain(requirement,installer)
...@@ -245,19 +245,20 @@ class PackageIndex(AvailableDistributions): ...@@ -245,19 +245,20 @@ class PackageIndex(AvailableDistributions):
def download(self, spec, tmpdir): def download(self, spec, tmpdir):
"""Locate and/or download `spec`, returning a local filename """Locate and/or download `spec` to `tmpdir`, returning a local path
`spec` may be a ``Requirement`` object, or a string containing a URL, `spec` may be a ``Requirement`` object, or a string containing a URL,
an existing local filename, or a package/version requirement spec an existing local filename, or a project/version requirement spec
(i.e. the string form of a ``Requirement`` object). (i.e. the string form of a ``Requirement`` object).
If necessary, the requirement is searched for in the package index. If `spec` is a ``Requirement`` object or a string containing a
If the download is successful, the return value is a local file path, project/version requirement spec, this method is equivalent to
and it is a subpath of `tmpdir` if the distribution had to be the ``fetch()`` method. If `spec` is a local, existing file or
downloaded. If no matching distribution is found, return ``None``. directory name, it is simply returned unchanged. If `spec` is a URL,
Various errors may be raised if a problem occurs during downloading. it is downloaded to a subpath of `tmpdir`, and the local filename is
returned. Various errors may be raised if a problem occurs during
downloading.
""" """
if not isinstance(spec,Requirement): if not isinstance(spec,Requirement):
scheme = URL_SCHEME(spec) scheme = URL_SCHEME(spec)
if scheme: if scheme:
...@@ -275,16 +276,56 @@ class PackageIndex(AvailableDistributions): ...@@ -275,16 +276,56 @@ class PackageIndex(AvailableDistributions):
"Not a URL, existing file, or requirement spec: %r" % "Not a URL, existing file, or requirement spec: %r" %
(spec,) (spec,)
) )
return self.fetch(spec, tmpdir, force_scan)
def fetch(self, requirement, tmpdir, force_scan=False):
"""Obtain a file suitable for fulfilling `requirement`
`requirement` must be a ``pkg_resources.Requirement`` instance.
If necessary, or if the `force_scan` flag is set, the requirement is
searched for in the (online) package index as well as the locally
installed packages. If a distribution matching `requirement` is found,
the return value is the same as if you had called the ``download()``
method with the matching distribution's URL. If no matching
distribution is found, returns ``None``.
"""
# process a Requirement # process a Requirement
self.info("Searching for %s", spec) self.info("Searching for %s", requirement)
dist = self.best_match(spec,[])
if force_scan:
self.find_packages(requirement)
dist = self.best_match(requirement, []) # XXX
if dist is not None: if dist is not None:
self.info("Best match: %s", dist) self.info("Best match: %s", dist)
return self.download(dist.path, tmpdir) return self.download(dist.path, tmpdir)
self.warn("No local packages or download links found for %s", spec) self.warn(
"No local packages or download links found for %s", requirement
)
return None return None
dl_blocksize = 8192 dl_blocksize = 8192
def _download_to(self, url, filename): def _download_to(self, url, filename):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment