Commit da688181 authored by jim's avatar jim

Features:

- The easy_install module install and build functions now accept a
  versions argument that supplied to mapping from project name to
  version numbers.  This can be used to fix version numbers for
  required distributions and their depenencies.

When a version isn't fixed, using either a versions option or using
  a fixed version number in a requirement, then a debug log message is
  emitted indicating the version picked.  This is useful for setting
  versions options.

- Added a remove testing helper function that removes files or directories.


git-svn-id: http://svn.zope.org/repos/main/zc.buildout/trunk@73006 62d5b8a3-27da-0310-9561-8e5933582275
parent 984767c8
......@@ -23,10 +23,22 @@ Change History
Feature Changes
---------------
- The easy_install module install and build functions now accept a
versions argument that supplied to mapping from project name to
version numbers. This can be used to fix version numbers for
required distributions and their depenencies.
When a version isn't fixed, using either a versions option or using
a fixed version number in a requirement, then a debug log message is
emitted indicating the version picked. This is useful for setting
versions options.
- Adjusted the output for verbosity levels. Using a single -v option
no longer causes voluminous setuptools output. Uisng -vv and -vvv
now triggers extra setuptools output.
- Added a remove testing helper function that removes files or directories.
1.0.0b20 (2007-02-08)
=====================
......
......@@ -1598,8 +1598,10 @@ database is shown.
>>> print system(buildout+' -v'),
zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
zc.buildout.easy_install: We have a develop egg for zc.buildout
zc.buildout.easy_install: Picked version for zc.buildout = 1.0.0
zc.buildout.easy_install: We have the best distribution that satisfies
setuptools
zc.buildout.easy_install: Picked version for setuptools = 0.6
<BLANKLINE>
Configuration data:
[buildout]
......
......@@ -45,6 +45,10 @@ buildout_and_setuptools_path = [
pkg_resources.Requirement.parse('zc.buildout')).location,
]
class IncompatibleVersionError(zc.buildout.UserError):
"""A specified version is incompatible with a given requirement.
"""
_versions = {sys.executable: '%d.%d' % sys.version_info[:2]}
def _get_version(executable):
try:
......@@ -109,6 +113,7 @@ class Installer:
always_unzip=False,
path=None,
newest=True,
versions=None,
):
self._dest = dest
self._links = list(links)
......@@ -123,6 +128,7 @@ class Installer:
self._env = pkg_resources.Environment(path,
python=_get_version(executable))
self._index = _get_index(executable, index, links)
self._versions = versions or {}
def _satisfied(self, req):
dists = [dist for dist in self._env[req.project_name] if dist in req]
......@@ -246,7 +252,7 @@ class Installer:
if self._dest is not None:
logger.info("Getting new distribution for %s", requirement)
# Retrieve the dist:
# Retrieve the dist:grokonepage
index = self._index
dist = index.obtain(requirement)
if dist is None:
......@@ -336,6 +342,13 @@ class Installer:
self._index = _get_index(self._executable,
self._index_url, self._links)
# Check whether we picked a version and, if we did, report it:
if not (
len(requirement.specs) == 1 and requirement.specs[0][0] == '=='
):
logger.debug('Picked version for %s = %s',
dist.project_name, dist.version)
return dist
def _maybe_add_setuptools(self, ws, dist):
......@@ -351,12 +364,27 @@ class Installer:
"uses namespace packages but the distribution "
"does not require setuptools.",
dist)
requirement = pkg_resources.Requirement.parse('setuptools')
requirement = self._constrain(
pkg_resources.Requirement.parse('setuptools')
)
if ws.find(requirement) is None:
dist = self._get_dist(requirement, ws, False)
ws.add(dist)
def _constrain(self, requirement):
version = self._versions.get(requirement.project_name)
if version:
if version not in requirement:
logger.error("The version, %s, is not consistent with the "
"requirement, %s", version, requirement)
raise IncompatibleVersionError("Bad version", version)
requirement = pkg_resources.Requirement.parse(
"%s ==%s" % (requirement.project_name, version))
return requirement
def install(self, specs, working_set=None):
logger.debug('Installing %r', specs)
......@@ -366,9 +394,11 @@ class Installer:
if dest is not None and dest not in path:
path.insert(0, dest)
requirements = [pkg_resources.Requirement.parse(spec)
requirements = [self._constrain(pkg_resources.Requirement.parse(spec))
for spec in specs]
if working_set is None:
ws = pkg_resources.WorkingSet([])
else:
......@@ -392,6 +422,7 @@ class Installer:
ws.resolve(requirements)
except pkg_resources.DistributionNotFound, err:
[requirement] = err
requirement = self._constrain(requirement)
if dest:
logger.debug('Getting required %s', requirement)
dist = self._get_dist(requirement, ws, self._always_unzip)
......@@ -405,7 +436,7 @@ class Installer:
def build(self, spec, build_ext):
logger.debug('Building %r', spec)
requirement = pkg_resources.Requirement.parse(spec)
requirement = self._constrain(pkg_resources.Requirement.parse(spec))
dist = self._satisfied(requirement)
if dist is not None:
......@@ -465,17 +496,18 @@ class Installer:
def install(specs, dest,
links=(), index=None,
executable=sys.executable, always_unzip=False,
path=None, working_set=None, newest=True):
path=None, working_set=None, newest=True, versions=None):
installer = Installer(dest, links, index, executable, always_unzip, path,
newest)
newest, versions)
return installer.install(specs, working_set)
def build(spec, dest, build_ext,
links=(), index=None,
executable=sys.executable,
path=None, newest=True):
installer = Installer(dest, links, index, executable, True, path, newest)
path=None, newest=True, versions=None):
installer = Installer(dest, links, index, executable, True, path, newest,
versions)
return installer.build(spec, build_ext)
......
......@@ -78,6 +78,11 @@ newest
not None, then the install function will search for the newest
distributions that satisfy the requirements.
versions
A dictionary mapping project names to version numbers to be used
when selecting distributions. This can be used to specify a set of
distribution versions independent of other requirements.
The install method returns a working set containing the distributions
needed to meet the given requirements.
......@@ -185,6 +190,76 @@ can be useful when debugging.
d demo-0.3-py2.4.egg
d demoneeded-1.1-py2.4.egg
Specifying version information indepenent of requirements
---------------------------------------------------------
Sometimes it's useful to specify version information indepenent of
normal requirements specifications. For example, a buildout may need
to lock down a set of versions, without having to put put version
numbers in setup files or part definitions. If a dictionary is passed
to the install function, mapping project names to version numbers,
then the versions numbers will be used.
>>> ws = zc.buildout.easy_install.install(
... ['demo'], dest, links=[link_server], index=link_server+'index/',
... versions = dict(demo='0.2', demoneeded='1.0'))
>>> [d.version for d in ws]
['0.2', '1.0']
In this example, we specified a version for demoneeded, even though we
didn't define a requirement for it. The versions specified apply to
depenencies as well as the specified requirements.
If we specify a version that's incompatible with a requirement, then
we'll get an error:
>>> from zope.testing.loggingsupport import InstalledHandler
>>> handler = InstalledHandler('zc.buildout.easy_install')
>>> import logging
>>> logging.getLogger('zc.buildout.easy_install').propagate = False
>>> ws = zc.buildout.easy_install.install(
... ['demo >0.2'], dest, links=[link_server],
... index=link_server+'index/',
... versions = dict(demo='0.2', demoneeded='1.0'))
Traceback (most recent call last):
...
IncompatibleVersionError: Bad version 0.2
>>> print handler
zc.buildout.easy_install DEBUG
Installing ['demo >0.2']
zc.buildout.easy_install ERROR
The version, 0.2, is not consistent with the requirement, demo>0.2
>>> handler.clear()
If no versions are specified, a debugging message will be output
reporting that a version was picked automatically:
>>> ws = zc.buildout.easy_install.install(
... ['demo'], dest, links=[link_server], index=link_server+'index/',
... )
>>> print handler
zc.buildout.easy_install DEBUG
Installing ['demo']
zc.buildout.easy_install DEBUG
We have the best distribution that satisfies
demo
zc.buildout.easy_install DEBUG
Picked version for demo = 0.3
zc.buildout.easy_install DEBUG
Getting required demoneeded
zc.buildout.easy_install DEBUG
We have the best distribution that satisfies
demoneeded
zc.buildout.easy_install DEBUG
Picked version for demoneeded = 1.1
>>> handler.uninstall()
>>> logging.getLogger('zc.buildout.easy_install').propagate = True
Script generation
-----------------
......@@ -506,6 +581,11 @@ newest
not None, then the install function will search for the newest
distributions that satisfy the requirements.
versions
A dictionary mapping project names to version numbers to be used
when selecting distributions. This can be used to specify a set of
distribution versions independent of other requirements.
Our link server included a source distribution that includes a simple
extension, extdemo.c::
......@@ -553,7 +633,9 @@ The function returns the list of eggs
Now if we look in our destination directory, we see we have an extdemo egg:
>>> ls(dest)
- demo-0.2-py2.4.egg
d demo-0.3-py2.4.egg
- demoneeded-1.0-py2.4.egg
d demoneeded-1.1-py2.4.egg
d extdemo-1.4-py2.4-unix-i686.egg
......@@ -589,7 +671,9 @@ If we run build with newest set to False, we won't get an update:
'/sample-install/extdemo-1.4-py2.4-linux-i686.egg'
>>> ls(dest)
- demo-0.2-py2.4.egg
d demo-0.3-py2.4.egg
- demoneeded-1.0-py2.4.egg
d demoneeded-1.1-py2.4.egg
d extdemo-1.4-py2.4-unix-i686.egg
......@@ -602,11 +686,32 @@ get an updated egg:
... links=[link_server], index=link_server+'index/')
'/sample-install/extdemo-1.5-py2.4-unix-i686.egg'
>>> ls(dest)
- demo-0.2-py2.4.egg
d demo-0.3-py2.4.egg
- demoneeded-1.0-py2.4.egg
d demoneeded-1.1-py2.4.egg
d extdemo-1.4-py2.4-unix-i686.egg
d extdemo-1.5-py2.4-unix-i686.egg
The versions option also influences the versions used. For example,
if we specify a version for extdemo, then that will be used, even
though it isn't the newest. Let's clean out the destimation directory
first:
>>> import os
>>> for name in os.listdir(dest):
... remove(dest, name)
>>> zc.buildout.easy_install.build(
... 'extdemo', dest,
... {'include-dirs': os.path.join(sample_buildout, 'include')},
... links=[link_server], index=link_server+'index/',
... versions=dict(extdemo='1.4'))
'/sample-install/extdemo-1.4-py2.4-unix-i686.egg'
>>> ls(dest)
d extdemo-1.4-py2.4-unix-i686.egg
Handling custom build options for extensions in develop eggs
------------------------------------------------------------
......@@ -657,10 +762,7 @@ Now if we look in our destination directory, we see we have an extdemo
egg link:
>>> ls(dest)
d demo-0.3-py2.4.egg
d demoneeded-1.1-py2.4.egg
d extdemo-1.4-py2.4-linux-i686.egg
d extdemo-1.5-py2.4-linux-i686.egg
d extdemo-1.4-py2.4-unix-i686.egg
- extdemo.egg-link
And that the source directory contains the compiled extension:
......
......@@ -55,6 +55,13 @@ def ls(dir, *subs):
def mkdir(*path):
os.mkdir(os.path.join(*path))
def remove(*path):
path = os.path.join(*path)
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(path)
def rmdir(*path):
shutil.rmtree(os.path.join(*path))
......@@ -202,6 +209,7 @@ def buildoutSetUp(test):
cat = cat,
mkdir = mkdir,
rmdir = rmdir,
remove = remove,
tmpdir = tmpdir,
write = write,
system = system,
......
......@@ -37,6 +37,10 @@ number of names to the test namespace:
Remove a directory. The directory path is provided as one or
more strings, to be joined with os.path.join.
``remove(*path)``
Remove a directory or file. The path is provided as one or
more strings, to be joined with os.path.join.
``tmpdir(name)``
Create a temporary directory with the given name. The directory
will be automatically removed at the end of the test. The path of
......
......@@ -1436,6 +1436,8 @@ def test_suite():
'zc.buildout.egg'),
(re.compile('creating \S*setup.cfg'), 'creating setup.cfg'),
(re.compile('hello\%ssetup' % os.path.sep), 'hello/setup'),
(re.compile('Picked version for (\S+) = \S+'),
'Picked version for \\1 = V.V'),
])
),
......
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