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 created some "new releases" of buildout and setuptools in a new_releases folder: >>> ls(new_releases) d setuptools - setuptools-99.99-py2.4.egg d zc.buildout - zc.buildout-99.99-py2.4.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 versions of setuptools and zc.buildout used: >>> mkdir(sample_buildout, 'showversions') >>> write(sample_buildout, 'showversions', 'showversions.py', ... """ ... import pkg_resources ... import sys ... print_ = lambda *a: sys.stdout.write(' '.join(map(str, a))+'\\n') ... ... 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) ... return () ... update = install ... """) >>> 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: >>> print_(system(buildout), end='') Getting distribution for 'zc.buildout>=1.99'. Got zc.buildout 99.99. Getting distribution for 'setuptools'. Got setuptools 99.99. Upgraded: zc.buildout version 99.99, setuptools version 99.99; restarting. Generated script '/sample-buildout/bin/buildout'. Develop: '/sample-buildout/showversions' Installing show-versions. 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.7 import sys sys.path[0:0] = [ '/sample-buildout/eggs/zc.buildout-99.99-py2.4.egg', '/sample-buildout/eggs/setuptools-99.99-py2.4.egg', ] import zc.buildout.buildout if __name__ == '__main__': sys.exit(zc.buildout.buildout.main()) Now, let's recreate the sample buildout. If we specify constraints on the versions of zc.buildout and setuptools to use, running the buildout will install earlier versions of these packages: >>> write(sample_buildout, 'buildout.cfg', ... """ ... [buildout] ... find-links = %(new_releases)s ... index = %(new_releases)s ... parts = show-versions ... develop = showversions ... ... [versions] ... zc.buildout = < 99 ... setuptools = < 99 ... ... [show-versions] ... recipe = showversions ... """ % dict(new_releases=new_releases)) Now we can see that we actually "upgrade" to an earlier version. >>> print_(system(buildout), end='') Upgraded: zc.buildout version 1.4.4; setuptools version 0.6; restarting. Generated script '/sample-buildout/bin/buildout'. Develop: '/sample-buildout/showversions' Updating show-versions. zc.buildout 1.0.0 setuptools 0.6 There are a number of cases, described below, in which the updates don't happen. We won't upgrade in offline mode: >>> 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)) >>> print_(system(buildout+' -o'), end='') Develop: '/sample-buildout/showversions' Updating show-versions. zc.buildout 1.0.0 setuptools 0.6 Or in non-newest mode: >>> print_(system(buildout+' -N'), end='') Develop: '/sample-buildout/showversions' Updating show-versions. zc.buildout 1.0.0 setuptools 0.6 We also won't upgrade if the buildout script being run isn't in the buildouts bin directory. To see this we'll create a new buildout directory: >>> sample_buildout2 = tmpdir('sample_buildout2') >>> write(sample_buildout2, 'buildout.cfg', ... """ ... [buildout] ... find-links = %(new_releases)s ... index = %(new_releases)s ... parts = ... """ % dict(new_releases=new_releases)) >>> cd(sample_buildout2) >>> print_(system(buildout), end='') Creating directory '/sample_buildout2/bin'. Creating directory '/sample_buildout2/parts'. Creating directory '/sample_buildout2/eggs'. Creating directory '/sample_buildout2/develop-eggs'. Getting distribution for 'zc.buildout>=1.99'. Got zc.buildout 99.99. Getting distribution for 'setuptools'. Got setuptools 99.99. Not upgrading because not running a local buildout command. >>> ls('bin') .. The relative-paths option is honored: >>> cd(sample_buildout) >>> write(sample_buildout, 'buildout.cfg', ... """ ... [buildout] ... find-links = %(new_releases)s ... index = %(new_releases)s ... parts = show-versions ... develop = showversions ... relative-paths = true ... ... [show-versions] ... recipe = showversions ... """ % dict(new_releases=new_releases)) >>> print_(system(buildout), end='') Upgraded: zc.buildout version 99.99, setuptools version 99.99; restarting. Generated script '/sample-buildout/bin/buildout'. Develop: '/sample-buildout/showversions' Unused options for buildout: 'relative-paths'. Updating show-versions. zc.buildout 99.99 setuptools 99.99 >>> cat('bin', 'buildout') # doctest +ELL #!/usr/local/bin/python2.7 import os join = os.path.join base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) base = os.path.dirname(base) import sys sys.path[0:0] = [ join(base, 'eggs/zc.buildout-99.99-py3.3.egg'), join(base, 'eggs/setuptools-99.99-py3.3.egg'), ] import zc.buildout.buildout if __name__ == '__main__': sys.exit(zc.buildout.buildout.main()) When buildout restarts and the restarted buildout exits with an error code, the original buildout that called the second buildout also exits with that error code. Otherwise build scripts can erroneously detect a successful buildout run even if it failed. Make a recipe that fails: >>> mkdir(sample_buildout, 'failrecipe') >>> write(sample_buildout, 'failrecipe', 'failrecipe.py', ... """ ... import pkg_resources ... import sys ... print_ = lambda *a: sys.stdout.write(' '.join(map(str, a))+'\\n') ... ... class Recipe: ... ... def __init__(self, buildout, name, options): ... sys.exit('recipe sys-exits') ... ... def install(self): ... pass ... ... update = install ... """) >>> write(sample_buildout, 'failrecipe', 'setup.py', ... """ ... from setuptools import setup ... ... setup( ... name = "failrecipe", ... entry_points = {'zc.buildout': ['default = failrecipe:Recipe']}, ... ) ... """) Let's downgrade again, triggering a restart. And use the failing recipe that gives us a sys.exit: >>> write(sample_buildout, 'buildout.cfg', ... """ ... [buildout] ... find-links = %(new_releases)s ... index = %(new_releases)s ... parts = fail ... develop = failrecipe ... ... [versions] ... zc.buildout = < 99 ... setuptools = < 99 ... ... [fail] ... recipe = failrecipe ... """ % dict(new_releases=new_releases)) Run the buildout: >>> print_(system(buildout, with_exit_code=True), end='') Upgraded: zc.buildout version 1.4.4; setuptools version 0.6; restarting. Generated script '/sample-buildout/bin/buildout'. Develop: '/sample-buildout/failrecipe' recipe sys-exits EXIT CODE: 1