[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