[Checkins] SVN: zc.buildout/trunk/ Features:

Jim Fulton jim at zope.com
Tue Mar 6 11:03:00 EST 2007


Log message for revision 73006:
  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.
  

Changed:
  U   zc.buildout/trunk/CHANGES.txt
  U   zc.buildout/trunk/src/zc/buildout/buildout.txt
  U   zc.buildout/trunk/src/zc/buildout/easy_install.py
  U   zc.buildout/trunk/src/zc/buildout/easy_install.txt
  U   zc.buildout/trunk/src/zc/buildout/testing.py
  U   zc.buildout/trunk/src/zc/buildout/testing.txt
  U   zc.buildout/trunk/src/zc/buildout/tests.py

-=-
Modified: zc.buildout/trunk/CHANGES.txt
===================================================================
--- zc.buildout/trunk/CHANGES.txt	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/CHANGES.txt	2007-03-06 16:02:59 UTC (rev 73006)
@@ -23,10 +23,22 @@
 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)
 =====================
 

Modified: zc.buildout/trunk/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.txt	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/src/zc/buildout/buildout.txt	2007-03-06 16:02:59 UTC (rev 73006)
@@ -1598,8 +1598,10 @@
     >>> 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]

Modified: zc.buildout/trunk/src/zc/buildout/easy_install.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.py	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.py	2007-03-06 16:02:59 UTC (rev 73006)
@@ -45,6 +45,10 @@
         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 @@
                  always_unzip=False,
                  path=None,
                  newest=True,
+                 versions=None,
                  ):
         self._dest = dest
         self._links = list(links)
@@ -123,6 +128,7 @@
         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 @@
             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 @@
                     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 @@
                         "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 @@
         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 @@
                 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 @@
     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 @@
 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)
 
         

Modified: zc.buildout/trunk/src/zc/buildout/easy_install.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/easy_install.txt	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/src/zc/buildout/easy_install.txt	2007-03-06 16:02:59 UTC (rev 73006)
@@ -78,6 +78,11 @@
    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 @@
     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,7 +581,12 @@
    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 @@
 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 @@
     '/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,12 +686,33 @@
     ...   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 @@
 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:

Modified: zc.buildout/trunk/src/zc/buildout/testing.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/testing.py	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/src/zc/buildout/testing.py	2007-03-06 16:02:59 UTC (rev 73006)
@@ -55,6 +55,13 @@
 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 @@
         cat = cat,
         mkdir = mkdir,
         rmdir = rmdir,
+        remove = remove,
         tmpdir = tmpdir,
         write = write,
         system = system,

Modified: zc.buildout/trunk/src/zc/buildout/testing.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/testing.txt	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/src/zc/buildout/testing.txt	2007-03-06 16:02:59 UTC (rev 73006)
@@ -37,6 +37,10 @@
     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

Modified: zc.buildout/trunk/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/tests.py	2007-03-06 11:35:37 UTC (rev 73005)
+++ zc.buildout/trunk/src/zc/buildout/tests.py	2007-03-06 16:02:59 UTC (rev 73006)
@@ -1436,6 +1436,8 @@
                 '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'),
                ])
             ),
 



More information about the Checkins mailing list