[Checkins] SVN: zc.buildout/trunk/s Added a self-updating feature, so buildouts now update themselves.

Jim Fulton jim at zope.com
Fri Sep 1 19:54:30 EDT 2006


Log message for revision 69924:
  Added a self-updating feature, so buildouts now update themselves.
  

Changed:
  U   zc.buildout/trunk/setup.py
  U   zc.buildout/trunk/src/zc/buildout/buildout.py
  U   zc.buildout/trunk/src/zc/buildout/buildout.txt
  U   zc.buildout/trunk/src/zc/buildout/tests.py
  A   zc.buildout/trunk/src/zc/buildout/update.txt

-=-
Modified: zc.buildout/trunk/setup.py
===================================================================
--- zc.buildout/trunk/setup.py	2006-09-01 23:54:27 UTC (rev 69923)
+++ zc.buildout/trunk/setup.py	2006-09-01 23:54:29 UTC (rev 69924)
@@ -23,4 +23,5 @@
     entry_points = {'console_scripts':
                     ['buildout = %s.buildout:main' % name]}, 
     dependency_links = ['http://download.zope.org/distribution/'],
+    zip_safe=False,
     )

Modified: zc.buildout/trunk/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.py	2006-09-01 23:54:27 UTC (rev 69923)
+++ zc.buildout/trunk/src/zc/buildout/buildout.py	2006-09-01 23:54:29 UTC (rev 69924)
@@ -25,7 +25,6 @@
 import sys
 import ConfigParser
 
-import zc.buildout.easy_install
 import pkg_resources
 import zc.buildout.easy_install
 
@@ -245,6 +244,9 @@
         # for eggs:
         sys.path.insert(0, self['buildout']['develop-eggs-directory'])
 
+        # Check for updates. This could cause the process to be rstarted
+        self._maybe_upgrade()
+
         # Build develop eggs
         self._develop()
 
@@ -524,6 +526,54 @@
                 _save_options(section, self[section], sys.stdout)
             print    
 
+    def _maybe_upgrade(self):
+        # See if buildout or setuptools need to be upgraded.
+        # If they do, do the upgrade and return true.
+        # Otherwise, return False.
+        ws = zc.buildout.easy_install.install(
+            [
+            (spec + ' ' + self['buildout'].get(spec+'-version', '')).strip()
+            for spec in ('zc.buildout', 'setuptools')
+            ],
+            self['buildout']['eggs-directory'],
+            links = self['buildout'].get('find-links', '').split(),
+            index = self['buildout'].get('index'),
+            path = [self['buildout']['develop-eggs-directory']],
+            )
+
+        upgraded = []
+        for project in 'zc.buildout', 'setuptools':
+            req = pkg_resources.Requirement.parse(project)
+            if ws.find(req) != pkg_resources.working_set.find(req):
+                upgraded.append(ws.find(req))
+
+        if not upgraded:
+            return
+        
+        self._logger.info("Upgraded: %s, restarting.",
+                          ",  ".join([("%s version %s"
+                                       % (dist.project_name, dist.version)
+                                       )
+                                      for dist in upgraded
+                                      ]
+                                     ),
+                          )
+                
+        # the new dist is different, so we've upgraded.
+        # Update the scripts and return True
+        zc.buildout.easy_install.scripts(
+            ['zc.buildout'], ws, sys.executable,
+            self['buildout']['bin-directory'],
+            )
+
+        # Restart
+        args = map(zc.buildout.easy_install._safe_arg, sys.argv)
+        if not __debug__:
+            args.insert(0, '-O')
+        args.insert(0, sys.executable)
+        sys.exit(os.spawnv(os.P_WAIT, sys.executable, args))
+
+
 _spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*'
                         '|'
                         '^[ \t\r\f\v]+'

Modified: zc.buildout/trunk/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-09-01 23:54:27 UTC (rev 69923)
+++ zc.buildout/trunk/src/zc/buildout/buildout.txt	2006-09-01 23:54:29 UTC (rev 69924)
@@ -1103,6 +1103,10 @@
     python = buildout
     verbosity = 20
     <BLANKLINE>
+    zc.buildout.easy_install: Installing ['zc.buildout', 'setuptools']
+    zc.buildout.easy_install: We have a develop egg for zc.buildout
+    zc.buildout.easy_install: We have the best distributon that satisfies
+    setuptools
 
 All of these options can be overridden by configuration files or by
 command-line assignments.  We've discussed most of these options

Modified: zc.buildout/trunk/src/zc/buildout/tests.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/tests.py	2006-09-01 23:54:27 UTC (rev 69923)
+++ zc.buildout/trunk/src/zc/buildout/tests.py	2006-09-01 23:54:29 UTC (rev 69924)
@@ -16,9 +16,10 @@
 $Id$
 """
 
-import os, re, shutil, sys, unittest
+import os, re, shutil, sys, unittest, zipfile
 from zope.testing import doctest, renormalizing
-import zc.buildout.testing
+import pkg_resources
+import zc.buildout.testing, zc.buildout.easy_install
 
 os_path_sep = os.path.sep
 if os_path_sep == '\\':
@@ -321,6 +322,75 @@
 
         return result
 
+        
+
+egg_parse = re.compile('([0-9a-zA-Z_.]+)-([0-9a-zA-Z_.]+)-py(\d[.]\d).egg$'
+                       ).match
+def makeNewRelease(project, ws, dest):
+    dist = ws.find(pkg_resources.Requirement.parse(project))
+    eggname, oldver, pyver = egg_parse(
+        os.path.basename(dist.location)
+        ).groups()
+    dest = os.path.join(dest, "%s-99.99-py%s.egg" % (eggname, pyver)) 
+    if os.path.isfile(dist.location):
+        shutil.copy(dist.location, dest)
+        zip = zipfile.ZipFile(dest, 'a')
+        zip.writestr(
+            'EGG-INFO/PKG-INFO',
+            zip.read('EGG-INFO/PKG-INFO').replace("Version: %s" % oldver, 
+                                                  "Version: 99.99")
+            )
+        zip.close()
+    else:
+        shutil.copy(dist.location, dest)
+        info_path = os.path.join(dest, 'EGG-INFO', 'PKG-INFO')
+        info = open(info_path).read().replace("Version: %s" % oldver, 
+                                              "Version: 99.99")
+        open(info_path, 'w').write(info)
+
+
+def updateSetup(test):
+    zc.buildout.testing.buildoutSetUp(test)
+    test.globs['new_releases'] = new_releases = test.globs['mkdtemp']()
+    sample_buildout = test.globs['sample_buildout']
+    eggs = os.path.join(sample_buildout, 'eggs')
+
+    # If the zc.buildout dist is a develo dist, convert it to a
+    # regular egg in the sample buildout
+    req = pkg_resources.Requirement.parse('zc.buildout')
+    dist = pkg_resources.working_set.find(req)
+    if dist.precedence == pkg_resources.DEVELOP_DIST:
+        # We have a develop egg, create a real egg for it:
+        here = os.getcwd()
+        os.chdir(os.path.dirname(dist.location))
+        assert os.spawnle(
+            os.P_WAIT, sys.executable, sys.executable,
+            os.path.join(os.path.dirname(dist.location), 'setup.py'),
+            '-q', 'bdist_egg', '-d', eggs,
+            dict(os.environ,
+                 PYTHONPATH=pkg_resources.working_set.find(
+                               pkg_resources.Requirement.parse('setuptools')
+                               ).location,
+                 ),
+            ) == 0
+        os.chdir(here)
+        os.remove(os.path.join(eggs, 'zc.buildout.egg-link'))
+
+        # Rebuild the buildout script
+        ws = pkg_resources.WorkingSet([eggs])
+        ws.require('zc.buildout')
+        zc.buildout.easy_install.scripts(
+            ['zc.buildout'], ws, sys.executable,
+            os.path.join(sample_buildout, 'bin'))
+    else:
+        ws = pkg_resources.working_set
+
+    # now let's make the new releases
+    makeNewRelease('zc.buildout', ws, new_releases)
+    makeNewRelease('setuptools', ws, new_releases)
+
+    os.mkdir(os.path.join(new_releases, 'zc.buildout'))
+    os.mkdir(os.path.join(new_releases, 'setuptools'))
     
 def test_suite():
     return unittest.TestSuite((
@@ -345,6 +415,17 @@
                (re.compile("(\w)%s(\w)" % os_path_sep), r"\1/\2"),
                ])
             ),
+
+        doctest.DocFileSuite(
+            'update.txt',
+            setUp=updateSetup,
+            tearDown=zc.buildout.testing.buildoutTearDown,
+            checker=renormalizing.RENormalizing([
+               (re.compile('#!\S+python\S+'), '#!python'),
+               (re.compile('\S+sample-(\w+)'), r'/sample-\1'),
+               (re.compile('-py\d[.]\d.egg'), r'-py2.3.egg'),
+               ])
+            ),
         
         doctest.DocFileSuite(
             'easy_install.txt', 

Added: zc.buildout/trunk/src/zc/buildout/update.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/update.txt	2006-09-01 23:54:27 UTC (rev 69923)
+++ zc.buildout/trunk/src/zc/buildout/update.txt	2006-09-01 23:54:29 UTC (rev 69924)
@@ -0,0 +1,84 @@
+Automatic Buildout Updates
+==========================
+
+When a buildout is run, one of the first steps performed is to check
+for updates to either zc.buildout or setuptools.  To demonstrate this,
+we've creates some "new releases" of buildout and setuptools in a
+new_releases folder:
+
+    >>> ls(new_releases)
+    d  setuptools
+    -  setuptools-99.99-py2.3.egg
+    d  zc.buildout
+    -  zc.buildout-99.99-py2.3.egg
+
+Let's update the sample buildout.cfg to look in this area:
+
+    >>> write(sample_buildout, 'buildout.cfg',
+    ... """
+    ... [buildout]
+    ... find-links = %(new_releases)s
+    ... index = %(new_releases)s
+    ... parts = show-versions
+    ... develop = showversions
+    ... 
+    ... [show-versions]
+    ... recipe = showversions
+    ... """ % dict(new_releases=new_releases))
+
+We'll also include a recipe that echos the version sof setuptools and
+zc.buildout used:
+
+    >>> mkdir(sample_buildout, 'showversions')
+
+    >>> write(sample_buildout, 'showversions', 'showversions.py', 
+    ... """
+    ... import pkg_resources
+    ...
+    ... class Recipe:
+    ...
+    ...     def __init__(self, buildout, name, options):
+    ...         pass
+    ...
+    ...     def install(self):
+    ...         for project in 'zc.buildout', 'setuptools':
+    ...             req = pkg_resources.Requirement.parse(project)
+    ...             print project, pkg_resources.working_set.find(req).version
+    ... """)
+
+
+    >>> write(sample_buildout, 'showversions', 'setup.py',
+    ... """
+    ... from setuptools import setup
+    ... 
+    ... setup(
+    ...     name = "showversions",
+    ...     entry_points = {'zc.buildout': ['default = showversions:Recipe']},
+    ...     )
+    ... """)
+
+Now if we run the buildout, the buildout will upgrade itself to the
+new versions found in new releases:
+
+    >>> import os
+    >>> os.chdir(sample_buildout)
+    >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
+    >>> print system(buildout),
+    zc.buildout 99.99
+    setuptools 99.99
+
+Our buildout script has been updated to use the new eggs:
+
+    >>> cat(sample_buildout, 'bin', 'buildout') 
+    #!/usr/local/bin/python2.3
+    <BLANKLINE>
+    import sys
+    sys.path[0:0] = [
+      '/tmp/tmpc4avO6sample-buildout/eggs/zc.buildout-99.99-py2.3.egg',
+      '/tmp/tmpc4avO6sample-buildout/eggs/setuptools-99.99-py2.3.egg'
+      ]
+    <BLANKLINE>
+    import zc.buildout.buildout
+    <BLANKLINE>
+    if __name__ == '__main__':
+        zc.buildout.buildout.main()


Property changes on: zc.buildout/trunk/src/zc/buildout/update.txt
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list