[Checkins] SVN: zc.buildout/branches/gary-support-system-python/src/zc/buildout/ fix a bug that you often encounter when you use a system Python.
Gary Poster
gary.poster at canonical.com
Sat Jul 11 12:49:15 EDT 2009
Log message for revision 101821:
fix a bug that you often encounter when you use a system Python.
Changed:
U zc.buildout/branches/gary-support-system-python/src/zc/buildout/easy_install.py
U zc.buildout/branches/gary-support-system-python/src/zc/buildout/testing.py
U zc.buildout/branches/gary-support-system-python/src/zc/buildout/tests.py
-=-
Modified: zc.buildout/branches/gary-support-system-python/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/branches/gary-support-system-python/src/zc/buildout/easy_install.py 2009-07-11 12:21:04 UTC (rev 101820)
+++ zc.buildout/branches/gary-support-system-python/src/zc/buildout/easy_install.py 2009-07-11 16:49:15 UTC (rev 101821)
@@ -112,6 +112,7 @@
site_packages = [p for p in get_sys_path() if p not in stdlib]
return (stdlib, site_packages)
+
class IncompatibleVersionError(zc.buildout.UserError):
"""A specified version is incompatible with a given requirement.
"""
@@ -140,6 +141,7 @@
FILE_SCHEME = re.compile('file://', re.I).match
+
class AllowHostsPackageIndex(setuptools.package_index.PackageIndex):
"""Will allow urls that are local to the system.
@@ -730,35 +732,53 @@
self._maybe_add_setuptools(ws, dist)
# OK, we have the requested distributions and they're in the working
- # set, but they may have unmet requirements. We'll simply keep
- # trying to resolve requirements, adding missing requirements as they
- # are reported.
+ # set, but they may have unmet requirements. We'll resolve these
+ # requirements. This is code modified from
+ # pkg_resources.WorkingSet.resolve. We can't reuse that code directly
+ # because we have to constrain our requirements (see
+ # versions_section_ignored_for_dependency_in_favor_of_site_packages in
+ # zc.buildout.tests).
#
- # Note that we don't pass in the environment, because we want
+ requirements.reverse() # set up the stack
+ processed = {} # set of processed requirements
+ best = {} # key -> dist
+ #
+ # Note that we don't use the existing environment, because we want
# to look for new eggs unless what we have is the best that
# matches the requirement.
- while 1:
- try:
- ws.resolve(requirements)
- except pkg_resources.DistributionNotFound, err:
- [requirement] = err
- requirement = self._constrain(requirement)
- if dest:
- logger.debug('Getting required %r', str(requirement))
- else:
- logger.debug('Adding required %r', str(requirement))
- _log_requirement(ws, requirement)
+ env = pkg_resources.Environment(ws.entries)
+ while requirements:
+ # process dependencies breadth-first
+ req = self._constrain(requirements.pop(0))
+ if req in processed:
+ # Ignore cyclic or redundant dependencies
+ continue
+ dist = best.get(req.key)
+ if dist is None:
+ # Find the best distribution and add it to the map
+ dist = ws.by_key.get(req.key)
+ if dist is None:
+ try:
+ dist = best[req.key] = env.best_match(req, ws)
+ except pkg_resources.VersionConflict, err:
+ raise VersionConflict(err, ws)
+ if dist is None:
+ if dest:
+ logger.debug('Getting required %r', str(req))
+ else:
+ logger.debug('Adding required %r', str(req))
+ _log_requirement(ws, req)
+ for dist in self._get_dist(req,
+ ws, self._always_unzip):
+ ws.add(dist)
+ self._maybe_add_setuptools(ws, dist)
+ if dist not in req:
+ # Oops, the "best" so far conflicts with a dependency
+ raise VersionConflict(
+ pkg_resources.VersionConflict(dist, req), ws)
+ requirements.extend(dist.requires(req.extras)[::-1])
+ processed[req] = True
- for dist in self._get_dist(requirement, ws, self._always_unzip
- ):
-
- ws.add(dist)
- self._maybe_add_setuptools(ws, dist)
- except pkg_resources.VersionConflict, err:
- raise VersionConflict(err, ws)
- else:
- break
-
return ws
def build(self, spec, build_ext):
Modified: zc.buildout/branches/gary-support-system-python/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/branches/gary-support-system-python/src/zc/buildout/testing.py 2009-07-11 12:21:04 UTC (rev 101820)
+++ zc.buildout/branches/gary-support-system-python/src/zc/buildout/testing.py 2009-07-11 16:49:15 UTC (rev 101821)
@@ -140,6 +140,11 @@
def bdist_egg(setup, executable, dest):
_runsetup(setup, executable, 'bdist_egg', '-d', dest)
+def sys_install(setup, dest):
+ _runsetup(setup, sys.executable, 'install', '--home', dest,
+ '--single-version-externally-managed',
+ '--record', os.path.join(dest, 'added'))
+
def find_python(version):
e = os.environ.get('PYTHON%s' % version)
if e is not None:
Modified: zc.buildout/branches/gary-support-system-python/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/branches/gary-support-system-python/src/zc/buildout/tests.py 2009-07-11 12:21:04 UTC (rev 101820)
+++ zc.buildout/branches/gary-support-system-python/src/zc/buildout/tests.py 2009-07-11 16:49:15 UTC (rev 101821)
@@ -1768,6 +1768,91 @@
1 2
"""
+def versions_section_ignored_for_dependency_in_favor_of_site_packages():
+ r"""
+This is a test for a bugfix.
+
+The error showed itself when at least two dependencies were in a shared
+location like site-packages, and the first one met the "versions" setting. The
+first dependency would be added, but subsequent dependencies from the same
+location (e.g., site-packages) would use the version of the package found in
+the shared location, ignoring the version setting.
+
+We begin with a Python that has demoneeded version 1.1 installed and a demo
+version 0.3, all in a site-packages-like shared directory. We need to create
+this.
+
+ >>> executable_buildout = tmpdir('executable_buildout')
+ >>> old_wd = os.getcwd()
+ >>> os.chdir(executable_buildout)
+ >>> import textwrap
+ >>> write('buildout.cfg', textwrap.dedent(
+ ... '''
+ ... [buildout]
+ ... parts = interpreter
+ ...
+ ... [interpreter]
+ ... recipe = zc.recipe.egg
+ ... scripts = py
+ ... interpreter = py
+ ... extra-paths = %(site-packages)s
+ ... include-site-packages = false
+ ... eggs = setuptools
+ ... ''') % {'site-packages': join(site_packages, 'lib', 'python')})
+ >>> zc.buildout.buildout.Buildout(
+ ... 'buildout.cfg',
+ ... [('buildout', 'log-level', 'WARNING'),
+ ... # trick bootstrap into putting the buildout develop egg
+ ... # in the eggs dir.
+ ... ('buildout', 'develop-eggs-directory', 'eggs'),
+ ... ]
+ ... ).bootstrap([])
+ >>> os.mkdir('develop-eggs')
+ >>> zc.buildout.testing.install_develop(
+ ... 'zc.recipe.egg',
+ ... os.path.join(executable_buildout, 'develop-eggs'))
+ >>> print system(
+ ... os.path.join(executable_buildout, 'bin', 'buildout'))
+ Installing interpreter.
+ Generated interpreter '/executable_buildout/bin/py'.
+ <BLANKLINE>
+ >>> os.chdir(old_wd)
+ >>> primed_executable = os.path.join(executable_buildout, 'bin', 'py')
+
+``eggrecipedemo.main()`` shows the number after the dot (that is, ``X`` in
+``1.X``), for the demo package and the demoneeded package, so this demonstrates
+that our Python does in fact have demo version 0.3 and demoneeded version 1.1.
+
+ >>> print system(primed_executable+" -c "+
+ ... "'import eggrecipedemo; eggrecipedemo.main()'")
+ 3 1
+ <BLANKLINE>
+
+Now we will install bigdemo, specifying different versions of demo
+and demoneeded in a versions section. Before the bugfix, the demo version
+would be honored, but not the demoneeded.
+
+Now here's a setup that would expose the bug, using the
+zc.buildout.easy_install API.
+
+ >>> example_dest = tmpdir('example_dest')
+ >>> workingset = zc.buildout.easy_install.install(
+ ... ['bigdemo'], example_dest, links=[sample_eggs],
+ ... executable=primed_executable,
+ ... index=None, include_site_packages=True,
+ ... versions={'demoneeded': '1.2c1', 'demo': '0.3'})
+ >>> for dist in workingset:
+ ... print dist
+ bigdemo 0.1
+ demo 0.3
+ demoneeded 1.2c1
+
+Before the bugfix, the demoneeded distribution was not included in the working
+set, and the demoneeded in site-packages (of the wrong number) would have been
+used.
+
+ """
+
if sys.version_info > (2, 4):
def test_exit_codes():
"""
@@ -2704,6 +2789,8 @@
def create_sample_eggs(test, executable=sys.executable):
write = test.globs['write']
dest = test.globs['sample_eggs']
+ site_packages = test.globs['tmpdir']('site_packages')
+ test.globs['site_packages'] = site_packages
tmp = tempfile.mkdtemp()
try:
write(tmp, 'README.txt', '')
@@ -2720,6 +2807,8 @@
% (i, c1)
)
zc.buildout.testing.sdist(tmp, dest)
+ if i==1:
+ zc.buildout.testing.sys_install(tmp, site_packages)
write(
tmp, 'setup.py',
@@ -2749,6 +2838,8 @@
" zip_safe=True, version='0.%s%s')\n" % (i, c1)
)
zc.buildout.testing.bdist_egg(tmp, executable, dest)
+ if i==3:
+ zc.buildout.testing.sys_install(tmp, site_packages)
write(tmp, 'eggrecipebigdemo.py', 'import eggrecipedemo')
write(
@@ -2825,10 +2916,16 @@
zc.buildout.testing.install_develop('zc.recipe.egg', test)
# most tests don't need this set up, and it takes some time, so we just
# make it available as a convenience.
- def get_executable_with_site_packages():
+ def get_executable_with_site_packages(requirements=None):
executable_buildout = test.globs['tmpdir']('executable')
old_wd = os.getcwd()
os.chdir(executable_buildout)
+ if requirements is None:
+ requirements = ['demoneeded', 'setuptools', 'other']
+ elif len([req for req in requirements
+ if req.startswith('setuptools')]) == 0:
+ requirements.append('setuptools') # you always need that.
+ requirements = '\n '.join(requirements)
test.globs['write']('buildout.cfg', textwrap.dedent(
'''
[buildout]
@@ -2839,10 +2936,9 @@
[interpreter]
recipe = zc.recipe.egg
interpreter = py
- eggs = demoneeded
- setuptools
- other
- ''' % test.globs))
+ eggs = %(requirements)s
+ ''') % {'requirements': requirements,
+ 'link_server': test.globs['link_server']})
zc.buildout.buildout.Buildout(
'buildout.cfg',
[('buildout', 'log-level', 'WARNING'),
More information about the Checkins
mailing list