diff options
Diffstat (limited to 'eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout')
29 files changed, 16902 insertions, 0 deletions
diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/__init__.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/__init__.py new file mode 100644 index 0000000..ac268c0 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/__init__.py @@ -0,0 +1,24 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Buildout package + +$Id: __init__.py 69990 2006-09-05 22:55:53Z jim $ +""" + +class UserError(Exception): + """Errors made by a user + """ + + def __str__(self): + return " ".join(map(str, self)) diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/allowhosts.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/allowhosts.txt new file mode 100644 index 0000000..be5ab4f --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/allowhosts.txt @@ -0,0 +1,128 @@ +Allow hosts +----------- + +On some environments the links visited by `zc.buildout` can be forbidden +by paranoiac firewalls. These URL might be on the chain of links +visited by `zc.buildout` whether they are defined in the `find-links` option +or by various eggs in their `url`, `download_url` and `dependency_links` metadata. + +It is even harder to track that package_index works like a spider and +might visit links and go to other location. + +The `allow-hosts` option provides a way to prevent this, and +works exactly like the one provided in `easy_install` +(see `easy_install allow-hosts option`_). + +You can provide a list of allowed host, together with wildcards:: + + [buildout] + ... + + allow-hosts = + *.python.org + example.com + +Let's create a develop egg in our buildout that specifies +`dependency_links` which points to a server in the outside world:: + + >>> mkdir(sample_buildout, 'allowdemo') + >>> write(sample_buildout, 'allowdemo', 'dependencydemo.py', + ... 'import eggrecipekss.core') + >>> write(sample_buildout, 'allowdemo', 'setup.py', + ... '''from setuptools import setup; setup( + ... name='allowdemo', py_modules=['dependencydemo'], + ... install_requires = 'kss.core', + ... dependency_links = ['http://dist.plone.org'], + ... zip_safe=True, version='1') + ... ''') + +Now let's configure the buildout to use the develop egg, +together with some rules that disallow any website but PyPI and +local files:: + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = allowdemo + ... parts = eggs + ... allow-hosts = + ... pypi.python.org + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = allowdemo + ... ''') + +Now we can run the buildout and make sure all attempts to dist.plone.org fails:: + + >>> print system(buildout) # doctest: +ELLIPSIS + Develop: '/sample-buildout/allowdemo' + ... + Link to http://dist.plone.org ***BLOCKED*** by --allow-hosts + ... + While: + Installing eggs. + Getting distribution for 'kss.core'. + Error: Couldn't find a distribution for 'kss.core'. + <BLANKLINE> + +That's what we wanted : this will prevent any attempt to access +unwanted domains. For instance, some packages are listing in their +links `svn://` links. These can lead to error in some cases, and +can therefore be protected like this:: + +XXX (showcase with a svn:// file) + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = allowdemo + ... parts = eggs + ... allow-hosts = + ... ^(!svn://).* + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = allowdemo + ... ''') + +Now we can run the buildout and make sure all attempts to dist.plone.org fails:: + + >>> print system(buildout) # doctest: +ELLIPSIS + Develop: '/sample-buildout/allowdemo' + ... + Link to http://dist.plone.org ***BLOCKED*** by --allow-hosts + ... + While: + Installing eggs. + Getting distribution for 'kss.core'. + Error: Couldn't find a distribution for 'kss.core'. + <BLANKLINE> + +Test for issues +--------------- + +Test for 1.0.5 breakage as in https://bugs.launchpad.net/zc.buildout/+bug/239212:: + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... parts=python + ... foo = ${python:interpreter} + ... + ... [python] + ... recipe=zc.recipe.egg + ... eggs=zc.buildout + ... interpreter=python + ... ''') + >>> print system(buildout) + Unused options for buildout: 'foo'. + Installing python. + Generated script '/sample-buildout/bin/buildout'. + Generated interpreter '/sample-buildout/bin/python'. + <BLANKLINE> + +The bug 239212 above would have got us an *AttributeError* on *buildout._allow_hosts*. +This was fixed in this changeset: +http://svn.zope.org/zc.buildout/trunk/src/zc/buildout/buildout.py?rev=87309&r1=87277&r2=87309 + diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/bootstrap.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/bootstrap.txt new file mode 100644 index 0000000..794b912 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/bootstrap.txt @@ -0,0 +1,360 @@ +Make sure the bootstrap script actually works:: + + >>> import os, sys + >>> from os.path import dirname, join + >>> import zc.buildout + >>> bootstrap_py = join( + ... dirname( + ... dirname( + ... dirname( + ... dirname(zc.buildout.__file__) + ... ) + ... ) + ... ), + ... 'bootstrap', 'bootstrap.py') + >>> sample_buildout = tmpdir('sample') + >>> os.chdir(sample_buildout) + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = + ... ''') + >>> write('bootstrap.py', open(bootstrap_py).read()) + >>> print 'X'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py'); print 'X' # doctest: +ELLIPSIS + X... + Creating directory '/sample/bin'. + Creating directory '/sample/parts'. + Creating directory '/sample/eggs'. + Creating directory '/sample/develop-eggs'. + Generated script '/sample/bin/buildout'. + ... + + >>> ls(sample_buildout) + d bin + - bootstrap.py + - buildout.cfg + d develop-eggs + d eggs + d parts + + + >>> ls(sample_buildout, 'bin') + - buildout + + >>> print 'X'; ls(sample_buildout, 'eggs') # doctest: +ELLIPSIS + X... + d zc.buildout-...egg + +The buildout script it has generated is a new-style script, using a +customized site.py. + + >>> buildout_script = join(sample_buildout, 'bin', 'buildout') + >>> if sys.platform.startswith('win'): + ... buildout_script += '-script.py' + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #... + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample/parts/buildout', + ] + <BLANKLINE> + <BLANKLINE> + import os + path = sys.path[0] + if os.environ.get('PYTHONPATH'): + path = os.pathsep.join([path, os.environ['PYTHONPATH']]) + os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + os.environ['PYTHONPATH'] = path + import site # imports custom buildout-generated site.py + <BLANKLINE> + import zc.buildout.buildout + <BLANKLINE> + if __name__ == '__main__': + zc.buildout.buildout.main() + <BLANKLINE> + +The bootstrap process prefers final versions of zc.buildout, so it has +selected the (generated-locally) 99.99 egg rather than the also-available +100.0b1 egg. We can see that in the buildout script's site.py. + + >>> buildout_site_py = join( + ... sample_buildout, 'parts', 'buildout', 'site.py') + >>> print open(buildout_site_py).read() # doctest: +ELLIPSIS + "... + buildout_paths = [ + '/sample/eggs/setuptools-...egg', + '/sample/eggs/zc.buildout-99.99-pyN.N.egg' + ] + ... + +If you want to accept early releases of zc.buildout, you either need to +specify an explicit version (using --version here and specifying the +version in the buildout configuration file using the +``buildout-version`` option or the ``versions`` option) or specify that you +accept early releases by using ``--accept-buildout-test-releases`` on the +bootstrap script. + +Here's an example. + + >>> ignored = system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --accept-buildout-test-releases') + >>> print open(buildout_site_py).read() # doctest: +ELLIPSIS + "... + buildout_paths = [ + '/sample/eggs/setuptools-...egg', + '/sample/eggs/zc.buildout-100.0b1-pyN.N.egg' + ] + ... + +Notice we are now using zc.buildout 100.0b1, a non-final release. + +The buildout script remembers the decision to accept early releases, and +alerts the user. + + >>> print system(join('bin', 'buildout')), + ... # doctest: +NORMALIZE_WHITESPACE + NOTE: Accepting early releases of build system packages. Rerun bootstrap + without --accept-buildout-test-releases (-t) to return to default + behavior. + +This is accomplished within the script itself. + + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #... + sys.argv.insert(1, 'buildout:accept-buildout-test-releases=true') + print ('NOTE: Accepting early releases of build system packages. Rerun ' + 'bootstrap without --accept-buildout-test-releases (-t) to return to ' + 'default behavior.') + ... + +As the note says, to undo, you just need to re-run bootstrap without +--accept-buildout-test-releases. + + >>> ignored = system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py') + >>> print open(buildout_site_py).read() # doctest: +ELLIPSIS + "... + buildout_paths = [ + '/sample/eggs/setuptools-...egg', + '/sample/eggs/zc.buildout-99.99-pyN.N.egg' + ] + ... + >>> ('buildout:accept-buildout-test-releases=true' in + ... open(buildout_script).read()) + False + +Now we will try the `--version` option, which lets you define a version for +`zc.buildout`. If not provided, bootstrap will look for the latest one. + +Let's try with an unknown version:: + + >>> print 'XX'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --version UNKNOWN'); print 'X' # doctest: +ELLIPSIS + ... + X... + No local packages or download links found for zc.buildout==UNKNOWN... + ... + +Now let's try with `1.1.2`, which happens to exist:: + + >>> print 'X'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --version 1.1.2'); print 'X' + ... + X + Generated script '/sample/bin/buildout'. + <BLANKLINE> + X + +Versions older than 1.5.0 put their egg dependencies in the ``buildout`` script. +Let's make sure it was generated as we expect:: + + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #... + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample/eggs/setuptools-...egg', + '/sample/eggs/zc.buildout-1.1.2...egg', + ] + <BLANKLINE> + import zc.buildout.buildout + <BLANKLINE> + if __name__ == '__main__': + zc.buildout.buildout.main() + <BLANKLINE> + +Let's try with `1.2.1`:: + + >>> print 'X'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --version 1.2.1'); print 'X' # doctest: +ELLIPSIS + ... + X + Generated script '/sample/bin/buildout'. + <BLANKLINE> + X + +Let's make sure the generated ``buildout`` script uses it:: + + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #... + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample/eggs/setuptools-...egg', + '/sample/eggs/zc.buildout-1.2.1...egg', + ] + <BLANKLINE> + import zc.buildout.buildout + <BLANKLINE> + if __name__ == '__main__': + zc.buildout.buildout.main() + <BLANKLINE> + +``zc.buildout`` now can also run with `Distribute` with the `--distribute` +option:: + + >>> print 'X'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --distribute'); print 'X' # doctest: +ELLIPSIS + ... + X + ... + Generated script '/sample/bin/buildout'... + X + +Let's make sure the generated ``site.py`` uses it:: + >>> print open(buildout_site_py).read() # doctest: +ELLIPSIS + "... + buildout_paths = [ + '/sample/eggs/distribute-...egg', + '/sample/eggs/zc.buildout-99.99-pyN.N.egg' + ] + ... + +Make sure both options can be used together:: + + >>> print 'X'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --distribute --version 1.2.1'); print 'X' + ... # doctest: +ELLIPSIS + ... + X + ... + Generated script '/sample/bin/buildout'... + X + +Let's make sure the old-style generated ``buildout`` script uses +``Distribute`` *and* ``zc.buildout-1.2.1``:: + + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #... + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample/eggs/distribute-...egg', + '/sample/eggs/zc.buildout-1.2.1...egg', + ] + <BLANKLINE> + import zc.buildout.buildout + <BLANKLINE> + if __name__ == '__main__': + zc.buildout.buildout.main() + <BLANKLINE> + +Last, the -c option needs to work on bootstrap.py:: + + >>> conf_file = os.path.join(sample_buildout, 'other.cfg') + >>> f = open(conf_file, 'w') + >>> f.write('[buildout]\nparts=\n\n') + >>> f.close() + >>> print 'X'; print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py -c %s --distribute' % conf_file); print 'X' # doctest: +ELLIPSIS + ... + X + ... + Generated script '/sample/bin/buildout'... + X + +You can specify a location of ez_setup.py or distribute_setup, so you +can rely on a local or remote location. We'll write our own ez_setup.py +that we will also use to test some other bootstrap options. + + >>> write('ez_setup.py', '''\ + ... def use_setuptools(**kwargs): + ... import sys, pprint + ... pprint.pprint(kwargs, width=40) + ... sys.exit() + ... ''') + >>> print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --setup-source=./ez_setup.py') + ... # doctest: +ELLIPSIS + {'download_delay': 0, + 'to_dir': '...'} + <BLANKLINE> + +You can also pass a download-cache, and a place in which eggs should be stored +(they are normally stored in a temporary directory). + + >>> print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --setup-source=./ez_setup.py '+ + ... '--download-base=./download-cache --eggs=eggs') + ... # doctest: +ELLIPSIS + {'download_base': '/sample/download-cache/', + 'download_delay': 0, + 'to_dir': '/sample/eggs'} + <BLANKLINE> + +Here's the entire help text. + + >>> print system( + ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ + ... 'bootstrap.py --help'), + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + Usage: [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + <BLANKLINE> + Bootstraps a buildout-based project. + <BLANKLINE> + Simply run this script in a directory containing a buildout.cfg, using the + Python that you want bin/buildout to use. + <BLANKLINE> + Note that by using --setup-source and --download-base to point to + local resources, you can keep this script from going over the network. + <BLANKLINE> + <BLANKLINE> + Options: + -h, --help show this help message and exit + -v VERSION, --version=VERSION + use a specific zc.buildout version + -d, --distribute Use Distribute rather than Setuptools. + --setup-source=SETUP_SOURCE + Specify a URL or file location for the setup file. If + you use Setuptools, this will default to + http://peak.telecommunity.com/dist/ez_setup.py; if you + use Distribute, this will default to http://python- + distribute.org/distribute_setup.py. + --download-base=DOWNLOAD_BASE + Specify a URL or directory for downloading zc.buildout + and either Setuptools or Distribute. Defaults to PyPI. + --eggs=EGGS Specify a directory for storing eggs. Defaults to a + temporary directory that is deleted when the bootstrap + script completes. + -t, --accept-buildout-test-releases + Normally, if you do not specify a --version, the + bootstrap script and buildout gets the newest *final* + versions of zc.buildout and its recipes and extensions + for you. If you use this flag, bootstrap and buildout + will get the newest releases even if they are alphas + or betas. + -c CONFIG_FILE Specify the path to the buildout configuration file to + be used. diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/buildout.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/buildout.py new file mode 100644 index 0000000..6fe4e8b --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/buildout.py @@ -0,0 +1,1835 @@ +############################################################################## +# +# Copyright (c) 2005-2009 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Buildout main script +""" + +from rmtree import rmtree +try: + from hashlib import md5 +except ImportError: + # Python 2.4 and older + from md5 import md5 + +import ConfigParser +import copy +import distutils.errors +import glob +import itertools +import logging +import os +import pkg_resources +import re +import shutil +import sys +import tempfile +import UserDict +import warnings +import zc.buildout +import zc.buildout.download +import zc.buildout.easy_install + + +realpath = zc.buildout.easy_install.realpath + +pkg_resources_loc = pkg_resources.working_set.find( + pkg_resources.Requirement.parse('setuptools')).location + +_isurl = re.compile('([a-zA-Z0-9+.-]+)://').match + +is_jython = sys.platform.startswith('java') + +if is_jython: + import subprocess + +_sys_executable_has_broken_dash_S = ( + zc.buildout.easy_install._has_broken_dash_S(sys.executable)) + +class MissingOption(zc.buildout.UserError, KeyError): + """A required option was missing. + """ + +class MissingSection(zc.buildout.UserError, KeyError): + """A required section is missing. + """ + + def __str__(self): + return "The referenced section, %r, was not defined." % self[0] + + +def _annotate_section(section, note): + for key in section: + section[key] = (section[key], note) + return section + +def _annotate(data, note): + for key in data: + data[key] = _annotate_section(data[key], note) + return data + +def _print_annotate(data): + sections = data.keys() + sections.sort() + print + print "Annotated sections" + print "="*len("Annotated sections") + for section in sections: + print + print '[%s]' % section + keys = data[section].keys() + keys.sort() + for key in keys: + value, notes = data[section][key] + keyvalue = "%s= %s" % (key, value) + print keyvalue + line = ' ' + for note in notes.split(): + if note == '[+]': + line = '+= ' + elif note == '[-]': + line = '-= ' + else: + print line, note + line = ' ' + print + + +def _unannotate_section(section): + for key in section: + value, note = section[key] + section[key] = value + return section + +def _unannotate(data): + for key in data: + data[key] = _unannotate_section(data[key]) + return data + +_buildout_default_options = _annotate_section({ + 'accept-buildout-test-releases': 'false', + 'allow-hosts': '*', + 'allow-picked-versions': 'true', + 'allowed-eggs-from-site-packages': '*', + 'bin-directory': 'bin', + 'develop-eggs-directory': 'develop-eggs', + 'eggs-directory': 'eggs', + 'executable': sys.executable, + 'exec-sitecustomize': 'true', + 'find-links': '', + 'include-site-packages': 'true', + 'install-from-cache': 'false', + 'installed': '.installed.cfg', + 'log-format': '', + 'log-level': 'INFO', + 'newest': 'true', + 'offline': 'false', + 'parts-directory': 'parts', + 'prefer-final': 'false', + 'python': 'buildout', + 'relative-paths': 'false', + 'socket-timeout': '', + 'unzip': 'false', + 'use-dependency-links': 'true', + }, 'DEFAULT_VALUE') + + +class Buildout(UserDict.DictMixin): + + def __init__(self, config_file, cloptions, + user_defaults=True, windows_restart=False, command=None): + + __doing__ = 'Initializing.' + + self.__windows_restart = windows_restart + + # default options + data = dict(buildout=_buildout_default_options.copy()) + self._buildout_dir = os.getcwd() + + if not _isurl(config_file): + config_file = os.path.abspath(config_file) + base = os.path.dirname(config_file) + if not os.path.exists(config_file): + if command == 'init': + print 'Creating %r.' % config_file + open(config_file, 'w').write('[buildout]\nparts = \n') + elif command == 'setup': + # Sigh. This model of a buildout instance + # with methods is breaking down. :( + config_file = None + data['buildout']['directory'] = ('.', 'COMPUTED_VALUE') + else: + raise zc.buildout.UserError( + "Couldn't open %s" % config_file) + + if config_file: + data['buildout']['directory'] = (os.path.dirname(config_file), + 'COMPUTED_VALUE') + else: + base = None + + + cloptions = dict( + (section, dict((option, (value, 'COMMAND_LINE_VALUE')) + for (_, option, value) in v)) + for (section, v) in itertools.groupby(sorted(cloptions), + lambda v: v[0]) + ) + override = cloptions.get('buildout', {}).copy() + + # load user defaults, which override defaults + if user_defaults: + user_config = os.path.join(os.path.expanduser('~'), + '.buildout', 'default.cfg') + if os.path.exists(user_config): + _update(data, _open(os.path.dirname(user_config), user_config, + [], data['buildout'].copy(), override)) + + # load configuration files + if config_file: + _update(data, _open(os.path.dirname(config_file), config_file, [], + data['buildout'].copy(), override)) + + # apply command-line options + _update(data, cloptions) + + self._annotated = copy.deepcopy(data) + self._raw = _unannotate(data) + self._data = {} + self._parts = [] + # provide some defaults before options are parsed + # because while parsing options those attributes might be + # used already (Gottfried Ganssauge) + buildout_section = data['buildout'] + + # Try to make sure we have absolute paths for standard + # directories. We do this before doing substitutions, in case + # a one of these gets read by another section. If any + # variable references are used though, we leave it as is in + # _buildout_path. + if 'directory' in buildout_section: + self._buildout_dir = buildout_section['directory'] + for name in ('bin', 'parts', 'eggs', 'develop-eggs'): + d = self._buildout_path(buildout_section[name+'-directory']) + buildout_section[name+'-directory'] = d + + # Attributes on this buildout object shouldn't be used by + # recipes in their __init__. It can cause bugs, because the + # recipes will be instantiated below (``options = self['buildout']``) + # before this has completed initializing. These attributes are + # left behind for legacy support but recipe authors should + # beware of using them. A better practice is for a recipe to + # use the buildout['buildout'] options. + links = buildout_section['find-links'] + self._links = links and links.split() or () + allow_hosts = buildout_section['allow-hosts'].split('\n') + self._allow_hosts = tuple([host.strip() for host in allow_hosts + if host.strip() != '']) + self._logger = logging.getLogger('zc.buildout') + self.offline = (buildout_section['offline'] == 'true') + self.newest = (buildout_section['newest'] == 'true') + self.accept_buildout_test_releases = ( + buildout_section['accept-buildout-test-releases'] == 'true') + + ################################################################## + ## WARNING!!! + ## ALL ATTRIBUTES MUST HAVE REASONABLE DEFAULTS AT THIS POINT + ## OTHERWISE ATTRIBUTEERRORS MIGHT HAPPEN ANY TIME FROM RECIPES. + ## RECIPES SHOULD GENERALLY USE buildout['buildout'] OPTIONS, NOT + ## BUILDOUT ATTRIBUTES. + ################################################################## + # initialize some attrs and buildout directories. + options = self['buildout'] + + # now reinitialize + links = options.get('find-links', '') + self._links = links and links.split() or () + + allow_hosts = options['allow-hosts'].split('\n') + self._allow_hosts = tuple([host.strip() for host in allow_hosts + if host.strip() != '']) + + self._buildout_dir = options['directory'] + + # Make sure we have absolute paths for standard directories. We do this + # a second time here in case someone overrode these in their configs. + for name in ('bin', 'parts', 'eggs', 'develop-eggs'): + d = self._buildout_path(options[name+'-directory']) + options[name+'-directory'] = d + + if options['installed']: + options['installed'] = os.path.join(options['directory'], + options['installed']) + + self._setup_logging() + + versions = options.get('versions') + if versions: + zc.buildout.easy_install.default_versions(dict(self[versions])) + + + self.offline = options.get_bool('offline') + if self.offline: + options['newest'] = 'false' + self.newest = options.get_bool('newest') + zc.buildout.easy_install.prefer_final( + options.get_bool('prefer-final')) + self.accept_buildout_test_releases = options.get_bool( + 'accept-buildout-test-releases') + zc.buildout.easy_install.use_dependency_links( + options.get_bool('use-dependency-links')) + zc.buildout.easy_install.allow_picked_versions( + options.get_bool('allow-picked-versions')) + zc.buildout.easy_install.install_from_cache( + options.get_bool('install-from-cache')) + zc.buildout.easy_install.always_unzip(options.get_bool('unzip')) + allowed_eggs = tuple(name.strip() for name in options[ + 'allowed-eggs-from-site-packages'].split('\n')) + self.include_site_packages = options.get_bool('include-site-packages') + self.exec_sitecustomize = options.get_bool('exec-sitecustomize') + if (_sys_executable_has_broken_dash_S and + (not self.include_site_packages or allowed_eggs != ('*',))): + # We can't do this if the executable has a broken -S. + warnings.warn(zc.buildout.easy_install.BROKEN_DASH_S_WARNING) + self.include_site_packages = True + zc.buildout.easy_install.allowed_eggs_from_site_packages(allowed_eggs) + zc.buildout.easy_install.include_site_packages( + self.include_site_packages) + + download_cache = options.get('download-cache') + if download_cache: + download_cache = os.path.join(options['directory'], download_cache) + if not os.path.isdir(download_cache): + raise zc.buildout.UserError( + 'The specified download cache:\n' + '%r\n' + "Doesn't exist.\n" + % download_cache) + download_cache = os.path.join(download_cache, 'dist') + if not os.path.isdir(download_cache): + os.mkdir(download_cache) + + zc.buildout.easy_install.download_cache(download_cache) + + # "Use" each of the defaults so they aren't reported as unused options. + for name in _buildout_default_options: + options[name] + + # Do the same for extends-cache which is not among the defaults but + # wasn't recognized as having been used since it was used before + # tracking was turned on. + options.get('extends-cache') + + os.chdir(options['directory']) + + def _buildout_path(self, name): + if '${' in name: + return name + return os.path.join(self._buildout_dir, name) + + def bootstrap(self, args): + __doing__ = 'Bootstrapping.' + + self._setup_directories() + + options = self['buildout'] + + # Get a base working set for our distributions that corresponds to the + # stated desires in the configuration. + distributions = ['setuptools', 'zc.buildout'] + if options.get('offline') == 'true': + ws = zc.buildout.easy_install.working_set( + distributions, options['executable'], + [options['develop-eggs-directory'], + options['eggs-directory']], + prefer_final=not self.accept_buildout_test_releases, + ) + else: + ws = zc.buildout.easy_install.install( + distributions, options['eggs-directory'], + links=self._links, + index=options.get('index'), + executable=options['executable'], + path=[options['develop-eggs-directory']], + newest=self.newest, + allow_hosts=self._allow_hosts, + prefer_final=not self.accept_buildout_test_releases, + ) + + # Now copy buildout and setuptools eggs, and record destination eggs: + entries = [] + for name in 'setuptools', 'zc.buildout': + r = pkg_resources.Requirement.parse(name) + dist = ws.find(r) + if dist.precedence == pkg_resources.DEVELOP_DIST: + dest = os.path.join(self['buildout']['develop-eggs-directory'], + name+'.egg-link') + open(dest, 'w').write(dist.location) + entries.append(dist.location) + else: + dest = os.path.join(self['buildout']['eggs-directory'], + os.path.basename(dist.location)) + entries.append(dest) + if not os.path.exists(dest): + if os.path.isdir(dist.location): + shutil.copytree(dist.location, dest) + else: + shutil.copy2(dist.location, dest) + + # Create buildout script. + # Ideally the (possibly) new version of buildout would get a + # chance to write the script. Not sure how to do that. + ws = pkg_resources.WorkingSet(entries) + ws.require('zc.buildout') + partsdir = os.path.join(options['parts-directory'], 'buildout') + if not os.path.exists(partsdir): + os.mkdir(partsdir) + # (Honor the relative-paths option.) + relative_paths = options.get('relative-paths', 'false') + if relative_paths == 'true': + relative_paths = options['directory'] + else: + assert relative_paths == 'false' + relative_paths = '' + if (self.accept_buildout_test_releases and + self._annotated['buildout']['accept-buildout-test-releases'][1] == + 'COMMAND_LINE_VALUE'): + # Bootstrap was called with '--accept-buildout-test-releases'. + # Continue to honor that setting. + script_initialization = _early_release_initialization_code + else: + script_initialization = '' + zc.buildout.easy_install.sitepackage_safe_scripts( + options['bin-directory'], ws, options['executable'], partsdir, + reqs=['zc.buildout'], relative_paths=relative_paths, + include_site_packages=self.include_site_packages, + script_initialization=script_initialization, + exec_sitecustomize=self.exec_sitecustomize, + ) + + init = bootstrap + + def install(self, install_args): + __doing__ = 'Installing.' + + self._load_extensions() + self._setup_directories() + + # Add develop-eggs directory to path so that it gets searched + # for eggs: + sys.path.insert(0, self['buildout']['develop-eggs-directory']) + + # Check for updates. This could cause the process to be restarted. + self._maybe_upgrade() + + # load installed data + (installed_part_options, installed_exists + )= self._read_installed_part_options() + + # Remove old develop eggs + self._uninstall( + installed_part_options['buildout'].get( + 'installed_develop_eggs', '') + ) + + # Build develop eggs + installed_develop_eggs = self._develop() + installed_part_options['buildout']['installed_develop_eggs' + ] = installed_develop_eggs + + if installed_exists: + self._update_installed( + installed_develop_eggs=installed_develop_eggs) + + # get configured and installed part lists + conf_parts = self['buildout']['parts'] + conf_parts = conf_parts and conf_parts.split() or [] + installed_parts = installed_part_options['buildout']['parts'] + installed_parts = installed_parts and installed_parts.split() or [] + + if install_args: + install_parts = install_args + uninstall_missing = False + else: + install_parts = conf_parts + uninstall_missing = True + + # load and initialize recipes + [self[part]['recipe'] for part in install_parts] + if not install_args: + install_parts = self._parts + + if self._log_level < logging.DEBUG: + sections = list(self) + sections.sort() + print + print 'Configuration data:' + for section in self._data: + _save_options(section, self[section], sys.stdout) + print + + + # compute new part recipe signatures + self._compute_part_signatures(install_parts) + + # uninstall parts that are no-longer used or whose configs + # have changed + for part in reversed(installed_parts): + if part in install_parts: + old_options = installed_part_options[part].copy() + installed_files = old_options.pop('__buildout_installed__') + new_options = self.get(part) + if old_options == new_options: + # The options are the same, but are all of the + # installed files still there? If not, we should + # reinstall. + if not installed_files: + continue + for f in installed_files.split('\n'): + if not os.path.exists(self._buildout_path(f)): + break + else: + continue + + # output debugging info + if self._logger.getEffectiveLevel() < logging.DEBUG: + for k in old_options: + if k not in new_options: + self._logger.debug("Part %s, dropped option %s.", + part, k) + elif old_options[k] != new_options[k]: + self._logger.debug( + "Part %s, option %s changed:\n%r != %r", + part, k, new_options[k], old_options[k], + ) + for k in new_options: + if k not in old_options: + self._logger.debug("Part %s, new option %s.", + part, k) + + elif not uninstall_missing: + continue + + self._uninstall_part(part, installed_part_options) + installed_parts = [p for p in installed_parts if p != part] + + if installed_exists: + self._update_installed(parts=' '.join(installed_parts)) + + # Check for unused buildout options: + _check_for_unused_options_in_section(self, 'buildout') + + # install new parts + for part in install_parts: + signature = self[part].pop('__buildout_signature__') + saved_options = self[part].copy() + recipe = self[part].recipe + if part in installed_parts: # update + need_to_save_installed = False + __doing__ = 'Updating %s.', part + self._logger.info(*__doing__) + old_options = installed_part_options[part] + old_installed_files = old_options['__buildout_installed__'] + + try: + update = recipe.update + except AttributeError: + update = recipe.install + self._logger.warning( + "The recipe for %s doesn't define an update " + "method. Using its install method.", + part) + + try: + installed_files = self[part]._call(update) + except: + installed_parts.remove(part) + self._uninstall(old_installed_files) + if installed_exists: + self._update_installed( + parts=' '.join(installed_parts)) + raise + + old_installed_files = old_installed_files.split('\n') + if installed_files is None: + installed_files = old_installed_files + else: + if isinstance(installed_files, str): + installed_files = [installed_files] + else: + installed_files = list(installed_files) + + need_to_save_installed = [ + p for p in installed_files + if p not in old_installed_files] + + if need_to_save_installed: + installed_files = (old_installed_files + + need_to_save_installed) + + else: # install + need_to_save_installed = True + __doing__ = 'Installing %s.', part + self._logger.info(*__doing__) + installed_files = self[part]._call(recipe.install) + if installed_files is None: + self._logger.warning( + "The %s install returned None. A path or " + "iterable of paths should be returned.", + part) + installed_files = () + elif isinstance(installed_files, str): + installed_files = [installed_files] + else: + installed_files = list(installed_files) + + installed_part_options[part] = saved_options + saved_options['__buildout_installed__' + ] = '\n'.join(installed_files) + saved_options['__buildout_signature__'] = signature + + installed_parts = [p for p in installed_parts if p != part] + installed_parts.append(part) + _check_for_unused_options_in_section(self, part) + + if need_to_save_installed: + installed_part_options['buildout']['parts'] = ( + ' '.join(installed_parts)) + self._save_installed_options(installed_part_options) + installed_exists = True + else: + assert installed_exists + self._update_installed(parts=' '.join(installed_parts)) + + if installed_develop_eggs: + if not installed_exists: + self._save_installed_options(installed_part_options) + elif (not installed_parts) and installed_exists: + os.remove(self['buildout']['installed']) + + self._unload_extensions() + + def _update_installed(self, **buildout_options): + installed = self['buildout']['installed'] + f = open(installed, 'a') + f.write('\n[buildout]\n') + for option, value in buildout_options.items(): + _save_option(option, value, f) + f.close() + + def _uninstall_part(self, part, installed_part_options): + # uninstall part + __doing__ = 'Uninstalling %s.', part + self._logger.info(*__doing__) + + # run uninstall recipe + recipe, entry = _recipe(installed_part_options[part]) + try: + uninstaller = _install_and_load( + recipe, 'zc.buildout.uninstall', entry, self) + self._logger.info('Running uninstall recipe.') + uninstaller(part, installed_part_options[part]) + except (ImportError, pkg_resources.DistributionNotFound), v: + pass + + # remove created files and directories + self._uninstall( + installed_part_options[part]['__buildout_installed__']) + + def _setup_directories(self): + __doing__ = 'Setting up buildout directories' + + # Create buildout directories + for name in ('bin', 'parts', 'eggs', 'develop-eggs'): + d = self['buildout'][name+'-directory'] + if not os.path.exists(d): + self._logger.info('Creating directory %r.', d) + os.mkdir(d) + + def _develop(self): + """Install sources by running setup.py develop on them + """ + __doing__ = 'Processing directories listed in the develop option' + + develop = self['buildout'].get('develop') + if not develop: + return '' + + dest = self['buildout']['develop-eggs-directory'] + old_files = os.listdir(dest) + + env = dict(os.environ, PYTHONPATH=pkg_resources_loc) + here = os.getcwd() + try: + try: + for setup in develop.split(): + setup = self._buildout_path(setup) + files = glob.glob(setup) + if not files: + self._logger.warn("Couldn't develop %r (not found)", + setup) + else: + files.sort() + for setup in files: + self._logger.info("Develop: %r", setup) + __doing__ = 'Processing develop directory %r.', setup + zc.buildout.easy_install.develop(setup, dest) + except: + # if we had an error, we need to roll back changes, by + # removing any files we created. + self._sanity_check_develop_eggs_files(dest, old_files) + self._uninstall('\n'.join( + [os.path.join(dest, f) + for f in os.listdir(dest) + if f not in old_files + ])) + raise + + else: + self._sanity_check_develop_eggs_files(dest, old_files) + return '\n'.join([os.path.join(dest, f) + for f in os.listdir(dest) + if f not in old_files + ]) + + finally: + os.chdir(here) + + + def _sanity_check_develop_eggs_files(self, dest, old_files): + for f in os.listdir(dest): + if f in old_files: + continue + if not (os.path.isfile(os.path.join(dest, f)) + and f.endswith('.egg-link')): + self._logger.warning( + "Unexpected entry, %r, in develop-eggs directory.", f) + + def _compute_part_signatures(self, parts): + # Compute recipe signature and add to options + for part in parts: + options = self.get(part) + if options is None: + options = self[part] = {} + recipe, entry = _recipe(options) + req = pkg_resources.Requirement.parse(recipe) + sig = _dists_sig(pkg_resources.working_set.resolve([req])) + options['__buildout_signature__'] = ' '.join(sig) + + def _read_installed_part_options(self): + old = self['buildout']['installed'] + if old and os.path.isfile(old): + parser = ConfigParser.RawConfigParser() + parser.optionxform = lambda s: s + parser.read(old) + result = {} + for section in parser.sections(): + options = {} + for option, value in parser.items(section): + if '%(' in value: + for k, v in _spacey_defaults: + value = value.replace(k, v) + options[option] = value + result[section] = Options(self, section, options) + + return result, True + else: + return ({'buildout': Options(self, 'buildout', {'parts': ''})}, + False, + ) + + def _uninstall(self, installed): + for f in installed.split('\n'): + if not f: + continue + f = self._buildout_path(f) + if os.path.isdir(f): + rmtree(f) + elif os.path.isfile(f): + try: + os.remove(f) + except OSError: + if not ( + sys.platform == 'win32' and + (realpath(os.path.join(os.path.dirname(sys.argv[0]), + 'buildout.exe')) + == + realpath(f) + ) + # Sigh. This is the exectable used to run the buildout + # and, of course, it's in use. Leave it. + ): + raise + + def _install(self, part): + options = self[part] + recipe, entry = _recipe(options) + recipe_class = pkg_resources.load_entry_point( + recipe, 'zc.buildout', entry) + installed = recipe_class(self, part, options).install() + if installed is None: + installed = [] + elif isinstance(installed, basestring): + installed = [installed] + base = self._buildout_path('') + installed = [d.startswith(base) and d[len(base):] or d + for d in installed] + return ' '.join(installed) + + + def _save_installed_options(self, installed_options): + installed = self['buildout']['installed'] + if not installed: + return + f = open(installed, 'w') + _save_options('buildout', installed_options['buildout'], f) + for part in installed_options['buildout']['parts'].split(): + print >>f + _save_options(part, installed_options[part], f) + f.close() + + def _error(self, message, *args): + raise zc.buildout.UserError(message % args) + + def _setup_logging(self): + root_logger = logging.getLogger() + self._logger = logging.getLogger('zc.buildout') + handler = logging.StreamHandler(sys.stdout) + log_format = self['buildout']['log-format'] + if not log_format: + # No format specified. Use different formatter for buildout + # and other modules, showing logger name except for buildout + log_format = '%(name)s: %(message)s' + buildout_handler = logging.StreamHandler(sys.stdout) + buildout_handler.setFormatter(logging.Formatter('%(message)s')) + self._logger.propagate = False + self._logger.addHandler(buildout_handler) + + handler.setFormatter(logging.Formatter(log_format)) + root_logger.addHandler(handler) + + level = self['buildout']['log-level'] + if level in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'): + level = getattr(logging, level) + else: + try: + level = int(level) + except ValueError: + self._error("Invalid logging level %s", level) + verbosity = self['buildout'].get('verbosity', 0) + try: + verbosity = int(verbosity) + except ValueError: + self._error("Invalid verbosity %s", verbosity) + + level -= verbosity + root_logger.setLevel(level) + self._log_level = level + + def _maybe_upgrade(self): + # See if buildout or setuptools need to be upgraded. + # If they do, do the upgrade and restart the buildout process. + __doing__ = 'Checking for upgrades.' + + if not self.newest: + return + + options = self['buildout'] + + specs = ['zc.buildout'] + if zc.buildout.easy_install.is_distribute: + specs.append('distribute') + else: + specs.append('setuptools') + ws = zc.buildout.easy_install.install( + [ + (spec + ' ' + options.get(spec+'-version', '')).strip() + for spec in specs + ], + options['eggs-directory'], + links = options.get('find-links', '').split(), + index = options.get('index'), + path = [options['develop-eggs-directory']], + allow_hosts = self._allow_hosts, + prefer_final=not self.accept_buildout_test_releases, + ) + + upgraded = [] + for project in 'zc.buildout', 'setuptools': + req = pkg_resources.Requirement.parse(project) + project_location = pkg_resources.working_set.find(req).location + if ws.find(req).location != project_location: + upgraded.append(ws.find(req)) + + if not upgraded: + return + + __doing__ = 'Upgrading.' + + should_run = realpath( + os.path.join(os.path.abspath(options['bin-directory']), + 'buildout') + ) + if sys.platform == 'win32': + should_run += '-script.py' + + if (realpath(os.path.abspath(sys.argv[0])) != should_run): + self._logger.debug("Running %r.", realpath(sys.argv[0])) + self._logger.debug("Local buildout is %r.", should_run) + self._logger.warn("Not upgrading because not running a local " + "buildout command.") + return + + if sys.platform == 'win32' and not self.__windows_restart: + args = map(zc.buildout.easy_install._safe_arg, sys.argv) + args.insert(1, '-W') + if not __debug__: + args.insert(0, '-O') + args.insert(0, zc.buildout.easy_install._safe_arg (sys.executable)) + os.execv(sys.executable, args) + + self._logger.info("Upgraded:\n %s;\nrestarting.", + ",\n ".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 + # Ideally the new version of buildout would get a chance to write the + # script. Not sure how to do that. + partsdir = os.path.join(options['parts-directory'], 'buildout') + if os.path.exists(partsdir): + # This is primarily for unit tests, in which .py files change too + # fast for Python to know to regenerate the .pyc/.pyo files. + shutil.rmtree(partsdir) + os.mkdir(partsdir) + if (self.accept_buildout_test_releases and + self._annotated['buildout']['accept-buildout-test-releases'][1] == + 'COMMAND_LINE_VALUE'): + # Bootstrap was called with '--accept-buildout-test-releases'. + # Continue to honor that setting. + script_initialization = _early_release_initialization_code + else: + script_initialization = '' + # (Honor the relative-paths option.) + relative_paths = options.get('relative-paths', 'false') + if relative_paths == 'true': + relative_paths = options['directory'] + else: + assert relative_paths == 'false' + relative_paths = '' + zc.buildout.easy_install.sitepackage_safe_scripts( + options['bin-directory'], ws, options['executable'], partsdir, + reqs=['zc.buildout'], relative_paths=relative_paths, + include_site_packages=self.include_site_packages, + script_initialization=script_initialization, + exec_sitecustomize=self.exec_sitecustomize, + ) + + # Restart + args = map(zc.buildout.easy_install._safe_arg, sys.argv) + if not __debug__: + args.insert(0, '-O') + args.insert(0, zc.buildout.easy_install._safe_arg(sys.executable)) + # We want to make sure that our new site.py is used for rerunning + # buildout, so we put the partsdir in PYTHONPATH for our restart. + # This overrides any set PYTHONPATH, but since we generally are + # trying to run with a completely "clean" python (only the standard + # library) then that should be fine. + env = os.environ.copy() + env['PYTHONPATH'] = partsdir + if is_jython: + sys.exit( + subprocess.Popen( + [sys.executable] + list(args), env=env).wait()) + else: + sys.exit(os.spawnve(os.P_WAIT, sys.executable, args, env)) + + def _load_extensions(self): + __doing__ = 'Loading extensions.' + specs = self['buildout'].get('extensions', '').split() + if specs: + path = [self['buildout']['develop-eggs-directory']] + if self.offline: + dest = None + path.append(self['buildout']['eggs-directory']) + else: + dest = self['buildout']['eggs-directory'] + if not os.path.exists(dest): + self._logger.info('Creating directory %r.', dest) + os.mkdir(dest) + + zc.buildout.easy_install.install( + specs, dest, path=path, + working_set=pkg_resources.working_set, + links = self['buildout'].get('find-links', '').split(), + index = self['buildout'].get('index'), + newest=self.newest, allow_hosts=self._allow_hosts, + prefer_final=not self.accept_buildout_test_releases) + + # Clear cache because extensions might now let us read pages we + # couldn't read before. + zc.buildout.easy_install.clear_index_cache() + + for ep in pkg_resources.iter_entry_points('zc.buildout.extension'): + ep.load()(self) + + def _unload_extensions(self): + __doing__ = 'Unloading extensions.' + specs = self['buildout'].get('extensions', '').split() + if specs: + for ep in pkg_resources.iter_entry_points( + 'zc.buildout.unloadextension'): + ep.load()(self) + + def setup(self, args): + if not args: + raise zc.buildout.UserError( + "The setup command requires the path to a setup script or \n" + "directory containing a setup script, and its arguments." + ) + setup = args.pop(0) + if os.path.isdir(setup): + setup = os.path.join(setup, 'setup.py') + + self._logger.info("Running setup script %r.", setup) + setup = os.path.abspath(setup) + + fd, tsetup = tempfile.mkstemp() + exe = zc.buildout.easy_install._safe_arg(sys.executable) + try: + os.write(fd, zc.buildout.easy_install.runsetup_template % dict( + setuptools=pkg_resources_loc, + setupdir=os.path.dirname(setup), + setup=setup, + __file__ = setup, + )) + if is_jython: + arg_list = list() + + for a in args: + arg_list.append(zc.buildout.easy_install._safe_arg(a)) + + subprocess.Popen([exe] + list(tsetup) + arg_list).wait() + + else: + os.spawnl(os.P_WAIT, sys.executable, exe, tsetup, + *[zc.buildout.easy_install._safe_arg(a) + for a in args]) + finally: + os.close(fd) + os.remove(tsetup) + + runsetup = setup # backward compat. + + def annotate(self, args): + _print_annotate(self._annotated) + + def __getitem__(self, section): + __doing__ = 'Getting section %s.', section + try: + return self._data[section] + except KeyError: + pass + + try: + data = self._raw[section] + except KeyError: + raise MissingSection(section) + + options = Options(self, section, data) + self._data[section] = options + options._initialize() + return options + + def __setitem__(self, key, value): + raise NotImplementedError('__setitem__') + + def __delitem__(self, key): + raise NotImplementedError('__delitem__') + + def keys(self): + return self._raw.keys() + + def __iter__(self): + return iter(self._raw) + + +def _install_and_load(spec, group, entry, buildout): + __doing__ = 'Loading recipe %r.', spec + try: + req = pkg_resources.Requirement.parse(spec) + + buildout_options = buildout['buildout'] + if pkg_resources.working_set.find(req) is None: + __doing__ = 'Installing recipe %s.', spec + if buildout.offline: + dest = None + path = [buildout_options['develop-eggs-directory'], + buildout_options['eggs-directory'], + ] + else: + dest = buildout_options['eggs-directory'] + path = [buildout_options['develop-eggs-directory']] + + zc.buildout.easy_install.install( + [spec], dest, + links=buildout._links, + index=buildout_options.get('index'), + path=path, + working_set=pkg_resources.working_set, + newest=buildout.newest, + allow_hosts=buildout._allow_hosts, + prefer_final=not buildout.accept_buildout_test_releases) + + __doing__ = 'Loading %s recipe entry %s:%s.', group, spec, entry + return pkg_resources.load_entry_point( + req.project_name, group, entry) + + except Exception, v: + buildout._logger.log( + 1, + "Could't load %s entry point %s\nfrom %s:\n%s.", + group, entry, spec, v) + raise + + +class Options(UserDict.DictMixin): + + def __init__(self, buildout, section, data): + self.buildout = buildout + self.name = section + self._raw = data + self._cooked = {} + self._data = {} + + def _initialize(self): + name = self.name + __doing__ = 'Initializing section %s.', name + + if '<' in self._raw: + self._raw = self._do_extend_raw(name, self._raw, []) + + # force substitutions + for k, v in self._raw.items(): + if '${' in v: + self._dosub(k, v) + + if self.name == 'buildout': + return # buildout section can never be a part + + recipe = self.get('recipe') + if not recipe: + return + + reqs, entry = _recipe(self._data) + buildout = self.buildout + recipe_class = _install_and_load(reqs, 'zc.buildout', entry, buildout) + + __doing__ = 'Initializing part %s.', name + self.recipe = recipe_class(buildout, name, self) + buildout._parts.append(name) + + def _do_extend_raw(self, name, data, doing): + if name == 'buildout': + return data + if name in doing: + raise zc.buildout.UserError("Infinite extending loop %r" % name) + doing.append(name) + try: + to_do = data.pop('<', None) + if to_do is None: + return data + __doing__ = 'Loading input sections for %r', name + + result = {} + for iname in to_do.split('\n'): + iname = iname.strip() + if not iname: + continue + raw = self.buildout._raw.get(iname) + if raw is None: + raise zc.buildout.UserError("No section named %r" % iname) + result.update(self._do_extend_raw(iname, raw, doing)) + + result.update(data) + return result + finally: + assert doing.pop() == name + + def _dosub(self, option, v): + __doing__ = 'Getting option %s:%s.', self.name, option + seen = [(self.name, option)] + v = '$$'.join([self._sub(s, seen) for s in v.split('$$')]) + self._cooked[option] = v + + def get(self, option, default=None, seen=None): + try: + return self._data[option] + except KeyError: + pass + + v = self._cooked.get(option) + if v is None: + v = self._raw.get(option) + if v is None: + return default + + __doing__ = 'Getting option %s:%s.', self.name, option + + if '${' in v: + key = self.name, option + if seen is None: + seen = [key] + elif key in seen: + raise zc.buildout.UserError( + "Circular reference in substitutions.\n" + ) + else: + seen.append(key) + v = '$$'.join([self._sub(s, seen) for s in v.split('$$')]) + seen.pop() + + self._data[option] = v + return v + + _template_split = re.compile('([$]{[^}]*})').split + _simple = re.compile('[-a-zA-Z0-9 ._]+$').match + _valid = re.compile('\${[-a-zA-Z0-9 ._]*:[-a-zA-Z0-9 ._]+}$').match + def _sub(self, template, seen): + value = self._template_split(template) + subs = [] + for ref in value[1::2]: + s = tuple(ref[2:-1].split(':')) + if not self._valid(ref): + if len(s) < 2: + raise zc.buildout.UserError("The substitution, %s,\n" + "doesn't contain a colon." + % ref) + if len(s) > 2: + raise zc.buildout.UserError("The substitution, %s,\n" + "has too many colons." + % ref) + if not self._simple(s[0]): + raise zc.buildout.UserError( + "The section name in substitution, %s,\n" + "has invalid characters." + % ref) + if not self._simple(s[1]): + raise zc.buildout.UserError( + "The option name in substitution, %s,\n" + "has invalid characters." + % ref) + + section, option = s + if not section: + section = self.name + v = self.buildout[section].get(option, None, seen) + if v is None: + if option == '_buildout_section_name_': + v = self.name + else: + raise MissingOption("Referenced option does not exist:", + section, option) + subs.append(v) + subs.append('') + + return ''.join([''.join(v) for v in zip(value[::2], subs)]) + + def __getitem__(self, key): + try: + return self._data[key] + except KeyError: + pass + + v = self.get(key) + if v is None: + raise MissingOption("Missing option: %s:%s" % (self.name, key)) + return v + + def __setitem__(self, option, value): + if not isinstance(value, str): + raise TypeError('Option values must be strings', value) + self._data[option] = value + + def __delitem__(self, key): + if key in self._raw: + del self._raw[key] + if key in self._data: + del self._data[key] + if key in self._cooked: + del self._cooked[key] + elif key in self._data: + del self._data[key] + else: + raise KeyError, key + + def keys(self): + raw = self._raw + return list(self._raw) + [k for k in self._data if k not in raw] + + def copy(self): + result = self._raw.copy() + result.update(self._cooked) + result.update(self._data) + return result + + def _call(self, f): + buildout_directory = self.buildout['buildout']['directory'] + self._created = [] + try: + try: + os.chdir(buildout_directory) + return f() + except: + for p in self._created: + if os.path.isdir(p): + rmtree(p) + elif os.path.isfile(p): + os.remove(p) + else: + self.buildout._logger.warn("Couldn't clean up %r.", p) + raise + finally: + self._created = None + os.chdir(buildout_directory) + + def created(self, *paths): + try: + self._created.extend(paths) + except AttributeError: + raise TypeError( + "Attempt to register a created path while not installing", + self.name) + return self._created + + def query_bool(self, name, default=None): + """Given a name, return a boolean value for that name. + + ``default``, if given, should be 'true', 'false', or None. + """ + if default is not None: + value = self.setdefault(name, default=default) + else: + value = self.get(name) + if value is None: + return value + return _convert_bool(name, value) + + def get_bool(self, name): + """Given a name, return a boolean value for that name. + """ + return _convert_bool(name, self[name]) + + +def _convert_bool(name, value): + if value not in ('true', 'false'): + raise zc.buildout.UserError( + 'Invalid value for %s option: %s' % (name, value)) + else: + return value == 'true' + +_spacey_nl = re.compile('[ \t\r\f\v]*\n[ \t\r\f\v\n]*' + '|' + '^[ \t\r\f\v]+' + '|' + '[ \t\r\f\v]+$' + ) + +_spacey_defaults = [ + ('%(__buildout_space__)s', ' '), + ('%(__buildout_space_n__)s', '\n'), + ('%(__buildout_space_r__)s', '\r'), + ('%(__buildout_space_f__)s', '\f'), + ('%(__buildout_space_v__)s', '\v'), + ] + +def _quote_spacey_nl(match): + match = match.group(0).split('\n', 1) + result = '\n\t'.join( + [(s + .replace(' ', '%(__buildout_space__)s') + .replace('\r', '%(__buildout_space_r__)s') + .replace('\f', '%(__buildout_space_f__)s') + .replace('\v', '%(__buildout_space_v__)s') + .replace('\n', '%(__buildout_space_n__)s') + ) + for s in match] + ) + return result + +def _save_option(option, value, f): + value = _spacey_nl.sub(_quote_spacey_nl, value) + if value.startswith('\n\t'): + value = '%(__buildout_space_n__)s' + value[2:] + if value.endswith('\n\t'): + value = value[:-2] + '%(__buildout_space_n__)s' + print >>f, option, '=', value + +def _save_options(section, options, f): + print >>f, '[%s]' % section + items = options.items() + items.sort() + for option, value in items: + _save_option(option, value, f) + +def _open(base, filename, seen, dl_options, override): + """Open a configuration file and return the result as a dictionary, + + Recursively open other files based on buildout options found. + """ + _update_section(dl_options, override) + _dl_options = _unannotate_section(dl_options.copy()) + is_temp = False + download = zc.buildout.download.Download( + _dl_options, cache=_dl_options.get('extends-cache'), fallback=True, + hash_name=True) + if _isurl(filename): + path, is_temp = download(filename) + fp = open(path) + base = filename[:filename.rfind('/')] + elif _isurl(base): + if os.path.isabs(filename): + fp = open(filename) + base = os.path.dirname(filename) + else: + filename = base + '/' + filename + path, is_temp = download(filename) + fp = open(path) + base = filename[:filename.rfind('/')] + else: + filename = os.path.join(base, filename) + fp = open(filename) + base = os.path.dirname(filename) + + if filename in seen: + if is_temp: + fp.close() + os.remove(path) + raise zc.buildout.UserError("Recursive file include", seen, filename) + + root_config_file = not seen + seen.append(filename) + + result = {} + + parser = ConfigParser.RawConfigParser() + parser.optionxform = lambda s: s + parser.readfp(fp) + if is_temp: + fp.close() + os.remove(path) + + extends = extended_by = None + for section in parser.sections(): + options = dict(parser.items(section)) + if section == 'buildout': + extends = options.pop('extends', extends) + extended_by = options.pop('extended-by', extended_by) + result[section] = options + + result = _annotate(result, filename) + + if root_config_file and 'buildout' in result: + dl_options = _update_section(dl_options, result['buildout']) + + if extends: + extends = extends.split() + eresult = _open(base, extends.pop(0), seen, dl_options, override) + for fname in extends: + _update(eresult, _open(base, fname, seen, dl_options, override)) + result = _update(eresult, result) + + if extended_by: + self._logger.warn( + "The extendedBy option is deprecated. Stop using it." + ) + for fname in extended_by.split(): + result = _update(result, + _open(base, fname, seen, dl_options, override)) + + seen.pop() + return result + + +ignore_directories = '.svn', 'CVS' +def _dir_hash(dir): + hash = md5() + for (dirpath, dirnames, filenames) in os.walk(dir): + dirnames[:] = [n for n in dirnames if n not in ignore_directories] + filenames[:] = [f for f in filenames + if (not (f.endswith('pyc') or f.endswith('pyo')) + and os.path.exists(os.path.join(dirpath, f))) + ] + hash.update(' '.join(dirnames)) + hash.update(' '.join(filenames)) + for name in filenames: + hash.update(open(os.path.join(dirpath, name)).read()) + return hash.digest().encode('base64').strip() + +def _dists_sig(dists): + result = [] + for dist in dists: + location = dist.location + if dist.precedence == pkg_resources.DEVELOP_DIST: + result.append(dist.project_name + '-' + _dir_hash(location)) + else: + result.append(os.path.basename(location)) + return result + +def _update_section(s1, s2): + s2 = s2.copy() # avoid mutating the second argument, which is unexpected + for k, v in s2.items(): + v2, note2 = v + if k.endswith('+'): + key = k.rstrip(' +') + v1, note1 = s1.get(key, ("", "")) + newnote = ' [+] '.join((note1, note2)).strip() + s2[key] = "\n".join((v1).split('\n') + + v2.split('\n')), newnote + del s2[k] + elif k.endswith('-'): + key = k.rstrip(' -') + v1, note1 = s1.get(key, ("", "")) + newnote = ' [-] '.join((note1, note2)).strip() + s2[key] = ("\n".join( + [v for v in v1.split('\n') + if v not in v2.split('\n')]), newnote) + del s2[k] + + s1.update(s2) + return s1 + +def _update(d1, d2): + for section in d2: + if section in d1: + d1[section] = _update_section(d1[section], d2[section]) + else: + d1[section] = d2[section] + return d1 + +def _recipe(options): + recipe = options['recipe'] + if ':' in recipe: + recipe, entry = recipe.split(':') + else: + entry = 'default' + + return recipe, entry + +def _doing(): + _, v, tb = sys.exc_info() + message = str(v) + doing = [] + while tb is not None: + d = tb.tb_frame.f_locals.get('__doing__') + if d: + doing.append(d) + tb = tb.tb_next + + if doing: + sys.stderr.write('While:\n') + for d in doing: + if not isinstance(d, str): + d = d[0] % d[1:] + sys.stderr.write(' %s\n' % d) + +def _error(*message): + sys.stderr.write('Error: ' + ' '.join(message) +'\n') + sys.exit(1) + +_internal_error_template = """ +An internal error occurred due to a bug in either zc.buildout or in a +recipe being used: +""" + +def _check_for_unused_options_in_section(buildout, section): + options = buildout[section] + unused = [option for option in options._raw if option not in options._data] + if unused: + buildout._logger.warn("Unused options for %s: %s." + % (section, ' '.join(map(repr, unused))) + ) + +_early_release_initialization_code = """\ +sys.argv.insert(1, 'buildout:accept-buildout-test-releases=true') +print ('NOTE: Accepting early releases of build system packages. Rerun ' + 'bootstrap without --accept-buildout-test-releases (-t) to return to ' + 'default behavior.') +""" + +_usage = """\ +Usage: buildout [options] [assignments] [command [command arguments]] + +Options: + + -h, --help + + Print this message and exit. + + -v + + Increase the level of verbosity. This option can be used multiple times. + + -q + + Decrease the level of verbosity. This option can be used multiple times. + + -c config_file + + Specify the path to the buildout configuration file to be used. + This defaults to the file named "buildout.cfg" in the current + working directory. + + -t socket_timeout + + Specify the socket timeout in seconds. + + -U + + Don't read user defaults. + + -o + + Run in off-line mode. This is equivalent to the assignment + buildout:offline=true. + + -O + + Run in non-off-line mode. This is equivalent to the assignment + buildout:offline=false. This is the default buildout mode. The + -O option would normally be used to override a true offline + setting in a configuration file. + + -n + + Run in newest mode. This is equivalent to the assignment + buildout:newest=true. With this setting, which is the default, + buildout will try to find the newest versions of distributions + available that satisfy its requirements. + + -N + + Run in non-newest mode. This is equivalent to the assignment + buildout:newest=false. With this setting, buildout will not seek + new distributions if installed distributions satisfy it's + requirements. + + -D + + Debug errors. If an error occurs, then the post-mortem debugger + will be started. This is especially useful for debuging recipe + problems. + + -s + + Squelch warnings about using an executable with a broken -S + implementation. + +Assignments are of the form: section:option=value and are used to +provide configuration options that override those given in the +configuration file. For example, to run the buildout in offline mode, +use buildout:offline=true. + +Options and assignments can be interspersed. + +Commands: + + install [parts] + + Install parts. If no command arguments are given, then the parts + definition from the configuration file is used. Otherwise, the + arguments specify the parts to be installed. + + Note that the semantics differ depending on whether any parts are + specified. If parts are specified, then only those parts will be + installed. If no parts are specified, then the parts specified by + the buildout parts option will be installed along with all of + their dependencies. + + bootstrap + + Create a new buildout in the current working directory, copying + the buildout and setuptools eggs and, creating a basic directory + structure and a buildout-local buildout script. + + init + + Initialize a buildout, creating a buildout.cfg file if it doesn't + exist and then performing the same actions as for the buildout + command. + + setup script [setup command and options] + + Run a given setup script arranging that setuptools is in the + script's path and and that it has been imported so that + setuptools-provided commands (like bdist_egg) can be used even if + the setup script doesn't import setuptools itself. + + The script can be given either as a script path or a path to a + directory containing a setup.py script. + + annotate + + Display annotated sections. All sections are displayed, sorted + alphabetically. For each section, all key-value pairs are displayed, + sorted alphabetically, along with the origin of the value (file name or + COMPUTED_VALUE, DEFAULT_VALUE, COMMAND_LINE_VALUE). + +""" +def _help(): + print _usage + sys.exit(0) + +def main(args=None): + if args is None: + args = sys.argv[1:] + + config_file = 'buildout.cfg' + verbosity = 0 + options = [] + windows_restart = False + user_defaults = True + debug = False + ignore_broken_dash_s = False + while args: + if args[0][0] == '-': + op = orig_op = args.pop(0) + op = op[1:] + while op and op[0] in 'vqhWUoOnNDAs': + if op[0] == 'v': + verbosity += 10 + elif op[0] == 'q': + verbosity -= 10 + elif op[0] == 'W': + windows_restart = True + elif op[0] == 'U': + user_defaults = False + elif op[0] == 'o': + options.append(('buildout', 'offline', 'true')) + elif op[0] == 'O': + options.append(('buildout', 'offline', 'false')) + elif op[0] == 'n': + options.append(('buildout', 'newest', 'true')) + elif op[0] == 'N': + options.append(('buildout', 'newest', 'false')) + elif op[0] == 'D': + debug = True + elif op[0] == 's': + ignore_broken_dash_s = True + else: + _help() + op = op[1:] + + if op[:1] in ('c', 't'): + op_ = op[:1] + op = op[1:] + + if op_ == 'c': + if op: + config_file = op + else: + if args: + config_file = args.pop(0) + else: + _error("No file name specified for option", orig_op) + elif op_ == 't': + try: + timeout = int(args.pop(0)) + except IndexError: + _error("No timeout value specified for option", orig_op) + except ValueError: + _error("No timeout value must be numeric", orig_op) + + import socket + print 'Setting socket time out to %d seconds' % timeout + socket.setdefaulttimeout(timeout) + + elif op: + if orig_op == '--help': + _help() + _error("Invalid option", '-'+op[0]) + elif '=' in args[0]: + option, value = args.pop(0).split('=', 1) + if len(option.split(':')) != 2: + _error('Invalid option:', option) + section, option = option.split(':') + options.append((section.strip(), option.strip(), value.strip())) + else: + # We've run out of command-line options and option assignnemnts + # The rest should be commands, so we'll stop here + break + + if verbosity < 0 or ignore_broken_dash_s: + broken_dash_S_filter_action = 'ignore' + elif verbosity == 0: # This is the default. + broken_dash_S_filter_action = 'once' + else: + broken_dash_S_filter_action = 'default' + warnings.filterwarnings( + broken_dash_S_filter_action, + re.escape( + zc.buildout.easy_install.BROKEN_DASH_S_WARNING), + UserWarning) + if verbosity: + options.append(('buildout', 'verbosity', str(verbosity))) + + if args: + command = args.pop(0) + if command not in ( + 'install', 'bootstrap', 'runsetup', 'setup', 'init', + 'annotate', + ): + _error('invalid command:', command) + else: + command = 'install' + + try: + try: + buildout = Buildout(config_file, options, + user_defaults, windows_restart, command) + getattr(buildout, command)(args) + except SystemExit: + pass + except Exception, v: + _doing() + exc_info = sys.exc_info() + import pdb, traceback + if debug: + traceback.print_exception(*exc_info) + sys.stderr.write('\nStarting pdb:\n') + pdb.post_mortem(exc_info[2]) + else: + if isinstance(v, (zc.buildout.UserError, + distutils.errors.DistutilsError, + ) + ): + _error(str(v)) + else: + sys.stderr.write(_internal_error_template) + traceback.print_exception(*exc_info) + sys.exit(1) + + + finally: + logging.shutdown() + +if sys.version_info[:2] < (2, 4): + def reversed(iterable): + result = list(iterable); + result.reverse() + return result diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/buildout.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/buildout.txt new file mode 100644 index 0000000..ba3a72f --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/buildout.txt @@ -0,0 +1,2831 @@ +Buildouts +========= + +The word "buildout" refers to a description of a set of parts and the +software to create and assemble them. It is often used informally to +refer to an installed system based on a buildout definition. For +example, if we are creating an application named "Foo", then "the Foo +buildout" is the collection of configuration and application-specific +software that allows an instance of the application to be created. We +may refer to such an instance of the application informally as "a Foo +buildout". + +This document describes how to define buildouts using buildout +configuration files and recipes. There are three ways to set up the +buildout software and create a buildout instance: + +1. Install the zc.buildout egg with easy_install and use the buildout + script installed in a Python scripts area. + +2. Use the buildout bootstrap script to create a buildout that + includes both the setuptools and zc.buildout eggs. This allows you + to use the buildout software without modifying a Python install. + The buildout script is installed into your buildout local scripts + area. + +3. Use a buildout command from an already installed buildout to + bootstrap a new buildout. (See the section on bootstraping later + in this document.) + +Often, a software project will be managed in a software repository, +such as a subversion repository, that includes some software source +directories, buildout configuration files, and a copy of the buildout +bootstrap script. To work on the project, one would check out the +project from the repository and run the bootstrap script which +installs setuptools and zc.buildout into the checkout as well as any +parts defined. + +We have a sample buildout that we created using the bootstrap command +of an existing buildout (method 3 above). It has the absolute minimum +information. We have bin, develop-eggs, eggs and parts directories, +and a configuration file: + + >>> ls(sample_buildout) + d bin + - buildout.cfg + d develop-eggs + d eggs + d parts + +The bin directory contains scripts. + + >>> ls(sample_buildout, 'bin') + - buildout + + >>> ls(sample_buildout, 'eggs') + - setuptools-0.6-py2.4.egg + - zc.buildout-1.0-py2.4.egg + +The develop-eggs directory is initially empty: + + >>> ls(sample_buildout, 'develop-eggs') + +The develop-eggs directory holds egg links for software being +developed in the buildout. We separate develop-eggs and other eggs to +allow eggs directories to be shared across multiple buildouts. For +example, a common developer technique is to define a common eggs +directory in their home that all non-develop eggs are stored in. This +allows larger buildouts to be set up much more quickly and saves disk +space. + +The parts directory just contains some helpers for the buildout script +itself. + + >>> ls(sample_buildout, 'parts') + d buildout + +The parts directory provides an area where recipes can install +part data. For example, if we built a custom Python, we would +install it in the part directory. Part data is stored in a +sub-directory of the parts directory with the same name as the part. + +Buildouts are defined using configuration files. These are in the +format defined by the Python ConfigParser module, with extensions +that we'll describe later. By default, when a buildout is run, it +looks for the file buildout.cfg in the directory where the buildout is +run. + +The minimal configuration file has a buildout section that defines no +parts: + + >>> cat(sample_buildout, 'buildout.cfg') + [buildout] + parts = + +A part is simply something to be created by a buildout. It can be +almost anything, such as a Python package, a program, a directory, or +even a configuration file. + +Recipes +------- + +A part is created by a recipe. Recipes are always installed as Python +eggs. They can be downloaded from a package server, such as the +Python Package Index, or they can be developed as part of a project +using a "develop" egg. + +A develop egg is a special kind of egg that gets installed as an "egg +link" that contains the name of a source directory. Develop eggs +don't have to be packaged for distribution to be used and can be +modified in place, which is especially useful while they are being +developed. + +Let's create a recipe as part of the sample project. We'll create a +recipe for creating directories. First, we'll create a recipes source +directory for our local recipes: + + >>> mkdir(sample_buildout, 'recipes') + +and then we'll create a source file for our mkdir recipe: + + >>> write(sample_buildout, 'recipes', 'mkdir.py', + ... """ + ... import logging, os, zc.buildout + ... + ... class Mkdir: + ... + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... options['path'] = os.path.join( + ... buildout['buildout']['directory'], + ... options['path'], + ... ) + ... if not os.path.isdir(os.path.dirname(options['path'])): + ... logging.getLogger(self.name).error( + ... 'Cannot create %s. %s is not a directory.', + ... options['path'], os.path.dirname(options['path'])) + ... raise zc.buildout.UserError('Invalid Path') + ... + ... + ... def install(self): + ... path = self.options['path'] + ... logging.getLogger(self.name).info( + ... 'Creating directory %s', os.path.basename(path)) + ... os.mkdir(path) + ... return path + ... + ... def update(self): + ... pass + ... """) + +Currently, recipes must define 3 methods [#future_recipe_methods]_: + +- a constructor, + +- an install method, and + +- an update method. + +The constructor is responsible for updating a parts options to reflect +data read from other sections. The buildout system keeps track of +whether a part specification has changed. A part specification has +changed if it's options, after adjusting for data read from other +sections, has changed, or if the recipe has changed. Only the options +for the part are considered. If data are read from other sections, +then that information has to be reflected in the parts options. In +the Mkdir example, the given path is interpreted relative to the +buildout directory, and data from the buildout directory is read. The +path option is updated to reflect this. If the directory option was +changed in the buildout sections, we would know to update parts +created using the mkdir recipe using relative path names. + +When buildout is run, it saves configuration data for installed parts +in a file named ".installed.cfg". In subsequent runs, it compares +part-configuration data stored in the .installed.cfg file and the +part-configuration data loaded from the configuration files as +modified by recipe constructors to decide if the configuration of a +part has changed. If the configuration has changed, or if the recipe +has changed, then the part is uninstalled and reinstalled. The +buildout only looks at the part's options, so any data used to +configure the part needs to be reflected in the part's options. It is +the job of a recipe constructor to make sure that the options include +all relevant data. + +Of course, parts are also uninstalled if they are no-longer used. + +The recipe defines a constructor that takes a buildout object, a part +name, and an options dictionary. It saves them in instance attributes. +If the path is relative, we'll interpret it as relative to the +buildout directory. The buildout object passed in is a mapping from +section name to a mapping of options for that section. The buildout +directory is available as the directory option of the buildout +section. We normalize the path and save it back into the options +directory. + +The install method is responsible for creating the part. In this +case, we need the path of the directory to create. We'll use a path +option from our options dictionary. The install method logs what it's +doing using the Python logging call. We return the path that we +installed. If the part is uninstalled or reinstalled, then the path +returned will be removed by the buildout machinery. A recipe install +method is expected to return a string, or an iterable of strings +containing paths to be removed if a part is uninstalled. For most +recipes, this is all of the uninstall support needed. For more complex +uninstallation scenarios use `Uninstall recipes`_. + +The update method is responsible for updating an already installed +part. An empty method is often provided, as in this example, if parts +can't be updated. An update method can return None, a string, or an +iterable of strings. If a string or iterable of strings is returned, +then the saved list of paths to be uninstalled is updated with the new +information by adding any new files returned by the update method. + +We need to provide packaging information so that our recipe can be +installed as a develop egg. The minimum information we need to specify +[#packaging_info]_ is a name. For recipes, we also need to define the +names of the recipe classes as entry points. Packaging information is +provided via a setup.py script: + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... """ + ... from setuptools import setup + ... + ... setup( + ... name = "recipes", + ... entry_points = {'zc.buildout': ['mkdir = mkdir:Mkdir']}, + ... ) + ... """) + +Our setup script defines an entry point. Entry points provide +a way for an egg to define the services it provides. Here we've said +that we define a zc.buildout entry point named mkdir. Recipe +classes must be exposed as entry points in the zc.buildout group. we +give entry points names within the group. + +We also need a README.txt for our recipes to avoid an annoying warning +from distutils, on which setuptools and zc.buildout are based: + + >>> write(sample_buildout, 'recipes', 'README.txt', " ") + +Now let's update our buildout.cfg: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mystuff + ... """) + +Let's go through the changes one by one:: + + develop = recipes + +This tells the buildout to install a development egg for our recipes. +Any number of paths can be listed. The paths can be relative or +absolute. If relative, they are treated as relative to the buildout +directory. They can be directory or file paths. If a file path is +given, it should point to a Python setup script. If a directory path +is given, it should point to a directory containing a setup.py file. +Development eggs are installed before building any parts, as they may +provide locally-defined recipes needed by the parts. + +:: + + parts = data-dir + +Here we've named a part to be "built". We can use any name we want +except that different part names must be unique and recipes will often +use the part name to decide what to do. + +:: + + [data-dir] + recipe = recipes:mkdir + path = mystuff + + +When we name a part, we also create a section of the same +name that contains part data. In this section, we'll define +the recipe to be used to install the part. In this case, we also +specify the path to be created. + +Let's run the buildout. We do so by running the build script in the +buildout: + + >>> import os + >>> os.chdir(sample_buildout) + >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout') + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing data-dir. + data-dir: Creating directory mystuff + +We see that the recipe created the directory, as expected: + + >>> ls(sample_buildout) + - .installed.cfg + d bin + - buildout.cfg + d develop-eggs + d eggs + d mystuff + d parts + d recipes + +In addition, .installed.cfg has been created containing information +about the part we installed: + + >>> cat(sample_buildout, '.installed.cfg') + [buildout] + installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link + parts = data-dir + <BLANKLINE> + [data-dir] + __buildout_installed__ = /sample-buildout/mystuff + __buildout_signature__ = recipes-c7vHV6ekIDUPy/7fjAaYjg== + path = /sample-buildout/mystuff + recipe = recipes:mkdir + +Note that the directory we installed is included in .installed.cfg. +In addition, the path option includes the actual destination +directory. + +If we change the name of the directory in the configuration file, +we'll see that the directory gets removed and recreated: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mydata + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling data-dir. + Installing data-dir. + data-dir: Creating directory mydata + + >>> ls(sample_buildout) + - .installed.cfg + d bin + - buildout.cfg + d develop-eggs + d eggs + d mydata + d parts + d recipes + +If any of the files or directories created by a recipe are removed, +the part will be reinstalled: + + >>> rmdir(sample_buildout, 'mydata') + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling data-dir. + Installing data-dir. + data-dir: Creating directory mydata + +Error reporting +--------------- + +If a user makes an error, an error needs to be printed and work needs +to stop. This is accomplished by logging a detailed error message and +then raising a (or an instance of a subclass of a) +zc.buildout.UserError exception. Raising an error other than a +UserError still displays the error, but labels it as a bug in the +buildout software or recipe. In the sample above, of someone gives a +non-existent directory to create the directory in: + + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = /xxx/mydata + ... """) + +We'll get a user error, not a traceback. + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + data-dir: Cannot create /xxx/mydata. /xxx is not a directory. + While: + Installing. + Getting section data-dir. + Initializing part data-dir. + Error: Invalid Path + + +Recipe Error Handling +--------------------- + +If an error occurs during installation, it is up to the recipe to +clean up any system side effects, such as files created. Let's update +the mkdir recipe to support multiple paths: + + >>> write(sample_buildout, 'recipes', 'mkdir.py', + ... """ + ... import logging, os, zc.buildout + ... + ... class Mkdir: + ... + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... + ... # Normalize paths and check that their parent + ... # directories exist: + ... paths = [] + ... for path in options['path'].split(): + ... path = os.path.join(buildout['buildout']['directory'], path) + ... if not os.path.isdir(os.path.dirname(path)): + ... logging.getLogger(self.name).error( + ... 'Cannot create %s. %s is not a directory.', + ... options['path'], os.path.dirname(options['path'])) + ... raise zc.buildout.UserError('Invalid Path') + ... paths.append(path) + ... options['path'] = ' '.join(paths) + ... + ... def install(self): + ... paths = self.options['path'].split() + ... for path in paths: + ... logging.getLogger(self.name).info( + ... 'Creating directory %s', os.path.basename(path)) + ... os.mkdir(path) + ... return paths + ... + ... def update(self): + ... pass + ... """) + +If there is an error creating a path, the install method will exit and +leave previously created paths in place: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = foo bin + ... """) + + >>> print system(buildout), # doctest: +ELLIPSIS + Develop: '/sample-buildout/recipes' + Uninstalling data-dir. + Installing data-dir. + data-dir: Creating directory foo + data-dir: Creating directory bin + While: + Installing data-dir. + <BLANKLINE> + An internal error occurred due to a bug in either zc.buildout or in a + recipe being used: + Traceback (most recent call last): + ... + OSError: [Errno 17] File exists: '/sample-buildout/bin' + +We meant to create a directory bins, but typed bin. Now foo was +left behind. + + >>> os.path.exists('foo') + True + +If we fix the typo: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = foo bins + ... """) + + >>> print system(buildout), # doctest: +ELLIPSIS + Develop: '/sample-buildout/recipes' + Installing data-dir. + data-dir: Creating directory foo + While: + Installing data-dir. + <BLANKLINE> + An internal error occurred due to a bug in either zc.buildout or in a + recipe being used: + Traceback (most recent call last): + ... + OSError: [Errno 17] File exists: '/sample-buildout/foo' + +Now they fail because foo exists, because it was left behind. + + >>> remove('foo') + +Let's fix the recipe: + + >>> write(sample_buildout, 'recipes', 'mkdir.py', + ... """ + ... import logging, os, zc.buildout + ... + ... class Mkdir: + ... + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... + ... # Normalize paths and check that their parent + ... # directories exist: + ... paths = [] + ... for path in options['path'].split(): + ... path = os.path.join(buildout['buildout']['directory'], path) + ... if not os.path.isdir(os.path.dirname(path)): + ... logging.getLogger(self.name).error( + ... 'Cannot create %s. %s is not a directory.', + ... options['path'], os.path.dirname(options['path'])) + ... raise zc.buildout.UserError('Invalid Path') + ... paths.append(path) + ... options['path'] = ' '.join(paths) + ... + ... def install(self): + ... paths = self.options['path'].split() + ... created = [] + ... try: + ... for path in paths: + ... logging.getLogger(self.name).info( + ... 'Creating directory %s', os.path.basename(path)) + ... os.mkdir(path) + ... created.append(path) + ... except: + ... for d in created: + ... os.rmdir(d) + ... raise + ... + ... return paths + ... + ... def update(self): + ... pass + ... """) + +And put back the typo: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = foo bin + ... """) + +When we rerun the buildout: + + >>> print system(buildout), # doctest: +ELLIPSIS + Develop: '/sample-buildout/recipes' + Installing data-dir. + data-dir: Creating directory foo + data-dir: Creating directory bin + While: + Installing data-dir. + <BLANKLINE> + An internal error occurred due to a bug in either zc.buildout or in a + recipe being used: + Traceback (most recent call last): + ... + OSError: [Errno 17] File exists: '/sample-buildout/bin' + +.. Wait for the file to really disappear. My linux is weird. + + >>> wait_until("foo goes away", lambda : not os.path.exists('foo'), + ... timeout=200) + +we get the same error, but we don't get the directory left behind: + + >>> os.path.exists('foo') + False + +It's critical that recipes clean up partial effects when errors +occur. Because recipes most commonly create files and directories, +buildout provides a helper API for removing created files when an +error occurs. Option objects have a created method that can be called +to record files as they are created. If the install or update method +returns with an error, then any registered paths are removed +automatically. The method returns the files registered and can be +used to return the files created. Let's use this API to simplify the +recipe: + + >>> write(sample_buildout, 'recipes', 'mkdir.py', + ... """ + ... import logging, os, zc.buildout + ... + ... class Mkdir: + ... + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... + ... # Normalize paths and check that their parent + ... # directories exist: + ... paths = [] + ... for path in options['path'].split(): + ... path = os.path.join(buildout['buildout']['directory'], path) + ... if not os.path.isdir(os.path.dirname(path)): + ... logging.getLogger(self.name).error( + ... 'Cannot create %s. %s is not a directory.', + ... options['path'], os.path.dirname(options['path'])) + ... raise zc.buildout.UserError('Invalid Path') + ... paths.append(path) + ... options['path'] = ' '.join(paths) + ... + ... def install(self): + ... paths = self.options['path'].split() + ... for path in paths: + ... logging.getLogger(self.name).info( + ... 'Creating directory %s', os.path.basename(path)) + ... os.mkdir(path) + ... self.options.created(path) + ... + ... return self.options.created() + ... + ... def update(self): + ... pass + ... """) + +.. + + >>> remove(sample_buildout, 'recipes', 'mkdir.pyc') + +We returned by calling created, taking advantage of the fact that it +returns the registered paths. We did this for illustrative purposes. +It would be simpler just to return the paths as before. + +If we rerun the buildout, again, we'll get the error and no +directories will be created: + + >>> print system(buildout), # doctest: +ELLIPSIS + Develop: '/sample-buildout/recipes' + Installing data-dir. + data-dir: Creating directory foo + data-dir: Creating directory bin + While: + Installing data-dir. + <BLANKLINE> + An internal error occurred due to a bug in either zc.buildout or in a + recipe being used: + Traceback (most recent call last): + ... + OSError: [Errno 17] File exists: '/sample-buildout/bin' + + >>> os.path.exists('foo') + False + +Now, we'll fix the typo again and we'll get the directories we expect: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = foo bins + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing data-dir. + data-dir: Creating directory foo + data-dir: Creating directory bins + + >>> os.path.exists('foo') + True + >>> os.path.exists('bins') + True + +Configuration file syntax +------------------------- + +As mentioned earlier, buildout configuration files use the format +defined by the Python ConfigParser module with extensions. The +extensions are: + +- option names are case sensitive + +- option values can use a substitution syntax, described below, to + refer to option values in specific sections. + +- option values can be appended or removed using the - and + + operators. + +The ConfigParser syntax is very flexible. Section names can contain +any characters other than newlines and right square braces ("]"). +Option names can contain any characters other than newlines, colons, +and equal signs, can not start with a space, and don't include +trailing spaces. + +It is likely that, in the future, some characters will be given +special buildout-defined meanings. This is already true of the +characters ":", "$", "%", "(", and ")". For now, it is a good idea to +keep section and option names simple, sticking to alphanumeric +characters, hyphens, and periods. + +Annotated sections +------------------ + +When used with the `annotate` command, buildout displays annotated sections. +All sections are displayed, sorted alphabetically. For each section, +all key-value pairs are displayed, sorted alphabetically, along with +the origin of the value (file name or COMPUTED_VALUE, DEFAULT_VALUE, +COMMAND_LINE_VALUE). + + >>> print system(buildout+ ' annotate'), + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + <BLANKLINE> + Annotated sections + ================== + <BLANKLINE> + [buildout] + accept-buildout-test-releases= false + DEFAULT_VALUE + allow-hosts= * + DEFAULT_VALUE + allow-picked-versions= true + DEFAULT_VALUE + allowed-eggs-from-site-packages= * + DEFAULT_VALUE + bin-directory= bin + DEFAULT_VALUE + develop= recipes + /sample-buildout/buildout.cfg + develop-eggs-directory= develop-eggs + DEFAULT_VALUE + directory= /sample-buildout + COMPUTED_VALUE + eggs-directory= eggs + DEFAULT_VALUE + exec-sitecustomize= true + DEFAULT_VALUE + executable= ... + DEFAULT_VALUE + find-links= + DEFAULT_VALUE + include-site-packages= true + DEFAULT_VALUE + install-from-cache= false + DEFAULT_VALUE + installed= .installed.cfg + DEFAULT_VALUE + log-format= + DEFAULT_VALUE + log-level= INFO + DEFAULT_VALUE + newest= true + DEFAULT_VALUE + offline= false + DEFAULT_VALUE + parts= data-dir + /sample-buildout/buildout.cfg + parts-directory= parts + DEFAULT_VALUE + prefer-final= false + DEFAULT_VALUE + python= buildout + DEFAULT_VALUE + relative-paths= false + DEFAULT_VALUE + socket-timeout= + DEFAULT_VALUE + unzip= false + DEFAULT_VALUE + use-dependency-links= true + DEFAULT_VALUE + <BLANKLINE> + [data-dir] + path= foo bins + /sample-buildout/buildout.cfg + recipe= recipes:mkdir + /sample-buildout/buildout.cfg + <BLANKLINE> + +Variable substitutions +---------------------- + +Buildout configuration files support variable substitution. +To illustrate this, we'll create an debug recipe to +allow us to see interactions with the buildout: + + >>> write(sample_buildout, 'recipes', 'debug.py', + ... """ + ... class Debug: + ... + ... def __init__(self, buildout, name, options): + ... self.buildout = buildout + ... self.name = name + ... self.options = options + ... + ... def install(self): + ... items = self.options.items() + ... items.sort() + ... for option, value in items: + ... print option, value + ... return () + ... + ... update = install + ... """) + +This recipe doesn't actually create anything. The install method +doesn't return anything, because it didn't create any files or +directories. + +We also have to update our setup script: + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... """ + ... from setuptools import setup + ... entry_points = ( + ... ''' + ... [zc.buildout] + ... mkdir = mkdir:Mkdir + ... debug = debug:Debug + ... ''') + ... setup(name="recipes", entry_points=entry_points) + ... """) + +We've rearranged the script a bit to make the entry points easier to +edit. In particular, entry points are now defined as a configuration +string, rather than a dictionary. + +Let's update our configuration to provide variable substitution +examples: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir debug + ... log-level = INFO + ... + ... [debug] + ... recipe = recipes:debug + ... File 1 = ${data-dir:path}/file + ... File 2 = ${debug:File 1}/log + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mydata + ... """) + +We used a string-template substitution for File 1 and File 2. This +type of substitution uses the string.Template syntax. Names +substituted are qualified option names, consisting of a section name +and option name joined by a colon. + +Now, if we run the buildout, we'll see the options with the values +substituted. + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling data-dir. + Installing data-dir. + data-dir: Creating directory mydata + Installing debug. + File 1 /sample-buildout/mydata/file + File 2 /sample-buildout/mydata/file/log + recipe recipes:debug + +Note that the substitution of the data-dir path option reflects the +update to the option performed by the mkdir recipe. + +It might seem surprising that mydata was created again. This is +because we changed our recipes package by adding the debug module. +The buildout system didn't know if this module could effect the mkdir +recipe, so it assumed it could and reinstalled mydata. If we rerun +the buildout: + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Updating data-dir. + Updating debug. + File 1 /sample-buildout/mydata/file + File 2 /sample-buildout/mydata/file/log + recipe recipes:debug + +We can see that mydata was not recreated. + +Note that, in this case, we didn't specify a log level, so +we didn't get output about what the buildout was doing. + +Section and option names in variable substitutions are only allowed to +contain alphanumeric characters, hyphens, periods and spaces. This +restriction might be relaxed in future releases. + +We can ommit the section name in a variable substitution to refer to +the current section. We can also use the special option, +_buildout_section_name_ to get the current section name. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir debug + ... log-level = INFO + ... + ... [debug] + ... recipe = recipes:debug + ... File 1 = ${data-dir:path}/file + ... File 2 = ${:File 1}/log + ... my_name = ${:_buildout_section_name_} + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mydata + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Updating data-dir. + Installing debug. + File 1 /sample-buildout/mydata/file + File 2 /sample-buildout/mydata/file/log + my_name debug + recipe recipes:debug + +Automatic part selection and ordering +------------------------------------- + +When a section with a recipe is referred to, either through variable +substitution or by an initializing recipe, the section is treated as a +part and added to the part list before the referencing part. For +example, we can leave data-dir out of the parts list: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... log-level = INFO + ... + ... [debug] + ... recipe = recipes:debug + ... File 1 = ${data-dir:path}/file + ... File 2 = ${debug:File 1}/log + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mydata + ... """) + + +It will still be treated as a part: + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Updating data-dir. + Installing debug. + File 1 /sample-buildout/mydata/file + File 2 /sample-buildout/mydata/file/log + recipe recipes:debug + + >>> cat('.installed.cfg') # doctest: +ELLIPSIS + [buildout] + installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link + parts = data-dir debug + ... + +Note that the data-dir part is included *before* the debug part, +because the debug part refers to the data-dir part. Even if we list +the data-dir part after the debug part, it will be included before: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug data-dir + ... log-level = INFO + ... + ... [debug] + ... recipe = recipes:debug + ... File 1 = ${data-dir:path}/file + ... File 2 = ${debug:File 1}/log + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mydata + ... """) + + +It will still be treated as a part: + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Updating data-dir. + Updating debug. + File 1 /sample-buildout/mydata/file + File 2 /sample-buildout/mydata/file/log + recipe recipes:debug + + >>> cat('.installed.cfg') # doctest: +ELLIPSIS + [buildout] + installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link + parts = data-dir debug + ... + +Extending sections (macros) +--------------------------- + +A section (other than the buildout section) can extend one or more +other sections using the ``<=`` option. Options from the referenced +sections are copied to the refering section *before* variable +substitution. This, together with the ability to refer to variables +of the current section allows sections to be used as macros. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = myfiles + ... log-level = INFO + ... + ... [debug] + ... recipe = recipes:debug + ... + ... [with_file1] + ... <= debug + ... file1 = ${:path}/file1 + ... color = red + ... + ... [with_file2] + ... <= debug + ... file2 = ${:path}/file2 + ... color = blue + ... + ... [myfiles] + ... <= with_file1 + ... with_file2 + ... path = mydata + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Uninstalling data-dir. + Installing myfiles. + color blue + file1 mydata/file1 + file2 mydata/file2 + path mydata + recipe recipes:debug + +In this example, the debug, with_file1 and with_file2 sections act as +macros. In particular, the variable substitutions are performed +relative to the myfiles section. + +Adding and removing options +--------------------------- + +We can append and remove values to an option by using the + and - +operators. + +This is illustrated below; first we define a base configuration. + + >>> write(sample_buildout, 'base.cfg', + ... """ + ... [buildout] + ... parts = part1 part2 part3 + ... + ... [part1] + ... recipe = + ... option = a1 a2 + ... + ... [part2] + ... recipe = + ... option = b1 b2 b3 b4 + ... + ... [part3] + ... recipe = + ... option = c1 c2 + ... + ... """) + +Extending this configuration, we can "adjust" the values set in the +base configuration file. + + >>> write(sample_buildout, 'extension1.cfg', + ... """ + ... [buildout] + ... extends = base.cfg + ... + ... # appending values + ... [part1] + ... option += a3 a4 + ... + ... # removing values + ... [part2] + ... option -= b1 b2 + ... + ... # alt. spelling + ... [part3] + ... option+=c3 c4 c5 + ... + ... # normal assignment + ... [part4] + ... option = h1 h2 + ... + ... """) + +An additional extension. + + >>> write(sample_buildout, 'extension2.cfg', + ... """ + ... [buildout] + ... extends = extension1.cfg + ... + ... # appending values + ... [part1] + ... option += a5 + ... + ... # removing values + ... [part2] + ... option -= b1 b2 b3 + ... + ... """) + +To verify that the options are adjusted correctly, we'll set up an +extension that prints out the options. + + >>> mkdir(sample_buildout, 'demo') + >>> write(sample_buildout, 'demo', 'demo.py', + ... """ + ... def ext(buildout): + ... print [part['option'] for name, part in buildout.items() \ + ... if name.startswith('part')] + ... """) + + >>> write(sample_buildout, 'demo', 'setup.py', + ... """ + ... from setuptools import setup + ... + ... setup( + ... name="demo", + ... entry_points={'zc.buildout.extension': ['ext = demo:ext']}, + ... ) + ... """) + +Set up a buildout configuration for this extension. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = demo + ... parts = + ... """) + + >>> os.chdir(sample_buildout) + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + Develop: '/sample-buildout/demo' + Uninstalling myfiles. + Getting distribution for 'recipes'. + zip_safe flag not set; analyzing archive contents... + Got recipes 0.0.0. + warning: install_lib: 'build/lib' does not exist -- no Python modules to install + +Verify option values. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = demo + ... extensions = demo + ... extends = extension2.cfg + ... """) + + >>> print system(os.path.join('bin', 'buildout')), + ['a1 a2/na3 a4/na5', 'b1 b2 b3 b4', 'c1 c2/nc3 c4 c5', 'h1 h2'] + Develop: '/sample-buildout/demo' + +Annotated sections output shows which files are responsible for which +operations. + + >>> print system(os.path.join('bin', 'buildout') + ' annotate'), + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + <BLANKLINE> + Annotated sections + ================== + ... + <BLANKLINE> + [part1] + option= a1 a2 + a3 a4 + a5 + /sample-buildout/base.cfg + += /sample-buildout/extension1.cfg + += /sample-buildout/extension2.cfg + recipe= + /sample-buildout/base.cfg + <BLANKLINE> + [part2] + option= b1 b2 b3 b4 + /sample-buildout/base.cfg + -= /sample-buildout/extension1.cfg + -= /sample-buildout/extension2.cfg + recipe= + /sample-buildout/base.cfg + <BLANKLINE> + [part3] + option= c1 c2 + c3 c4 c5 + /sample-buildout/base.cfg + += /sample-buildout/extension1.cfg + recipe= + /sample-buildout/base.cfg + <BLANKLINE> + [part4] + option= h1 h2 + /sample-buildout/extension1.cfg + +Cleanup. + + >>> os.remove(os.path.join(sample_buildout, 'base.cfg')) + >>> os.remove(os.path.join(sample_buildout, 'extension1.cfg')) + >>> os.remove(os.path.join(sample_buildout, 'extension2.cfg')) + +Multiple configuration files +---------------------------- + +A configuration file can "extend" another configuration file. +Options are read from the other configuration file if they aren't +already defined by your configuration file. + +The configuration files your file extends can extend +other configuration files. The same file may be +used more than once although, of course, cycles aren't allowed. + +To see how this works, we use an example: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... extends = base.cfg + ... + ... [debug] + ... op = buildout + ... """) + + >>> write(sample_buildout, 'base.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... + ... [debug] + ... recipe = recipes:debug + ... op = base + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing debug. + op buildout + recipe recipes:debug + +The example is pretty trivial, but the pattern it illustrates is +pretty common. In a more practical example, the base buildout might +represent a product and the extending buildout might be a +customization. + +Here is a more elaborate example. + + >>> other = tmpdir('other') + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... extends = b1.cfg b2.cfg %(b3)s + ... + ... [debug] + ... op = buildout + ... """ % dict(b3=os.path.join(other, 'b3.cfg'))) + + >>> write(sample_buildout, 'b1.cfg', + ... """ + ... [buildout] + ... extends = base.cfg + ... + ... [debug] + ... op1 = b1 1 + ... op2 = b1 2 + ... """) + + >>> write(sample_buildout, 'b2.cfg', + ... """ + ... [buildout] + ... extends = base.cfg + ... + ... [debug] + ... op2 = b2 2 + ... op3 = b2 3 + ... """) + + >>> write(other, 'b3.cfg', + ... """ + ... [buildout] + ... extends = b3base.cfg + ... + ... [debug] + ... op4 = b3 4 + ... """) + + >>> write(other, 'b3base.cfg', + ... """ + ... [debug] + ... op5 = b3base 5 + ... """) + + >>> write(sample_buildout, 'base.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... + ... [debug] + ... recipe = recipes:debug + ... name = base + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing debug. + name base + op buildout + op1 b1 1 + op2 b2 2 + op3 b2 3 + op4 b3 4 + op5 b3base 5 + recipe recipes:debug + +There are several things to note about this example: + +- We can name multiple files in an extends option. + +- We can reference files recursively. + +- Relative file names in extended options are interpreted relative to + the directory containing the referencing configuration file. + +Loading Configuration from URLs +------------------------------- + +Configuration files can be loaded from URLs. To see how this works, +we'll set up a web server with some configuration files. + + >>> server_data = tmpdir('server_data') + + >>> write(server_data, "r1.cfg", + ... """ + ... [debug] + ... op1 = r1 1 + ... op2 = r1 2 + ... """) + + >>> write(server_data, "r2.cfg", + ... """ + ... [buildout] + ... extends = r1.cfg + ... + ... [debug] + ... op2 = r2 2 + ... op3 = r2 3 + ... """) + + >>> server_url = start_server(server_data) + + >>> write('client.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... extends = %(url)s/r2.cfg + ... + ... [debug] + ... recipe = recipes:debug + ... name = base + ... """ % dict(url=server_url)) + + + >>> print system(buildout+ ' -c client.cfg'), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing debug. + name base + op1 r1 1 + op2 r2 2 + op3 r2 3 + recipe recipes:debug + +Here we specified a URL for the file we extended. The file we +downloaded, itself referred to a file on the server using a relative +URL reference. Relative references are interpreted relative to the +base URL when they appear in configuration files loaded via URL. + +We can also specify a URL as the configuration file to be used by a +buildout. + + >>> os.remove('client.cfg') + >>> write(server_data, 'remote.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... extends = r2.cfg + ... + ... [debug] + ... recipe = recipes:debug + ... name = remote + ... """) + + >>> print system(buildout + ' -c ' + server_url + '/remote.cfg'), + While: + Initializing. + Error: Missing option: buildout:directory + +Normally, the buildout directory defaults to directory +containing a configuration file. This won't work for configuration +files loaded from URLs. In this case, the buildout directory would +normally be defined on the command line: + + >>> print system(buildout + ... + ' -c ' + server_url + '/remote.cfg' + ... + ' buildout:directory=' + sample_buildout + ... ), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing debug. + name remote + op1 r1 1 + op2 r2 2 + op3 r2 3 + recipe recipes:debug + +User defaults +------------- + +If the file $HOME/.buildout/default.cfg, exists, it is read before +reading the configuration file. ($HOME is the value of the HOME +environment variable. The '/' is replaced by the operating system file +delimiter.) + + >>> old_home = os.environ['HOME'] + >>> home = tmpdir('home') + >>> mkdir(home, '.buildout') + >>> write(home, '.buildout', 'default.cfg', + ... """ + ... [debug] + ... op1 = 1 + ... op7 = 7 + ... """) + + >>> os.environ['HOME'] = home + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing debug. + name base + op buildout + op1 b1 1 + op2 b2 2 + op3 b2 3 + op4 b3 4 + op5 b3base 5 + op7 7 + recipe recipes:debug + +A buildout command-line argument, -U, can be used to suppress reading +user defaults: + + >>> print system(buildout + ' -U'), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing debug. + name base + op buildout + op1 b1 1 + op2 b2 2 + op3 b2 3 + op4 b3 4 + op5 b3base 5 + recipe recipes:debug + + >>> os.environ['HOME'] = old_home + +Log level +--------- + +We can control the level of logging by specifying a log level in out +configuration file. For example, so suppress info messages, we can +set the logging level to WARNING + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... log-level = WARNING + ... extends = b1.cfg b2.cfg + ... """) + + >>> print system(buildout), + name base + op1 b1 1 + op2 b2 2 + op3 b2 3 + recipe recipes:debug + +Uninstall recipes +----------------- + +As we've seen, when parts are installed, buildout keeps track of files +and directories that they create. When the parts are uninstalled these +files and directories are deleted. + +Sometimes more clean up is needed. For example, a recipe might add a +system service by calling chkconfig --add during installation. Later +during uninstallation, chkconfig --del will need to be called to +remove the system service. + +In order to deal with these uninstallation issues, you can register +uninstall recipes. Uninstall recipes are registered using the +'zc.buildout.uninstall' entry point. Parts specify uninstall recipes +using the 'uninstall' option. + +In comparison to regular recipes, uninstall recipes are much +simpler. They are simply callable objects that accept the name of the +part to be uninstalled and the part's options dictionary. Uninstall +recipes don't have access to the part itself since it maybe not be +able to be instantiated at uninstallation time. + +Here's a recipe that simulates installation of a system service, along +with an uninstall recipe that simulates removing the service. + + >>> write(sample_buildout, 'recipes', 'service.py', + ... """ + ... class Service: + ... + ... def __init__(self, buildout, name, options): + ... self.buildout = buildout + ... self.name = name + ... self.options = options + ... + ... def install(self): + ... print "chkconfig --add %s" % self.options['script'] + ... return () + ... + ... def update(self): + ... pass + ... + ... + ... def uninstall_service(name, options): + ... print "chkconfig --del %s" % options['script'] + ... """) + +To use these recipes we must register them using entry points. Make +sure to use the same name for the recipe and uninstall recipe. This is +required to let buildout know which uninstall recipe goes with which +recipe. + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... """ + ... from setuptools import setup + ... entry_points = ( + ... ''' + ... [zc.buildout] + ... mkdir = mkdir:Mkdir + ... debug = debug:Debug + ... service = service:Service + ... + ... [zc.buildout.uninstall] + ... service = service:uninstall_service + ... ''') + ... setup(name="recipes", entry_points=entry_points) + ... """) + +Here's how these recipes could be used in a buildout: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = service + ... + ... [service] + ... recipe = recipes:service + ... script = /path/to/script + ... """) + +When the buildout is run the service will be installed + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing service. + chkconfig --add /path/to/script + <BLANKLINE> + +The service has been installed. If the buildout is run again with no +changes, the service shouldn't be changed. + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Updating service. + <BLANKLINE> + +Now we change the service part to trigger uninstallation and +re-installation. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = service + ... + ... [service] + ... recipe = recipes:service + ... script = /path/to/a/different/script + ... """) + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Uninstalling service. + Running uninstall recipe. + chkconfig --del /path/to/script + Installing service. + chkconfig --add /path/to/a/different/script + <BLANKLINE> + +Now we remove the service part, and add another part. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... + ... [debug] + ... recipe = recipes:debug + ... """) + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Uninstalling service. + Running uninstall recipe. + chkconfig --del /path/to/a/different/script + Installing debug. + recipe recipes:debug + <BLANKLINE> + +Uninstall recipes don't have to take care of removing all the files +and directories created by the part. This is still done automatically, +following the execution of the uninstall recipe. An upshot is that an +uninstallation recipe can access files and directories created by a +recipe before they are deleted. + +For example, here's an uninstallation recipe that simulates backing up +a directory before it is deleted. It is designed to work with the +mkdir recipe introduced earlier. + + >>> write(sample_buildout, 'recipes', 'backup.py', + ... """ + ... import os + ... def backup_directory(name, options): + ... path = options['path'] + ... size = len(os.listdir(path)) + ... print "backing up directory %s of size %s" % (path, size) + ... """) + +It must be registered with the zc.buildout.uninstall entry +point. Notice how it is given the name 'mkdir' to associate it with +the mkdir recipe. + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... """ + ... from setuptools import setup + ... entry_points = ( + ... ''' + ... [zc.buildout] + ... mkdir = mkdir:Mkdir + ... debug = debug:Debug + ... service = service:Service + ... + ... [zc.buildout.uninstall] + ... uninstall_service = service:uninstall_service + ... mkdir = backup:backup_directory + ... ''') + ... setup(name="recipes", entry_points=entry_points) + ... """) + +Now we can use it with a mkdir part. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = dir debug + ... + ... [dir] + ... recipe = recipes:mkdir + ... path = my_directory + ... + ... [debug] + ... recipe = recipes:debug + ... """) + +Run the buildout to install the part. + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing dir. + dir: Creating directory my_directory + Installing debug. + recipe recipes:debug + <BLANKLINE> + +Now we remove the part from the configuration file. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... + ... [debug] + ... recipe = recipes:debug + ... """) + +When the buildout is run the part is removed, and the uninstall recipe +is run before the directory is deleted. + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Uninstalling dir. + Running uninstall recipe. + backing up directory /sample-buildout/my_directory of size 0 + Updating debug. + recipe recipes:debug + <BLANKLINE> + +Now we will return the registration to normal for the benefit of the +rest of the examples. + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... """ + ... from setuptools import setup + ... entry_points = ( + ... ''' + ... [zc.buildout] + ... mkdir = mkdir:Mkdir + ... debug = debug:Debug + ... ''') + ... setup(name="recipes", entry_points=entry_points) + ... """) + + +Command-line usage +------------------ + +A number of arguments can be given on the buildout command line. The +command usage is:: + + buildout [options and assignments] [command [command arguments]] + +The following options are supported: + +-h (or --help) + Print basic usage information. If this option is used, then all + other options are ignored. + +-c filename + The -c option can be used to specify a configuration file, rather than + buildout.cfg in the current directory. + + +-t socket_timeout + + Specify the socket timeout in seconds. + +-v + Increment the verbosity by 10. The verbosity is used to adjust + the logging level. The verbosity is subtracted from the numeric + value of the log-level option specified in the configuration file. + +-q + Decrement the verbosity by 10. + +-U + Don't read user-default configuration. + +-o + Run in off-line mode. This is equivalent to the assignment + buildout:offline=true. + +-O + Run in non-off-line mode. This is equivalent to the assignment + buildout:offline=false. This is the default buildout mode. The + -O option would normally be used to override a true offline + setting in a configuration file. + +-n + Run in newest mode. This is equivalent to the assignment + buildout:newest=true. With this setting, which is the default, + buildout will try to find the newest versions of distributions + available that satisfy its requirements. + +-N + Run in non-newest mode. This is equivalent to the assignment + buildout:newest=false. With this setting, buildout will not seek + new distributions if installed distributions satisfy it's + requirements. + +Assignments are of the form:: + + section_name:option_name=value + +Options and assignments can be given in any order. + +Here's an example: + + >>> write(sample_buildout, 'other.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... installed = .other.cfg + ... log-level = WARNING + ... + ... [debug] + ... name = other + ... recipe = recipes:debug + ... """) + +Note that we used the installed buildout option to specify an +alternate file to store information about installed parts. + + >>> print system(buildout+' -c other.cfg debug:op1=foo -v'), + Develop: '/sample-buildout/recipes' + Installing debug. + name other + op1 foo + recipe recipes:debug + +Here we used the -c option to specify an alternate configuration file, +and the -v option to increase the level of logging from the default, +WARNING. + +Options can also be combined in the usual Unix way, as in: + + >>> print system(buildout+' -vcother.cfg debug:op1=foo'), + Develop: '/sample-buildout/recipes' + Updating debug. + name other + op1 foo + recipe recipes:debug + +Here we combined the -v and -c options with the configuration file +name. Note that the -c option has to be last, because it takes an +argument. + + >>> os.remove(os.path.join(sample_buildout, 'other.cfg')) + >>> os.remove(os.path.join(sample_buildout, '.other.cfg')) + +The most commonly used command is 'install' and it takes a list of +parts to install. if any parts are specified, only those parts are +installed. To illustrate this, we'll update our configuration and run +the buildout in the usual way: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug d1 d2 d3 + ... + ... [d1] + ... recipe = recipes:mkdir + ... path = d1 + ... + ... [d2] + ... recipe = recipes:mkdir + ... path = d2 + ... + ... [d3] + ... recipe = recipes:mkdir + ... path = d3 + ... + ... [debug] + ... recipe = recipes:debug + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling debug. + Installing debug. + recipe recipes:debug + Installing d1. + d1: Creating directory d1 + Installing d2. + d2: Creating directory d2 + Installing d3. + d3: Creating directory d3 + + >>> ls(sample_buildout) + - .installed.cfg + - b1.cfg + - b2.cfg + - base.cfg + d bin + - buildout.cfg + d d1 + d d2 + d d3 + d demo + d develop-eggs + d eggs + d parts + d recipes + + >>> cat(sample_buildout, '.installed.cfg') + ... # doctest: +NORMALIZE_WHITESPACE + [buildout] + installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link + parts = debug d1 d2 d3 + <BLANKLINE> + [debug] + __buildout_installed__ = + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + recipe = recipes:debug + <BLANKLINE> + [d1] + __buildout_installed__ = /sample-buildout/d1 + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/d1 + recipe = recipes:mkdir + <BLANKLINE> + [d2] + __buildout_installed__ = /sample-buildout/d2 + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/d2 + recipe = recipes:mkdir + <BLANKLINE> + [d3] + __buildout_installed__ = /sample-buildout/d3 + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/d3 + recipe = recipes:mkdir + +Now we'll update our configuration file: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug d2 d3 d4 + ... + ... [d2] + ... recipe = recipes:mkdir + ... path = data2 + ... + ... [d3] + ... recipe = recipes:mkdir + ... path = data3 + ... + ... [d4] + ... recipe = recipes:mkdir + ... path = ${d2:path}-extra + ... + ... [debug] + ... recipe = recipes:debug + ... x = 1 + ... """) + +and run the buildout specifying just d3 and d4: + + >>> print system(buildout+' install d3 d4'), + Develop: '/sample-buildout/recipes' + Uninstalling d3. + Installing d3. + d3: Creating directory data3 + Installing d4. + d4: Creating directory data2-extra + + >>> ls(sample_buildout) + - .installed.cfg + - b1.cfg + - b2.cfg + - base.cfg + d bin + - buildout.cfg + d d1 + d d2 + d data2-extra + d data3 + d demo + d develop-eggs + d eggs + d parts + d recipes + +Only the d3 and d4 recipes ran. d3 was removed and data3 and data2-extra +were created. + +The .installed.cfg is only updated for the recipes that ran: + + >>> cat(sample_buildout, '.installed.cfg') + ... # doctest: +NORMALIZE_WHITESPACE + [buildout] + installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link + parts = debug d1 d2 d3 d4 + <BLANKLINE> + [debug] + __buildout_installed__ = + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + recipe = recipes:debug + <BLANKLINE> + [d1] + __buildout_installed__ = /sample-buildout/d1 + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/d1 + recipe = recipes:mkdir + <BLANKLINE> + [d2] + __buildout_installed__ = /sample-buildout/d2 + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/d2 + recipe = recipes:mkdir + <BLANKLINE> + [d3] + __buildout_installed__ = /sample-buildout/data3 + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/data3 + recipe = recipes:mkdir + <BLANKLINE> + [d4] + __buildout_installed__ = /sample-buildout/data2-extra + __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== + path = /sample-buildout/data2-extra + recipe = recipes:mkdir + +Note that the installed data for debug, d1, and d2 haven't changed, +because we didn't install those parts and that the d1 and d2 +directories are still there. + +Now, if we run the buildout without the install command: + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling d2. + Uninstalling d1. + Uninstalling debug. + Installing debug. + recipe recipes:debug + x 1 + Installing d2. + d2: Creating directory data2 + Updating d3. + Updating d4. + +We see the output of the debug recipe and that data2 was created. We +also see that d1 and d2 have gone away: + + >>> ls(sample_buildout) + - .installed.cfg + - b1.cfg + - b2.cfg + - base.cfg + d bin + - buildout.cfg + d data2 + d data2-extra + d data3 + d demo + d develop-eggs + d eggs + d parts + d recipes + +Alternate directory and file locations +-------------------------------------- + +The buildout normally puts the bin, eggs, and parts directories in the +directory in the directory containing the configuration file. You can +provide alternate locations, and even names for these directories. + + >>> alt = tmpdir('sample-alt') + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = + ... develop-eggs-directory = %(developbasket)s + ... eggs-directory = %(basket)s + ... bin-directory = %(scripts)s + ... parts-directory = %(work)s + ... """ % dict( + ... developbasket = os.path.join(alt, 'developbasket'), + ... basket = os.path.join(alt, 'basket'), + ... scripts = os.path.join(alt, 'scripts'), + ... work = os.path.join(alt, 'work'), + ... )) + + >>> print system(buildout), + Creating directory '/sample-alt/scripts'. + Creating directory '/sample-alt/work'. + Creating directory '/sample-alt/basket'. + Creating directory '/sample-alt/developbasket'. + Develop: '/sample-buildout/recipes' + Uninstalling d4. + Uninstalling d3. + Uninstalling d2. + Uninstalling debug. + + >>> ls(alt) + d basket + d developbasket + d scripts + d work + + >>> ls(alt, 'developbasket') + - recipes.egg-link + +You can also specify an alternate buildout directory: + + >>> rmdir(alt) + >>> alt = tmpdir('sample-alt') + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... directory = %(alt)s + ... develop = %(recipes)s + ... parts = + ... """ % dict( + ... alt=alt, + ... recipes=os.path.join(sample_buildout, 'recipes'), + ... )) + + >>> print system(buildout), + Creating directory '/sample-alt/bin'. + Creating directory '/sample-alt/parts'. + Creating directory '/sample-alt/eggs'. + Creating directory '/sample-alt/develop-eggs'. + Develop: '/sample-buildout/recipes' + + >>> ls(alt) + - .installed.cfg + d bin + d develop-eggs + d eggs + d parts + + >>> ls(alt, 'develop-eggs') + - recipes.egg-link + +Logging control +--------------- + +Three buildout options are used to control logging: + +log-level + specifies the log level + +verbosity + adjusts the log level + +log-format + allows an alternate logging for mat to be specified + +We've already seen the log level and verbosity. Let's look at an example +of changing the format: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = + ... log-level = 25 + ... verbosity = 5 + ... log-format = %(levelname)s %(message)s + ... """) + +Here, we've changed the format to include the log-level name, rather +than the logger name. + +We've also illustrated, with a contrived example, that the log level +can be a numeric value and that the verbosity can be specified in the +configuration file. Because the verbosity is subtracted from the log +level, we get a final log level of 20, which is the INFO level. + + >>> print system(buildout), + INFO Develop: '/sample-buildout/recipes' + +Predefined buildout options +--------------------------- + +Buildouts have a number of predefined options that recipes can use +and that users can override in their configuration files. To see +these, we'll run a minimal buildout configuration with a debug logging +level. One of the features of debug logging is that the configuration +database is shown. + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... parts = + ... """) + + >>> print system(buildout+' -vv'), # doctest: +NORMALIZE_WHITESPACE + Installing 'zc.buildout', 'setuptools'. + We have a develop egg: zc.buildout X.X. + We have the best distribution that satisfies 'setuptools'. + Picked: setuptools = V.V + <BLANKLINE> + Configuration data: + [buildout] + accept-buildout-test-releases = false + allow-hosts = * + allow-picked-versions = true + allowed-eggs-from-site-packages = * + bin-directory = /sample-buildout/bin + develop-eggs-directory = /sample-buildout/develop-eggs + directory = /sample-buildout + eggs-directory = /sample-buildout/eggs + exec-sitecustomize = true + executable = python + find-links = + include-site-packages = true + install-from-cache = false + installed = /sample-buildout/.installed.cfg + log-format = + log-level = INFO + newest = true + offline = false + parts = + parts-directory = /sample-buildout/parts + prefer-final = false + python = buildout + relative-paths = false + socket-timeout = + unzip = false + use-dependency-links = true + verbosity = 20 + <BLANKLINE> + +All of these options can be overridden by configuration files or by +command-line assignments. We've discussed most of these options +already, but let's review them and touch on some we haven't discussed: + +allowed-eggs-from-site-packages + Sometimes you need or want to control what eggs from site-packages are + used. The allowed-eggs-from-site-packages option allows you to specify a + whitelist of project names that may be included from site-packages. You + can use globs to specify the value. It defaults to a single value of '*', + indicating that any package may come from site-packages. + + Here's a usage example:: + + [buildout] + ... + + allowed-eggs-from-site-packages = + demo + bigdemo + zope.* + + This option interacts with the ``include-site-packages`` option in the + following ways. + + If ``include-site-packages`` is true, then + ``allowed-eggs-from-site-packages`` filters what eggs from site-packages + may be chosen. Therefore, if ``allowed-eggs-from-site-packages`` is an + empty list, then no eggs from site-packages are chosen, but site-packages + will still be included at the end of path lists. + + If ``include-site-packages`` is false, the value of + ``allowed-eggs-from-site-packages`` is irrelevant. + + See the ``include-site-packages`` description for more information. + +bin-directory + The directory path where scripts are written. This can be a + relative path, which is interpreted relative to the directory + option. + +develop-eggs-directory + The directory path where development egg links are created for software + being created in the local project. This can be a relative path, + which is interpreted relative to the directory option. + +directory + The buildout directory. This is the base for other buildout file + and directory locations, when relative locations are used. + +eggs-directory + The directory path where downloaded eggs are put. It is common to share + this directory across buildouts. Eggs in this directory should + *never* be modified. This can be a relative path, which is + interpreted relative to the directory option. + +exec-sitecustomize + Normally the Python's real sitecustomize module is processed. + If you do not want it to be processed in order to increase the + repeatability of your buildout, set this value to 'false'. This will + be honored irrespective of the setting for include-site-packages. + This option will be honored by some recipes and not others. + z3c.recipe.scripts honors this and zc.recipe.egg does not, for + instance. + +executable + The Python executable used to run the buildout. See the python + option below. + +include-site-packages + You can choose not to have the site-packages of the underlying Python + available to your script or interpreter, in addition to the packages + from your eggs. This can increase repeatability for your buildout. + This option will be better used by some recipes than others. + z3c.recipe.scripts honors this fully and zc.recipe.egg only + partially, for instance. + +installed + The file path where information about the results of the previous + buildout run is written. This can be a relative path, which is + interpreted relative to the directory option. This file provides + an inventory of installed parts with information needed to decide + which if any parts need to be uninstalled. + +log-format + The format used for logging messages. + +log-level + The log level before verbosity adjustment + +parts + A white space separated list of parts to be installed. + +parts-directory + A working directory that parts can used to store data. + +python + The name of a section containing information about the default + Python interpreter. Recipes that need a installation + typically have options to tell them which Python installation to + use. By convention, if a section-specific option isn't used, the + option is looked for in the buildout section. The option must + point to a section with an executable option giving the path to a + Python executable. By default, the buildout section defines the + default Python as the Python used to run the buildout. + +relative-paths + The paths generated by zc.buildout are absolute by default, and this + option is ``false``. However, if you set this value to be ``true``, + bin/buildout will be generated with code that makes the paths relative. + Some recipes, such as zc.recipe.egg and z3c.recipe.scripts, honor this + value as well. + +unzip + By default, zc.buildout doesn't unzip zip-safe eggs ("unzip = false"). + This follows the policy followed by setuptools itself. Experience shows + this policy to to be inconvenient. Zipped eggs make debugging more + difficult and often import more slowly. You can include an unzip option in + the buildout section to change the default unzipping policy ("unzip = + true"). + +use-dependency-links + By default buildout will obey the setuptools dependency_links metadata + when it looks for dependencies. This behavior can be controlled with + the use-dependency-links buildout option:: + + [buildout] + ... + use-dependency-links = false + + The option defaults to true. If you set it to false, then dependency + links are only looked for in the locations specified by find-links. + +verbosity + A log-level adjustment. Typically, this is set via the -q and -v + command-line options. + + +Creating new buildouts and bootstrapping +---------------------------------------- + +If zc.buildout is installed, you can use it to create a new buildout +with it's own local copies of zc.buildout and setuptools and with +local buildout scripts. + + >>> sample_bootstrapped = tmpdir('sample-bootstrapped') + + >>> print system(buildout + ... +' -c'+os.path.join(sample_bootstrapped, 'setup.cfg') + ... +' init'), + Creating '/sample-bootstrapped/setup.cfg'. + Creating directory '/sample-bootstrapped/bin'. + Creating directory '/sample-bootstrapped/parts'. + Creating directory '/sample-bootstrapped/eggs'. + Creating directory '/sample-bootstrapped/develop-eggs'. + Generated script '/sample-bootstrapped/bin/buildout'. + +Note that a basic setup.cfg was created for us. + + >>> ls(sample_bootstrapped) + d bin + d develop-eggs + d eggs + d parts + - setup.cfg + + >>> ls(sample_bootstrapped, 'bin') + - buildout + + >>> _ = (ls(sample_bootstrapped, 'eggs'), + ... ls(sample_bootstrapped, 'develop-eggs')) + - setuptools-0.6-py2.3.egg + - zc.buildout-1.0-py2.3.egg + +(We list both the eggs and develop-eggs directories because the +buildout or setuptools egg could be installed in the develop-eggs +directory if the original buildout had develop eggs for either +buildout or setuptools.) + +If relative-paths is ``true``, the buildout script uses relative paths. + + >>> write(sample_bootstrapped, 'setup.cfg', + ... ''' + ... [buildout] + ... relative-paths = true + ... parts = + ... ''') + + >>> print system(buildout + ... +' -c'+os.path.join(sample_bootstrapped, 'setup.cfg') + ... +' bootstrap'), + Generated script '/sample-bootstrapped/bin/buildout'. + + >>> buildout_script = join(sample_bootstrapped, 'bin', 'buildout') + >>> import sys + >>> if sys.platform.startswith('win'): + ... buildout_script += '-script.py' + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #!... -S + <BLANKLINE> + import os + <BLANKLINE> + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + <BLANKLINE> + import sys + sys.path[0:0] = [ + join(base, 'parts/buildout'), + ] + <BLANKLINE> + <BLANKLINE> + import os + path = sys.path[0] + if os.environ.get('PYTHONPATH'): + path = os.pathsep.join([path, os.environ['PYTHONPATH']]) + os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + os.environ['PYTHONPATH'] = path + import site # imports custom buildout-generated site.py + <BLANKLINE> + import zc.buildout.buildout + <BLANKLINE> + if __name__ == '__main__': + zc.buildout.buildout.main() + <BLANKLINE> + + +Note that, in the above two examples, the buildout script was installed +but not run. To run the buildout, we'd have to run the installed +buildout script. + +If we have an existing buildout that already has a buildout.cfg, we'll +normally use the bootstrap command instead of init. It will complain +if there isn't a configuration file: + + >>> sample_bootstrapped2 = tmpdir('sample-bootstrapped2') + + >>> print system(buildout + ... +' -c'+os.path.join(sample_bootstrapped2, 'setup.cfg') + ... +' bootstrap'), + While: + Initializing. + Error: Couldn't open /sample-bootstrapped2/setup.cfg + + >>> write(sample_bootstrapped2, 'setup.cfg', + ... """ + ... [buildout] + ... parts = + ... """) + + >>> print system(buildout + ... +' -c'+os.path.join(sample_bootstrapped2, 'setup.cfg') + ... +' bootstrap'), + Creating directory '/sample-bootstrapped2/bin'. + Creating directory '/sample-bootstrapped2/parts'. + Creating directory '/sample-bootstrapped2/eggs'. + Creating directory '/sample-bootstrapped2/develop-eggs'. + Generated script '/sample-bootstrapped2/bin/buildout'. + + +Newest and Offline Modes +------------------------ + +By default buildout and recipes will try to find the newest versions +of distributions needed to satisfy requirements. This can be very +time consuming, especially when incrementally working on setting up a +buildout or working on a recipe. The buildout newest option can be +used to to suppress this. If the newest option is set to false, then +new distributions won't be sought if an installed distribution meets +requirements. The newest option can be set to false using the -N +command-line option. + +The offline option goes a bit further. If the buildout offline option +is given a value of "true", the buildout and recipes that are aware of +the option will avoid doing network access. This is handy when +running the buildout when not connected to the internet. It also +makes buildouts run much faster. This option is typically set using +the buildout -o option. + +Preferring Final Releases +------------------------- + +Currently, when searching for new releases of your project's +dependencies, the newest available release is used. This isn't usually +ideal, as you may get a development release or alpha releases not ready +to be widely used. You can request that final releases be preferred +using the ``prefer-final`` option in the buildout section:: + + [buildout] + ... + prefer-final = true + +When the ``prefer-final`` option is set to true, then when searching for +new releases, final releases are preferred. If there are final +releases that satisfy distribution requirements, then those releases +are used even if newer non-final releases are available. + +In buildout version 2, all final releases will be preferred by +default--that is ``prefer-final`` will also default to 'true'. You will +then need to use a 'false' value for ``prefer-final`` to get the newest +releases. + +A separate option controls the behavior of the build system itself. +When buildout looks for recipes, extensions, and for updates to itself, +it does prefer final releases by default, as of the 1.5.0 release. The +``accept-buildout-test-releases`` option will let you override this behavior. +However, it is typically changed by the --accept-buildout-test-releases +option to the bootstrap script, since bootstrapping is the first step to +selecting a buildout. + +Finding distributions +--------------------- + +By default, buildout searches the Python Package Index when looking +for distributions. You can, instead, specify your own index to search +using the `index` option:: + + [buildout] + ... + index = http://index.example.com/ + +This index, or the default of http://pypi.python.org/simple/ if no +index is specified, will always be searched for distributions unless +running buildout with options that prevent searching for +distributions. The latest version of the distribution that meets the +requirements of the buildout will always be used. + +You can also specify more locations to search for distributions using +the `find-links` option. All locations specified will be searched for +distributions along with the package index as described before. + +Locations can be urls:: + + [buildout] + ... + find-links = http://download.zope.org/distribution/ + +They can also be directories on disk:: + + [buildout] + ... + find-links = /some/path + +Finally, they can also be direct paths to distributions:: + + [buildout] + ... + find-links = /some/path/someegg-1.0.0-py2.3.egg + +Any number of locations can be specified in the `find-links` option:: + + [buildout] + ... + find-links = + http://download.zope.org/distribution/ + /some/otherpath + /some/path/someegg-1.0.0-py2.3.egg + +Dependency links +---------------- + +By default buildout will obey the setuptools dependency_links metadata +when it looks for dependencies. This behavior can be controlled with +the use-dependency-links buildout option:: + + [buildout] + ... + use-dependency-links = false + +The option defaults to true. If you set it to false, then dependency +links are only looked for in the locations specified by find-links. + +Controlling the installation database +------------------------------------- + +The buildout installed option is used to specify the file used to save +information on installed parts. This option is initialized to +".installed.cfg", but it can be overridden in the configuration file +or on the command line: + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = debug + ... + ... [debug] + ... recipe = recipes:debug + ... """) + + >>> print system(buildout+' buildout:installed=inst.cfg'), + Develop: '/sample-buildout/recipes' + Installing debug. + recipe recipes:debug + + >>> ls(sample_buildout) + - b1.cfg + - b2.cfg + - base.cfg + d bin + - buildout.cfg + d demo + d develop-eggs + d eggs + - inst.cfg + d parts + d recipes + +The installation database can be disabled by supplying an empty +buildout installed option: + + >>> os.remove('inst.cfg') + >>> print system(buildout+' buildout:installed='), + Develop: '/sample-buildout/recipes' + Installing debug. + recipe recipes:debug + + >>> ls(sample_buildout) + - b1.cfg + - b2.cfg + - base.cfg + d bin + - buildout.cfg + d demo + d develop-eggs + d eggs + d parts + d recipes + + +Note that there will be no installation database if there are no parts: + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... parts = + ... """) + + >>> print system(buildout+' buildout:installed=inst.cfg'), + + >>> ls(sample_buildout) + - b1.cfg + - b2.cfg + - base.cfg + d bin + - buildout.cfg + d demo + d develop-eggs + d eggs + d parts + d recipes + +Extensions +---------- + +A feature allows code to be loaded and run after +configuration files have been read but before the buildout has begun +any processing. The intent is to allow special plugins such as +urllib2 request handlers to be loaded. + +To load an extension, we use the extensions option and list one or +more distribution requirements, on separate lines. The distributions +named will be loaded and any ``zc.buildout.extension`` entry points found +will be called with the buildout as an argument. When buildout +finishes processing, any ``zc.buildout.unloadextension`` entry points +found will be called with the buildout as an argument. + +Let's create a sample extension in our sample buildout created in the +previous section: + + >>> mkdir(sample_bootstrapped, 'demo') + + >>> write(sample_bootstrapped, 'demo', 'demo.py', + ... """ + ... def ext(buildout): + ... print 'ext', list(buildout) + ... def unload(buildout): + ... print 'unload', list(buildout) + ... """) + + >>> write(sample_bootstrapped, 'demo', 'setup.py', + ... """ + ... from setuptools import setup + ... + ... setup( + ... name = "demo", + ... entry_points = { + ... 'zc.buildout.extension': ['ext = demo:ext'], + ... 'zc.buildout.unloadextension': ['ext = demo:unload'], + ... }, + ... ) + ... """) + +Our extension just prints out the word 'demo', and lists the sections +found in the buildout passed to it. + +We'll update our buildout.cfg to list the demo directory as a develop +egg to be built: + + >>> write(sample_bootstrapped, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = demo + ... parts = + ... """) + + >>> os.chdir(sample_bootstrapped) + >>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')), + Develop: '/sample-bootstrapped/demo' + +Now we can add the extensions option. We were a bit tricky and ran +the buildout once with the demo develop egg defined but without the +extension option. This is because extensions are loaded before the +buildout creates develop eggs. We needed to use a separate buildout +run to create the develop egg. Normally, when eggs are loaded from +the network, we wouldn't need to do anything special. + + >>> write(sample_bootstrapped, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = demo + ... extensions = demo + ... parts = + ... """) + +We see that our extension is loaded and executed: + + >>> print system(os.path.join(sample_bootstrapped, 'bin', 'buildout')), + ext ['buildout'] + Develop: '/sample-bootstrapped/demo' + unload ['buildout'] + +Allow hosts +----------- + +On some environments the links visited by `zc.buildout` can be forbidden +by paranoiac firewalls. These URL might be on the chain of links +visited by `zc.buildout` wheter they are defined in the `find-links` option, +wheter they are defined by various eggs in their `url`, `download_url`, +`dependency_links` metadata. + +It is even harder to track that package_index works like a spider and +might visit links and go to other location. + +The `allow-hosts` option provides a way to prevent this, and +works exactly like the one provided in `easy_install`. + +You can provide a list of allowed host, together with wildcards:: + + [buildout] + ... + + allow-hosts = + *.python.org + example.com + +All urls that does not match these hosts will not be visited. + +.. [#future_recipe_methods] In the future, additional methods may be + added. Older recipes with fewer methods will still be + supported. + +.. [#packaging_info] If we wanted to create a distribution from this + package, we would need specify much more information. See the + `setuptools documentation + <http://peak.telecommunity.com/DevCenter/setuptools>`_. diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/debugging.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/debugging.txt new file mode 100644 index 0000000..1a421a0 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/debugging.txt @@ -0,0 +1,97 @@ +Debugging buildouts +=================== + +Buildouts can be pretty complex. When things go wrong, it isn't +always obvious why. Errors can occur due to problems in user input or +due to bugs in zc.buildout or recipes. When an error occurs, Python's +post-mortem debugger can be used to inspect the state of the buildout +or recipe code where the error occurred. To enable this, use the -D +option to the buildout. Let's create a recipe that has a bug: + + >>> mkdir(sample_buildout, 'recipes') + + >>> write(sample_buildout, 'recipes', 'mkdir.py', + ... """ + ... import os, zc.buildout + ... + ... class Mkdir: + ... + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... options['path'] = os.path.join( + ... buildout['buildout']['directory'], + ... options['path'], + ... ) + ... + ... def install(self): + ... directory = self.options['directory'] + ... os.mkdir(directory) + ... return directory + ... + ... def update(self): + ... pass + ... """) + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... """ + ... from setuptools import setup + ... + ... setup(name = "recipes", + ... entry_points = {'zc.buildout': ['mkdir = mkdir:Mkdir']}, + ... ) + ... """) + +And create a buildout that uses it: + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... path = mystuff + ... """) + +If we run the buildout, we'll get an error: + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing data-dir. + While: + Installing data-dir. + Error: Missing option: data-dir:directory + + +If we want to debug the error, we can add the -D option. Here's we'll +supply some input: + + >>> print system(buildout+" -D", """\ + ... up + ... p self.options.keys() + ... q + ... """), + Develop: '/sample-buildout/recipes' + Installing data-dir. + > /zc/buildout/buildout.py(925)__getitem__() + -> raise MissingOption("Missing option: %s:%s" % (self.name, key)) + (Pdb) > /sample-buildout/recipes/mkdir.py(14)install() + -> directory = self.options['directory'] + (Pdb) ['path', 'recipe'] + (Pdb) While: + Installing data-dir. + Traceback (most recent call last): + File "/zc/buildout/buildout.py", line 1352, in main + getattr(buildout, command)(args) + File "/zc/buildout/buildout.py", line 383, in install + installed_files = self[part]._call(recipe.install) + File "/zc/buildout/buildout.py", line 961, in _call + return f() + File "/sample-buildout/recipes/mkdir.py", line 14, in install + directory = self.options['directory'] + File "/zc/buildout/buildout.py", line 925, in __getitem__ + raise MissingOption("Missing option: %s:%s" % (self.name, key)) + MissingOption: Missing option: data-dir:directory + <BLANKLINE> + Starting pdb: diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/dependencylinks.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/dependencylinks.txt new file mode 100644 index 0000000..4414feb --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/dependencylinks.txt @@ -0,0 +1,199 @@ +Dependency links +---------------- + +By default buildout will obey the setuptools dependency_links metadata +when it looks for dependencies. This behavior can be controlled with +the use-dependency-links buildout option. + + [buildout] + ... + use-dependency-links = false + +The option defaults to true. If you set it to false, then dependency +links are only looked for in the locations specified by find-links. + +Let's see this feature in action. To begin, let's create a new egg +repository. This repository uses the same sample eggs as the normal +testing repository. + + >>> link_server2 = start_server(sample_eggs) + +Turn on logging on this server so that we can see when eggs are pulled +from it. + + >>> get(link_server2 + 'enable_server_logging') + GET 200 /enable_server_logging + '' + +Let's create a develop egg in our buildout that specifies +dependency_links which point to the new server. + + >>> mkdir(sample_buildout, 'depdemo') + >>> write(sample_buildout, 'depdemo', 'dependencydemo.py', + ... 'import eggrecipedemoneeded') + >>> write(sample_buildout, 'depdemo', 'setup.py', + ... '''from setuptools import setup; setup( + ... name='depdemo', py_modules=['dependencydemo'], + ... install_requires = 'demoneeded', + ... dependency_links = ['%s'], + ... zip_safe=True, version='1') + ... ''' % link_server2) + +Now let's configure the buildout to use the develop egg. + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = depdemo + ... parts = eggs + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = depdemo + ... ''') + +Now we can run the buildout. + + >>> print system(buildout) + GET 200 / + GET 200 /demoneeded-1.2c1.zip + Develop: '/sample-buildout/depdemo' + Installing eggs. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + <BLANKLINE> + +Notice that the egg was retrieved from the logging server. + +Now let's change the egg so that it doesn't specify dependency links. + + >>> write(sample_buildout, 'depdemo', 'setup.py', + ... '''from setuptools import setup; setup( + ... name='depdemo', py_modules=['dependencydemo'], + ... install_requires = 'demoneeded', + ... zip_safe=True, version='1') + ... ''') + +Now we'll remove the existing dependency egg, and rerunning the +buildout to see where the egg comes from this time. + + >>> from glob import glob + >>> from os.path import join + >>> def remove_demoneeded_egg(): + ... for egg in glob(join(sample_buildout, 'eggs', 'demoneeded*.egg')): + ... remove(sample_buildout, 'eggs', egg) + >>> remove_demoneeded_egg() + >>> print system(buildout) # doctest: +ELLIPSIS + Develop: '/sample-buildout/depdemo' + ... + Getting distribution for 'demoneeded'. + While: + Updating eggs. + Getting distribution for 'demoneeded'. + Error: Couldn't find a distribution for 'demoneeded'. + <BLANKLINE> + +Now it can't find the dependency since neither the buildout +configuration nor setup specifies where to look. + +Let's change things so that the buildout configuration specifies where +to look for eggs. + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = depdemo + ... parts = eggs + ... find-links = %s + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = depdemo + ... ''' % link_server) + + >>> print system(buildout) + Develop: '/sample-buildout/depdemo' + Installing eggs. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + <BLANKLINE> + +This time the dependency egg was found on the server without logging +configured. + +Now let's change things once again so that both buildout and setup +specify different places to look for the dependency egg. + + >>> write(sample_buildout, 'depdemo', 'setup.py', + ... '''from setuptools import setup; setup( + ... name='depdemo', py_modules=['dependencydemo'], + ... install_requires = 'demoneeded', + ... dependency_links = ['%s'], + ... zip_safe=True, version='1') + ... ''' % link_server2) + + >>> remove_demoneeded_egg() + >>> print system(buildout) #doctest: +ELLIPSIS + GET 200 /... + Develop: '/sample-buildout/depdemo' + Updating eggs. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + <BLANKLINE> + +So when both setuptools and buildout specify places to search for +eggs, the dependency_links takes precedence over find-links. + +There is a buildout option that you can specify to change this +behavior. It is the use-dependency-links option. This option defaults +to true. When you specify false for this option, buildout will ignore +dependency_links and only look for eggs using find-links. + +Here is an example of using this option to disable dependency_links. + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = depdemo + ... parts = eggs + ... find-links = %s + ... use-dependency-links = false + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = depdemo + ... ''' % link_server) + + >>> remove_demoneeded_egg() + >>> print system(buildout) + Develop: '/sample-buildout/depdemo' + Updating eggs. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + <BLANKLINE> + +Notice that this time the egg isn't downloaded from the logging server. + +If we set the option to true, things return to the way they were +before. The dependency's are looked for first in the logging server. + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = depdemo + ... parts = eggs + ... find-links = %s + ... use-dependency-links = true + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = depdemo + ... ''' % link_server) + >>> remove_demoneeded_egg() + >>> print system(buildout) #doctest: +ELLIPSIS + GET 200 /... + Develop: '/sample-buildout/depdemo' + Updating eggs. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + <BLANKLINE> diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/distribute.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/distribute.txt new file mode 100644 index 0000000..da6b723 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/distribute.txt @@ -0,0 +1,23 @@ +Distribute Support +================== + +Distribute is a drop-in replacement for Setuptools. + +zc.buildout is now compatible with Distribute 0.6. To use Distribute in your +buildout, you need use the ``--distribute`` option of the ``bootstrap.py`` +script:: + + $ python bootstrap.py --distribute + +This will download and install the latest Distribute 0.6 release in the +``eggs`` directory, and use this version for the scripts that are created +in ``bin``. + +Notice that if you have a shared eggs directory, a buildout that uses +Distribute will not interfer with other buildouts that are based on Setuptools +and that are sharing the same eggs directory. + +Form more information about the Distribute project, see: +http://python-distribute.org + + diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/download.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/download.py new file mode 100644 index 0000000..3d1295b --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/download.py @@ -0,0 +1,251 @@ +############################################################################## +# +# Copyright (c) 2009 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Buildout download infrastructure""" + +try: + from hashlib import md5 +except ImportError: + from md5 import new as md5 +from zc.buildout.easy_install import realpath +import logging +import os +import os.path +import re +import shutil +import tempfile +import urllib +import urlparse +import zc.buildout + + +class URLOpener(urllib.FancyURLopener): + http_error_default = urllib.URLopener.http_error_default + + +class ChecksumError(zc.buildout.UserError): + pass + + +url_opener = URLOpener() + + +class Download(object): + """Configurable download utility. + + Handles the download cache and offline mode. + + Download(options=None, cache=None, namespace=None, hash_name=False) + + options: mapping of buildout options (e.g. a ``buildout`` config section) + cache: path to the download cache (excluding namespaces) + namespace: namespace directory to use inside the cache + hash_name: whether to use a hash of the URL as cache file name + logger: an optional logger to receive download-related log messages + + """ + + def __init__(self, options={}, cache=-1, namespace=None, + offline=-1, fallback=False, hash_name=False, logger=None): + self.directory = options.get('directory', '') + self.cache = cache + if cache == -1: + self.cache = options.get('download-cache') + self.namespace = namespace + self.offline = offline + if offline == -1: + self.offline = (options.get('offline') == 'true' + or options.get('install-from-cache') == 'true') + self.fallback = fallback + self.hash_name = hash_name + self.logger = logger or logging.getLogger('zc.buildout') + + @property + def download_cache(self): + if self.cache is not None: + return realpath(os.path.join(self.directory, self.cache)) + + @property + def cache_dir(self): + if self.download_cache is not None: + return os.path.join(self.download_cache, self.namespace or '') + + def __call__(self, url, md5sum=None, path=None): + """Download a file according to the utility's configuration. + + url: URL to download + md5sum: MD5 checksum to match + path: where to place the downloaded file + + Returns the path to the downloaded file. + + """ + if self.cache: + local_path, is_temp = self.download_cached(url, md5sum) + else: + local_path, is_temp = self.download(url, md5sum, path) + + return locate_at(local_path, path), is_temp + + def download_cached(self, url, md5sum=None): + """Download a file from a URL using the cache. + + This method assumes that the cache has been configured. Optionally, it + raises a ChecksumError if a cached copy of a file has an MD5 mismatch, + but will not remove the copy in that case. + + """ + if not os.path.exists(self.download_cache): + raise zc.buildout.UserError( + 'The directory:\n' + '%r\n' + "to be used as a download cache doesn't exist.\n" + % self.download_cache) + cache_dir = self.cache_dir + if not os.path.exists(cache_dir): + os.mkdir(cache_dir) + cache_key = self.filename(url) + cached_path = os.path.join(cache_dir, cache_key) + + self.logger.debug('Searching cache at %s' % cache_dir) + if os.path.isfile(cached_path): + is_temp = False + if self.fallback: + try: + _, is_temp = self.download(url, md5sum, cached_path) + except ChecksumError: + raise + except Exception: + pass + + if not check_md5sum(cached_path, md5sum): + raise ChecksumError( + 'MD5 checksum mismatch for cached download ' + 'from %r at %r' % (url, cached_path)) + self.logger.debug('Using cache file %s' % cached_path) + else: + self.logger.debug('Cache miss; will cache %s as %s' % + (url, cached_path)) + _, is_temp = self.download(url, md5sum, cached_path) + + return cached_path, is_temp + + def download(self, url, md5sum=None, path=None): + """Download a file from a URL to a given or temporary path. + + An online resource is always downloaded to a temporary file and moved + to the specified path only after the download is complete and the + checksum (if given) matches. If path is None, the temporary file is + returned and the client code is responsible for cleaning it up. + + """ + if re.match(r"^[A-Za-z]:\\", url): + url = 'file:' + url + parsed_url = urlparse.urlparse(url, 'file') + url_scheme, _, url_path = parsed_url[:3] + if url_scheme == 'file': + self.logger.debug('Using local resource %s' % url) + if not check_md5sum(url_path, md5sum): + raise ChecksumError( + 'MD5 checksum mismatch for local resource at %r.' % + url_path) + return locate_at(url_path, path), False + + if self.offline: + raise zc.buildout.UserError( + "Couldn't download %r in offline mode." % url) + + self.logger.info('Downloading %s' % url) + urllib._urlopener = url_opener + handle, tmp_path = tempfile.mkstemp(prefix='buildout-') + try: + try: + tmp_path, headers = urllib.urlretrieve(url, tmp_path) + if not check_md5sum(tmp_path, md5sum): + raise ChecksumError( + 'MD5 checksum mismatch downloading %r' % url) + finally: + os.close(handle) + except: + os.remove(tmp_path) + raise + + if path: + shutil.move(tmp_path, path) + return path, False + else: + return tmp_path, True + + def filename(self, url): + """Determine a file name from a URL according to the configuration. + + """ + if self.hash_name: + return md5(url).hexdigest() + else: + if re.match(r"^[A-Za-z]:\\", url): + url = 'file:' + url + parsed = urlparse.urlparse(url, 'file') + url_path = parsed[2] + + if parsed[0] == 'file': + while True: + url_path, name = os.path.split(url_path) + if name: + return name + if not url_path: + break + else: + for name in reversed(url_path.split('/')): + if name: + return name + + url_host, url_port = parsed[-2:] + return '%s:%s' % (url_host, url_port) + + +def check_md5sum(path, md5sum): + """Tell whether the MD5 checksum of the file at path matches. + + No checksum being given is considered a match. + + """ + if md5sum is None: + return True + + f = open(path, 'rb') + checksum = md5() + try: + chunk = f.read(2**16) + while chunk: + checksum.update(chunk) + chunk = f.read(2**16) + return checksum.hexdigest() == md5sum + finally: + f.close() + + +def remove(path): + if os.path.exists(path): + os.remove(path) + + +def locate_at(source, dest): + if dest is None or realpath(dest) == realpath(source): + return source + + try: + os.link(source, dest) + except (AttributeError, OSError): + shutil.copyfile(source, dest) + return dest diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/download.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/download.txt new file mode 100644 index 0000000..5184800 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/download.txt @@ -0,0 +1,550 @@ +Using the download utility +========================== + +The ``zc.buildout.download`` module provides a download utility that handles +the details of downloading files needed for a buildout run from the internet. +It downloads files to the local file system, using the download cache if +desired and optionally checking the downloaded files' MD5 checksum. + +We setup an HTTP server that provides a file we want to download: + +>>> server_data = tmpdir('sample_files') +>>> write(server_data, 'foo.txt', 'This is a foo text.') +>>> server_url = start_server(server_data) + +We also use a fresh directory for temporary files in order to make sure that +all temporary files have been cleaned up in the end: + +>>> import tempfile +>>> old_tempdir = tempfile.tempdir +>>> tempfile.tempdir = tmpdir('tmp') + + +Downloading without using the cache +----------------------------------- + +If no download cache should be used, the download utility is instantiated +without any arguments: + +>>> from zc.buildout.download import Download +>>> download = Download() +>>> print download.cache_dir +None + +Downloading a file is achieved by calling the utility with the URL as an +argument. A tuple is returned that consists of the path to the downloaded copy +of the file and a boolean value indicating whether this is a temporary file +meant to be cleaned up during the same buildout run: + +>>> path, is_temp = download(server_url+'foo.txt') +>>> print path +/.../buildout-... +>>> cat(path) +This is a foo text. + +As we aren't using the download cache and haven't specified a target path +either, the download has ended up in a temporary file: + +>>> is_temp +True + +>>> import tempfile +>>> path.startswith(tempfile.gettempdir()) +True + +We are responsible for cleaning up temporary files behind us: + +>>> remove(path) + +When trying to access a file that doesn't exist, we'll get an exception: + +>>> try: download(server_url+'not-there') # doctest: +ELLIPSIS +... except: print 'download error' +... else: print 'woops' +download error + +Downloading a local file doesn't produce a temporary file but simply returns +the local file itself: + +>>> download(join(server_data, 'foo.txt')) +('/sample_files/foo.txt', False) + +We can also have the downloaded file's MD5 sum checked: + +>>> try: from hashlib import md5 +... except ImportError: from md5 import new as md5 + +>>> path, is_temp = download(server_url+'foo.txt', +... md5('This is a foo text.').hexdigest()) +>>> is_temp +True +>>> remove(path) + +>>> download(server_url+'foo.txt', +... md5('The wrong text.').hexdigest()) +Traceback (most recent call last): +ChecksumError: MD5 checksum mismatch downloading 'http://localhost/foo.txt' + +The error message in the event of an MD5 checksum mismatch for a local file +reads somewhat differently: + +>>> download(join(server_data, 'foo.txt'), +... md5('This is a foo text.').hexdigest()) +('/sample_files/foo.txt', False) + +>>> download(join(server_data, 'foo.txt'), +... md5('The wrong text.').hexdigest()) +Traceback (most recent call last): +ChecksumError: MD5 checksum mismatch for local resource at '/sample_files/foo.txt'. + +Finally, we can download the file to a specified place in the file system: + +>>> target_dir = tmpdir('download-target') +>>> path, is_temp = download(server_url+'foo.txt', +... path=join(target_dir, 'downloaded.txt')) +>>> print path +/download-target/downloaded.txt +>>> cat(path) +This is a foo text. +>>> is_temp +False + +Trying to download a file in offline mode will result in an error: + +>>> download = Download(cache=None, offline=True) +>>> download(server_url+'foo.txt') +Traceback (most recent call last): +UserError: Couldn't download 'http://localhost/foo.txt' in offline mode. + +As an exception to this rule, file system paths and URLs in the ``file`` +scheme will still work: + +>>> cat(download(join(server_data, 'foo.txt'))[0]) +This is a foo text. +>>> cat(download('file:' + join(server_data, 'foo.txt'))[0]) +This is a foo text. + +>>> remove(path) + + +Downloading using the download cache +------------------------------------ + +In order to make use of the download cache, we need to configure the download +utility differently. To do this, we pass a directory path as the ``cache`` +attribute upon instantiation: + +>>> cache = tmpdir('download-cache') +>>> download = Download(cache=cache) +>>> print download.cache_dir +/download-cache/ + +Simple usage +~~~~~~~~~~~~ + +When using the cache, a file will be stored in the cache directory when it is +first downloaded. The file system path returned by the download utility points +to the cached copy: + +>>> ls(cache) +>>> path, is_temp = download(server_url+'foo.txt') +>>> print path +/download-cache/foo.txt +>>> cat(path) +This is a foo text. +>>> is_temp +False + +Whenever the file is downloaded again, the cached copy is used. Let's change +the file on the server to see this: + +>>> write(server_data, 'foo.txt', 'The wrong text.') +>>> path, is_temp = download(server_url+'foo.txt') +>>> print path +/download-cache/foo.txt +>>> cat(path) +This is a foo text. + +If we specify an MD5 checksum for a file that is already in the cache, the +cached copy's checksum will be verified: + +>>> download(server_url+'foo.txt', md5('The wrong text.').hexdigest()) +Traceback (most recent call last): +ChecksumError: MD5 checksum mismatch for cached download + from 'http://localhost/foo.txt' at '/download-cache/foo.txt' + +Trying to access another file at a different URL which has the same base name +will result in the cached copy being used: + +>>> mkdir(server_data, 'other') +>>> write(server_data, 'other', 'foo.txt', 'The wrong text.') +>>> path, is_temp = download(server_url+'other/foo.txt') +>>> print path +/download-cache/foo.txt +>>> cat(path) +This is a foo text. + +Given a target path for the download, the utility will provide a copy of the +file at that location both when first downloading the file and when using a +cached copy: + +>>> remove(cache, 'foo.txt') +>>> ls(cache) +>>> write(server_data, 'foo.txt', 'This is a foo text.') + +>>> path, is_temp = download(server_url+'foo.txt', +... path=join(target_dir, 'downloaded.txt')) +>>> print path +/download-target/downloaded.txt +>>> cat(path) +This is a foo text. +>>> is_temp +False +>>> ls(cache) +- foo.txt + +>>> remove(path) +>>> write(server_data, 'foo.txt', 'The wrong text.') + +>>> path, is_temp = download(server_url+'foo.txt', +... path=join(target_dir, 'downloaded.txt')) +>>> print path +/download-target/downloaded.txt +>>> cat(path) +This is a foo text. +>>> is_temp +False + +In offline mode, downloads from any URL will be successful if the file is +found in the cache: + +>>> download = Download(cache=cache, offline=True) +>>> cat(download(server_url+'foo.txt')[0]) +This is a foo text. + +Local resources will be cached just like any others since download caches are +sometimes used to create source distributions: + +>>> remove(cache, 'foo.txt') +>>> ls(cache) + +>>> write(server_data, 'foo.txt', 'This is a foo text.') +>>> download = Download(cache=cache) + +>>> cat(download('file:' + join(server_data, 'foo.txt'), path=path)[0]) +This is a foo text. +>>> ls(cache) +- foo.txt + +>>> remove(cache, 'foo.txt') + +>>> cat(download(join(server_data, 'foo.txt'), path=path)[0]) +This is a foo text. +>>> ls(cache) +- foo.txt + +>>> remove(cache, 'foo.txt') + +However, resources with checksum mismatches will not be copied to the cache: + +>>> download(server_url+'foo.txt', md5('The wrong text.').hexdigest()) +Traceback (most recent call last): +ChecksumError: MD5 checksum mismatch downloading 'http://localhost/foo.txt' +>>> ls(cache) + +>>> remove(path) + +Finally, let's see what happens if the download cache to be used doesn't exist +as a directory in the file system yet: + +>>> Download(cache=join(cache, 'non-existent'))(server_url+'foo.txt') +Traceback (most recent call last): +UserError: The directory: +'/download-cache/non-existent' +to be used as a download cache doesn't exist. + +Using namespace sub-directories of the download cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is common to store cached copies of downloaded files within sub-directories +of the download cache to keep some degree of order. For example, zc.buildout +stores downloaded distributions in a sub-directory named "dist". Those +sub-directories are also known as namespaces. So far, we haven't specified any +namespaces to use, so the download utility stored files directly inside the +download cache. Let's use a namespace "test" instead: + +>>> download = Download(cache=cache, namespace='test') +>>> print download.cache_dir +/download-cache/test + +The namespace sub-directory hasn't been created yet: + +>>> ls(cache) + +Downloading a file now creates the namespace sub-directory and places a copy +of the file inside it: + +>>> path, is_temp = download(server_url+'foo.txt') +>>> print path +/download-cache/test/foo.txt +>>> ls(cache) +d test +>>> ls(cache, 'test') +- foo.txt +>>> cat(path) +This is a foo text. +>>> is_temp +False + +The next time we want to download that file, the copy from inside the cache +namespace is used. To see this clearly, we put a file with the same name but +different content both on the server and in the cache's root directory: + +>>> write(server_data, 'foo.txt', 'The wrong text.') +>>> write(cache, 'foo.txt', 'The wrong text.') + +>>> path, is_temp = download(server_url+'foo.txt') +>>> print path +/download-cache/test/foo.txt +>>> cat(path) +This is a foo text. + +>>> rmdir(cache, 'test') +>>> remove(cache, 'foo.txt') +>>> write(server_data, 'foo.txt', 'This is a foo text.') + +Using a hash of the URL as the filename in the cache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So far, the base name of the downloaded file read from the URL has been used +for the name of the cached copy of the file. This may not be desirable in some +cases, for example when downloading files from different locations that have +the same base name due to some naming convention, or if the file content +depends on URL parameters. In such cases, an MD5 hash of the complete URL may +be used as the filename in the cache: + +>>> download = Download(cache=cache, hash_name=True) +>>> path, is_temp = download(server_url+'foo.txt') +>>> print path +/download-cache/09f5793fcdc1716727f72d49519c688d +>>> cat(path) +This is a foo text. +>>> ls(cache) +- 09f5793fcdc1716727f72d49519c688d + +The path was printed just to illustrate matters; we cannot know the real +checksum since we don't know which port the server happens to listen at when +the test is run, so we don't actually know the full URL of the file. Let's +check that the checksum actually belongs to the particular URL used: + +>>> path.lower() == join(cache, md5(server_url+'foo.txt').hexdigest()).lower() +True + +The cached copy is used when downloading the file again: + +>>> write(server_data, 'foo.txt', 'The wrong text.') +>>> (path, is_temp) == download(server_url+'foo.txt') +True +>>> cat(path) +This is a foo text. +>>> ls(cache) +- 09f5793fcdc1716727f72d49519c688d + +If we change the URL, even in such a way that it keeps the base name of the +file the same, the file will be downloaded again this time and put in the +cache under a different name: + +>>> path2, is_temp = download(server_url+'other/foo.txt') +>>> print path2 +/download-cache/537b6d73267f8f4447586989af8c470e +>>> path == path2 +False +>>> path2.lower() == join(cache, md5(server_url+'other/foo.txt').hexdigest()).lower() +True +>>> cat(path) +This is a foo text. +>>> cat(path2) +The wrong text. +>>> ls(cache) +- 09f5793fcdc1716727f72d49519c688d +- 537b6d73267f8f4447586989af8c470e + +>>> remove(path) +>>> remove(path2) +>>> write(server_data, 'foo.txt', 'This is a foo text.') + + +Using the cache purely as a fall-back +------------------------------------- + +Sometimes it is desirable to try downloading a file from the net if at all +possible, and use the cache purely as a fall-back option when a server is +down or if we are in offline mode. This mode is only in effect if a download +cache is configured in the first place: + +>>> download = Download(cache=cache, fallback=True) +>>> print download.cache_dir +/download-cache/ + +A downloaded file will be cached: + +>>> ls(cache) +>>> path, is_temp = download(server_url+'foo.txt') +>>> ls(cache) +- foo.txt +>>> cat(cache, 'foo.txt') +This is a foo text. +>>> is_temp +False + +If the file cannot be served, the cached copy will be used: + +>>> remove(server_data, 'foo.txt') +>>> try: Download()(server_url+'foo.txt') # doctest: +ELLIPSIS +... except: print 'download error' +... else: print 'woops' +download error +>>> path, is_temp = download(server_url+'foo.txt') +>>> cat(path) +This is a foo text. +>>> is_temp +False + +Similarly, if the file is served but we're in offline mode, we'll fall back to +using the cache: + +>>> write(server_data, 'foo.txt', 'The wrong text.') +>>> get(server_url+'foo.txt') +'The wrong text.' + +>>> offline_download = Download(cache=cache, offline=True, fallback=True) +>>> path, is_temp = offline_download(server_url+'foo.txt') +>>> print path +/download-cache/foo.txt +>>> cat(path) +This is a foo text. +>>> is_temp +False + +However, when downloading the file normally with the cache being used in +fall-back mode, the file will be downloaded from the net and the cached copy +will be replaced with the new content: + +>>> cat(download(server_url+'foo.txt')[0]) +The wrong text. +>>> cat(cache, 'foo.txt') +The wrong text. + +When trying to download a resource whose checksum does not match, the cached +copy will neither be used nor overwritten: + +>>> write(server_data, 'foo.txt', 'This is a foo text.') +>>> download(server_url+'foo.txt', md5('The wrong text.').hexdigest()) +Traceback (most recent call last): +ChecksumError: MD5 checksum mismatch downloading 'http://localhost/foo.txt' +>>> cat(cache, 'foo.txt') +The wrong text. + + +Configuring the download utility from buildout options +------------------------------------------------------ + +The configuration options explained so far derive from the build logic +implemented by the calling code. Other options configure the download utility +for use in a particular project or buildout run; they are read from the +``buildout`` configuration section. The latter can be passed directly as the +first argument to the download utility's constructor. + +The location of the download cache is specified by the ``download-cache`` +option: + +>>> download = Download({'download-cache': cache}, namespace='cmmi') +>>> print download.cache_dir +/download-cache/cmmi + +If the ``download-cache`` option specifies a relative path, it is understood +relative to the current working directory, or to the buildout directory if +that is given: + +>>> download = Download({'download-cache': 'relative-cache'}) +>>> print download.cache_dir +/sample-buildout/relative-cache/ + +>>> download = Download({'directory': join(sample_buildout, 'root'), +... 'download-cache': 'relative-cache'}) +>>> print download.cache_dir +/sample-buildout/root/relative-cache/ + +Keyword parameters take precedence over the corresponding options: + +>>> download = Download({'download-cache': cache}, cache=None) +>>> print download.cache_dir +None + +Whether to assume offline mode can be inferred from either the ``offline`` or +the ``install-from-cache`` option. As usual with zc.buildout, these options +must assume one of the values 'true' and 'false': + +>>> download = Download({'offline': 'true'}) +>>> download.offline +True + +>>> download = Download({'offline': 'false'}) +>>> download.offline +False + +>>> download = Download({'install-from-cache': 'true'}) +>>> download.offline +True + +>>> download = Download({'install-from-cache': 'false'}) +>>> download.offline +False + +These two options are combined using logical 'or': + +>>> download = Download({'offline': 'true', 'install-from-cache': 'false'}) +>>> download.offline +True + +>>> download = Download({'offline': 'false', 'install-from-cache': 'true'}) +>>> download.offline +True + +The ``offline`` keyword parameter takes precedence over both the ``offline`` +and ``install-from-cache`` options: + +>>> download = Download({'offline': 'true'}, offline=False) +>>> download.offline +False + +>>> download = Download({'install-from-cache': 'false'}, offline=True) +>>> download.offline +True + + +Regressions +----------- + +MD5 checksum calculation needs to be reliable on all supported systems, which +requires text files to be treated as binary to avoid implicit line-ending +conversions: + +>>> text = 'First line of text.\r\nSecond line.\r\n' +>>> f = open(join(server_data, 'foo.txt'), 'wb') +>>> f.write(text) +>>> f.close() +>>> path, is_temp = Download()(server_url+'foo.txt', md5(text).hexdigest()) +>>> remove(path) + + +Clean up +-------- + +We should have cleaned up all temporary files created by downloading things: + +>>> ls(tempfile.tempdir) + +Reset the global temporary directory: + +>>> tempfile.tempdir = old_tempdir diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/downloadcache.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/downloadcache.txt new file mode 100644 index 0000000..6035bd2 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/downloadcache.txt @@ -0,0 +1,141 @@ +Using a download cache +====================== + +Normally, when distributions are installed, if any processing is +needed, they are downloaded from the internet to a temporary directory +and then installed from there. A download cache can be used to avoid +the download step. This can be useful to reduce network access and to +create source distributions of an entire buildout. + +The buildout download-cache option can be used to specify a directory +to be used as a download cache. + +In this example, we'll create a directory to hold the cache: + + >>> cache = tmpdir('cache') + +And set up a buildout that downloads some eggs: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... download-cache = %(cache)s + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = demo ==0.2 + ... ''' % globals()) + +We specified a link server that has some distributions available for +download: + + >>> print get(link_server), + <html><body> + <a href="bigdemo-0.1-py2.4.egg">bigdemo-0.1-py2.4.egg</a><br> + <a href="demo-0.1-py2.4.egg">demo-0.1-py2.4.egg</a><br> + <a href="demo-0.2-py2.4.egg">demo-0.2-py2.4.egg</a><br> + <a href="demo-0.3-py2.4.egg">demo-0.3-py2.4.egg</a><br> + <a href="demo-0.4c1-py2.4.egg">demo-0.4c1-py2.4.egg</a><br> + <a href="demoneeded-1.0.zip">demoneeded-1.0.zip</a><br> + <a href="demoneeded-1.1.zip">demoneeded-1.1.zip</a><br> + <a href="demoneeded-1.2c1.zip">demoneeded-1.2c1.zip</a><br> + <a href="extdemo-1.4.zip">extdemo-1.4.zip</a><br> + <a href="index/">index/</a><br> + <a href="other-1.0-py2.4.egg">other-1.0-py2.4.egg</a><br> + </body></html> + + +We'll enable logging on the link server so we can see what's going on: + + >>> get(link_server+'enable_server_logging') + GET 200 /enable_server_logging + '' + +We also specified a download cache. + +If we run the buildout, we'll see the eggs installed from the link +server as usual: + + >>> print system(buildout), + GET 200 / + GET 200 /demo-0.2-py2.4.egg + GET 200 /demoneeded-1.2c1.zip + Installing eggs. + Getting distribution for 'demo==0.2'. + Got demo 0.2. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + Generated script '/sample-buildout/bin/demo'. + +We'll also get the download cache populated. The buildout doesn't put +files in the cache directly. It creates an intermediate directory, +dist: + + + >>> ls(cache) + d dist + + >>> ls(cache, 'dist') + - demo-0.2-py2.4.egg + - demoneeded-1.2c1.zip + +If we remove the installed eggs from eggs directory and re-run the buildout: + + >>> import os + >>> for f in os.listdir('eggs'): + ... if f.startswith('demo'): + ... remove('eggs', f) + + >>> print system(buildout), + GET 200 / + Updating eggs. + Getting distribution for 'demo==0.2'. + Got demo 0.2. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + +We see that the distributions aren't downloaded, because they're +downloaded from the cache. + +Installing solely from a download cache +--------------------------------------- + +A download cache can be used as the basis of application source +releases. In an application source release, we want to distribute an +application that can be built without making any network accesses. In +this case, we distribute a buildout with download cache and tell the +buildout to install from the download cache only, without making +network accesses. The buildout install-from-cache option can be used +to signal that packages should be installed only from the download +cache. + +Let's remove our installed eggs and run the buildout with the +install-from-cache option set to true: + + >>> for f in os.listdir('eggs'): + ... if f.startswith('demo'): + ... remove('eggs', f) + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... download-cache = %(cache)s + ... install-from-cache = true + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout), + Uninstalling eggs. + Installing eggs. + Getting distribution for 'demo'. + Got demo 0.2. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + Generated script '/sample-buildout/bin/demo'. diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.py new file mode 100644 index 0000000..b09c0c5 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.py @@ -0,0 +1,1906 @@ +############################################################################# +# +# Copyright (c) 2005 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Python easy_install API + +This module provides a high-level Python API for installing packages. +It doesn't install scripts. It uses setuptools and requires it to be +installed. +""" + +import distutils.errors +import fnmatch +import glob +import logging +import os +import pkg_resources +import py_compile +import re +import setuptools.archive_util +import setuptools.command.setopt +import setuptools.package_index +import shutil +import subprocess +import sys +import tempfile +import warnings +import zc.buildout +import zipimport + +_oprp = getattr(os.path, 'realpath', lambda path: path) +def realpath(path): + return os.path.normcase(os.path.abspath(_oprp(path))) + +default_index_url = os.environ.get( + 'buildout-testing-index-url', + 'http://pypi.python.org/simple', + ) + +logger = logging.getLogger('zc.buildout.easy_install') + +url_match = re.compile('[a-z0-9+.-]+://').match + +is_win32 = sys.platform == 'win32' +is_jython = sys.platform.startswith('java') +is_distribute = ( + pkg_resources.Requirement.parse('setuptools').key=='distribute') + +BROKEN_DASH_S_WARNING = ( + 'Buildout has been asked to exclude or limit site-packages so that ' + 'builds can be repeatable when using a system Python. However, ' + 'the chosen Python executable has a broken implementation of -S (see ' + 'https://bugs.launchpad.net/virtualenv/+bug/572545 for an example ' + "problem) and this breaks buildout's ability to isolate site-packages. " + "If the executable already has a clean site-packages (e.g., " + "using virtualenv's ``--no-site-packages`` option) you may be getting " + 'equivalent repeatability. To silence this warning, use the -s argument ' + 'to the buildout script. Alternatively, use a Python executable with a ' + 'working -S (such as a standard Python binary).') + +if is_jython: + import java.lang.System + jython_os_name = (java.lang.System.getProperties()['os.name']).lower() + +setuptools_loc = pkg_resources.working_set.find( + pkg_resources.Requirement.parse('setuptools') + ).location + +# Include buildout and setuptools eggs in paths. We prevent dupes just to +# keep from duplicating any log messages about them. +buildout_loc = pkg_resources.working_set.find( + pkg_resources.Requirement.parse('zc.buildout')).location +buildout_and_setuptools_path = [setuptools_loc] +if os.path.normpath(setuptools_loc) != os.path.normpath(buildout_loc): + buildout_and_setuptools_path.append(buildout_loc) + +def _has_broken_dash_S(executable): + """Detect https://bugs.launchpad.net/virtualenv/+bug/572545 .""" + # The first attempt here was to simply have the executable attempt to import + # ConfigParser and return the return code. That worked except for tests on + # Windows, where the return code was wrong for the fake Python executable + # generated by the virtualenv.txt test, apparently because setuptools' .exe + # file does not pass the -script.py's returncode back properly, at least in + # some circumstances. Therefore...print statements. + stdout, stderr = subprocess.Popen( + [executable, '-Sc', + 'try:\n' + ' import ConfigParser\n' + 'except ImportError:\n' + ' print 1\n' + 'else:\n' + ' print 0\n'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + return bool(int(stdout.strip())) + +def _get_system_paths(executable): + """Return lists of standard lib and site paths for executable. + """ + # We want to get a list of the site packages, which is not easy. + # The canonical way to do this is to use + # distutils.sysconfig.get_python_lib(), but that only returns a + # single path, which does not reflect reality for many system + # Pythons, which have multiple additions. Instead, we start Python + # with -S, which does not import site.py and set up the extra paths + # like site-packages or (Ubuntu/Debian) dist-packages and + # python-support. We then compare that sys.path with the normal one + # (minus user packages if this is Python 2.6, because we don't + # support those (yet?). The set of the normal one minus the set of + # the ones in ``python -S`` is the set of packages that are + # effectively site-packages. + # + # The given executable might not be the current executable, so it is + # appropriate to do another subprocess to figure out what the + # additional site-package paths are. Moreover, even if this + # executable *is* the current executable, this code might be run in + # the context of code that has manipulated the sys.path--for + # instance, to add local zc.buildout or setuptools eggs. + def get_sys_path(*args, **kwargs): + cmd = [executable] + cmd.extend(args) + cmd.extend([ + "-c", "import sys, os;" + "print repr([os.path.normpath(p) for p in sys.path if p])"]) + # Windows needs some (as yet to be determined) part of the real env. + env = os.environ.copy() + # We need to make sure that PYTHONPATH, which will often be set + # to include a custom buildout-generated site.py, is not set, or + # else we will not get an accurate sys.path for the executable. + env.pop('PYTHONPATH', None) + env.update(kwargs) + _proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + stdout, stderr = _proc.communicate(); + if _proc.returncode: + raise RuntimeError( + 'error trying to get system packages:\n%s' % (stderr,)) + res = eval(stdout.strip()) + try: + res.remove('.') + except ValueError: + pass + return res + stdlib = get_sys_path('-S') # stdlib only + no_user_paths = get_sys_path(PYTHONNOUSERSITE='x') + site_paths = [p for p in no_user_paths if p not in stdlib] + return (stdlib, site_paths) + +def _get_version_info(executable): + cmd = [executable, '-Sc', + 'import sys; print(repr(tuple(x for x in sys.version_info)))'] + _proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = _proc.communicate(); + if _proc.returncode: + raise RuntimeError( + 'error trying to get system packages:\n%s' % (stderr,)) + return eval(stdout.strip()) + + +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: + return _versions[executable] + except KeyError: + cmd = _safe_arg(executable) + ' -V' + p = subprocess.Popen(cmd, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + close_fds=not is_win32) + i, o = (p.stdin, p.stdout) + i.close() + version = o.read().strip() + o.close() + pystring, version = version.split() + assert pystring == 'Python' + version = re.match('(\d[.]\d)([.].*\d)?$', version).group(1) + _versions[executable] = version + return version + +FILE_SCHEME = re.compile('file://', re.I).match + + +class AllowHostsPackageIndex(setuptools.package_index.PackageIndex): + """Will allow urls that are local to the system. + + No matter what is allow_hosts. + """ + def url_ok(self, url, fatal=False): + if FILE_SCHEME(url): + return True + return setuptools.package_index.PackageIndex.url_ok(self, url, False) + + +_indexes = {} +def _get_index(executable, index_url, find_links, allow_hosts=('*',), + path=None): + # If path is None, the index will use sys.path. If you provide an empty + # path ([]), it will complain uselessly about missing index pages for + # packages found in the paths that you expect to use. Therefore, this path + # is always the same as the _env path in the Installer. + key = executable, index_url, tuple(find_links) + index = _indexes.get(key) + if index is not None: + return index + + if index_url is None: + index_url = default_index_url + index = AllowHostsPackageIndex( + index_url, hosts=allow_hosts, search_path=path, + python=_get_version(executable) + ) + + if find_links: + index.add_find_links(find_links) + + _indexes[key] = index + return index + +clear_index_cache = _indexes.clear + +if is_win32: + # work around spawn lamosity on windows + # XXX need safe quoting (see the subprocess.list2cmdline) and test + def _safe_arg(arg): + return '"%s"' % arg +else: + _safe_arg = str + +# The following string is used to run easy_install in +# Installer._call_easy_install. It is usually started with python -S +# (that is, don't import site at start). That flag, and all of the code +# in this snippet above the last two lines, exist to work around a +# relatively rare problem. If +# +# - your buildout configuration is trying to install a package that is within +# a namespace package, and +# +# - you use a Python that has a different version of this package +# installed in in its site-packages using +# --single-version-externally-managed (that is, using the mechanism +# sometimes used by system packagers: +# http://peak.telecommunity.com/DevCenter/setuptools#install-command ), and +# +# - the new package tries to do sys.path tricks in the setup.py to get a +# __version__, +# +# then the older package will be loaded first, making the setup version +# the wrong number. While very arguably packages simply shouldn't do +# the sys.path tricks, some do, and we don't want buildout to fall over +# when they do. +# +# The namespace packages installed in site-packages with +# --single-version-externally-managed use a mechanism that cause them to +# be processed when site.py is imported (see +# http://mail.python.org/pipermail/distutils-sig/2009-May/011730.html +# for another description of the problem). Simply starting Python with +# -S addresses the problem in Python 2.4 and 2.5, but Python 2.6's +# distutils imports a value from the site module, so we unfortunately +# have to do more drastic surgery in the _easy_install_preface code below. +# +# Here's an example of the .pth files created by setuptools when using that +# flag: +# +# import sys,new,os; +# p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('<NAMESPACE>',)); +# ie = os.path.exists(os.path.join(p,'__init__.py')); +# m = not ie and sys.modules.setdefault('<NAMESPACE>',new.module('<NAMESPACE>')); +# mp = (m or []) and m.__dict__.setdefault('__path__',[]); +# (p not in mp) and mp.append(p) +# +# The code, below, then, runs under -S, indicating that site.py should +# not be loaded initially. It gets the initial sys.path under these +# circumstances, and then imports site (because Python 2.6's distutils +# will want it, as mentioned above). It then reinstates the old sys.path +# value. Then it removes namespace packages (created by the setuptools +# code above) from sys.modules. It identifies namespace packages by +# iterating over every loaded module. It first looks if there is a +# __path__, so it is a package; and then it sees if that __path__ does +# not have an __init__.py. (Note that PEP 382, +# http://www.python.org/dev/peps/pep-0382, makes it possible to have a +# namespace package that has an __init__.py, but also should make it +# unnecessary for site.py to preprocess these packages, so it should be +# fine, as far as can be guessed as of this writing.) Finally, it +# imports easy_install and runs it. +_easy_install_preface = '''\ +import sys,os;\ +p = sys.path[:];\ +import site;\ +sys.path[:] = p;\ +[sys.modules.pop(k) for k, v in sys.modules.items()\ + if hasattr(v, '__path__') and len(v.__path__)==1 and\ + not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))];''' +_easy_install_cmd = ( + 'from setuptools.command.easy_install import main;main()') + + +class Installer: + + _versions = {} + _download_cache = None + _install_from_cache = False + _prefer_final = True + _use_dependency_links = True + _allow_picked_versions = True + _always_unzip = False + _include_site_packages = True + _allowed_eggs_from_site_packages = ('*',) + + def __init__(self, + dest=None, + links=(), + index=None, + executable=sys.executable, + always_unzip=None, + path=None, + newest=True, + versions=None, + use_dependency_links=None, + allow_hosts=('*',), + include_site_packages=None, + allowed_eggs_from_site_packages=None, + prefer_final=None, + ): + self._dest = dest + self._allow_hosts = allow_hosts + + if self._install_from_cache: + if not self._download_cache: + raise ValueError("install_from_cache set to true with no" + " download cache") + links = () + index = 'file://' + self._download_cache + + if use_dependency_links is not None: + self._use_dependency_links = use_dependency_links + if prefer_final is not None: + self._prefer_final = prefer_final + self._links = links = list(_fix_file_links(links)) + if self._download_cache and (self._download_cache not in links): + links.insert(0, self._download_cache) + + self._index_url = index + self._executable = executable + self._has_broken_dash_S = _has_broken_dash_S(self._executable) + if always_unzip is not None: + self._always_unzip = always_unzip + path = (path and path[:] or []) + if include_site_packages is not None: + self._include_site_packages = include_site_packages + if allowed_eggs_from_site_packages is not None: + self._allowed_eggs_from_site_packages = tuple( + allowed_eggs_from_site_packages) + if self._has_broken_dash_S: + if (not self._include_site_packages or + self._allowed_eggs_from_site_packages != ('*',)): + # We can't do this if the executable has a broken -S. + warnings.warn(BROKEN_DASH_S_WARNING) + self._include_site_packages = True + self._allowed_eggs_from_site_packages = ('*',) + self._easy_install_cmd = _easy_install_cmd + else: + self._easy_install_cmd = _easy_install_preface + _easy_install_cmd + self._easy_install_cmd = _safe_arg(self._easy_install_cmd) + stdlib, self._site_packages = _get_system_paths(executable) + version_info = _get_version_info(executable) + if version_info == sys.version_info: + # Maybe we can add the buildout and setuptools path. If we + # are including site_packages, we only have to include the extra + # bits here, so we don't duplicate. On the other hand, if we + # are not including site_packages, we only want to include the + # parts that are not in site_packages, so the code is the same. + path.extend( + set(buildout_and_setuptools_path).difference( + self._site_packages)) + if self._include_site_packages: + path.extend(self._site_packages) + if dest is not None and dest not in path: + path.insert(0, dest) + self._path = path + if self._dest is None: + newest = False + self._newest = newest + self._env = pkg_resources.Environment(path, + python=_get_version(executable)) + self._index = _get_index(executable, index, links, self._allow_hosts, + self._path) + + if versions is not None: + self._versions = versions + + _allowed_eggs_from_site_packages_regex = None + def allow_site_package_egg(self, name): + if (not self._include_site_packages or + not self._allowed_eggs_from_site_packages): + # If the answer is a blanket "no," perform a shortcut. + return False + if self._allowed_eggs_from_site_packages_regex is None: + pattern = '(%s)' % ( + '|'.join( + fnmatch.translate(name) + for name in self._allowed_eggs_from_site_packages), + ) + self._allowed_eggs_from_site_packages_regex = re.compile(pattern) + return bool(self._allowed_eggs_from_site_packages_regex.match(name)) + + def _satisfied(self, req, source=None): + # We get all distributions that match the given requirement. If we are + # not supposed to include site-packages for the given egg, we also + # filter those out. Even if include_site_packages is False and so we + # have excluded site packages from the _env's paths (see + # Installer.__init__), we need to do the filtering here because an + # .egg-link, such as one for setuptools or zc.buildout installed by + # zc.buildout.buildout.Buildout.bootstrap, can indirectly include a + # path in our _site_packages. + dists = [dist for dist in self._env[req.project_name] if ( + dist in req and ( + dist.location not in self._site_packages or + self.allow_site_package_egg(dist.project_name)) + ) + ] + if not dists: + logger.debug('We have no distributions for %s that satisfies %r.', + req.project_name, str(req)) + + return None, self._obtain(req, source) + + # Note that dists are sorted from best to worst, as promised by + # env.__getitem__ + + for dist in dists: + if (dist.precedence == pkg_resources.DEVELOP_DIST and + dist.location not in self._site_packages): + # System eggs are sometimes installed as develop eggs. + # Those are not the kind of develop eggs we are looking for + # here: we want ones that the buildout itself has locally as + # develop eggs. + logger.debug('We have a develop egg: %s', dist) + return dist, None + + # Special common case, we have a specification for a single version: + specs = req.specs + if len(specs) == 1 and specs[0][0] == '==': + logger.debug('We have the distribution that satisfies %r.', + str(req)) + return dists[0], None + + if self._prefer_final: + fdists = [dist for dist in dists + if _final_version(dist.parsed_version) + ] + if fdists: + # There are final dists, so only use those + dists = fdists + + if not self._newest: + # We don't need the newest, so we'll use the newest one we + # find, which is the first returned by + # Environment.__getitem__. + return dists[0], None + + best_we_have = dists[0] # Because dists are sorted from best to worst + + # We have some installed distros. There might, theoretically, be + # newer ones. Let's find out which ones are available and see if + # any are newer. We only do this if we're willing to install + # something, which is only true if dest is not None: + + if self._dest is not None: + best_available = self._obtain(req, source) + else: + best_available = None + + if best_available is None: + # That's a bit odd. There aren't any distros available. + # We should use the best one we have that meets the requirement. + logger.debug( + 'There are no distros available that meet %r.\n' + 'Using our best, %s.', + str(req), best_available) + return best_we_have, None + + if self._prefer_final: + if _final_version(best_available.parsed_version): + if _final_version(best_we_have.parsed_version): + if (best_we_have.parsed_version + < + best_available.parsed_version + ): + return None, best_available + else: + return None, best_available + else: + if (not _final_version(best_we_have.parsed_version) + and + (best_we_have.parsed_version + < + best_available.parsed_version + ) + ): + return None, best_available + else: + if (best_we_have.parsed_version + < + best_available.parsed_version + ): + return None, best_available + + logger.debug( + 'We have the best distribution that satisfies %r.', + str(req)) + return best_we_have, None + + def _load_dist(self, dist): + dists = pkg_resources.Environment( + dist.location, + python=_get_version(self._executable), + )[dist.project_name] + assert len(dists) == 1 + return dists[0] + + def _call_easy_install(self, spec, ws, dest, dist): + + tmp = tempfile.mkdtemp(dir=dest) + try: + path = setuptools_loc + + args = ('-c', self._easy_install_cmd, '-mUNxd', _safe_arg(tmp)) + if not self._has_broken_dash_S: + args = ('-S',) + args + if self._always_unzip: + args += ('-Z', ) + level = logger.getEffectiveLevel() + if level > 0: + args += ('-q', ) + elif level < 0: + args += ('-v', ) + + args += (_safe_arg(spec), ) + + if level <= logging.DEBUG: + logger.debug('Running easy_install:\n%s "%s"\npath=%s\n', + self._executable, '" "'.join(args), path) + + if is_jython: + extra_env = dict(os.environ, PYTHONPATH=path) + else: + args += (dict(os.environ, PYTHONPATH=path), ) + + sys.stdout.flush() # We want any pending output first + + if is_jython: + exit_code = subprocess.Popen( + [_safe_arg(self._executable)] + list(args), + env=extra_env).wait() + else: + exit_code = os.spawnle( + os.P_WAIT, self._executable, _safe_arg (self._executable), + *args) + + dists = [] + env = pkg_resources.Environment( + [tmp], + python=_get_version(self._executable), + ) + for project in env: + dists.extend(env[project]) + + if exit_code: + logger.error( + "An error occurred when trying to install %s. " + "Look above this message for any errors that " + "were output by easy_install.", + dist) + + if not dists: + raise zc.buildout.UserError("Couldn't install: %s" % dist) + + if len(dists) > 1: + logger.warn("Installing %s\n" + "caused multiple distributions to be installed:\n" + "%s\n", + dist, '\n'.join(map(str, dists))) + else: + d = dists[0] + if d.project_name != dist.project_name: + logger.warn("Installing %s\n" + "Caused installation of a distribution:\n" + "%s\n" + "with a different project name.", + dist, d) + if d.version != dist.version: + logger.warn("Installing %s\n" + "Caused installation of a distribution:\n" + "%s\n" + "with a different version.", + dist, d) + + result = [] + for d in dists: + newloc = os.path.join(dest, os.path.basename(d.location)) + if os.path.exists(newloc): + if os.path.isdir(newloc): + shutil.rmtree(newloc) + else: + os.remove(newloc) + os.rename(d.location, newloc) + + [d] = pkg_resources.Environment( + [newloc], + python=_get_version(self._executable), + )[d.project_name] + + result.append(d) + + return result + + finally: + shutil.rmtree(tmp) + + def _obtain(self, requirement, source=None): + # initialize out index for this project: + index = self._index + + if index.obtain(requirement) is None: + # Nothing is available. + return None + + # Filter the available dists for the requirement and source flag. If + # we are not supposed to include site-packages for the given egg, we + # also filter those out. Even if include_site_packages is False and so + # we have excluded site packages from the _env's paths (see + # Installer.__init__), we need to do the filtering here because an + # .egg-link, such as one for setuptools or zc.buildout installed by + # zc.buildout.buildout.Buildout.bootstrap, can indirectly include a + # path in our _site_packages. + dists = [dist for dist in index[requirement.project_name] if ( + dist in requirement and ( + dist.location not in self._site_packages or + self.allow_site_package_egg(dist.project_name)) + and ( + (not source) or + (dist.precedence == pkg_resources.SOURCE_DIST)) + ) + ] + + # If we prefer final dists, filter for final and use the + # result if it is non empty. + if self._prefer_final: + fdists = [dist for dist in dists + if _final_version(dist.parsed_version) + ] + if fdists: + # There are final dists, so only use those + dists = fdists + + # Now find the best one: + best = [] + bestv = () + for dist in dists: + distv = dist.parsed_version + if distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + + if not best: + return None + + if len(best) == 1: + return best[0] + + if self._download_cache: + for dist in best: + if (realpath(os.path.dirname(dist.location)) + == + self._download_cache + ): + return dist + + best.sort() + return best[-1] + + def _fetch(self, dist, tmp, download_cache): + if (download_cache + and (realpath(os.path.dirname(dist.location)) == download_cache) + ): + return dist + + new_location = self._index.download(dist.location, tmp) + if (download_cache + and (realpath(new_location) == realpath(dist.location)) + and os.path.isfile(new_location) + ): + # setuptools avoids making extra copies, but we want to copy + # to the download cache + shutil.copy2(new_location, tmp) + new_location = os.path.join(tmp, os.path.basename(new_location)) + + return dist.clone(location=new_location) + + def _get_dist(self, requirement, ws, always_unzip): + + __doing__ = 'Getting distribution for %r.', str(requirement) + + # Maybe an existing dist is already the best dist that satisfies the + # requirement + dist, avail = self._satisfied(requirement) + + if dist is None: + if self._dest is not None: + logger.info(*__doing__) + + # Retrieve the dist: + if avail is None: + raise MissingDistribution(requirement, ws) + + # We may overwrite distributions, so clear importer + # cache. + sys.path_importer_cache.clear() + + tmp = self._download_cache + if tmp is None: + tmp = tempfile.mkdtemp('get_dist') + + try: + dist = self._fetch(avail, tmp, self._download_cache) + + if dist is None: + raise zc.buildout.UserError( + "Couldn't download distribution %s." % avail) + + if dist.precedence == pkg_resources.EGG_DIST: + # It's already an egg, just fetch it into the dest + + newloc = os.path.join( + self._dest, os.path.basename(dist.location)) + + if os.path.isdir(dist.location): + # we got a directory. It must have been + # obtained locally. Just copy it. + shutil.copytree(dist.location, newloc) + else: + + if self._always_unzip: + should_unzip = True + else: + metadata = pkg_resources.EggMetadata( + zipimport.zipimporter(dist.location) + ) + should_unzip = ( + metadata.has_metadata('not-zip-safe') + or + not metadata.has_metadata('zip-safe') + ) + + if should_unzip: + setuptools.archive_util.unpack_archive( + dist.location, newloc) + else: + shutil.copyfile(dist.location, newloc) + + redo_pyc(newloc) + + # Getting the dist from the environment causes the + # distribution meta data to be read. Cloning isn't + # good enough. + dists = pkg_resources.Environment( + [newloc], + python=_get_version(self._executable), + )[dist.project_name] + else: + # It's some other kind of dist. We'll let easy_install + # deal with it: + dists = self._call_easy_install( + dist.location, ws, self._dest, dist) + for dist in dists: + redo_pyc(dist.location) + + finally: + if tmp != self._download_cache: + shutil.rmtree(tmp) + + self._env.scan([self._dest]) + dist = self._env.best_match(requirement, ws) + logger.info("Got %s.", dist) + + else: + dists = [dist] + + for dist in dists: + if (dist.has_metadata('dependency_links.txt') + and not self._install_from_cache + and self._use_dependency_links + ): + for link in dist.get_metadata_lines('dependency_links.txt'): + link = link.strip() + if link not in self._links: + logger.debug('Adding find link %r from %s', link, dist) + self._links.append(link) + self._index = _get_index(self._executable, + self._index_url, self._links, + self._allow_hosts, self._path) + + for dist in dists: + # Check whether we picked a version and, if we did, report it: + if not ( + dist.precedence == pkg_resources.DEVELOP_DIST + or + (len(requirement.specs) == 1 + and + requirement.specs[0][0] == '==') + ): + logger.debug('Picked: %s = %s', + dist.project_name, dist.version) + if not self._allow_picked_versions: + raise zc.buildout.UserError( + 'Picked: %s = %s' % (dist.project_name, dist.version) + ) + + return dists + + def _maybe_add_setuptools(self, ws, dist): + if dist.has_metadata('namespace_packages.txt'): + for r in dist.requires(): + if r.project_name in ('setuptools', 'distribute'): + break + else: + # We have a namespace package but no requirement for setuptools + if dist.precedence == pkg_resources.DEVELOP_DIST: + logger.warn( + "Develop distribution: %s\n" + "uses namespace packages but the distribution " + "does not require setuptools.", + dist) + requirement = self._constrain( + pkg_resources.Requirement.parse('setuptools') + ) + if ws.find(requirement) is None: + for dist in self._get_dist(requirement, ws, False): + ws.add(dist) + + + def _constrain(self, requirement): + if is_distribute and requirement.key == 'setuptools': + requirement = pkg_resources.Requirement.parse('distribute') + 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, %r.", version, str(requirement)) + raise IncompatibleVersionError("Bad version", version) + + requirement = pkg_resources.Requirement.parse( + "%s[%s] ==%s" % (requirement.project_name, + ','.join(requirement.extras), + version)) + + return requirement + + def install(self, specs, working_set=None): + + logger.debug('Installing %s.', repr(specs)[1:-1]) + + path = self._path + destination = self._dest + if destination is not None and destination not in path: + path.insert(0, destination) + + requirements = [self._constrain(pkg_resources.Requirement.parse(spec)) + for spec in specs] + + + + if working_set is None: + ws = pkg_resources.WorkingSet([]) + else: + ws = working_set + + for requirement in requirements: + for dist in self._get_dist(requirement, ws, self._always_unzip): + ws.add(dist) + 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 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). + requirements.reverse() # Set up the stack. + processed = {} # This is a set of processed requirements. + best = {} # This is a mapping of 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. + 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 or ( + dist.location in self._site_packages and not + self.allow_site_package_egg(dist.project_name)): + # If we didn't find a distribution in the + # environment, or what we found is from site + # packages and not allowed to be there, try + # again. + if destination: + 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 + if dist.location in self._site_packages: + logger.debug('Egg from site-packages: %s', dist) + return ws + + def build(self, spec, build_ext): + + requirement = self._constrain(pkg_resources.Requirement.parse(spec)) + + dist, avail = self._satisfied(requirement, 1) + if dist is not None: + return [dist.location] + + # Retrieve the dist: + if avail is None: + raise zc.buildout.UserError( + "Couldn't find a source distribution for %r." + % str(requirement)) + + logger.debug('Building %r', spec) + + tmp = self._download_cache + if tmp is None: + tmp = tempfile.mkdtemp('get_dist') + + try: + dist = self._fetch(avail, tmp, self._download_cache) + + build_tmp = tempfile.mkdtemp('build') + try: + setuptools.archive_util.unpack_archive(dist.location, + build_tmp) + if os.path.exists(os.path.join(build_tmp, 'setup.py')): + base = build_tmp + else: + setups = glob.glob( + os.path.join(build_tmp, '*', 'setup.py')) + if not setups: + raise distutils.errors.DistutilsError( + "Couldn't find a setup script in %s" + % os.path.basename(dist.location) + ) + if len(setups) > 1: + raise distutils.errors.DistutilsError( + "Multiple setup scripts in %s" + % os.path.basename(dist.location) + ) + base = os.path.dirname(setups[0]) + + setup_cfg = os.path.join(base, 'setup.cfg') + if not os.path.exists(setup_cfg): + f = open(setup_cfg, 'w') + f.close() + setuptools.command.setopt.edit_config( + setup_cfg, dict(build_ext=build_ext)) + + dists = self._call_easy_install( + base, pkg_resources.WorkingSet(), + self._dest, dist) + + for dist in dists: + redo_pyc(dist.location) + + return [dist.location for dist in dists] + finally: + shutil.rmtree(build_tmp) + + finally: + if tmp != self._download_cache: + shutil.rmtree(tmp) + +def default_versions(versions=None): + old = Installer._versions + if versions is not None: + Installer._versions = versions + return old + +def download_cache(path=-1): + old = Installer._download_cache + if path != -1: + if path: + path = realpath(path) + Installer._download_cache = path + return old + +def install_from_cache(setting=None): + old = Installer._install_from_cache + if setting is not None: + Installer._install_from_cache = bool(setting) + return old + +def prefer_final(setting=None): + old = Installer._prefer_final + if setting is not None: + Installer._prefer_final = bool(setting) + return old + +def include_site_packages(setting=None): + old = Installer._include_site_packages + if setting is not None: + Installer._include_site_packages = bool(setting) + return old + +def allowed_eggs_from_site_packages(setting=None): + old = Installer._allowed_eggs_from_site_packages + if setting is not None: + Installer._allowed_eggs_from_site_packages = tuple(setting) + return old + +def use_dependency_links(setting=None): + old = Installer._use_dependency_links + if setting is not None: + Installer._use_dependency_links = bool(setting) + return old + +def allow_picked_versions(setting=None): + old = Installer._allow_picked_versions + if setting is not None: + Installer._allow_picked_versions = bool(setting) + return old + +def always_unzip(setting=None): + old = Installer._always_unzip + if setting is not None: + Installer._always_unzip = bool(setting) + return old + +def install(specs, dest, + links=(), index=None, + executable=sys.executable, always_unzip=None, + path=None, working_set=None, newest=True, versions=None, + use_dependency_links=None, allow_hosts=('*',), + include_site_packages=None, allowed_eggs_from_site_packages=None, + prefer_final=None): + installer = Installer( + dest, links, index, executable, always_unzip, path, newest, + versions, use_dependency_links, allow_hosts=allow_hosts, + include_site_packages=include_site_packages, + allowed_eggs_from_site_packages=allowed_eggs_from_site_packages, + prefer_final=prefer_final) + return installer.install(specs, working_set) + + +def build(spec, dest, build_ext, + links=(), index=None, + executable=sys.executable, + path=None, newest=True, versions=None, allow_hosts=('*',), + include_site_packages=None, allowed_eggs_from_site_packages=None): + installer = Installer( + dest, links, index, executable, True, path, newest, versions, + allow_hosts=allow_hosts, + include_site_packages=include_site_packages, + allowed_eggs_from_site_packages=allowed_eggs_from_site_packages) + return installer.build(spec, build_ext) + + + +def _rm(*paths): + for path in paths: + if os.path.isdir(path): + shutil.rmtree(path) + elif os.path.exists(path): + os.remove(path) + +def _copyeggs(src, dest, suffix, undo): + result = [] + undo.append(lambda : _rm(*result)) + for name in os.listdir(src): + if name.endswith(suffix): + new = os.path.join(dest, name) + _rm(new) + os.rename(os.path.join(src, name), new) + result.append(new) + + assert len(result) == 1, str(result) + undo.pop() + + return result[0] + +def develop(setup, dest, + build_ext=None, + executable=sys.executable): + + if os.path.isdir(setup): + directory = setup + setup = os.path.join(directory, 'setup.py') + else: + directory = os.path.dirname(setup) + + undo = [] + try: + if build_ext: + setup_cfg = os.path.join(directory, 'setup.cfg') + if os.path.exists(setup_cfg): + os.rename(setup_cfg, setup_cfg+'-develop-aside') + def restore_old_setup(): + if os.path.exists(setup_cfg): + os.remove(setup_cfg) + os.rename(setup_cfg+'-develop-aside', setup_cfg) + undo.append(restore_old_setup) + else: + open(setup_cfg, 'w') + undo.append(lambda: os.remove(setup_cfg)) + setuptools.command.setopt.edit_config( + setup_cfg, dict(build_ext=build_ext)) + + fd, tsetup = tempfile.mkstemp() + undo.append(lambda: os.remove(tsetup)) + undo.append(lambda: os.close(fd)) + + os.write(fd, runsetup_template % dict( + setuptools=setuptools_loc, + setupdir=directory, + setup=setup, + __file__ = setup, + )) + + tmp3 = tempfile.mkdtemp('build', dir=dest) + undo.append(lambda : shutil.rmtree(tmp3)) + + args = [ + zc.buildout.easy_install._safe_arg(tsetup), + '-q', 'develop', '-mxN', + '-d', _safe_arg(tmp3), + ] + + log_level = logger.getEffectiveLevel() + if log_level <= 0: + if log_level == 0: + del args[1] + else: + args[1] == '-v' + if log_level < logging.DEBUG: + logger.debug("in: %r\n%s", directory, ' '.join(args)) + + if is_jython: + assert subprocess.Popen([_safe_arg(executable)] + args).wait() == 0 + else: + assert os.spawnl(os.P_WAIT, executable, _safe_arg(executable), + *args) == 0 + + return _copyeggs(tmp3, dest, '.egg-link', undo) + + finally: + undo.reverse() + [f() for f in undo] + +def working_set(specs, executable, path, include_site_packages=None, + allowed_eggs_from_site_packages=None, prefer_final=None): + return install( + specs, None, executable=executable, path=path, + include_site_packages=include_site_packages, + allowed_eggs_from_site_packages=allowed_eggs_from_site_packages, + prefer_final=prefer_final) + +############################################################################ +# Script generation functions + +def scripts( + reqs, working_set, executable, dest, + scripts=None, + extra_paths=(), + arguments='', + interpreter=None, + initialization='', + relative_paths=False, + ): + """Generate scripts and/or an interpreter. + + See sitepackage_safe_scripts for a version that can be used with a Python + that has code installed in site-packages. It has more options and a + different approach. + """ + path = _get_path(working_set, extra_paths) + if initialization: + initialization = '\n'+initialization+'\n' + generated = _generate_scripts( + reqs, working_set, dest, path, scripts, relative_paths, + initialization, executable, arguments) + if interpreter: + sname = os.path.join(dest, interpreter) + spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths) + generated.extend( + _pyscript(spath, sname, executable, rpsetup)) + return generated + +# We need to give an alternate name to the ``scripts`` function so that it +# can be referenced within sitepackage_safe_scripts, which uses ``scripts`` +# as an argument name. +_original_scripts_function = scripts + +def sitepackage_safe_scripts( + dest, working_set, executable, site_py_dest, + reqs=(), + scripts=None, + interpreter=None, + extra_paths=(), + initialization='', + include_site_packages=False, + exec_sitecustomize=False, + relative_paths=False, + script_arguments='', + script_initialization='', + ): + """Generate scripts and/or an interpreter from a system Python. + + This accomplishes the same job as the ``scripts`` function, above, + but it does so in an alternative way that allows safely including + Python site packages, if desired, and choosing to execute the Python's + sitecustomize. + """ + if _has_broken_dash_S(executable): + if not include_site_packages: + warnings.warn(BROKEN_DASH_S_WARNING) + return _original_scripts_function( + reqs, working_set, executable, dest, scripts, extra_paths, + script_arguments, interpreter, initialization, relative_paths) + generated = [] + generated.append(_generate_sitecustomize( + site_py_dest, executable, initialization, exec_sitecustomize)) + generated.append(_generate_site( + site_py_dest, working_set, executable, extra_paths, + include_site_packages, relative_paths)) + script_initialization = _script_initialization_template % dict( + site_py_dest=site_py_dest, + script_initialization=script_initialization) + if not script_initialization.endswith('\n'): + script_initialization += '\n' + generated.extend(_generate_scripts( + reqs, working_set, dest, [site_py_dest], scripts, relative_paths, + script_initialization, executable, script_arguments, block_site=True)) + if interpreter: + generated.extend(_generate_interpreter( + interpreter, dest, executable, site_py_dest, relative_paths)) + return generated + +_script_initialization_template = ''' +import os +path = sys.path[0] +if os.environ.get('PYTHONPATH'): + path = os.pathsep.join([path, os.environ['PYTHONPATH']]) +os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '') +os.environ['PYTHONPATH'] = path +import site # imports custom buildout-generated site.py +%(script_initialization)s''' + +# Utilities for the script generation functions. + +# These are shared by both ``scripts`` and ``sitepackage_safe_scripts`` + +def _get_path(working_set, extra_paths=()): + """Given working set and extra paths, return a normalized path list.""" + path = [dist.location for dist in working_set] + path.extend(extra_paths) + return map(realpath, path) + +def _generate_scripts(reqs, working_set, dest, path, scripts, relative_paths, + initialization, executable, arguments, + block_site=False): + """Generate scripts for the given requirements. + + - reqs is an iterable of string requirements or entry points. + - The requirements must be findable in the given working_set. + - The dest is the directory in which the scripts should be created. + - The path is a list of paths that should be added to sys.path. + - The scripts is an optional dictionary. If included, the keys should be + the names of the scripts that should be created, as identified in their + entry points; and the values should be the name the script should + actually be created with. + - relative_paths, if given, should be the path that is the root of the + buildout (the common path that should be the root of what is relative). + """ + if isinstance(reqs, str): + raise TypeError('Expected iterable of requirements or entry points,' + ' got string.') + generated = [] + entry_points = [] + for req in reqs: + if isinstance(req, str): + req = pkg_resources.Requirement.parse(req) + dist = working_set.find(req) + for name in pkg_resources.get_entry_map(dist, 'console_scripts'): + entry_point = dist.get_entry_info('console_scripts', name) + entry_points.append( + (name, entry_point.module_name, + '.'.join(entry_point.attrs)) + ) + else: + entry_points.append(req) + for name, module_name, attrs in entry_points: + if scripts is not None: + sname = scripts.get(name) + if sname is None: + continue + else: + sname = name + sname = os.path.join(dest, sname) + spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths) + generated.extend( + _script(sname, executable, rpsetup, spath, initialization, + module_name, attrs, arguments, block_site=block_site)) + return generated + +def _relative_path_and_setup(sname, path, + relative_paths=False, indent_level=1, + omit_os_import=False): + """Return a string of code of paths and of setup if appropriate. + + - sname is the full path to the script name to be created. + - path is the list of paths to be added to sys.path. + - relative_paths, if given, should be the path that is the root of the + buildout (the common path that should be the root of what is relative). + - indent_level is the number of four-space indents that the path should + insert before each element of the path. + """ + if relative_paths: + relative_paths = os.path.normcase(relative_paths) + sname = os.path.normcase(os.path.abspath(sname)) + spath = _format_paths( + [_relativitize(os.path.normcase(path_item), sname, relative_paths) + for path_item in path], indent_level=indent_level) + rpsetup = relative_paths_setup + if not omit_os_import: + rpsetup = '\n\nimport os\n' + rpsetup + for i in range(_relative_depth(relative_paths, sname)): + rpsetup += "\nbase = os.path.dirname(base)" + else: + spath = _format_paths((repr(p) for p in path), + indent_level=indent_level) + rpsetup = '' + return spath, rpsetup + +def _relative_depth(common, path): + """Return number of dirs separating ``path`` from ancestor, ``common``. + + For instance, if path is /foo/bar/baz/bing, and common is /foo, this will + return 2--in UNIX, the number of ".." to get from bing's directory + to foo. + + This is a helper for _relative_path_and_setup. + """ + n = 0 + while 1: + dirname = os.path.dirname(path) + if dirname == path: + raise AssertionError("dirname of %s is the same" % dirname) + if dirname == common: + break + n += 1 + path = dirname + return n + +def _relative_path(common, path): + """Return the relative path from ``common`` to ``path``. + + This is a helper for _relativitize, which is a helper to + _relative_path_and_setup. + """ + r = [] + while 1: + dirname, basename = os.path.split(path) + r.append(basename) + if dirname == common: + break + if dirname == path: + raise AssertionError("dirname of %s is the same" % dirname) + path = dirname + r.reverse() + return os.path.join(*r) + +def _relativitize(path, script, relative_paths): + """Return a code string for the given path. + + Path is relative to the base path ``relative_paths``if the common prefix + between ``path`` and ``script`` starts with ``relative_paths``. + """ + if path == script: + raise AssertionError("path == script") + common = os.path.dirname(os.path.commonprefix([path, script])) + if (common == relative_paths or + common.startswith(os.path.join(relative_paths, '')) + ): + return "join(base, %r)" % _relative_path(common, path) + else: + return repr(path) + +relative_paths_setup = """ +join = os.path.join +base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))""" + +def _write_script(full_name, contents, logged_type): + """Write contents of script in full_name, logging the action. + + The only tricky bit in this function is that it supports Windows by + creating exe files using a pkg_resources helper. + """ + generated = [] + script_name = full_name + if is_win32: + script_name += '-script.py' + # Generate exe file and give the script a magic name. + exe = full_name + '.exe' + new_data = pkg_resources.resource_string('setuptools', 'cli.exe') + if not os.path.exists(exe) or (open(exe, 'rb').read() != new_data): + # Only write it if it's different. + open(exe, 'wb').write(new_data) + generated.append(exe) + changed = not (os.path.exists(script_name) and + open(script_name).read() == contents) + if changed: + open(script_name, 'w').write(contents) + try: + os.chmod(script_name, 0755) + except (AttributeError, os.error): + pass + logger.info("Generated %s %r.", logged_type, full_name) + generated.append(script_name) + return generated + +def _format_paths(paths, indent_level=1): + """Format paths for inclusion in a script.""" + separator = ',\n' + indent_level * ' ' + return separator.join(paths) + +def _script(dest, executable, relative_paths_setup, path, initialization, + module_name, attrs, arguments, block_site=False): + if block_site: + dash_S = ' -S' + else: + dash_S = '' + contents = script_template % dict( + python=_safe_arg(executable), + dash_S=dash_S, + path=path, + module_name=module_name, + attrs=attrs, + arguments=arguments, + initialization=initialization, + relative_paths_setup=relative_paths_setup, + ) + return _write_script(dest, contents, 'script') + +if is_jython and jython_os_name == 'linux': + script_header = '#!/usr/bin/env %(python)s%(dash_S)s' +else: + script_header = '#!%(python)s%(dash_S)s' + +sys_path_template = '''\ +import sys +sys.path[0:0] = [ + %s, + ] +''' + +script_template = script_header + '''\ +%(relative_paths_setup)s + +import sys +sys.path[0:0] = [ + %(path)s, + ] + +%(initialization)s +import %(module_name)s + +if __name__ == '__main__': + %(module_name)s.%(attrs)s(%(arguments)s) +''' + +# These are used only by the older ``scripts`` function. + +def _pyscript(path, dest, executable, rsetup): + contents = py_script_template % dict( + python=_safe_arg(executable), + dash_S='', + path=path, + relative_paths_setup=rsetup, + ) + return _write_script(dest, contents, 'interpreter') + +py_script_template = script_header + '''\ +%(relative_paths_setup)s + +import sys + +sys.path[0:0] = [ + %(path)s, + ] + +_interactive = True +if len(sys.argv) > 1: + _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:') + _interactive = False + for (_opt, _val) in _options: + if _opt == '-i': + _interactive = True + elif _opt == '-c': + exec _val + elif _opt == '-m': + sys.argv[1:] = _args + _args = [] + __import__("runpy").run_module( + _val, {}, "__main__", alter_sys=True) + + if _args: + sys.argv[:] = _args + __file__ = _args[0] + del _options, _args + execfile(__file__) + +if _interactive: + del _interactive + __import__("code").interact(banner="", local=globals()) +''' + +# These are used only by the newer ``sitepackage_safe_scripts`` function. + +def _get_module_file(executable, name, silent=False): + """Return a module's file path. + + - executable is a path to the desired Python executable. + - name is the name of the (pure, not C) Python module. + """ + cmd = [executable, "-Sc", + "import imp; " + "fp, path, desc = imp.find_module(%r); " + "fp.close(); " + "print path" % (name,)] + env = os.environ.copy() + # We need to make sure that PYTHONPATH, which will often be set to + # include a custom buildout-generated site.py, is not set, or else + # we will not get an accurate value for the "real" site.py and + # sitecustomize.py. + env.pop('PYTHONPATH', None) + _proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + stdout, stderr = _proc.communicate(); + if _proc.returncode: + if not silent: + logger.info( + 'Could not find file for module %s:\n%s', name, stderr) + return None + # else: ... + res = stdout.strip() + if res.endswith('.pyc') or res.endswith('.pyo'): + raise RuntimeError('Cannot find uncompiled version of %s' % (name,)) + if not os.path.exists(res): + raise RuntimeError( + 'File does not exist for module %s:\n%s' % (name, res)) + return res + +def _generate_sitecustomize(dest, executable, initialization='', + exec_sitecustomize=False): + """Write a sitecustomize file with optional custom initialization. + + The created script will execute the underlying Python's + sitecustomize if exec_sitecustomize is True. + """ + sitecustomize_path = os.path.join(dest, 'sitecustomize.py') + sitecustomize = open(sitecustomize_path, 'w') + if initialization: + sitecustomize.write(initialization + '\n') + if exec_sitecustomize: + real_sitecustomize_path = _get_module_file( + executable, 'sitecustomize', silent=True) + if real_sitecustomize_path: + real_sitecustomize = open(real_sitecustomize_path, 'r') + sitecustomize.write( + '\n# The following is from\n# %s\n' % + (real_sitecustomize_path,)) + sitecustomize.write(real_sitecustomize.read()) + real_sitecustomize.close() + sitecustomize.close() + return sitecustomize_path + +def _generate_site(dest, working_set, executable, extra_paths=(), + include_site_packages=False, relative_paths=False): + """Write a site.py file with eggs from working_set. + + extra_paths will be added to the path. If include_site_packages is True, + paths from the underlying Python will be added. + """ + path = _get_path(working_set, extra_paths) + site_path = os.path.join(dest, 'site.py') + original_path_setup = preamble = '' + if include_site_packages: + stdlib, site_paths = _get_system_paths(executable) + # We want to make sure that paths from site-packages, such as those + # allowed by allowed_eggs_from_site_packages, always come last, or + # else site-packages paths may include packages that mask the eggs we + # really want. + path = [p for p in path if p not in site_paths] + # Now we set up the code we need. + original_path_setup = original_path_snippet % ( + _format_paths((repr(p) for p in site_paths), 2),) + distribution = working_set.find( + pkg_resources.Requirement.parse('setuptools')) + if distribution is not None: + # We need to worry about namespace packages. + if relative_paths: + location = _relativitize( + distribution.location, + os.path.normcase(os.path.abspath(site_path)), + relative_paths) + else: + location = repr(distribution.location) + preamble = namespace_include_site_packages_setup % (location,) + original_path_setup = ( + addsitedir_namespace_originalpackages_snippet + + original_path_setup) + else: + preamble = '\n setuptools_path = None' + egg_path_string, relative_preamble = _relative_path_and_setup( + site_path, path, relative_paths, indent_level=2, omit_os_import=True) + if relative_preamble: + relative_preamble = '\n'.join( + [(line and ' %s' % (line,) or line) + for line in relative_preamble.split('\n')]) + preamble = relative_preamble + preamble + addsitepackages_marker = 'def addsitepackages(' + enableusersite_marker = 'ENABLE_USER_SITE = ' + successful_rewrite = False + real_site_path = _get_module_file(executable, 'site') + real_site = open(real_site_path, 'r') + site = open(site_path, 'w') + try: + for line in real_site.readlines(): + if line.startswith(enableusersite_marker): + site.write(enableusersite_marker) + site.write('False # buildout does not support user sites.\n') + elif line.startswith(addsitepackages_marker): + site.write(addsitepackages_script % ( + preamble, egg_path_string, original_path_setup)) + site.write(line[len(addsitepackages_marker):]) + successful_rewrite = True + else: + site.write(line) + finally: + site.close() + real_site.close() + if not successful_rewrite: + raise RuntimeError( + 'Buildout did not successfully rewrite %s to %s' % + (real_site_path, site_path)) + return site_path + +namespace_include_site_packages_setup = ''' + setuptools_path = %s + sys.path.append(setuptools_path) + known_paths.add(os.path.normcase(setuptools_path)) + import pkg_resources''' + +addsitedir_namespace_originalpackages_snippet = ''' + pkg_resources.working_set.add_entry(sitedir)''' + +original_path_snippet = ''' + sys.__egginsert = len(buildout_paths) # Support distribute. + original_paths = [ + %s + ] + for path in original_paths: + if path == setuptools_path or path not in known_paths: + addsitedir(path, known_paths)''' + +addsitepackages_script = '''\ +def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + + See original_addsitepackages, below, for the original version."""%s + buildout_paths = [ + %s + ] + for path in buildout_paths: + sitedir, sitedircase = makepath(path) + if not sitedircase in known_paths and os.path.exists(sitedir): + sys.path.append(sitedir) + known_paths.add(sitedircase)%s + return known_paths + +def original_addsitepackages(''' + +def _generate_interpreter(name, dest, executable, site_py_dest, + relative_paths=False): + """Write an interpreter script, using the site.py approach.""" + full_name = os.path.join(dest, name) + site_py_dest_string, rpsetup = _relative_path_and_setup( + full_name, [site_py_dest], relative_paths, omit_os_import=True) + if rpsetup: + rpsetup += "\n" + if sys.platform == 'win32': + windows_import = '\nimport subprocess' + # os.exec* is a mess on Windows, particularly if the path + # to the executable has spaces and the Python is using MSVCRT. + # The standard fix is to surround the executable's path with quotes, + # but that has been unreliable in testing. + # + # Here's a demonstration of the problem. Given a Python + # compiled with a MSVCRT-based compiler, such as the free Visual + # C++ 2008 Express Edition, and an executable path with spaces + # in it such as the below, we see the following. + # + # >>> import os + # >>> p0 = 'C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe' + # >>> os.path.exists(p0) + # True + # >>> os.execv(p0, []) + # Traceback (most recent call last): + # File "<stdin>", line 1, in <module> + # OSError: [Errno 22] Invalid argument + # + # That seems like a standard problem. The standard solution is + # to quote the path (see, for instance + # http://bugs.python.org/issue436259). However, this solution, + # and other variations, fail: + # + # >>> p1 = '"C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe"' + # >>> os.execv(p1, []) + # Traceback (most recent call last): + # File "<stdin>", line 1, in <module> + # OSError: [Errno 22] Invalid argument + # + # We simply use subprocess instead, since it handles everything + # nicely, and the transparency of exec* (that is, not running, + # perhaps unexpectedly, in a subprocess) is arguably not a + # necessity, at least for many use cases. + execute = 'subprocess.call(argv, env=environ)' + else: + windows_import = '' + execute = 'os.execve(sys.executable, argv, environ)' + contents = interpreter_template % dict( + python=_safe_arg(executable), + dash_S=' -S', + site_dest=site_py_dest_string, + relative_paths_setup=rpsetup, + windows_import=windows_import, + execute=execute, + ) + return _write_script(full_name, contents, 'interpreter') + +interpreter_template = script_header + ''' +import os +import sys%(windows_import)s +%(relative_paths_setup)s +argv = [sys.executable] + sys.argv[1:] +environ = os.environ.copy() +path = %(site_dest)s +if environ.get('PYTHONPATH'): + path = os.pathsep.join([path, environ['PYTHONPATH']]) +environ['PYTHONPATH'] = path +%(execute)s +''' + +# End of script generation code. +############################################################################ + +runsetup_template = """ +import sys +sys.path.insert(0, %(setupdir)r) +sys.path.insert(0, %(setuptools)r) +import os, setuptools + +__file__ = %(__file__)r + +os.chdir(%(setupdir)r) +sys.argv[0] = %(setup)r +execfile(%(setup)r) +""" + + +class VersionConflict(zc.buildout.UserError): + + def __init__(self, err, ws): + ws = list(ws) + ws.sort() + self.err, self.ws = err, ws + + def __str__(self): + existing_dist, req = self.err + result = ["There is a version conflict.", + "We already have: %s" % existing_dist, + ] + for dist in self.ws: + if req in dist.requires(): + result.append("but %s requires %r." % (dist, str(req))) + return '\n'.join(result) + + +class MissingDistribution(zc.buildout.UserError): + + def __init__(self, req, ws): + ws = list(ws) + ws.sort() + self.data = req, ws + + def __str__(self): + req, ws = self.data + return "Couldn't find a distribution for %r." % str(req) + +def _log_requirement(ws, req): + ws = list(ws) + ws.sort() + for dist in ws: + if req in dist.requires(): + logger.debug(" required by %s." % dist) + +def _fix_file_links(links): + for link in links: + if link.startswith('file://') and link[-1] != '/': + if os.path.isdir(link[7:]): + # work around excessive restriction in setuptools: + link += '/' + yield link + +_final_parts = '*final-', '*final' +def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + +def redo_pyc(egg): + if not os.path.isdir(egg): + return + for dirpath, dirnames, filenames in os.walk(egg): + for filename in filenames: + if not filename.endswith('.py'): + continue + filepath = os.path.join(dirpath, filename) + if not (os.path.exists(filepath+'c') + or os.path.exists(filepath+'o')): + # If it wasn't compiled, it may not be compilable + continue + + # OK, it looks like we should try to compile. + + # Remove old files. + for suffix in 'co': + if os.path.exists(filepath+suffix): + os.remove(filepath+suffix) + + # Compile under current optimization + try: + py_compile.compile(filepath) + except py_compile.PyCompileError: + logger.warning("Couldn't compile %s", filepath) + else: + # Recompile under other optimization. :) + args = [_safe_arg(sys.executable)] + if __debug__: + args.append('-O') + args.extend(['-m', 'py_compile', _safe_arg(filepath)]) + + if is_jython: + subprocess.call([sys.executable, args]) + else: + os.spawnv(os.P_WAIT, sys.executable, args) + diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.txt new file mode 100644 index 0000000..ec654cd --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.txt @@ -0,0 +1,1985 @@ +Python API for egg and script installation +========================================== + +The easy_install module provides some functions to provide support for +egg and script installation. It provides functionality at the python +level that is similar to easy_install, with a few exceptions: + +- By default, we look for new packages *and* the packages that + they depend on. This is somewhat like (and uses) the --upgrade + option of easy_install, except that we also upgrade required + packages. + +- If the highest-revision package satisfying a specification is + already present, then we don't try to get another one. This saves a + lot of search time in the common case that packages are pegged to + specific versions. + +- If there is a develop egg that satisfies a requirement, we don't + look for additional distributions. We always give preference to + develop eggs. + +- Distutils options for building extensions can be passed. + +Distribution installation +------------------------- + +The easy_install module provides a function, install, for installing one +or more packages and their dependencies. The install function takes 2 +positional arguments: + +- An iterable of setuptools requirement strings for the distributions + to be installed, and + +- A destination directory to install to and to satisfy requirements + from. The destination directory can be None, in which case, no new + distributions are downloaded and there will be an error if the + needed distributions can't be found among those already installed. + +It supports a number of optional keyword arguments: + +links + A sequence of URLs, file names, or directories to look for + links to distributions. + +index + The URL of an index server, or almost any other valid URL. :) + + If not specified, the Python Package Index, + http://pypi.python.org/simple/, is used. You can specify an + alternate index with this option. If you use the links option and + if the links point to the needed distributions, then the index can + be anything and will be largely ignored. In the examples, here, + we'll just point to an empty directory on our link server. This + will make our examples run a little bit faster. + +executable + A path to a Python executable. Distributions will be installed + using this executable and will be for the matching Python version. + +path + A list of additional directories to search for locally-installed + distributions. + +always_unzip + A flag indicating that newly-downloaded distributions should be + directories even if they could be installed as zip files. + +working_set + An existing working set to be augmented with additional + distributions, if necessary to satisfy requirements. This allows + you to call install multiple times, if necessary, to gather + multiple sets of requirements. + +newest + A boolean value indicating whether to search for new distributions + when already-installed distributions meet the requirement. When + this is true, the default, and when the destination directory is + 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. + +use_dependency_links + A flag indicating whether to search for dependencies using the + setup dependency_links metadata or not. If true, links are searched + for using dependency_links in preference to other + locations. Defaults to true. + +include_site_packages + A flag indicating whether Python's non-standard-library packages should + be available for finding dependencies. Defaults to true. + + Paths outside of Python's standard library--or more precisely, those that + are not included when Python is started with the -S argument--are loosely + referred to as "site-packages" here. + +relative_paths + Adjust egg paths so they are relative to the script path. This + allows scripts to work when scripts and eggs are moved, as long as + they are both moved in the same way. + +The install method returns a working set containing the distributions +needed to meet the given requirements. + +We have a link server that has a number of eggs: + + >>> print get(link_server), + <html><body> + <a href="bigdemo-0.1-py2.4.egg">bigdemo-0.1-py2.4.egg</a><br> + <a href="demo-0.1-py2.4.egg">demo-0.1-py2.4.egg</a><br> + <a href="demo-0.2-py2.4.egg">demo-0.2-py2.4.egg</a><br> + <a href="demo-0.3-py2.4.egg">demo-0.3-py2.4.egg</a><br> + <a href="demo-0.4c1-py2.4.egg">demo-0.4c1-py2.4.egg</a><br> + <a href="demoneeded-1.0.zip">demoneeded-1.0.zip</a><br> + <a href="demoneeded-1.1.zip">demoneeded-1.1.zip</a><br> + <a href="demoneeded-1.2c1.zip">demoneeded-1.2c1.zip</a><br> + <a href="extdemo-1.4.zip">extdemo-1.4.zip</a><br> + <a href="index/">index/</a><br> + <a href="other-1.0-py2.4.egg">other-1.0-py2.4.egg</a><br> + </body></html> + +Let's make a directory and install the demo egg to it, using the demo: + + >>> dest = tmpdir('sample-install') + >>> import zc.buildout.easy_install + >>> ws = zc.buildout.easy_install.install( + ... ['demo==0.2'], dest, + ... links=[link_server], index=link_server+'index/') + +We requested version 0.2 of the demo distribution to be installed into +the destination server. We specified that we should search for links +on the link server and that we should use the (empty) link server +index directory as a package index. + +The working set contains the distributions we retrieved. + + >>> for dist in ws: + ... print dist + demo 0.2 + demoneeded 1.1 + +We got demoneeded because it was a dependency of demo. + +And the actual eggs were added to the eggs directory. + + >>> ls(dest) + - demo-0.2-py2.4.egg + - demoneeded-1.1-py2.4.egg + +If we remove the version restriction on demo, but specify a false +value for newest, no new distributions will be installed: + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... newest=False) + >>> ls(dest) + - demo-0.2-py2.4.egg + - demoneeded-1.1-py2.4.egg + +If we leave off the newest option, we'll get an update for demo: + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/') + >>> ls(dest) + - demo-0.2-py2.4.egg + - demo-0.3-py2.4.egg + - demoneeded-1.1-py2.4.egg + +Note that we didn't get the newest versions available. There were +release candidates for newer versions of both packages. By default, +final releases are preferred. We can change this behavior using the +prefer_final function: + + >>> zc.buildout.easy_install.prefer_final(False) + True + +The old setting is returned. + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/') + >>> for dist in ws: + ... print dist + demo 0.4c1 + demoneeded 1.2c1 + + >>> ls(dest) + - demo-0.2-py2.4.egg + - demo-0.3-py2.4.egg + - demo-0.4c1-py2.4.egg + - demoneeded-1.1-py2.4.egg + - demoneeded-1.2c1-py2.4.egg + +Let's put the setting back to the default. + + >>> zc.buildout.easy_install.prefer_final(True) + False + +We can supply additional distributions. We can also supply +specifications for distributions that would normally be found via +dependencies. We might do this to specify a specific version. + + >>> ws = zc.buildout.easy_install.install( + ... ['demo', 'other', 'demoneeded==1.0'], dest, + ... links=[link_server], index=link_server+'index/') + + >>> for dist in ws: + ... print dist + demo 0.3 + other 1.0 + demoneeded 1.0 + + >>> ls(dest) + - demo-0.2-py2.4.egg + - demo-0.3-py2.4.egg + - demo-0.4c1-py2.4.egg + - demoneeded-1.0-py2.4.egg + - demoneeded-1.1-py2.4.egg + - demoneeded-1.2c1-py2.4.egg + d other-1.0-py2.4.egg + +We can request that eggs be unzipped even if they are zip safe. This +can be useful when debugging. (Note that Distribute will unzip eggs by +default, so if you are using Distribute, most or all eggs will already be +unzipped without this flag.) + + >>> rmdir(dest) + >>> dest = tmpdir('sample-install') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... always_unzip=True) + + >>> ls(dest) + d demo-0.3-py2.4.egg + d demoneeded-1.1-py2.4.egg + + >>> rmdir(dest) + >>> dest = tmpdir('sample-install') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... always_unzip=False) + + >>> ls(dest) + - demo-0.3-py2.4.egg + - demoneeded-1.1-py2.4.egg + +We can also set a default by calling the always_unzip function: + + >>> zc.buildout.easy_install.always_unzip(True) + False + +The old default is returned: + + >>> rmdir(dest) + >>> dest = tmpdir('sample-install') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/') + + >>> ls(dest) + d demo-0.3-py2.4.egg + d demoneeded-1.1-py2.4.egg + + + >>> zc.buildout.easy_install.always_unzip(False) + True + + >>> rmdir(dest) + >>> dest = tmpdir('sample-install') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/') + + >>> ls(dest) + - demo-0.3-py2.4.egg + - demoneeded-1.1-py2.4.egg + + >>> rmdir(dest) + >>> dest = tmpdir('sample-install') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... always_unzip=True) + + >>> ls(dest) + d demo-0.3-py2.4.egg + d demoneeded-1.1-py2.4.egg + +Specifying version information independent of requirements +---------------------------------------------------------- + +Sometimes it's useful to specify version information independent 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 +dependencies 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: demo = 0.3 + zc.buildout.easy_install DEBUG + Getting required 'demoneeded' + zc.buildout.easy_install DEBUG + required by demo 0.3. + zc.buildout.easy_install DEBUG + We have the best distribution that satisfies 'demoneeded'. + zc.buildout.easy_install DEBUG + Picked: demoneeded = 1.1 + + >>> handler.uninstall() + >>> logging.getLogger('zc.buildout.easy_install').propagate = True + +We can request that we get an error if versions are picked: + + >>> zc.buildout.easy_install.allow_picked_versions(False) + True + +(The old setting is returned.) + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... ) + Traceback (most recent call last): + ... + UserError: Picked: demo = 0.3 + + >>> zc.buildout.easy_install.allow_picked_versions(True) + False + +The function default_versions can be used to get and set default +version information to be used when no version information is passes. +If called with an argument, it sets the default versions: + + >>> zc.buildout.easy_install.default_versions(dict(demoneeded='1')) + {} + +It always returns the previous default versions. If called without an +argument, it simply returns the default versions without changing +them: + + >>> zc.buildout.easy_install.default_versions() + {'demoneeded': '1'} + +So with the default versions set, we'll get the requested version even +if the versions option isn't used: + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... ) + + >>> [d.version for d in ws] + ['0.3', '1.0'] + +Of course, we can unset the default versions by passing an empty +dictionary: + + >>> zc.buildout.easy_install.default_versions({}) + {'demoneeded': '1'} + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], index=link_server+'index/', + ... ) + + >>> [d.version for d in ws] + ['0.3', '1.1'] + +Dependencies in Site Packages +----------------------------- + +Paths outside of Python's standard library--or more precisely, those that are +not included when Python is started with the -S argument--are loosely referred +to as "site-packages" here. These site-packages are searched by default for +distributions. This can be disabled, so that, for instance, a system Python +can be used with buildout, cleaned of any packages installed by a user or +system package manager. + +The default behavior can be controlled and introspected using +zc.buildout.easy_install.include_site_packages. + + >>> zc.buildout.easy_install.include_site_packages() + True + +Here's an example of using a Python executable that includes our dependencies. + +Our "py_path" will have the "demoneeded," and "demo" packages available. + We'll simply be asking for "demoneeded" here, but without any external + index or links. + + >>> from zc.buildout.tests import create_sample_sys_install + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None) + >>> [dist.project_name for dist in workingset] + ['demoneeded'] + +That worked fine. Let's try again with site packages not allowed. We'll +change the policy by changing the default. Notice that the function for +changing the default value returns the previous value. + + >>> zc.buildout.easy_install.include_site_packages(False) + True + + >>> zc.buildout.easy_install.include_site_packages() + False + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None) + Traceback (most recent call last): + ... + MissingDistribution: Couldn't find a distribution for 'demoneeded'. + >>> zc.buildout.easy_install.clear_index_cache() + +Now we'll reset the default. + + >>> zc.buildout.easy_install.include_site_packages(True) + False + + >>> zc.buildout.easy_install.include_site_packages() + True + +Dependency links +---------------- + +Setuptools allows metadata that describes where to search for package +dependencies. This option is called dependency_links. Buildout has its +own notion of where to look for dependencies, but it also uses the +setup tools dependency_links information if it's available. + +Let's demo this by creating an egg that specifies dependency_links. + +To begin, let's create a new egg repository. This repository hold a +newer version of the 'demoneeded' egg than the sample repository does. + + >>> repoloc = tmpdir('repo') + >>> from zc.buildout.tests import create_egg + >>> create_egg('demoneeded', '1.2', repoloc) + >>> link_server2 = start_server(repoloc) + +Turn on logging on this server so that we can see when eggs are pulled +from it. + + >>> get(link_server2 + 'enable_server_logging') + GET 200 /enable_server_logging + '' + +Now we can create an egg that specifies that its dependencies are +found on this server. + + >>> repoloc = tmpdir('repo2') + >>> create_egg('hasdeps', '1.0', repoloc, + ... install_requires = "'demoneeded'", + ... dependency_links = [link_server2]) + +Let's add the egg to another repository. + + >>> link_server3 = start_server(repoloc) + +Now let's install the egg. + + >>> example_dest = tmpdir('example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['hasdeps'], example_dest, + ... links=[link_server3], index=link_server3+'index/') + GET 200 / + GET 200 /demoneeded-1.2-pyN.N.egg + +The server logs show that the dependency was retrieved from the server +specified in the dependency_links. + +Now let's see what happens if we provide two different ways to retrieve +the dependencies. + + >>> rmdir(example_dest) + >>> example_dest = tmpdir('example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['hasdeps'], example_dest, index=link_server+'index/', + ... links=[link_server, link_server3]) + GET 200 / + GET 200 /demoneeded-1.2-pyN.N.egg + +Once again the dependency is fetched from the logging server even +though it is also available from the non-logging server. This is +because the version on the logging server is newer and buildout +normally chooses the newest egg available. + +If you wish to control where dependencies come from regardless of +dependency_links setup metadata use the 'use_dependency_links' option +to zc.buildout.easy_install.install(). + + >>> rmdir(example_dest) + >>> example_dest = tmpdir('example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['hasdeps'], example_dest, index=link_server+'index/', + ... links=[link_server, link_server3], + ... use_dependency_links=False) + +Notice that this time the dependency egg is not fetched from the +logging server. When you specify not to use dependency_links, eggs +will only be searched for using the links you explicitly provide. + +Another way to control this option is with the +zc.buildout.easy_install.use_dependency_links() function. This +function sets the default behavior for the zc.buildout.easy_install() +function. + + >>> zc.buildout.easy_install.use_dependency_links(False) + True + +The function returns its previous setting. + + >>> rmdir(example_dest) + >>> example_dest = tmpdir('example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['hasdeps'], example_dest, index=link_server+'index/', + ... links=[link_server, link_server3]) + +It can be overridden by passing a keyword argument to the install +function. + + >>> rmdir(example_dest) + >>> example_dest = tmpdir('example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['hasdeps'], example_dest, index=link_server+'index/', + ... links=[link_server, link_server3], + ... use_dependency_links=True) + GET 200 /demoneeded-1.2-pyN.N.egg + +To return the dependency_links behavior to normal call the function again. + + >>> zc.buildout.easy_install.use_dependency_links(True) + False + >>> rmdir(example_dest) + >>> example_dest = tmpdir('example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['hasdeps'], example_dest, index=link_server+'index/', + ... links=[link_server, link_server3]) + GET 200 /demoneeded-1.2-pyN.N.egg + + +Script generation +----------------- + +The easy_install module provides support for creating scripts from eggs. +It provides two competing functions. One, ``scripts``, is a +well-established approach to generating reliable scripts with a "clean" +Python--e.g., one that does not have any packages in its site-packages. +The other, ``sitepackage_safe_scripts``, is newer, a bit trickier, and is +designed to work with a Python that has code in its site-packages, such +as a system Python. + +Both are similar to setuptools except that they provides facilities for +baking a script's path into the script. This has two advantages: + +- The eggs to be used by a script are not chosen at run time, making + startup faster and, more importantly, deterministic. + +- The script doesn't have to import pkg_resources because the logic that + pkg_resources would execute at run time is executed at script-creation + time. (There is an exception in ``sitepackage_safe_scripts`` if you + want to have your Python's site packages available, as discussed + below, but even in that case pkg_resources is only partially + activated, which can be a significant time savings.) + + +The ``scripts`` function +~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``scripts`` function is the first way to generate scripts that we'll +examine. It is the earlier approach that the package offered. Let's +create a destination directory for it to place them in: + + >>> bin = tmpdir('bin') + +Now, we'll use the scripts function to generate scripts in this directory +from the demo egg: + + >>> import sys + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, bin) + +the four arguments we passed were: + +1. A sequence of distribution requirements. These are of the same + form as setuptools requirements. Here we passed a single + requirement, for the version 0.1 demo distribution. + +2. A working set, + +3. The Python executable to use, and + +3. The destination directory. + +The bin directory now contains a generated script: + + >>> ls(bin) + - demo + +The return value is a list of the scripts generated: + + >>> import os, sys + >>> if sys.platform == 'win32': + ... scripts == [os.path.join(bin, 'demo.exe'), + ... os.path.join(bin, 'demo-script.py')] + ... else: + ... scripts == [os.path.join(bin, 'demo')] + True + +Note that in Windows, 2 files are generated for each script. A script +file, ending in '-script.py', and an exe file that allows the script +to be invoked directly without having to specify the Python +interpreter and without having to provide a '.py' suffix. + +The demo script run the entry point defined in the demo egg: + + >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + ] + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main() + +Some things to note: + +- The demo and demoneeded eggs are added to the beginning of sys.path. + +- The module for the script entry point is imported and the entry + point, in this case, 'main', is run. + +Rather than requirement strings, you can pass tuples containing 3 +strings: + + - A script name, + + - A module, + + - An attribute expression for an entry point within the module. + +For example, we could have passed entry point information directly +rather than passing a requirement: + + >>> scripts = zc.buildout.easy_install.scripts( + ... [('demo', 'eggrecipedemo', 'main')], + ... ws, sys.executable, bin) + + >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + ] + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main() + +Passing entry-point information directly is handy when using eggs (or +distributions) that don't declare their entry points, such as +distributions that aren't based on setuptools. + +The interpreter keyword argument can be used to generate a script that can +be used to invoke the Python interactive interpreter with the path set +based on the working set. This generated script can also be used to +run other scripts with the path set on the working set: + + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, bin, interpreter='py') + + + >>> ls(bin) + - demo + - py + + >>> if sys.platform == 'win32': + ... scripts == [os.path.join(bin, 'demo.exe'), + ... os.path.join(bin, 'demo-script.py'), + ... os.path.join(bin, 'py.exe'), + ... os.path.join(bin, 'py-script.py')] + ... else: + ... scripts == [os.path.join(bin, 'demo'), + ... os.path.join(bin, 'py')] + True + +The py script simply runs the Python interactive interpreter with +the path set: + + >>> cat(bin, 'py') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + <BLANKLINE> + import sys + <BLANKLINE> + sys.path[0:0] = [ + '/sample-install/demo-0.3-pyN.N.egg', + '/sample-install/demoneeded-1.1-pyN.N.egg', + ] + <BLANKLINE> + _interactive = True + if len(sys.argv) > 1: + _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:') + _interactive = False + for (_opt, _val) in _options: + if _opt == '-i': + _interactive = True + elif _opt == '-c': + exec _val + elif _opt == '-m': + sys.argv[1:] = _args + _args = [] + __import__("runpy").run_module( + _val, {}, "__main__", alter_sys=True) + <BLANKLINE> + if _args: + sys.argv[:] = _args + __file__ = _args[0] + del _options, _args + execfile(__file__) + <BLANKLINE> + if _interactive: + del _interactive + __import__("code").interact(banner="", local=globals()) + +If invoked with a script name and arguments, it will run that script, instead. + + >>> write('ascript', ''' + ... "demo doc" + ... print sys.argv + ... print (__name__, __file__, __doc__) + ... ''') + >>> print system(join(bin, 'py')+' ascript a b c'), + ['ascript', 'a', 'b', 'c'] + ('__main__', 'ascript', 'demo doc') + +For Python 2.5 and higher, you can also use the -m option to run a +module: + + >>> print system(join(bin, 'py')+' -m pdb'), + usage: pdb.py scriptfile [arg] ... + + >>> print system(join(bin, 'py')+' -m pdb what'), + Error: what does not exist + +An additional argument can be passed to define which scripts to install +and to provide script names. The argument is a dictionary mapping +original script names to new script names. + + >>> bin = tmpdir('bin2') + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, bin, dict(demo='run')) + + >>> if sys.platform == 'win32': + ... scripts == [os.path.join(bin, 'run.exe'), + ... os.path.join(bin, 'run-script.py')] + ... else: + ... scripts == [os.path.join(bin, 'run')] + True + >>> ls(bin) + - run + + >>> print system(os.path.join(bin, 'run')), + 3 1 + +The ``scripts`` function: Including extra paths in scripts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We can pass a keyword argument, extra paths, to cause additional paths +to be included in the a generated script: + + >>> foo = tmpdir('foo') + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, bin, dict(demo='run'), + ... extra_paths=[foo]) + + >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + '/foo', + ] + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main() + +The ``scripts`` function: Providing script arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An "argument" keyword argument can be used to pass arguments to an +entry point. The value passed is a source string to be placed between the +parentheses in the call: + + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, bin, dict(demo='run'), + ... arguments='1, 2') + + >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + ] + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main(1, 2) + +The ``scripts`` function: Passing initialization code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also pass script initialization code: + + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, bin, dict(demo='run'), + ... arguments='1, 2', + ... initialization='import os\nos.chdir("foo")') + + >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + ] + <BLANKLINE> + import os + os.chdir("foo") + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main(1, 2) + +The ``scripts`` function: Relative paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, you want to be able to move a buildout directory around and +have scripts still work without having to rebuild them. We can +control this using the relative_paths option to install. You need +to pass a common base directory of the scripts and eggs: + + >>> bo = tmpdir('bo') + >>> ba = tmpdir('ba') + >>> mkdir(bo, 'eggs') + >>> mkdir(bo, 'bin') + >>> mkdir(bo, 'other') + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], join(bo, 'eggs'), links=[link_server], + ... index=link_server+'index/') + + >>> scripts = zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, join(bo, 'bin'), dict(demo='run'), + ... extra_paths=[ba, join(bo, 'bar')], + ... interpreter='py', + ... relative_paths=bo) + + >>> cat(bo, 'bin', 'run') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + <BLANKLINE> + import os + <BLANKLINE> + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + <BLANKLINE> + import sys + sys.path[0:0] = [ + join(base, 'eggs/demo-0.3-pyN.N.egg'), + join(base, 'eggs/demoneeded-1.1-pyN.N.egg'), + '/ba', + join(base, 'bar'), + ] + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main() + +Note that the extra path we specified that was outside the directory +passed as relative_paths wasn't converted to a relative path. + +Of course, running the script works: + + >>> print system(join(bo, 'bin', 'run')), + 3 1 + +We specified an interpreter and its paths are adjusted too: + + >>> cat(bo, 'bin', 'py') # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 + <BLANKLINE> + import os + <BLANKLINE> + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + <BLANKLINE> + import sys + <BLANKLINE> + sys.path[0:0] = [ + join(base, 'eggs/demo-0.3-pyN.N.egg'), + join(base, 'eggs/demoneeded-1.1-pyN.N.egg'), + '/ba', + join(base, 'bar'), + ] + <BLANKLINE> + _interactive = True + if len(sys.argv) > 1: + _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:') + _interactive = False + for (_opt, _val) in _options: + if _opt == '-i': + _interactive = True + elif _opt == '-c': + exec _val + elif _opt == '-m': + sys.argv[1:] = _args + _args = [] + __import__("runpy").run_module( + _val, {}, "__main__", alter_sys=True) + <BLANKLINE> + if _args: + sys.argv[:] = _args + __file__ = _args[0] + del _options, _args + execfile(__file__) + <BLANKLINE> + if _interactive: + del _interactive + __import__("code").interact(banner="", local=globals()) + +The ``sitepackage_safe_scripts`` function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The newer function for creating scripts is ``sitepackage_safe_scripts``. +It has the same basic functionality as the ``scripts`` function: it can +create scripts to run arbitrary entry points, and to run a Python +interpreter. The following are the differences from a user's +perspective. + +- It can be used safely with a Python that has packages installed itself, + such as a system-installed Python. + +- In contrast to the interpreter generated by the ``scripts`` method, which + supports only a small subset of the usual Python executable's options, + the interpreter generated by ``sitepackage_safe_scripts`` supports all + of them. This makes it possible to use as full Python replacement for + scripts that need the distributions specified in your buildout. + +- Both the interpreter and the entry point scripts allow you to include the + site packages, and/or the sitecustomize, of the Python executable, if + desired. + +It works by creating site.py and sitecustomize.py files that set up the +desired paths and initialization. These must be placed within an otherwise +empty directory. Typically this is in a recipe's parts directory. + +Here's the simplest example, building an interpreter script. + + >>> interpreter_dir = tmpdir('interpreter') + >>> interpreter_parts_dir = os.path.join( + ... interpreter_dir, 'parts', 'interpreter') + >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin') + >>> mkdir(interpreter_bin_dir) + >>> mkdir(interpreter_dir, 'eggs') + >>> mkdir(interpreter_dir, 'parts') + >>> mkdir(interpreter_parts_dir) + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], join(interpreter_dir, 'eggs'), links=[link_server], + ... index=link_server+'index/') + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py') + +Depending on whether the machine being used is running Windows or not, this +produces either three or four files. In both cases, we have site.py and +sitecustomize.py generated in the parts/interpreter directory. For Windows, +we have py.exe and py-script.py; for other operating systems, we have py. + + >>> sitecustomize_path = os.path.join( + ... interpreter_parts_dir, 'sitecustomize.py') + >>> site_path = os.path.join(interpreter_parts_dir, 'site.py') + >>> interpreter_path = os.path.join(interpreter_bin_dir, 'py') + >>> if sys.platform == 'win32': + ... py_path = os.path.join(interpreter_bin_dir, 'py-script.py') + ... expected = [sitecustomize_path, + ... site_path, + ... os.path.join(interpreter_bin_dir, 'py.exe'), + ... py_path] + ... else: + ... py_path = interpreter_path + ... expected = [sitecustomize_path, site_path, py_path] + ... + >>> assert generated == expected, repr((generated, expected)) + +We didn't ask for any initialization, and we didn't ask to use the underlying +sitecustomization, so sitecustomize.py is empty. + + >>> cat(sitecustomize_path) + +The interpreter script is simple. It puts the directory with the +site.py and sitecustomize.py on the PYTHONPATH and (re)starts Python. + + >>> cat(py_path) + #!/usr/bin/python -S + import os + import sys + <BLANKLINE> + argv = [sys.executable] + sys.argv[1:] + environ = os.environ.copy() + path = '/interpreter/parts/interpreter' + if environ.get('PYTHONPATH'): + path = os.pathsep.join([path, environ['PYTHONPATH']]) + environ['PYTHONPATH'] = path + os.execve(sys.executable, argv, environ) + +The site.py file is a modified version of the underlying Python's site.py. +The most important modification is that it has a different version of the +addsitepackages function. It sets up the Python path, similarly to the +behavior of the function it replaces. The following shows the part that +buildout inserts, in the simplest case. + + >>> sys.stdout.write('#\n'); cat(site_path) + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + #... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + buildout_paths = [ + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg' + ] + for path in buildout_paths: + sitedir, sitedircase = makepath(path) + if not sitedircase in known_paths and os.path.exists(sitedir): + sys.path.append(sitedir) + known_paths.add(sitedircase) + return known_paths + <BLANKLINE> + def original_addsitepackages(known_paths):... + +Here are some examples of the interpreter in use. + + >>> print call_py(interpreter_path, "print 16+26") + 42 + <BLANKLINE> + >>> res = call_py(interpreter_path, "import sys; print sys.path") + >>> print res # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ['', + '/interpreter/parts/interpreter', + ..., + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] + <BLANKLINE> + >>> clean_paths = eval(res.strip()) # This is used later for comparison. + +If you provide initialization, it goes in sitecustomize.py. + + >>> def reset_interpreter(): + ... # This is necessary because, in our tests, the timestamps of the + ... # .pyc files are not outdated when we want them to be. + ... rmdir(interpreter_bin_dir) + ... mkdir(interpreter_bin_dir) + ... rmdir(interpreter_parts_dir) + ... mkdir(interpreter_parts_dir) + ... + >>> reset_interpreter() + + >>> initialization_string = """\ + ... import os + ... os.environ['FOO'] = 'bar baz bing shazam'""" + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py', initialization=initialization_string) + >>> cat(sitecustomize_path) + import os + os.environ['FOO'] = 'bar baz bing shazam' + >>> print call_py(interpreter_path, "import os; print os.environ['FOO']") + bar baz bing shazam + <BLANKLINE> + +If you use relative paths, this affects the interpreter and site.py. (This is +again the UNIX version; the Windows version uses subprocess instead of +os.execve.) + + >>> reset_interpreter() + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py', relative_paths=interpreter_dir) + >>> cat(py_path) + #!/usr/bin/python -S + import os + import sys + <BLANKLINE> + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + <BLANKLINE> + argv = [sys.executable] + sys.argv[1:] + environ = os.environ.copy() + path = join(base, 'parts/interpreter') + if environ.get('PYTHONPATH'): + path = os.pathsep.join([path, environ['PYTHONPATH']]) + environ['PYTHONPATH'] = path + os.execve(sys.executable, argv, environ) + +For site.py, we again show only the pertinent parts. Notice that the egg +paths join a base to a path, as with the use of this argument in the +``scripts`` function. + + >>> sys.stdout.write('#\n'); cat(site_path) # doctest: +ELLIPSIS + #... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + base = os.path.dirname(base) + buildout_paths = [ + join(base, 'eggs/demo-0.3-pyN.N.egg'), + join(base, 'eggs/demoneeded-1.1-pyN.N.egg') + ]... + +The paths resolve in practice as you would expect. + + >>> print call_py(interpreter_path, + ... "import sys, pprint; pprint.pprint(sys.path)") + ... # doctest: +ELLIPSIS + ['', + '/interpreter/parts/interpreter', + ..., + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] + <BLANKLINE> + +The ``extra_paths`` argument affects the path in site.py. Notice that +/interpreter/other is added after the eggs. + + >>> reset_interpreter() + >>> mkdir(interpreter_dir, 'other') + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py', extra_paths=[join(interpreter_dir, 'other')]) + >>> sys.stdout.write('#\n'); cat(site_path) # doctest: +ELLIPSIS + #... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + buildout_paths = [ + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', + '/interpreter/other' + ]... + + >>> print call_py(interpreter_path, + ... "import sys, pprint; pprint.pprint(sys.path)") + ... # doctest: +ELLIPSIS + ['', + '/interpreter/parts/interpreter', + ..., + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', + '/interpreter/other'] + <BLANKLINE> + +The ``sitepackage_safe_scripts`` function: using site-packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``sitepackage_safe_scripts`` function supports including site +packages. This has some advantages and some serious dangers. + +A typical reason to include site-packages is that it is easier to +install one or more dependencies in your Python than it is with +buildout. Some packages, such as lxml or Python PostgreSQL integration, +have dependencies that can be much easier to build and/or install using +other mechanisms, such as your operating system's package manager. By +installing some core packages into your Python's site-packages, this can +significantly simplify some application installations. + +However, doing this has a significant danger. One of the primary goals +of buildout is to provide repeatability. Some packages (one of the +better known Python openid packages, for instance) change their behavior +depending on what packages are available. If Python curl bindings are +available, these may be preferred by the library. If a certain XML +package is installed, it may be preferred by the library. These hidden +choices may cause small or large behavior differences. The fact that +they can be rarely encountered can actually make it worse: you forget +that this might be a problem, and debugging the differences can be +difficult. If you allow site-packages to be included in your buildout, +and the Python you use is not managed precisely by your application (for +instance, it is a system Python), you open yourself up to these +possibilities. Don't be unaware of the dangers. + +That explained, let's see how it works. If you don't use namespace packages, +this is very straightforward. + + >>> reset_interpreter() + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py', include_site_packages=True) + >>> sys.stdout.write('#\n'); cat(site_path) + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + #... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + setuptools_path = None + buildout_paths = [ + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg' + ] + for path in buildout_paths: + sitedir, sitedircase = makepath(path) + if not sitedircase in known_paths and os.path.exists(sitedir): + sys.path.append(sitedir) + known_paths.add(sitedircase) + sys.__egginsert = len(buildout_paths) # Support distribute. + original_paths = [ + ... + ] + for path in original_paths: + if path == setuptools_path or path not in known_paths: + addsitedir(path, known_paths) + return known_paths + <BLANKLINE> + def original_addsitepackages(known_paths):... + +It simply adds the original paths using addsitedir after the code to add the +buildout paths. + +Here's an example of the new script in use. Other documents and tests in +this package give the feature a more thorough workout, but this should +give you an idea of the feature. + + >>> res = call_py(interpreter_path, "import sys; print sys.path") + >>> print res # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ['', + '/interpreter/parts/interpreter', + ..., + '/interpreter/eggs/demo-0.3-py2.4.egg', + '/interpreter/eggs/demoneeded-1.1-py2.4.egg', + ...] + <BLANKLINE> + +The clean_paths gathered earlier is a subset of this full list of paths. + + >>> full_paths = eval(res.strip()) + >>> len(clean_paths) < len(full_paths) + True + >>> set(os.path.normpath(p) for p in clean_paths).issubset( + ... os.path.normpath(p) for p in full_paths) + True + +Unfortunately, because of how setuptools namespace packages are implemented +differently for operating system packages (debs or rpms) as opposed to +standard setuptools installation, there's a slightly trickier dance if you +use them. To show this we'll needs some extra eggs that use namespaces. +We'll use the ``tellmy.fortune`` package, which we'll need to make an initial +call to another text fixture to create. + + >>> from zc.buildout.tests import create_sample_namespace_eggs + >>> namespace_eggs = tmpdir('namespace_eggs') + >>> create_sample_namespace_eggs(namespace_eggs) + + >>> reset_interpreter() + >>> ws = zc.buildout.easy_install.install( + ... ['demo', 'tellmy.fortune'], join(interpreter_dir, 'eggs'), + ... links=[link_server, namespace_eggs], index=link_server+'index/') + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py', include_site_packages=True) + >>> sys.stdout.write('#\n'); cat(site_path) + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + #... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + setuptools_path = '...setuptools...' + sys.path.append(setuptools_path) + known_paths.add(os.path.normcase(setuptools_path)) + import pkg_resources + buildout_paths = [ + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/tellmy.fortune-1.0-pyN.N.egg', + '...setuptools...', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg' + ] + for path in buildout_paths: + sitedir, sitedircase = makepath(path) + if not sitedircase in known_paths and os.path.exists(sitedir): + sys.path.append(sitedir) + known_paths.add(sitedircase) + pkg_resources.working_set.add_entry(sitedir) + sys.__egginsert = len(buildout_paths) # Support distribute. + original_paths = [ + ... + ] + for path in original_paths: + if path == setuptools_path or path not in known_paths: + addsitedir(path, known_paths) + return known_paths + <BLANKLINE> + def original_addsitepackages(known_paths):... + + >>> print call_py(interpreter_path, "import sys; print sys.path") + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ['', + '/interpreter/parts/interpreter', + ..., + '...setuptools...', + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/tellmy.fortune-1.0-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', + ...] + +As you can see, the script now first imports pkg_resources. Then we +need to process egg files specially to look for namespace packages there +*before* we process process lines in .pth files that use the "import" +feature--lines that might be part of the setuptools namespace package +implementation for system packages, as mentioned above, and that must +come after processing egg namespaces. + +The most complex that this function gets is if you use namespace packages, +include site-packages, and use relative paths. For completeness, we'll look +at that result. + + >>> reset_interpreter() + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... interpreter='py', include_site_packages=True, + ... relative_paths=interpreter_dir) + >>> sys.stdout.write('#\n'); cat(site_path) + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + #... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + base = os.path.dirname(base) + setuptools_path = '...setuptools...' + sys.path.append(setuptools_path) + known_paths.add(os.path.normcase(setuptools_path)) + import pkg_resources + buildout_paths = [ + join(base, 'eggs/demo-0.3-pyN.N.egg'), + join(base, 'eggs/tellmy.fortune-1.0-pyN.N.egg'), + '...setuptools...', + join(base, 'eggs/demoneeded-1.1-pyN.N.egg') + ] + for path in buildout_paths: + sitedir, sitedircase = makepath(path) + if not sitedircase in known_paths and os.path.exists(sitedir): + sys.path.append(sitedir) + known_paths.add(sitedircase) + pkg_resources.working_set.add_entry(sitedir) + sys.__egginsert = len(buildout_paths) # Support distribute. + original_paths = [ + ... + ] + for path in original_paths: + if path == setuptools_path or path not in known_paths: + addsitedir(path, known_paths) + return known_paths + <BLANKLINE> + def original_addsitepackages(known_paths):... + + >>> print call_py(interpreter_path, "import sys; print sys.path") + ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + ['', + '/interpreter/parts/interpreter', + ..., + '...setuptools...', + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/tellmy.fortune-1.0-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', + ...] + +The ``exec_sitecustomize`` argument does the same thing for the +sitecustomize module--it allows you to include the code from the +sitecustomize module in the underlying Python if you set the argument to +True. The z3c.recipe.scripts package sets up the full environment necessary +to demonstrate this piece. + +The ``sitepackage_safe_scripts`` function: writing scripts for entry points +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All of the examples so far for this function have been creating +interpreters. The function can also write scripts for entry +points. They are almost identical to the scripts that we saw for the +``scripts`` function except that they ``import site`` after setting the +sys.path to include our custom site.py and sitecustomize.py files. These +files then initialize the Python environment as we have already seen. Let's +see a simple example. + + >>> reset_interpreter() + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], join(interpreter_dir, 'eggs'), links=[link_server], + ... index=link_server+'index/') + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... reqs=['demo']) + +As before, in Windows, 2 files are generated for each script. A script +file, ending in '-script.py', and an exe file that allows the script +to be invoked directly without having to specify the Python +interpreter and without having to provide a '.py' suffix. This is in addition +to the site.py and sitecustomize.py files that are generated as with our +interpreter examples above. + + >>> if sys.platform == 'win32': + ... demo_path = os.path.join(interpreter_bin_dir, 'demo-script.py') + ... expected = [sitecustomize_path, + ... site_path, + ... os.path.join(interpreter_bin_dir, 'demo.exe'), + ... demo_path] + ... else: + ... demo_path = os.path.join(interpreter_bin_dir, 'demo') + ... expected = [sitecustomize_path, site_path, demo_path] + ... + >>> assert generated == expected, repr((generated, expected)) + +The demo script runs the entry point defined in the demo egg: + + >>> cat(demo_path) # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 -S + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/interpreter/parts/interpreter', + ] + <BLANKLINE> + <BLANKLINE> + import os + path = sys.path[0] + if os.environ.get('PYTHONPATH'): + path = os.pathsep.join([path, os.environ['PYTHONPATH']]) + os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + os.environ['PYTHONPATH'] = path + import site # imports custom buildout-generated site.py + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main() + + >>> demo_call = join(interpreter_bin_dir, 'demo') + >>> if sys.platform == 'win32': + ... demo_call = '"%s"' % demo_call + >>> print system(demo_call) + 3 1 + <BLANKLINE> + +There are a few differences from the ``scripts`` function. First, the +``reqs`` argument (an iterable of string requirements or entry point +tuples) is a keyword argument here. We see that in the example above. +Second, the ``arguments`` argument is now named ``script_arguments`` to +try and clarify that it does not affect interpreters. While the +``initialization`` argument continues to affect both the interpreters +and the entry point scripts, if you have initialization that is only +pertinent to the entry point scripts, you can use the +``script_initialization`` argument. + +Let's see ``script_arguments`` and ``script_initialization`` in action. + + >>> reset_interpreter() + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... reqs=['demo'], script_arguments='1, 2', + ... script_initialization='import os\nos.chdir("foo")') + + >>> cat(demo_path) # doctest: +NORMALIZE_WHITESPACE + #!/usr/local/bin/python2.4 -S + import sys + sys.path[0:0] = [ + '/interpreter/parts/interpreter', + ] + <BLANKLINE> + import os + path = sys.path[0] + if os.environ.get('PYTHONPATH'): + path = os.pathsep.join([path, os.environ['PYTHONPATH']]) + os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '') + os.environ['PYTHONPATH'] = path + import site # imports custom buildout-generated site.py + import os + os.chdir("foo") + <BLANKLINE> + import eggrecipedemo + <BLANKLINE> + if __name__ == '__main__': + eggrecipedemo.main(1, 2) + +Handling custom build options for extensions provided in source distributions +----------------------------------------------------------------------------- + +Sometimes, we need to control how extension modules are built. The +build function provides this level of control. It takes a single +package specification, downloads a source distribution, and builds it +with specified custom build options. + +The build function takes 3 positional arguments: + +spec + A package specification for a source distribution + +dest + A destination directory + +build_ext + A dictionary of options to be passed to the distutils build_ext + command when building extensions. + +It supports a number of optional keyword arguments: + +links + a sequence of URLs, file names, or directories to look for + links to distributions, + +index + The URL of an index server, or almost any other valid URL. :) + + If not specified, the Python Package Index, + http://pypi.python.org/simple/, is used. You can specify an + alternate index with this option. If you use the links option and + if the links point to the needed distributions, then the index can + be anything and will be largely ignored. In the examples, here, + we'll just point to an empty directory on our link server. This + will make our examples run a little bit faster. + +executable + A path to a Python executable. Distributions will be installed + using this executable and will be for the matching Python version. + +path + A list of additional directories to search for locally-installed + distributions. + +newest + A boolean value indicating whether to search for new distributions + when already-installed distributions meet the requirement. When + this is true, the default, and when the destination directory is + 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:: + + #include <Python.h> + #include <extdemo.h> + + static PyMethodDef methods[] = {}; + + PyMODINIT_FUNC + initextdemo(void) + { + PyObject *m; + m = Py_InitModule3("extdemo", methods, ""); + #ifdef TWO + PyModule_AddObject(m, "val", PyInt_FromLong(2)); + #else + PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO)); + #endif + } + +The extension depends on a system-dependent include file, extdemo.h, +that defines a constant, EXTDEMO, that is exposed by the extension. + +We'll add an include directory to our sample buildout and add the +needed include file to it: + + >>> mkdir('include') + >>> write('include', 'extdemo.h', + ... """ + ... #define EXTDEMO 42 + ... """) + +Now, we can use the build function to create an egg from the source +distribution: + + >>> zc.buildout.easy_install.build( + ... 'extdemo', dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}, + ... links=[link_server], index=link_server+'index/') + ['/sample-install/extdemo-1.4-py2.4-unix-i686.egg'] + +The function returns the list of eggs + +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 + +Let's update our link server with a new version of extdemo: + + >>> update_extdemo() + >>> print get(link_server), + <html><body> + <a href="bigdemo-0.1-py2.4.egg">bigdemo-0.1-py2.4.egg</a><br> + <a href="demo-0.1-py2.4.egg">demo-0.1-py2.4.egg</a><br> + <a href="demo-0.2-py2.4.egg">demo-0.2-py2.4.egg</a><br> + <a href="demo-0.3-py2.4.egg">demo-0.3-py2.4.egg</a><br> + <a href="demo-0.4c1-py2.4.egg">demo-0.4c1-py2.4.egg</a><br> + <a href="demoneeded-1.0.zip">demoneeded-1.0.zip</a><br> + <a href="demoneeded-1.1.zip">demoneeded-1.1.zip</a><br> + <a href="demoneeded-1.2c1.zip">demoneeded-1.2c1.zip</a><br> + <a href="extdemo-1.4.zip">extdemo-1.4.zip</a><br> + <a href="extdemo-1.5.zip">extdemo-1.5.zip</a><br> + <a href="index/">index/</a><br> + <a href="other-1.0-py2.4.egg">other-1.0-py2.4.egg</a><br> + </body></html> + +The easy_install caches information about servers to reduce network +access. To see the update, we have to call the clear_index_cache +function to clear the index cache: + + >>> zc.buildout.easy_install.clear_index_cache() + +If we run build with newest set to False, we won't get an update: + + >>> zc.buildout.easy_install.build( + ... 'extdemo', dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}, + ... links=[link_server], index=link_server+'index/', + ... newest=False) + ['/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 + +But if we run it with the default True setting for newest, then we'll +get an updated egg: + + >>> zc.buildout.easy_install.build( + ... 'extdemo', dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}, + ... 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 destination 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 +------------------------------------------------------------ + +The develop function is similar to the build function, except that, +rather than building an egg from a source directory containing a +setup.py script. + +The develop function takes 2 positional arguments: + +setup + The path to a setup script, typically named "setup.py", or a + directory containing a setup.py script. + +dest + The directory to install the egg link to + +It supports some optional keyword argument: + +build_ext + A dictionary of options to be passed to the distutils build_ext + command when building extensions. + +executable + A path to a Python executable. Distributions will be installed + using this executable and will be for the matching Python version. + +We have a local directory containing the extdemo source: + + >>> ls(extdemo) + - MANIFEST + - MANIFEST.in + - README + - extdemo.c + - setup.py + +Now, we can use the develop function to create a develop egg from the source +distribution: + + >>> zc.buildout.easy_install.develop( + ... extdemo, dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}) + '/sample-install/extdemo.egg-link' + +The name of the egg link created is returned. + +Now if we look in our destination directory, we see we have an extdemo +egg link: + + >>> ls(dest) + d extdemo-1.4-py2.4-unix-i686.egg + - extdemo.egg-link + +And that the source directory contains the compiled extension: + + >>> ls(extdemo) + - MANIFEST + - MANIFEST.in + - README + d build + - extdemo.c + d extdemo.egg-info + - extdemo.so + - setup.py + +Download cache +-------------- + +Normally, when distributions are installed, if any processing is +needed, they are downloaded from the internet to a temporary directory +and then installed from there. A download cache can be used to avoid +the download step. This can be useful to reduce network access and to +create source distributions of an entire buildout. + +A download cache is specified by calling the download_cache +function. The function always returns the previous setting. If no +argument is passed, then the setting is unchanged. If an argument is +passed, the download cache is set to the given path, which must point +to an existing directory. Passing None clears the cache setting. + +To see this work, we'll create a directory and set it as the cache +directory: + + >>> cache = tmpdir('cache') + >>> zc.buildout.easy_install.download_cache(cache) + +We'll recreate our destination directory: + + >>> remove(dest) + >>> dest = tmpdir('sample-install') + +We'd like to see what is being fetched from the server, so we'll +enable server logging: + + >>> get(link_server+'enable_server_logging') + GET 200 /enable_server_logging + '' + +Now, if we install demo, and extdemo: + + >>> ws = zc.buildout.easy_install.install( + ... ['demo==0.2'], dest, + ... links=[link_server], index=link_server+'index/', + ... always_unzip=True) + GET 200 / + GET 404 /index/demo/ + GET 200 /index/ + GET 200 /demo-0.2-py2.4.egg + GET 404 /index/demoneeded/ + GET 200 /demoneeded-1.1.zip + + >>> zc.buildout.easy_install.build( + ... 'extdemo', dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}, + ... links=[link_server], index=link_server+'index/') + GET 404 /index/extdemo/ + GET 200 /extdemo-1.5.zip + ['/sample-install/extdemo-1.5-py2.4-linux-i686.egg'] + +Not only will we get eggs in our destination directory: + + >>> ls(dest) + d demo-0.2-py2.4.egg + d demoneeded-1.1-py2.4.egg + d extdemo-1.5-py2.4-linux-i686.egg + +But we'll get distributions in the cache directory: + + >>> ls(cache) + - demo-0.2-py2.4.egg + - demoneeded-1.1.zip + - extdemo-1.5.zip + +The cache directory contains uninstalled distributions, such as zipped +eggs or source distributions. + +Let's recreate our destination directory and clear the index cache: + + >>> remove(dest) + >>> dest = tmpdir('sample-install') + >>> zc.buildout.easy_install.clear_index_cache() + +Now when we install the distributions: + + >>> ws = zc.buildout.easy_install.install( + ... ['demo==0.2'], dest, + ... links=[link_server], index=link_server+'index/', + ... always_unzip=True) + GET 200 / + GET 404 /index/demo/ + GET 200 /index/ + GET 404 /index/demoneeded/ + + >>> zc.buildout.easy_install.build( + ... 'extdemo', dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}, + ... links=[link_server], index=link_server+'index/') + GET 404 /index/extdemo/ + ['/sample-install/extdemo-1.5-py2.4-linux-i686.egg'] + + >>> ls(dest) + d demo-0.2-py2.4.egg + d demoneeded-1.1-py2.4.egg + d extdemo-1.5-py2.4-linux-i686.egg + +Note that we didn't download the distributions from the link server. + +If we remove the restriction on demo, we'll download a newer version +from the link server: + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, + ... links=[link_server], index=link_server+'index/', + ... always_unzip=True) + GET 200 /demo-0.3-py2.4.egg + +Normally, the download cache is the preferred source of downloads, but +not the only one. + +Installing solely from a download cache +--------------------------------------- + +A download cache can be used as the basis of application source +releases. In an application source release, we want to distribute an +application that can be built without making any network accesses. In +this case, we distribute a download cache and tell the easy_install +module to install from the download cache only, without making network +accesses. The install_from_cache function can be used to signal that +packages should be installed only from the download cache. The +function always returns the previous setting. Calling it with no +arguments returns the current setting without changing it: + + >>> zc.buildout.easy_install.install_from_cache() + False + +Calling it with a boolean value changes the setting and returns the +previous setting: + + >>> zc.buildout.easy_install.install_from_cache(True) + False + +Let's remove demo-0.3-py2.4.egg from the cache, clear the index cache, +recreate the destination directory, and reinstall demo: + + >>> for f in os.listdir(cache): + ... if f.startswith('demo-0.3-'): + ... remove(cache, f) + + >>> zc.buildout.easy_install.clear_index_cache() + >>> remove(dest) + >>> dest = tmpdir('sample-install') + + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, + ... links=[link_server], index=link_server+'index/', + ... always_unzip=True) + + >>> ls(dest) + d demo-0.2-py2.4.egg + d demoneeded-1.1-py2.4.egg + +This time, we didn't download from or even query the link server. + +.. Disable the download cache: + + >>> zc.buildout.easy_install.download_cache(None) + '/cache' + + >>> zc.buildout.easy_install.install_from_cache(False) + True diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/extends-cache.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/extends-cache.txt new file mode 100644 index 0000000..5a0fbce --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/extends-cache.txt @@ -0,0 +1,399 @@ +Caching extended configuration +============================== + +As mentioned in the general buildout documentation, configuration files can +extend each other, including the ability to download configuration being +extended from a URL. If desired, zc.buildout caches downloaded configuration +in order to be able to use it when run offline. + +As we're going to talk about downloading things, let's start an HTTP server. +Also, all of the following will take place inside the sample buildout. + +>>> server_data = tmpdir('server_data') +>>> server_url = start_server(server_data) +>>> cd(sample_buildout) + +We also use a fresh directory for temporary files in order to make sure that +all temporary files have been cleaned up in the end: + +>>> import tempfile +>>> old_tempdir = tempfile.tempdir +>>> tempfile.tempdir = tmpdir('tmp') + + +Basic use of the extends cache +------------------------------ + +We put some base configuration on a server and reference it from a sample +buildout: + +>>> write(server_data, 'base.cfg', """\ +... [buildout] +... parts = +... foo = bar +... """) + +>>> write('buildout.cfg', """\ +... [buildout] +... extends = %sbase.cfg +... """ % server_url) + +When trying to run this buildout offline, we'll find that we cannot read all +of the required configuration: + +>>> print system(buildout + ' -o') +While: + Initializing. +Error: Couldn't download 'http://localhost/base.cfg' in offline mode. + +Trying the same online, we can: + +>>> print system(buildout) +Unused options for buildout: 'foo'. + +As long as we haven't said anything about caching downloaded configuration, +nothing gets cached. Offline mode will still cause the buildout to fail: + +>>> print system(buildout + ' -o') +While: + Initializing. +Error: Couldn't download 'http://localhost/base.cfg' in offline mode. + +Let's now specify a cache for base configuration files. This cache is +different from the download cache used by recipes for caching distributions +and other files; one might, however, use a namespace subdirectory of the +download cache for it. The configuration cache we specify will be created when +running buildout and the base.cfg file will be put in it (with the file name +being a hash of the complete URL): + +>>> mkdir('cache') +>>> write('buildout.cfg', """\ +... [buildout] +... extends = %sbase.cfg +... extends-cache = cache +... """ % server_url) + +>>> print system(buildout) +Unused options for buildout: 'foo'. + +>>> cache = join(sample_buildout, 'cache') +>>> ls(cache) +- 5aedc98d7e769290a29d654a591a3a45 + +>>> import os +>>> cat(cache, os.listdir(cache)[0]) +[buildout] +parts = +foo = bar + +We can now run buildout offline as it will read base.cfg from the cache: + +>>> print system(buildout + ' -o') +Unused options for buildout: 'foo'. + +The cache is being used purely as a fall-back in case we are offline or don't +have access to a configuration file to be downloaded. As long as we are +online, buildout attempts to download a fresh copy of each file even if a +cached copy of the file exists. To see this, we put different configuration in +the same place on the server and run buildout in offline mode so it takes +base.cfg from the cache: + +>>> write(server_data, 'base.cfg', """\ +... [buildout] +... parts = +... bar = baz +... """) + +>>> print system(buildout + ' -o') +Unused options for buildout: 'foo'. + +In online mode, buildout will download and use the modified version: + +>>> print system(buildout) +Unused options for buildout: 'bar'. + +Trying offline mode again, the new version will be used as it has been put in +the cache now: + +>>> print system(buildout + ' -o') +Unused options for buildout: 'bar'. + +Clean up: + +>>> rmdir(cache) + + +Specifying extends cache and offline mode +----------------------------------------- + +Normally, the values of buildout options such as the location of a download +cache or whether to use offline mode are determined by first reading the +user's default configuration, updating it with the project's configuration and +finally applying command-line options. User and project configuration are +assembled by reading a file such as ``~/.buildout/default.cfg``, +``buildout.cfg`` or a URL given on the command line, recursively (depth-first) +downloading any base configuration specified by the ``buildout:extends`` +option read from each of those config files, and finally evaluating each +config file to provide default values for options not yet read. + +This works fine for all options that do not influence how configuration is +downloaded in the first place. The ``extends-cache`` and ``offline`` options, +however, are treated differently from the procedure described in order to make +it simple and obvious to see where a particular configuration file came from +under any particular circumstances. + +- Offline and extends-cache settings are read from the two root config files + exclusively. Otherwise one could construct configuration files that, when + read, imply that they should have been read from a different source than + they have. Also, specifying the extends cache within a file that might have + to be taken from the cache before being read wouldn't make a lot of sense. + +- Offline and extends-cache settings given by the user's defaults apply to the + process of assembling the project's configuration. If no extends cache has + been specified by the user's default configuration, the project's root + config file must be available, be it from disk or from the net. + +- Offline mode turned on by the ``-o`` command line option is honoured from + the beginning even though command line options are applied to the + configuration last. If offline mode is not requested by the command line, it + may be switched on by either the user's or the project's config root. + +Extends cache +~~~~~~~~~~~~~ + +Let's see the above rules in action. We create a new home directory for our +user and write user and project configuration that recursively extends online +bases, using different caches: + +>>> mkdir('home') +>>> mkdir('home', '.buildout') +>>> mkdir('cache') +>>> mkdir('user-cache') +>>> os.environ['HOME'] = join(sample_buildout, 'home') +>>> write('home', '.buildout', 'default.cfg', """\ +... [buildout] +... extends = fancy_default.cfg +... extends-cache = user-cache +... """) +>>> write('home', '.buildout', 'fancy_default.cfg', """\ +... [buildout] +... extends = %sbase_default.cfg +... """ % server_url) +>>> write(server_data, 'base_default.cfg', """\ +... [buildout] +... foo = bar +... offline = false +... """) + +>>> write('buildout.cfg', """\ +... [buildout] +... extends = fancy.cfg +... extends-cache = cache +... """) +>>> write('fancy.cfg', """\ +... [buildout] +... extends = %sbase.cfg +... """ % server_url) +>>> write(server_data, 'base.cfg', """\ +... [buildout] +... parts = +... offline = false +... """) + +Buildout will now assemble its configuration from all of these 6 files, +defaults first. The online resources end up in the respective extends caches: + +>>> print system(buildout) +Unused options for buildout: 'foo'. + +>>> ls('user-cache') +- 10e772cf422123ef6c64ae770f555740 +>>> cat('user-cache', os.listdir('user-cache')[0]) +[buildout] +foo = bar +offline = false + +>>> ls('cache') +- c72213127e6eb2208a3e1fc1dba771a7 +>>> cat('cache', os.listdir('cache')[0]) +[buildout] +parts = +offline = false + +If, on the other hand, the extends caches are specified in files that get +extended themselves, they won't be used for assembling the configuration they +belong to (user's or project's, resp.). The extends cache specified by the +user's defaults does, however, apply to downloading project configuration. +Let's rewrite the config files, clean out the caches and re-run buildout: + +>>> write('home', '.buildout', 'default.cfg', """\ +... [buildout] +... extends = fancy_default.cfg +... """) +>>> write('home', '.buildout', 'fancy_default.cfg', """\ +... [buildout] +... extends = %sbase_default.cfg +... extends-cache = user-cache +... """ % server_url) + +>>> write('buildout.cfg', """\ +... [buildout] +... extends = fancy.cfg +... """) +>>> write('fancy.cfg', """\ +... [buildout] +... extends = %sbase.cfg +... extends-cache = cache +... """ % server_url) + +>>> remove('user-cache', os.listdir('user-cache')[0]) +>>> remove('cache', os.listdir('cache')[0]) + +>>> print system(buildout) +Unused options for buildout: 'foo'. + +>>> ls('user-cache') +- 0548bad6002359532de37385bb532e26 +>>> cat('user-cache', os.listdir('user-cache')[0]) +[buildout] +parts = +offline = false + +>>> ls('cache') + +Clean up: + +>>> rmdir('user-cache') +>>> rmdir('cache') + +Offline mode and installation from cache +----------------------------~~~~~~~~~~~~ + +If we run buildout in offline mode now, it will fail because it cannot get at +the remote configuration file needed by the user's defaults: + +>>> print system(buildout + ' -o') +While: + Initializing. +Error: Couldn't download 'http://localhost/base_default.cfg' in offline mode. + +Let's now successively turn on offline mode by different parts of the +configuration and see when buildout applies this setting in each case: + +>>> write('home', '.buildout', 'default.cfg', """\ +... [buildout] +... extends = fancy_default.cfg +... offline = true +... """) +>>> print system(buildout) +While: + Initializing. +Error: Couldn't download 'http://localhost/base_default.cfg' in offline mode. + +>>> write('home', '.buildout', 'default.cfg', """\ +... [buildout] +... extends = fancy_default.cfg +... """) +>>> write('home', '.buildout', 'fancy_default.cfg', """\ +... [buildout] +... extends = %sbase_default.cfg +... offline = true +... """ % server_url) +>>> print system(buildout) +While: + Initializing. +Error: Couldn't download 'http://localhost/base.cfg' in offline mode. + +>>> write('home', '.buildout', 'fancy_default.cfg', """\ +... [buildout] +... extends = %sbase_default.cfg +... """ % server_url) +>>> write('buildout.cfg', """\ +... [buildout] +... extends = fancy.cfg +... offline = true +... """) +>>> print system(buildout) +While: + Initializing. +Error: Couldn't download 'http://localhost/base.cfg' in offline mode. + +>>> write('buildout.cfg', """\ +... [buildout] +... extends = fancy.cfg +... """) +>>> write('fancy.cfg', """\ +... [buildout] +... extends = %sbase.cfg +... offline = true +... """ % server_url) +>>> print system(buildout) +Unused options for buildout: 'foo'. + +The ``install-from-cache`` option is treated accordingly: + +>>> write('home', '.buildout', 'default.cfg', """\ +... [buildout] +... extends = fancy_default.cfg +... install-from-cache = true +... """) +>>> print system(buildout) +While: + Initializing. +Error: Couldn't download 'http://localhost/base_default.cfg' in offline mode. + +>>> write('home', '.buildout', 'default.cfg', """\ +... [buildout] +... extends = fancy_default.cfg +... """) +>>> write('home', '.buildout', 'fancy_default.cfg', """\ +... [buildout] +... extends = %sbase_default.cfg +... install-from-cache = true +... """ % server_url) +>>> print system(buildout) +While: + Initializing. +Error: Couldn't download 'http://localhost/base.cfg' in offline mode. + +>>> write('home', '.buildout', 'fancy_default.cfg', """\ +... [buildout] +... extends = %sbase_default.cfg +... """ % server_url) +>>> write('buildout.cfg', """\ +... [buildout] +... extends = fancy.cfg +... install-from-cache = true +... """) +>>> print system(buildout) +While: + Initializing. +Error: Couldn't download 'http://localhost/base.cfg' in offline mode. + +>>> write('buildout.cfg', """\ +... [buildout] +... extends = fancy.cfg +... """) +>>> write('fancy.cfg', """\ +... [buildout] +... extends = %sbase.cfg +... install-from-cache = true +... """ % server_url) +>>> print system(buildout) +While: + Installing. + Checking for upgrades. +An internal error occurred ... +ValueError: install_from_cache set to true with no download cache + + +Clean up +-------- + +We should have cleaned up all temporary files created by downloading things: + +>>> ls(tempfile.tempdir) + +Reset the global temporary directory: + +>>> tempfile.tempdir = old_tempdir diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/repeatable.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/repeatable.txt new file mode 100644 index 0000000..09078bd --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/repeatable.txt @@ -0,0 +1,180 @@ +Repeatable buildouts: controlling eggs used +=========================================== + +One of the goals of zc.buildout is to provide enough control to make +buildouts repeatable. It should be possible to check the buildout +configuration files for a project into a version control system and +later use the checked in files to get the same buildout, subject to +changes in the environment outside the buildout. + +An advantage of using Python eggs is that depenencies of eggs used are +automatically determined and used. The automatic inclusion of +depenent distributions is at odds with the goal of repeatable +buildouts. + +To support repeatable buildouts, a versions section can be created +with options for each distribution name whos version is to be fixed. +The section can then be specified via the buildout versions option. + +To see how this works, we'll create two versions of a recipe egg: + + >>> mkdir('recipe') + >>> write('recipe', 'recipe.py', + ... ''' + ... class Recipe: + ... def __init__(*a): pass + ... def install(self): + ... print 'recipe v1' + ... return () + ... update = install + ... ''') + + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='spam', version='1', py_modules=['recipe'], + ... entry_points={'zc.buildout': ['default = recipe:Recipe']}, + ... ) + ... ''') + + >>> write('recipe', 'README', '') + + >>> print system(buildout+' setup recipe bdist_egg'), # doctest: +ELLIPSIS + Running setup script 'recipe/setup.py'. + ... + + >>> rmdir('recipe', 'build') + + >>> write('recipe', 'recipe.py', + ... ''' + ... class Recipe: + ... def __init__(*a): pass + ... def install(self): + ... print 'recipe v2' + ... return () + ... update = install + ... ''') + + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='spam', version='2', py_modules=['recipe'], + ... entry_points={'zc.buildout': ['default = recipe:Recipe']}, + ... ) + ... ''') + + + >>> print system(buildout+' setup recipe bdist_egg'), # doctest: +ELLIPSIS + Running setup script 'recipe/setup.py'. + ... + +and we'll configure a buildout to use it: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = %s + ... + ... [foo] + ... recipe = spam + ... ''' % join('recipe', 'dist')) + +If we run the buildout, it will use version 2: + + >>> print system(buildout), + Getting distribution for 'spam'. + Got spam 2. + Installing foo. + recipe v2 + +We can specify a versions section that lists our recipe and name it in +the buildout section: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = %s + ... versions = release-1 + ... + ... [release-1] + ... spam = 1 + ... eggs = 2.2 + ... + ... [foo] + ... recipe = spam + ... ''' % join('recipe', 'dist')) + +Here we created a release-1 section listing the version 1 for the spam +distribution. We told the buildout to use it by specifying release-1 +as in the versions option. + +Now, if we run the buildout, we'll use version 1 of the spam recipe: + + >>> print system(buildout), + Getting distribution for 'spam==1'. + Got spam 1. + Uninstalling foo. + Installing foo. + recipe v1 + +Running the buildout in verbose mode will help us get information +about versions used. If we run the buildout in verbose mode without +specifying a versions section: + + >>> print system(buildout+' buildout:versions= -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + We have a develop egg: zc.buildout 1.0.0. + We have the best distribution that satisfies 'setuptools'. + Picked: setuptools = 0.6 + Installing 'spam'. + We have the best distribution that satisfies 'spam'. + Picked: spam = 2. + Uninstalling foo. + Installing foo. + recipe v2 + +We'll get output that includes lines that tell us what versions +buildout chose a for us, like:: + + zc.buildout.easy_install.picked: spam = 2 + +This allows us to discover versions that are picked dynamically, so +that we can fix them in a versions section. + +If we run the buildout with the versions section: + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + We have a develop egg: zc.buildout 1.0.0. + We have the best distribution that satisfies 'setuptools'. + Picked: setuptools = 0.6 + Installing 'spam'. + We have the distribution that satisfies 'spam==1'. + Uninstalling foo. + Installing foo. + recipe v1 + +We won't get output for the spam distribution, which we didn't pick, +but we will get output for setuptools, which we didn't specify +versions for. + +You can request buildout to generate an error if it picks any +versions: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = %s + ... versions = release-1 + ... allow-picked-versions = false + ... + ... [release-1] + ... spam = 1 + ... eggs = 2.2 + ... + ... [foo] + ... recipe = spam + ... ''' % join('recipe', 'dist')) diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/rmtree.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/rmtree.py new file mode 100644 index 0000000..a4995f2 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/rmtree.py @@ -0,0 +1,66 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + + +import shutil +import os +import doctest + +def rmtree (path): + """ + A variant of shutil.rmtree which tries hard to be successful + On windows shutil.rmtree aborts when it tries to delete a + read only file. + This tries to chmod the file to writeable and retries before giving up. + + >>> from tempfile import mkdtemp + + Let's make a directory ... + + >>> d = mkdtemp() + + and make sure it is actually there + + >>> os.path.isdir (d) + 1 + + Now create a file ... + + >>> foo = os.path.join (d, 'foo') + >>> open (foo, 'w').write ('huhu') + + and make it unwriteable + + >>> os.chmod (foo, 0400) + + rmtree should be able to remove it: + + >>> rmtree (d) + + and now the directory is gone + + >>> os.path.isdir (d) + 0 + """ + def retry_writeable (func, path, exc): + os.chmod (path, 0600) + func (path) + + shutil.rmtree (path, onerror = retry_writeable) + +def test_suite(): + return doctest.DocTestSuite() + +if "__main__" == __name__: + doctest.testmod() diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/runsetup.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/runsetup.txt new file mode 100644 index 0000000..469e623 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/runsetup.txt @@ -0,0 +1,42 @@ +Running setup scripts +===================== + +Buildouts are often used to work on packages that will be distributed +as eggs. During development, we use develop eggs. When you've +completed a development cycle, you'll need to run your setup script to +generate a distribution and, perhaps, uploaded it to the Python +package index. If your script uses setuptools, you'll need setuptools +in your Python path, which may be an issue if you haven't installed +setuptools into your Python installation. + +The buildout setup command is helpful in a situation like this. It +can be used to run a setup script and it does so with the setuptools +egg in the Python path and with setuptools already imported. The fact +that setuptools is imported means that you can use setuptools-based +commands, like bdist_egg even with packages that don't use setuptools. +To illustrate this, we'll create a package in a sample buildout: + + >>> mkdir('hello') + >>> write('hello', 'hello.py', 'print "Hello World!"') + >>> write('hello', 'README', 'This is hello') + >>> write('hello', 'setup.py', + ... """ + ... from distutils.core import setup + ... setup(name="hello", + ... version="1.0", + ... py_modules=["hello"], + ... author="Bob", + ... author_email="bob@foo.com", + ... ) + ... """) + +We can use the buildout command to generate the hello egg: + + >>> print system(buildout +' setup hello -q bdist_egg'), + Running setup script 'hello/setup.py'. + zip_safe flag not set; analyzing archive contents... + +The hello directory now has a hello egg in it's dist directory: + + >>> ls('hello', 'dist') + - hello-1.0-py2.4.egg diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/setup.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/setup.txt new file mode 100644 index 0000000..ff850fc --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/setup.txt @@ -0,0 +1,51 @@ +Using zc.buildout to run setup scripts +====================================== + +zc buildout has a convenience command for running setup scripts. Why? +There are two reasons. If a setup script doesn't import setuptools, +you can't use any setuptools-provided commands, like bdist_egg. When +buildout runs a setup script, it arranges to import setuptools before +running the script so setuptools-provided commands are available. + +If you use a squeaky-clean Python to do your development, the setup +script that would import setuptools because setuptools isn't in the +path. Because buildout requires setuptools and knows where it has +installed a setuptools egg, it adds the setuptools egg to the Python +path before running the script. To run a setup script, use the +buildout setup command, passing the name of a script or a directory +containing a setup script and arguments to the script. Let's look at +an example: + + >>> mkdir('test') + >>> cd('test') + >>> write('setup.py', + ... ''' + ... from distutils.core import setup + ... setup(name='sample') + ... ''') + +We've created a super simple (stupid) setup script. Note that it +doesn't import setuptools. Let's try running it to create an egg. +We'll use the buildout script from our sample buildout: + + >>> print system(buildout+' setup'), + ... # doctest: +NORMALIZE_WHITESPACE + Error: The setup command requires the path to a setup script or + directory containing a setup script, and its arguments. + +Oops, we forgot to give the name of the setup script: + + >>> print system(buildout+' setup setup.py bdist_egg'), + ... # doctest: +ELLIPSIS + Running setup script 'setup.py'. + ... + + >>> ls('dist') + - sample-0.0.0-py2.5.egg + +Note that we can specify a directory name. This is often shorter and +preferred by the lazy :) + + >>> print system(buildout+' setup . bdist_egg'), # doctest: +ELLIPSIS + Running setup script './setup.py'. + ... diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing.py new file mode 100644 index 0000000..768684c --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing.py @@ -0,0 +1,616 @@ +############################################################################# +# +# Copyright (c) 2004-2009 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Various test-support utility functions + +$Id: testing.py 116197 2010-09-04 00:02:53Z gary $ +""" + +import BaseHTTPServer +import errno +import logging +import os +import pkg_resources +import random +import re +import shutil +import socket +import subprocess +import sys +import tempfile +import textwrap +import threading +import time +import urllib2 + +import zc.buildout.buildout +import zc.buildout.easy_install +from zc.buildout.rmtree import rmtree + +fsync = getattr(os, 'fsync', lambda fileno: None) +is_win32 = sys.platform == 'win32' + +setuptools_location = pkg_resources.working_set.find( + pkg_resources.Requirement.parse('setuptools')).location + +def cat(dir, *names): + path = os.path.join(dir, *names) + if (not os.path.exists(path) + and is_win32 + and os.path.exists(path+'-script.py') + ): + path = path+'-script.py' + print open(path).read(), + +def ls(dir, *subs): + if subs: + dir = os.path.join(dir, *subs) + names = os.listdir(dir) + names.sort() + for name in names: + if os.path.isdir(os.path.join(dir, name)): + print 'd ', + elif os.path.islink(os.path.join(dir, name)): + print 'l ', + else: + print '- ', + print name + +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)) + +def write(dir, *args): + path = os.path.join(dir, *(args[:-1])) + f = open(path, 'w') + f.write(args[-1]) + f.flush() + fsync(f.fileno()) + f.close() + +## FIXME - check for other platforms +MUST_CLOSE_FDS = not sys.platform.startswith('win') + +def system(command, input=''): + env = dict(os.environ) + env['COLUMNS'] = '80' + p = subprocess.Popen(command, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=MUST_CLOSE_FDS, + env=env, + ) + i, o, e = (p.stdin, p.stdout, p.stderr) + if input: + i.write(input) + i.close() + result = o.read() + e.read() + o.close() + e.close() + return result + +def call_py(interpreter, cmd, flags=None): + if sys.platform == 'win32': + args = ['"%s"' % arg for arg in (interpreter, flags, cmd) if arg] + args.insert(-1, '"-c"') + return system('"%s"' % ' '.join(args)) + else: + cmd = repr(cmd) + return system( + ' '.join(arg for arg in (interpreter, flags, '-c', cmd) if arg)) + +def get(url): + return urllib2.urlopen(url).read() + +def _runsetup(setup, executable, *args): + if os.path.isdir(setup): + setup = os.path.join(setup, 'setup.py') + d = os.path.dirname(setup) + + args = [zc.buildout.easy_install._safe_arg(arg) + for arg in args] + args.insert(0, '-q') + env = dict(os.environ) + if executable == sys.executable: + env['PYTHONPATH'] = setuptools_location + # else pass an executable that has setuptools! See testselectingpython.py. + args.append(env) + + here = os.getcwd() + try: + os.chdir(d) + os.spawnle(os.P_WAIT, executable, + zc.buildout.easy_install._safe_arg(executable), + setup, *args) + if os.path.exists('build'): + rmtree('build') + finally: + os.chdir(here) + +def sdist(setup, dest): + _runsetup(setup, sys.executable, 'sdist', '-d', dest, '--formats=zip') + +def bdist_egg(setup, executable, dest): + _runsetup(setup, executable, 'bdist_egg', '-d', dest) + +def sys_install(setup, dest): + _runsetup(setup, sys.executable, 'install', '--install-purelib', dest, + '--record', os.path.join(dest, '__added_files__'), + '--single-version-externally-managed') + +def find_python(version): + e = os.environ.get('PYTHON%s' % version) + if e is not None: + return e + if is_win32: + e = '\Python%s%s\python.exe' % tuple(version.split('.')) + if os.path.exists(e): + return e + else: + cmd = 'python%s -c "import sys; print sys.executable"' % version + p = subprocess.Popen(cmd, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + close_fds=MUST_CLOSE_FDS) + i, o = (p.stdin, p.stdout) + i.close() + e = o.read().strip() + o.close() + if os.path.exists(e): + return e + cmd = 'python -c "import sys; print \'%s.%s\' % sys.version_info[:2]"' + p = subprocess.Popen(cmd, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + close_fds=MUST_CLOSE_FDS) + i, o = (p.stdin, p.stdout) + i.close() + e = o.read().strip() + o.close() + if e == version: + cmd = 'python -c "import sys; print sys.executable"' + p = subprocess.Popen(cmd, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + close_fds=MUST_CLOSE_FDS) + i, o = (p.stdin, p.stdout) + i.close() + e = o.read().strip() + o.close() + if os.path.exists(e): + return e + + raise ValueError( + "Couldn't figure out the executable for Python %(version)s.\n" + "Set the environment variable PYTHON%(version)s to the location\n" + "of the Python %(version)s executable before running the tests." + % {'version': version}) + +def wait_until(label, func, *args, **kw): + if 'timeout' in kw: + kw = dict(kw) + timeout = kw.pop('timeout') + else: + timeout = 30 + deadline = time.time()+timeout + while time.time() < deadline: + if func(*args, **kw): + return + time.sleep(0.01) + raise ValueError('Timed out waiting for: '+label) + +def get_installer_values(): + """Get the current values for the easy_install module. + + This is necessary because instantiating a Buildout will force the + Buildout's values on the installer. + + Returns a dict of names-values suitable for set_installer_values.""" + names = ('default_versions', 'download_cache', 'install_from_cache', + 'prefer_final', 'include_site_packages', + 'allowed_eggs_from_site_packages', 'use_dependency_links', + 'allow_picked_versions', 'always_unzip' + ) + values = {} + for name in names: + values[name] = getattr(zc.buildout.easy_install, name)() + return values + +def set_installer_values(values): + """Set the given values on the installer.""" + for name, value in values.items(): + getattr(zc.buildout.easy_install, name)(value) + +def make_buildout(executable=None): + """Make a buildout that uses this version of zc.buildout.""" + # Create a basic buildout.cfg to avoid a warning from buildout. + open('buildout.cfg', 'w').write( + "[buildout]\nparts =\n" + ) + # Get state of installer defaults so we can reinstate them (instantiating + # a Buildout will force the Buildout's defaults on the installer). + installer_values = get_installer_values() + # Use the buildout bootstrap command to create a buildout + config = [ + ('buildout', 'log-level', 'WARNING'), + # trick bootstrap into putting the buildout develop egg + # in the eggs dir. + ('buildout', 'develop-eggs-directory', 'eggs'), + ] + if executable is not None: + config.append(('buildout', 'executable', executable)) + zc.buildout.buildout.Buildout( + 'buildout.cfg', config, + user_defaults=False, + ).bootstrap([]) + # Create the develop-eggs dir, which didn't get created the usual + # way due to the trick above: + os.mkdir('develop-eggs') + # Reinstate the default values of the installer. + set_installer_values(installer_values) + +def buildoutSetUp(test): + + test.globs['__tear_downs'] = __tear_downs = [] + test.globs['register_teardown'] = register_teardown = __tear_downs.append + + installer_values = get_installer_values() + register_teardown( + lambda: set_installer_values(installer_values) + ) + + here = os.getcwd() + register_teardown(lambda: os.chdir(here)) + + handlers_before_set_up = logging.getLogger().handlers[:] + def restore_root_logger_handlers(): + root_logger = logging.getLogger() + for handler in root_logger.handlers[:]: + root_logger.removeHandler(handler) + for handler in handlers_before_set_up: + root_logger.addHandler(handler) + register_teardown(restore_root_logger_handlers) + + base = tempfile.mkdtemp('buildoutSetUp') + base = os.path.realpath(base) + register_teardown(lambda base=base: rmtree(base)) + + old_home = os.environ.get('HOME') + os.environ['HOME'] = os.path.join(base, 'bbbBadHome') + def restore_home(): + if old_home is None: + del os.environ['HOME'] + else: + os.environ['HOME'] = old_home + register_teardown(restore_home) + + base = os.path.join(base, '_TEST_') + os.mkdir(base) + + tmp = tempfile.mkdtemp('buildouttests') + register_teardown(lambda: rmtree(tmp)) + + zc.buildout.easy_install.default_index_url = 'file://'+tmp + os.environ['buildout-testing-index-url'] = ( + zc.buildout.easy_install.default_index_url) + os.environ.pop('PYTHONPATH', None) + + def tmpdir(name): + path = os.path.join(base, name) + mkdir(path) + return path + + sample = tmpdir('sample-buildout') + + os.chdir(sample) + make_buildout() + + def start_server(path): + port, thread = _start_server(path, name=path) + url = 'http://localhost:%s/' % port + register_teardown(lambda: stop_server(url, thread)) + return url + + def make_py(initialization=''): + """Returns paths to new executable and to its site-packages. + """ + buildout = tmpdir('executable_buildout') + site_packages_dir = os.path.join(buildout, 'site-packages') + mkdir(site_packages_dir) + old_wd = os.getcwd() + os.chdir(buildout) + make_buildout() + # Normally we don't process .pth files in extra-paths. We want to + # in this case so that we can test with setuptools system installs + # (--single-version-externally-managed), which use .pth files. + initialization = ( + ('import sys\n' + 'import site\n' + 'known_paths = set(sys.path)\n' + 'site_packages_dir = %r\n' + 'site.addsitedir(site_packages_dir, known_paths)\n' + ) % (site_packages_dir,)) + initialization + initialization = '\n'.join( + ' ' + line for line in initialization.split('\n')) + install_develop( + 'zc.recipe.egg', os.path.join(buildout, 'develop-eggs')) + install_develop( + 'z3c.recipe.scripts', os.path.join(buildout, 'develop-eggs')) + write('buildout.cfg', textwrap.dedent('''\ + [buildout] + parts = py + include-site-packages = false + exec-sitecustomize = false + + [py] + recipe = z3c.recipe.scripts + interpreter = py + initialization = + %(initialization)s + extra-paths = %(site-packages)s + eggs = setuptools + ''') % { + 'initialization': initialization, + 'site-packages': site_packages_dir}) + system(os.path.join(buildout, 'bin', 'buildout')) + os.chdir(old_wd) + return ( + os.path.join(buildout, 'bin', 'py'), site_packages_dir) + + test.globs.update(dict( + sample_buildout = sample, + ls = ls, + cat = cat, + mkdir = mkdir, + rmdir = rmdir, + remove = remove, + tmpdir = tmpdir, + write = write, + system = system, + call_py = call_py, + get = get, + cd = (lambda *path: os.chdir(os.path.join(*path))), + join = os.path.join, + sdist = sdist, + bdist_egg = bdist_egg, + start_server = start_server, + buildout = os.path.join(sample, 'bin', 'buildout'), + wait_until = wait_until, + make_py = make_py + )) + +def buildoutTearDown(test): + for f in test.globs['__tear_downs']: + f() + +class Server(BaseHTTPServer.HTTPServer): + + def __init__(self, tree, *args): + BaseHTTPServer.HTTPServer.__init__(self, *args) + self.tree = os.path.abspath(tree) + + __run = True + def serve_forever(self): + while self.__run: + self.handle_request() + + def handle_error(self, *_): + self.__run = False + +class Handler(BaseHTTPServer.BaseHTTPRequestHandler): + + Server.__log = False + + def __init__(self, request, address, server): + self.__server = server + self.tree = server.tree + BaseHTTPServer.BaseHTTPRequestHandler.__init__( + self, request, address, server) + + def do_GET(self): + if '__stop__' in self.path: + raise SystemExit + + if self.path == '/enable_server_logging': + self.__server.__log = True + self.send_response(200) + return + + if self.path == '/disable_server_logging': + self.__server.__log = False + self.send_response(200) + return + + path = os.path.abspath(os.path.join(self.tree, *self.path.split('/'))) + if not ( + ((path == self.tree) or path.startswith(self.tree+os.path.sep)) + and + os.path.exists(path) + ): + self.send_response(404, 'Not Found') + #self.send_response(200) + out = '<html><body>Not Found</body></html>' + #out = '\n'.join(self.tree, self.path, path) + self.send_header('Content-Length', str(len(out))) + self.send_header('Content-Type', 'text/html') + self.end_headers() + self.wfile.write(out) + return + + self.send_response(200) + if os.path.isdir(path): + out = ['<html><body>\n'] + names = os.listdir(path) + names.sort() + for name in names: + if os.path.isdir(os.path.join(path, name)): + name += '/' + out.append('<a href="%s">%s</a><br>\n' % (name, name)) + out.append('</body></html>\n') + out = ''.join(out) + self.send_header('Content-Length', str(len(out))) + self.send_header('Content-Type', 'text/html') + else: + out = open(path, 'rb').read() + self.send_header('Content-Length', len(out)) + if path.endswith('.egg'): + self.send_header('Content-Type', 'application/zip') + elif path.endswith('.gz'): + self.send_header('Content-Type', 'application/x-gzip') + elif path.endswith('.zip'): + self.send_header('Content-Type', 'application/x-gzip') + else: + self.send_header('Content-Type', 'text/html') + self.end_headers() + + self.wfile.write(out) + + def log_request(self, code): + if self.__server.__log: + print '%s %s %s' % (self.command, code, self.path) + +def _run(tree, port): + server_address = ('localhost', port) + httpd = Server(tree, server_address, Handler) + httpd.serve_forever() + +def get_port(): + for i in range(10): + port = random.randrange(20000, 30000) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + try: + s.connect(('localhost', port)) + except socket.error: + return port + finally: + s.close() + raise RuntimeError, "Can't find port" + +def _start_server(tree, name=''): + port = get_port() + thread = threading.Thread(target=_run, args=(tree, port), name=name) + thread.setDaemon(True) + thread.start() + wait(port, up=True) + return port, thread + +def start_server(tree): + return _start_server(tree)[0] + +def stop_server(url, thread=None): + try: + urllib2.urlopen(url+'__stop__') + except Exception: + pass + if thread is not None: + thread.join() # wait for thread to stop + +def wait(port, up): + addr = 'localhost', port + for i in range(120): + time.sleep(0.25) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(addr) + s.close() + if up: + break + except socket.error, e: + if e[0] not in (errno.ECONNREFUSED, errno.ECONNRESET): + raise + s.close() + if not up: + break + else: + if up: + raise + else: + raise SystemError("Couln't stop server") + +def install(project, destination): + if not isinstance(destination, basestring): + destination = os.path.join(destination.globs['sample_buildout'], + 'eggs') + + dist = pkg_resources.working_set.find( + pkg_resources.Requirement.parse(project)) + if dist.location.endswith('.egg'): + destination = os.path.join(destination, + os.path.basename(dist.location), + ) + if os.path.isdir(dist.location): + shutil.copytree(dist.location, destination) + else: + shutil.copyfile(dist.location, destination) + else: + # copy link + open(os.path.join(destination, project+'.egg-link'), 'w' + ).write(dist.location) + +def install_develop(project, destination): + if not isinstance(destination, basestring): + destination = os.path.join(destination.globs['sample_buildout'], + 'develop-eggs') + + dist = pkg_resources.working_set.find( + pkg_resources.Requirement.parse(project)) + open(os.path.join(destination, project+'.egg-link'), 'w' + ).write(dist.location) + +def _normalize_path(match): + path = match.group(1) + if os.path.sep == '\\': + path = path.replace('\\\\', '/') + if path.startswith('\\'): + path = path[1:] + return '/' + path.replace(os.path.sep, '/') + +if sys.platform == 'win32': + sep = r'[\\/]' # Windows uses both sometimes. +else: + sep = re.escape(os.path.sep) +normalize_path = ( + re.compile( + r'''[^'" \t\n\r!]+%(sep)s_[Tt][Ee][Ss][Tt]_%(sep)s([^"' \t\n\r]+)''' + % dict(sep=sep)), + _normalize_path, + ) + +normalize_endings = re.compile('\r\n'), '\n' + +normalize_script = ( + re.compile('(\n?)- ([a-zA-Z_.-]+)-script.py\n- \\2.exe\n'), + '\\1- \\2\n') + +normalize_egg_py = ( + re.compile('-py\d[.]\d(-\S+)?.egg'), + '-pyN.N.egg', + ) diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing.txt new file mode 100644 index 0000000..64cb1bf --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing.txt @@ -0,0 +1,181 @@ +Testing Support +=============== + +The zc.buildout.testing module provides an API that can be used when +writing recipe tests. This API is documented below. Many examples of +using this API can be found in the zc.buildout, zc.recipe.egg, and +zc.recipe.testrunner tests. + +zc.buildout.testing.buildoutSetUp(test) +--------------------------------------- + +The buildoutSetup function can be used as a doctest setup function. +It creates a sample buildout that can be used by tests, changing the +current working directory to the sample_buildout. It also adds a +number of names to the test namespace: + +``sample_buildout`` + This is the name of a buildout with a basic configuration. + +``buildout`` + This is the path of the buildout script in the sample buildout. + +``ls(*path)`` + List the contents of a directory. The directory path is provided as one or + more strings, to be joined with os.path.join. + +``cat(*path)`` + Display the contents of a file. The file path is provided as one or + more strings, to be joined with os.path.join. + + On Windows, if the file doesn't exist, the function will try + adding a '-script.py' suffix. This helps to work around a + difference in script generation on windows. + +``mkdir(*path)`` + Create a directory. The directory path is provided as one or + more strings, to be joined with os.path.join. + +``rmdir(*path)`` + 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 + the created directory is returned. + + Further, if the the normalize_path normlaizing substitution (see + below) is used, then any paths starting with this path will be + normalized to:: + + /name/restofpath + + No two temporary directories can be created with the same name. A + directory created with tmpdir can be removed with rmdir and recreated. + + Note that the sample_buildout directory is created by calling this + function. + +``write(*path_and_contents)`` + Create a file. The file path is provided as one or more strings, + to be joined with os.path.join. The last argument is the file contents. + +``system(command, input='')`` + Execute a system command with the given input passed to the + command's standard input. The output (error and regular output) + from the command is returned. + +``get(url)`` + Get a web page. + +``cd(*path)`` + Change to the given directory. The directory path is provided as one or + more strings, to be joined with os.path.join. + + The directory will be reset at the end of the test. + +``join(*path)`` + A convenient reference to os.path.join. + +``register_teardown(func)`` + Register a tear-down function. The function will be called with + no arguments at the end of the test. + +``start_server(path)`` + Start a web server on the given path. The server will be shut + down at the end of the test. The server URL is returned. + + You can cause the server to start and stop logging it's output + using: + + >>> get(server_url+'enable_server_logging') + + and: + + >>> get(server_url+'disable_server_logging') + + This can be useful to see how buildout is interacting with a + server. + + +``sdist(setup, dest)`` + Create a source distribution by running the given setup file and + placing the result in the given destination directory. If the + setup argument is a directory, the thge setup.py file in that + directory is used. + +``bdist_egg(setup, executable, dest)`` + Create an egg by running the given setup file with the given + Python executable and placing the result in the given destination + directory. If the setup argument is a directory, then the + setup.py file in that directory is used. + +``find_python(version)`` + Find a Python executable for the given version, where version is a + string like "2.4". + + This function uses the following strategy to find a Python of the + given version: + + - Look for an environment variable of the form PYTHON%(version)s. + + - On windows, look for \Pythonm%(version)s\python + + - on Unix, try running python%(version)s or just python to get the + executable + +``zc.buildout.testing.buildoutTearDown(test)`` +---------------------------------------------- + +Tear down everything set up by zc.buildout.testing.buildoutSetUp. Any +functions passed to register_teardown are called as well. + +``install(project, destination)`` +--------------------------------- + +Install eggs for a given project into a destination. If the +destination is a test object, then the eggs directory of the +sample buildout (sample_buildout) defined by the test will be used. +Tests will use this to install the distributions for the packages +being tested (and their dependencies) into a sample buildout. The egg +to be used should already be loaded, by importing one of the modules +provided, before calling this function. + +``install_develop(project, destination)`` +----------------------------------------- + +Like install, but a develop egg is installed even if the current egg +if not a develop egg. + +``Output normalization`` +------------------------ + +Recipe tests often generate output that is dependent on temporary file +locations, operating system conventions or Python versions. To deal +with these dependencies, we often use +zope.testing.renormalizing.RENormalizing to normalize test output. +zope.testing.renormalizing.RENormalizing takes pairs of regular +expressions and substitutions. The zc.buildout.testing module provides +a few helpful variables that define regular-expression/substitution +pairs that you can pass to zope.testing.renormalizing.RENormalizing. + + +``normalize_path`` + Converts tests paths, based on directories created with tmpdir(), + to simple paths. + +``normalize_script`` + On Unix-like systems, scripts are implemented in single files + without suffixes. On windows, scripts are implemented with 2 + files, a -script.py file and a .exe file. This normalization + converts directory listings of Windows scripts to the form + generated on UNix-like systems. + +``normalize_egg_py`` + Normalize Python version and platform indicators, if specified, in + egg names. diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing_bugfix.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing_bugfix.txt new file mode 100644 index 0000000..3bd13ee --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testing_bugfix.txt @@ -0,0 +1,39 @@ +Bug fixes in zc.buildout.testing +================================ + +Logging handler which did not get deleted +----------------------------------------- + +The buildout testing set up runs a buildout which adds a +``logging.StreamHandler`` to the root logger. But tear down did not +remove it. This can disturb other tests of packages reusing +zc.buildout.testing. + +The handers before calling set up are: + + >>> import logging + >>> len(logging.getLogger().handlers) + 1 + >>> logging.getLogger().handlers # doctest: +ELLIPSIS + [<zope...testrunner.logsupport.NullHandler instance at ...>] + +After calling it, a ``logging.StreamHandler`` was added: + + >>> import zc.buildout.testing + >>> import doctest + >>> test = doctest.DocTestParser().get_doctest( + ... '>>> x', {}, 'foo', 'foo.py', 0) + >>> zc.buildout.testing.buildoutSetUp(test) + >>> len(logging.getLogger().handlers) + 2 + >>> logging.getLogger().handlers # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + [<zope...testrunner.logsupport.NullHandler instance at ...>, + <logging.StreamHandler instance at ...>] + +But tear down removes the new logging handler: + + >>> zc.buildout.testing.buildoutTearDown(test) + >>> len(logging.getLogger().handlers) + 1 + >>> logging.getLogger().handlers # doctest: +ELLIPSIS + [<zope...testrunner.logsupport.NullHandler instance at ...>] diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testrecipes.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testrecipes.py new file mode 100644 index 0000000..f0eb9d7 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testrecipes.py @@ -0,0 +1,16 @@ + +class Debug: + + def __init__(self, buildout, name, options): + self.buildout = buildout + self.name = name + self.options = options + + def install(self): + items = self.options.items() + items.sort() + for option, value in items: + print " %s=%r" % (option, value) + return () + + update = install diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/tests.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/tests.py new file mode 100644 index 0000000..bf8a715 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/tests.py @@ -0,0 +1,4190 @@ +############################################################################## +# +# Copyright (c) 2004-2009 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +import doctest +from zope.testing import renormalizing +import os +import pkg_resources +import re +import shutil +import sys +import tempfile +import unittest +import zc.buildout.easy_install +import zc.buildout.testing +import zc.buildout.testselectingpython +import zipfile + +os_path_sep = os.path.sep +if os_path_sep == '\\': + os_path_sep *= 2 + + +def develop_w_non_setuptools_setup_scripts(): + """ +We should be able to deal with setup scripts that aren't setuptools based. + + >>> mkdir('foo') + >>> write('foo', 'setup.py', + ... ''' + ... from distutils.core import setup + ... setup(name="foo") + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = foo + ... parts = + ... ''') + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/foo' + + >>> ls('develop-eggs') + - foo.egg-link + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + + """ + +def develop_verbose(): + """ +We should be able to deal with setup scripts that aren't setuptools based. + + >>> mkdir('foo') + >>> write('foo', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name="foo") + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = foo + ... parts = + ... ''') + + >>> print system(join('bin', 'buildout')+' -vv'), # doctest: +ELLIPSIS + Installing... + Develop: '/sample-buildout/foo' + ... + Installed /sample-buildout/foo + ... + + >>> ls('develop-eggs') + - foo.egg-link + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + + >>> print system(join('bin', 'buildout')+' -vvv'), # doctest: +ELLIPSIS + Installing... + Develop: '/sample-buildout/foo' + in: '/sample-buildout/foo' + ... -q develop -mxN -d /sample-buildout/develop-eggs/... + + + """ + +def buildout_error_handling(): + r"""Buildout error handling + +Asking for a section that doesn't exist, yields a missing section error: + + >>> import os + >>> os.chdir(sample_buildout) + >>> import zc.buildout.buildout + >>> buildout = zc.buildout.buildout.Buildout('buildout.cfg', []) + >>> buildout['eek'] + Traceback (most recent call last): + ... + MissingSection: The referenced section, 'eek', was not defined. + +Asking for an option that doesn't exist, a MissingOption error is raised: + + >>> buildout['buildout']['eek'] + Traceback (most recent call last): + ... + MissingOption: Missing option: buildout:eek + +It is an error to create a variable-reference cycle: + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... parts = + ... x = ${buildout:y} + ... y = ${buildout:z} + ... z = ${buildout:x} + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + While: + Initializing. + Getting section buildout. + Initializing section buildout. + Getting option buildout:y. + Getting option buildout:z. + Getting option buildout:x. + Getting option buildout:y. + Error: Circular reference in substitutions. + +It is an error to use funny characters in variable refereces: + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = data_dir debug + ... x = ${bui$ldout:y} + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + While: + Initializing. + Getting section buildout. + Initializing section buildout. + Getting option buildout:x. + Error: The section name in substitution, ${bui$ldout:y}, + has invalid characters. + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = data_dir debug + ... x = ${buildout:y{z} + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + While: + Initializing. + Getting section buildout. + Initializing section buildout. + Getting option buildout:x. + Error: The option name in substitution, ${buildout:y{z}, + has invalid characters. + +and too have too many or too few colons: + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = data_dir debug + ... x = ${parts} + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + While: + Initializing. + Getting section buildout. + Initializing section buildout. + Getting option buildout:x. + Error: The substitution, ${parts}, + doesn't contain a colon. + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = data_dir debug + ... x = ${buildout:y:z} + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + While: + Initializing. + Getting section buildout. + Initializing section buildout. + Getting option buildout:x. + Error: The substitution, ${buildout:y:z}, + has too many colons. + +Al parts have to have a section: + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... parts = x + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + While: + Installing. + Getting section x. + Error: The referenced section, 'x', was not defined. + +and all parts have to have a specified recipe: + + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... parts = x + ... + ... [x] + ... foo = 1 + ... ''') + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')), + While: + Installing. + Error: Missing option: x:recipe + +""" + +make_dist_that_requires_setup_py_template = """ +from setuptools import setup +setup(name=%r, version=%r, + install_requires=%r, + ) +""" + +def make_dist_that_requires(dest, name, requires=[], version=1, egg=''): + os.mkdir(os.path.join(dest, name)) + open(os.path.join(dest, name, 'setup.py'), 'w').write( + make_dist_that_requires_setup_py_template + % (name, version, requires) + ) + +def show_who_requires_when_there_is_a_conflict(): + """ +It's a pain when we require eggs that have requirements that are +incompatible. We want the error we get to tell us what is missing. + +Let's make a few develop distros, some of which have incompatible +requirements. + + >>> make_dist_that_requires(sample_buildout, 'sampley', + ... ['demoneeded ==1.0']) + >>> make_dist_that_requires(sample_buildout, 'samplez', + ... ['demoneeded ==1.1']) + +Now, let's create a buildout that requires y and z: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... develop = sampley samplez + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = sampley + ... samplez + ... ''' % globals()) + + >>> print system(buildout), + Develop: '/sample-buildout/sampley' + Develop: '/sample-buildout/samplez' + Installing eggs. + Getting distribution for 'demoneeded==1.1'. + Got demoneeded 1.1. + While: + Installing eggs. + Error: There is a version conflict. + We already have: demoneeded 1.1 + but sampley 1 requires 'demoneeded==1.0'. + +Here, we see that sampley required an older version of demoneeded. What +if we hadn't required sampley ourselves: + + >>> make_dist_that_requires(sample_buildout, 'samplea', ['sampleb']) + >>> make_dist_that_requires(sample_buildout, 'sampleb', + ... ['sampley', 'samplea']) + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... develop = sampley samplez samplea sampleb + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = samplea + ... samplez + ... ''' % globals()) + +If we use the verbose switch, we can see where requirements are coming from: + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + We have a develop egg: zc.buildout 1.0.0 + We have the best distribution that satisfies 'setuptools'. + Picked: setuptools = 0.6 + Develop: '/sample-buildout/sampley' + Develop: '/sample-buildout/samplez' + Develop: '/sample-buildout/samplea' + Develop: '/sample-buildout/sampleb' + ...Installing eggs. + Installing 'samplea', 'samplez'. + We have a develop egg: samplea 1 + We have a develop egg: samplez 1 + Getting required 'demoneeded==1.1' + required by samplez 1. + We have the distribution that satisfies 'demoneeded==1.1'. + Getting required 'sampleb' + required by samplea 1. + We have a develop egg: sampleb 1 + Getting required 'sampley' + required by sampleb 1. + We have a develop egg: sampley 1 + While: + Installing eggs. + Error: There is a version conflict. + We already have: demoneeded 1.1 + but sampley 1 requires 'demoneeded==1.0'. + """ + +def show_who_requires_missing_distributions(): + """ + +When working with a lot of eggs, which require eggs recursively, it can +be hard to tell why we're requiring things we can't find. Fortunately, +buildout will tell us who's asking for something that we can't find. + + >>> make_dist_that_requires(sample_buildout, 'sampley', ['demoneeded']) + >>> make_dist_that_requires(sample_buildout, 'samplea', ['sampleb']) + >>> make_dist_that_requires(sample_buildout, 'sampleb', + ... ['sampley', 'samplea']) + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... develop = sampley samplea sampleb + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = samplea + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/sampley' + Develop: '/sample-buildout/samplea' + Develop: '/sample-buildout/sampleb' + Installing eggs. + Couldn't find index page for 'demoneeded' (maybe misspelled?) + Getting distribution for 'demoneeded'. + While: + Installing eggs. + Getting distribution for 'demoneeded'. + Error: Couldn't find a distribution for 'demoneeded'. + """ + +def show_eggs_from_site_packages(): + """ +Sometimes you want to know what eggs are coming from site-packages. This +might be for a diagnostic, or so that you can get a starting value for the +allowed-eggs-from-site-packages option. The -v flag will also include this +information. + +Our "py_path" has the "demoneeded," "demo" +packages available. We'll ask for "bigdemo," which will get both of them. + +Here's our set up. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... prefer-final = true + ... find-links = %(link_server)s + ... + ... [primed_python] + ... executable = %(py_path)s + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... python = primed_python + ... eggs = bigdemo + ... ''' % globals()) + +Now here is the output. The lines that begin with "Egg from site-packages:" +indicate the eggs from site-packages that have been selected. You'll see +we have two: demo 0.3 and demoneeded 1.1. + + >>> print system(buildout+" -v"), + Installing 'zc.buildout', 'setuptools'. + We have a develop egg: zc.buildout V + We have the best distribution that satisfies 'setuptools'. + Picked: setuptools = V + Installing 'zc.recipe.egg'. + We have a develop egg: zc.recipe.egg V + Installing eggs. + Installing 'bigdemo'. + We have no distributions for bigdemo that satisfies 'bigdemo'. + Getting distribution for 'bigdemo'. + Got bigdemo 0.1. + Picked: bigdemo = 0.1 + Getting required 'demo' + required by bigdemo 0.1. + We have the best distribution that satisfies 'demo'. + Egg from site-packages: demo 0.3 + Getting required 'demoneeded' + required by demo 0.3. + We have the best distribution that satisfies 'demoneeded'. + Egg from site-packages: demoneeded 1.1 + """ + +def test_comparing_saved_options_with_funny_characters(): + """ +If an option has newlines, extra/odd spaces or a %, we need to make sure +the comparison with the saved value works correctly. + + >>> mkdir(sample_buildout, 'recipes') + >>> write(sample_buildout, 'recipes', 'debug.py', + ... ''' + ... class Debug: + ... def __init__(self, buildout, name, options): + ... options['debug'] = \"\"\" <zodb> + ... + ... <filestorage> + ... path foo + ... </filestorage> + ... + ... </zodb> + ... \"\"\" + ... options['debug1'] = \"\"\" + ... <zodb> + ... + ... <filestorage> + ... path foo + ... </filestorage> + ... + ... </zodb> + ... \"\"\" + ... options['debug2'] = ' x ' + ... options['debug3'] = '42' + ... options['format'] = '%3d' + ... + ... def install(self): + ... open('t', 'w').write('t') + ... return 't' + ... + ... update = install + ... ''') + + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup( + ... name = "recipes", + ... entry_points = {'zc.buildout': ['default = debug:Debug']}, + ... ) + ... ''') + + >>> write(sample_buildout, 'recipes', 'README.txt', " ") + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = debug + ... + ... [debug] + ... recipe = recipes + ... ''') + + >>> os.chdir(sample_buildout) + >>> buildout = os.path.join(sample_buildout, 'bin', 'buildout') + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing debug. + +If we run the buildout again, we shoudn't get a message about +uninstalling anything because the configuration hasn't changed. + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Updating debug. +""" + +def finding_eggs_as_local_directories(): + r""" +It is possible to set up find-links so that we could install from +a local directory that may contained unzipped eggs. + + >>> src = tmpdir('src') + >>> write(src, 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='demo', py_modules=[''], + ... zip_safe=False, version='1.0', author='bob', url='bob', + ... author_email='bob') + ... ''') + + >>> write(src, 't.py', '#\n') + >>> write(src, 'README.txt', '') + >>> _ = system(join('bin', 'buildout')+' setup ' + src + ' bdist_egg') + +Install it so it gets unzipped: + + >>> d1 = tmpdir('d1') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], d1, links=[join(src, 'dist')], + ... ) + + >>> ls(d1) + d demo-1.0-py2.4.egg + +Then try to install it again: + + >>> d2 = tmpdir('d2') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], d2, links=[d1], + ... ) + + >>> ls(d2) + d demo-1.0-py2.4.egg + + """ + +def make_sure__get_version_works_with_2_digit_python_versions(): + """ + +This is a test of an internal function used by higher-level machinery. + +We'll start by creating a faux 'python' that executable that prints a +2-digit version. This is a bit of a pain to do portably. :( + + >>> mkdir('demo') + >>> write('demo', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='demo', + ... entry_points = {'console_scripts': ['demo = demo:main']}, + ... ) + ... ''') + >>> write('demo', 'demo.py', + ... ''' + ... def main(): + ... print 'Python 2.5' + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = demo + ... parts = + ... ''') + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/demo' + + >>> import zc.buildout.easy_install + >>> ws = zc.buildout.easy_install.working_set( + ... ['demo'], sys.executable, ['develop-eggs']) + >>> bool(zc.buildout.easy_install.scripts( + ... ['demo'], ws, sys.executable, 'bin')) + True + + >>> print system(join('bin', 'demo')), + Python 2.5 + +Now, finally, let's test _get_version: + + >>> zc.buildout.easy_install._get_version(join('bin', 'demo')) + '2.5' + + """ + +def create_sections_on_command_line(): + """ + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = + ... x = ${foo:bar} + ... ''') + + >>> print system(buildout + ' foo:bar=1 -vv'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + ... + [foo] + bar = 1 + ... + + """ + +def test_help(): + """ + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout')+' -h'), + ... # doctest: +ELLIPSIS + Usage: buildout [options] [assignments] [command [command arguments]] + <BLANKLINE> + Options: + <BLANKLINE> + -h, --help + ... + + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout') + ... +' --help'), + ... # doctest: +ELLIPSIS + Usage: buildout [options] [assignments] [command [command arguments]] + <BLANKLINE> + Options: + <BLANKLINE> + -h, --help + ... + """ + +def test_bootstrap_with_extension(): + """ +We had a problem running a bootstrap with an extension. Let's make +sure it is fixed. Basically, we don't load extensions when +bootstrapping. + + >>> d = tmpdir('sample-bootstrap') + + >>> write(d, 'buildout.cfg', + ... ''' + ... [buildout] + ... extensions = some_awsome_extension + ... parts = + ... ''') + + >>> os.chdir(d) + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout') + ... + ' bootstrap'), + Creating directory '/sample-bootstrap/bin'. + Creating directory '/sample-bootstrap/parts'. + Creating directory '/sample-bootstrap/eggs'. + Creating directory '/sample-bootstrap/develop-eggs'. + Generated script '/sample-bootstrap/bin/buildout'. + """ + + +def bug_92891_bootstrap_crashes_with_egg_recipe_in_buildout_section(): + """ + >>> d = tmpdir('sample-bootstrap') + + >>> write(d, 'buildout.cfg', + ... ''' + ... [buildout] + ... parts = buildout + ... eggs-directory = eggs + ... + ... [buildout] + ... recipe = zc.recipe.egg + ... eggs = zc.buildout + ... scripts = buildout=buildout + ... ''') + + >>> os.chdir(d) + >>> print system(os.path.join(sample_buildout, 'bin', 'buildout') + ... + ' bootstrap'), + Creating directory '/sample-bootstrap/bin'. + Creating directory '/sample-bootstrap/parts'. + Creating directory '/sample-bootstrap/eggs'. + Creating directory '/sample-bootstrap/develop-eggs'. + Generated script '/sample-bootstrap/bin/buildout'. + + >>> print system(os.path.join('bin', 'buildout')), + Unused options for buildout: 'scripts' 'eggs'. + + """ + +def removing_eggs_from_develop_section_causes_egg_link_to_be_removed(): + ''' + >>> cd(sample_buildout) + +Create a develop egg: + + >>> mkdir('foo') + >>> write('foo', 'setup.py', + ... """ + ... from setuptools import setup + ... setup(name='foox') + ... """) + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = foo + ... parts = + ... """) + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/foo' + + >>> ls('develop-eggs') + - foox.egg-link + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + +Create another: + + >>> mkdir('bar') + >>> write('bar', 'setup.py', + ... """ + ... from setuptools import setup + ... setup(name='fooy') + ... """) + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = foo bar + ... parts = + ... """) + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/foo' + Develop: '/sample-buildout/bar' + + >>> ls('develop-eggs') + - foox.egg-link + - fooy.egg-link + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + +Remove one: + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = bar + ... parts = + ... """) + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/bar' + +It is gone + + >>> ls('develop-eggs') + - fooy.egg-link + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + +Remove the other: + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... parts = + ... """) + >>> print system(join('bin', 'buildout')), + +All gone + + >>> ls('develop-eggs') + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + ''' + + +def add_setuptools_to_dependencies_when_namespace_packages(): + ''' +Often, a package depends on setuptools soley by virtue of using +namespace packages. In this situation, package authors often forget to +declare setuptools as a dependency. This is a mistake, but, +unfortunately, a common one that we need to work around. If an egg +uses namespace packages and does not include setuptools as a depenency, +we will still include setuptools in the working set. If we see this for +a devlop egg, we will also generate a warning. + + >>> mkdir('foo') + >>> mkdir('foo', 'src') + >>> mkdir('foo', 'src', 'stuff') + >>> write('foo', 'src', 'stuff', '__init__.py', + ... """__import__('pkg_resources').declare_namespace(__name__) + ... """) + >>> mkdir('foo', 'src', 'stuff', 'foox') + >>> write('foo', 'src', 'stuff', 'foox', '__init__.py', '') + >>> write('foo', 'setup.py', + ... """ + ... from setuptools import setup + ... setup(name='foox', + ... namespace_packages = ['stuff'], + ... package_dir = {'': 'src'}, + ... packages = ['stuff', 'stuff.foox'], + ... ) + ... """) + >>> write('foo', 'README.txt', '') + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = foo + ... parts = + ... """) + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/foo' + +Now, if we generate a working set using the egg link, we will get a warning +and we will get setuptools included in the working set. + + >>> import logging, zope.testing.loggingsupport + >>> handler = zope.testing.loggingsupport.InstalledHandler( + ... 'zc.buildout.easy_install', level=logging.WARNING) + >>> logging.getLogger('zc.buildout.easy_install').propagate = False + + >>> [dist.project_name + ... for dist in zc.buildout.easy_install.working_set( + ... ['foox'], sys.executable, + ... [join(sample_buildout, 'eggs'), + ... join(sample_buildout, 'develop-eggs'), + ... ])] + ['foox', 'setuptools'] + + >>> print handler + zc.buildout.easy_install WARNING + Develop distribution: foox 0.0.0 + uses namespace packages but the distribution does not require setuptools. + + >>> handler.clear() + +On the other hand, if we have a regular egg, rather than a develop egg: + + >>> os.remove(join('develop-eggs', 'foox.egg-link')) + + >>> _ = system(join('bin', 'buildout') + ' setup foo bdist_egg -d' + ... + join(sample_buildout, 'eggs')) + + >>> ls('develop-eggs') + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + + >>> print 'START ->'; ls('eggs') # doctest: +ELLIPSIS + START... + - foox-0.0.0-py2.4.egg + ... + +We do not get a warning, but we do get setuptools included in the working set: + + >>> [dist.project_name + ... for dist in zc.buildout.easy_install.working_set( + ... ['foox'], sys.executable, + ... [join(sample_buildout, 'eggs'), + ... join(sample_buildout, 'develop-eggs'), + ... ])] + ['foox', 'setuptools'] + + >>> print handler, + +We get the same behavior if the it is a depedency that uses a +namespace package. + + + >>> mkdir('bar') + >>> write('bar', 'setup.py', + ... """ + ... from setuptools import setup + ... setup(name='bar', install_requires = ['foox']) + ... """) + >>> write('bar', 'README.txt', '') + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = foo bar + ... parts = + ... """) + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/foo' + Develop: '/sample-buildout/bar' + + >>> [dist.project_name + ... for dist in zc.buildout.easy_install.working_set( + ... ['bar'], sys.executable, + ... [join(sample_buildout, 'eggs'), + ... join(sample_buildout, 'develop-eggs'), + ... ])] + ['bar', 'foox', 'setuptools'] + + >>> print handler, + zc.buildout.easy_install WARNING + Develop distribution: foox 0.0.0 + uses namespace packages but the distribution does not require setuptools. + + + >>> logging.getLogger('zc.buildout.easy_install').propagate = True + >>> handler.uninstall() + + ''' + +def develop_preserves_existing_setup_cfg(): + """ + +See "Handling custom build options for extensions in develop eggs" in +easy_install.txt. This will be very similar except that we'll have an +existing setup.cfg: + + >>> write(extdemo, "setup.cfg", + ... ''' + ... # sampe cfg file + ... + ... [foo] + ... bar = 1 + ... + ... [build_ext] + ... define = X,Y + ... ''') + + >>> mkdir('include') + >>> write('include', 'extdemo.h', + ... ''' + ... #define EXTDEMO 42 + ... ''') + + >>> dest = tmpdir('dest') + >>> zc.buildout.easy_install.develop( + ... extdemo, dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}) + '/dest/extdemo.egg-link' + + >>> ls(dest) + - extdemo.egg-link + + >>> cat(extdemo, "setup.cfg") + <BLANKLINE> + # sampe cfg file + <BLANKLINE> + [foo] + bar = 1 + <BLANKLINE> + [build_ext] + define = X,Y + +""" + +def uninstall_recipes_used_for_removal(): + """ +Uninstall recipes need to be called when a part is removed too: + + >>> mkdir("recipes") + >>> write("recipes", "setup.py", + ... ''' + ... from setuptools import setup + ... setup(name='recipes', + ... entry_points={ + ... 'zc.buildout': ["demo=demo:Install"], + ... 'zc.buildout.uninstall': ["demo=demo:uninstall"], + ... }) + ... ''') + + >>> write("recipes", "demo.py", + ... ''' + ... class Install: + ... def __init__(*args): pass + ... def install(self): + ... print 'installing' + ... return () + ... def uninstall(name, options): print 'uninstalling' + ... ''') + + >>> write('buildout.cfg', ''' + ... [buildout] + ... develop = recipes + ... parts = demo + ... [demo] + ... recipe = recipes:demo + ... ''') + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/recipes' + Installing demo. + installing + + + >>> write('buildout.cfg', ''' + ... [buildout] + ... develop = recipes + ... parts = demo + ... [demo] + ... recipe = recipes:demo + ... x = 1 + ... ''') + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/recipes' + Uninstalling demo. + Running uninstall recipe. + uninstalling + Installing demo. + installing + + + >>> write('buildout.cfg', ''' + ... [buildout] + ... develop = recipes + ... parts = + ... ''') + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/recipes' + Uninstalling demo. + Running uninstall recipe. + uninstalling + +""" + +def extensions_installed_as_eggs_work_in_offline_mode(): + ''' + >>> mkdir('demo') + + >>> write('demo', 'demo.py', + ... """ + ... def ext(buildout): + ... print 'ext', list(buildout) + ... """) + + >>> write('demo', 'setup.py', + ... """ + ... from setuptools import setup + ... + ... setup( + ... name = "demo", + ... py_modules=['demo'], + ... entry_points = {'zc.buildout.extension': ['ext = demo:ext']}, + ... ) + ... """) + + >>> bdist_egg(join(sample_buildout, "demo"), sys.executable, + ... join(sample_buildout, "eggs")) + + >>> write(sample_buildout, 'buildout.cfg', + ... """ + ... [buildout] + ... extensions = demo + ... parts = + ... offline = true + ... """) + + >>> print system(join(sample_buildout, 'bin', 'buildout')), + ext ['buildout'] + + + ''' + +def changes_in_svn_or_CVS_dont_affect_sig(): + """ + +If we have a develop recipe, it's signature shouldn't be affected to +changes in .svn or CVS directories. + + >>> mkdir('recipe') + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='recipe', + ... entry_points={'zc.buildout': ['default=foo:Foo']}) + ... ''') + >>> write('recipe', 'foo.py', + ... ''' + ... class Foo: + ... def __init__(*args): pass + ... def install(*args): return () + ... update = install + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipe + ... parts = foo + ... + ... [foo] + ... recipe = recipe + ... ''') + + + >>> print system(join(sample_buildout, 'bin', 'buildout')), + Develop: '/sample-buildout/recipe' + Installing foo. + + >>> mkdir('recipe', '.svn') + >>> mkdir('recipe', 'CVS') + >>> print system(join(sample_buildout, 'bin', 'buildout')), + Develop: '/sample-buildout/recipe' + Updating foo. + + >>> write('recipe', '.svn', 'x', '1') + >>> write('recipe', 'CVS', 'x', '1') + + >>> print system(join(sample_buildout, 'bin', 'buildout')), + Develop: '/sample-buildout/recipe' + Updating foo. + + """ + +if hasattr(os, 'symlink'): + def bug_250537_broken_symlink_doesnt_affect_sig(): + """ +If we have a develop recipe, it's signature shouldn't be affected by +broken symlinks, and better yet, computing the hash should not break +because of the missing target file. + + >>> mkdir('recipe') + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='recipe', + ... entry_points={'zc.buildout': ['default=foo:Foo']}) + ... ''') + >>> write('recipe', 'foo.py', + ... ''' + ... class Foo: + ... def __init__(*args): pass + ... def install(*args): return () + ... update = install + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipe + ... parts = foo + ... + ... [foo] + ... recipe = recipe + ... ''') + + + >>> print system(join(sample_buildout, 'bin', 'buildout')), + Develop: '/sample-buildout/recipe' + Installing foo. + + >>> write('recipe', 'some-file', '1') + >>> os.symlink(join('recipe', 'some-file'), + ... join('recipe', 'another-file')) + >>> ls('recipe') + l another-file + - foo.py + - foo.pyc + d recipe.egg-info + - setup.py + - some-file + + >>> remove('recipe', 'some-file') + + >>> print system(join(sample_buildout, 'bin', 'buildout')), + Develop: '/sample-buildout/recipe' + Updating foo. + + """ + +def o_option_sets_offline(): + """ + >>> print system(join(sample_buildout, 'bin', 'buildout')+' -vvo'), + ... # doctest: +ELLIPSIS + <BLANKLINE> + ... + offline = true + ... + """ + +def recipe_upgrade(): + """ + +The buildout will upgrade recipes in newest (and non-offline) mode. + +Let's create a recipe egg + + >>> mkdir('recipe') + >>> write('recipe', 'recipe.py', + ... ''' + ... class Recipe: + ... def __init__(*a): pass + ... def install(self): + ... print 'recipe v1' + ... return () + ... update = install + ... ''') + + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='recipe', version='1', py_modules=['recipe'], + ... entry_points={'zc.buildout': ['default = recipe:Recipe']}, + ... ) + ... ''') + + >>> write('recipe', 'README', '') + + >>> print system(buildout+' setup recipe bdist_egg'), # doctest: +ELLIPSIS + Running setup script 'recipe/setup.py'. + ... + + >>> rmdir('recipe', 'build') + +And update our buildout to use it. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = %s + ... + ... [foo] + ... recipe = recipe + ... ''' % join('recipe', 'dist')) + + >>> print system(buildout), + Getting distribution for 'recipe'. + Got recipe 1. + Installing foo. + recipe v1 + +Now, if we update the recipe egg: + + >>> write('recipe', 'recipe.py', + ... ''' + ... class Recipe: + ... def __init__(*a): pass + ... def install(self): + ... print 'recipe v2' + ... return () + ... update = install + ... ''') + + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='recipe', version='2', py_modules=['recipe'], + ... entry_points={'zc.buildout': ['default = recipe:Recipe']}, + ... ) + ... ''') + + + >>> print system(buildout+' setup recipe bdist_egg'), # doctest: +ELLIPSIS + Running setup script 'recipe/setup.py'. + ... + +We won't get the update if we specify -N: + + >>> print system(buildout+' -N'), + Updating foo. + recipe v1 + +or if we use -o: + + >>> print system(buildout+' -o'), + Updating foo. + recipe v1 + +But we will if we use neither of these: + + >>> print system(buildout), + Getting distribution for 'recipe'. + Got recipe 2. + Uninstalling foo. + Installing foo. + recipe v2 + +We can also select a particular recipe version: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = %s + ... + ... [foo] + ... recipe = recipe ==1 + ... ''' % join('recipe', 'dist')) + + >>> print system(buildout), + Uninstalling foo. + Installing foo. + recipe v1 + + """ + +def update_adds_to_uninstall_list(): + """ + +Paths returned by the update method are added to the list of paths to +uninstall + + >>> mkdir('recipe') + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='recipe', + ... entry_points={'zc.buildout': ['default = recipe:Recipe']}, + ... ) + ... ''') + + >>> write('recipe', 'recipe.py', + ... ''' + ... import os + ... class Recipe: + ... def __init__(*_): pass + ... def install(self): + ... r = ('a', 'b', 'c') + ... for p in r: os.mkdir(p) + ... return r + ... def update(self): + ... r = ('c', 'd', 'e') + ... for p in r: + ... if not os.path.exists(p): + ... os.mkdir(p) + ... return r + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipe + ... parts = foo + ... + ... [foo] + ... recipe = recipe + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/recipe' + Installing foo. + + >>> print system(buildout), + Develop: '/sample-buildout/recipe' + Updating foo. + + >>> cat('.installed.cfg') # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + [buildout] + ... + [foo] + __buildout_installed__ = a + b + c + d + e + __buildout_signature__ = ... + +""" + +def log_when_there_are_not_local_distros(): + """ + >>> from zope.testing.loggingsupport import InstalledHandler + >>> handler = InstalledHandler('zc.buildout.easy_install') + >>> import logging + >>> logger = logging.getLogger('zc.buildout.easy_install') + >>> old_propogate = logger.propagate + >>> logger.propagate = False + + >>> dest = tmpdir('sample-install') + >>> import zc.buildout.easy_install + >>> ws = zc.buildout.easy_install.install( + ... ['demo==0.2'], dest, + ... links=[link_server], index=link_server+'index/') + + >>> print handler # doctest: +ELLIPSIS + zc.buildout.easy_install DEBUG + Installing 'demo==0.2'. + zc.buildout.easy_install DEBUG + We have no distributions for demo that satisfies 'demo==0.2'. + ... + + >>> handler.uninstall() + >>> logger.propagate = old_propogate + + """ + +def internal_errors(): + """Internal errors are clearly marked and don't generate tracebacks: + + >>> mkdir(sample_buildout, 'recipes') + + >>> write(sample_buildout, 'recipes', 'mkdir.py', + ... ''' + ... class Mkdir: + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... options['path'] = os.path.join( + ... buildout['buildout']['directory'], + ... options['path'], + ... ) + ... ''') + + >>> write(sample_buildout, 'recipes', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name = "recipes", + ... entry_points = {'zc.buildout': ['mkdir = mkdir:Mkdir']}, + ... ) + ... ''') + + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = data-dir + ... + ... [data-dir] + ... recipe = recipes:mkdir + ... ''') + + >>> print system(buildout), # doctest: +ELLIPSIS + Develop: '/sample-buildout/recipes' + While: + Installing. + Getting section data-dir. + Initializing part data-dir. + <BLANKLINE> + An internal error occurred due to a bug in either zc.buildout or in a + recipe being used: + Traceback (most recent call last): + ... + NameError: global name 'os' is not defined + """ + +def whine_about_unused_options(): + ''' + + >>> write('foo.py', + ... """ + ... class Foo: + ... + ... def __init__(self, buildout, name, options): + ... self.name, self.options = name, options + ... options['x'] + ... + ... def install(self): + ... self.options['y'] + ... return () + ... """) + + >>> write('setup.py', + ... """ + ... from setuptools import setup + ... setup(name = "foo", + ... entry_points = {'zc.buildout': ['default = foo:Foo']}, + ... ) + ... """) + + >>> write('buildout.cfg', + ... """ + ... [buildout] + ... develop = . + ... parts = foo + ... a = 1 + ... + ... [foo] + ... recipe = foo + ... x = 1 + ... y = 1 + ... z = 1 + ... """) + + >>> print system(buildout), + Develop: '/sample-buildout/.' + Unused options for buildout: 'a'. + Installing foo. + Unused options for foo: 'z'. + ''' + +def abnormal_exit(): + """ +People sometimes hit control-c while running a builout. We need to make +sure that the installed database Isn't corrupted. To test this, we'll create +some evil recipes that exit uncleanly: + + >>> mkdir('recipes') + >>> write('recipes', 'recipes.py', + ... ''' + ... import os + ... + ... class Clean: + ... def __init__(*_): pass + ... def install(_): return () + ... def update(_): pass + ... + ... class EvilInstall(Clean): + ... def install(_): os._exit(1) + ... + ... class EvilUpdate(Clean): + ... def update(_): os._exit(1) + ... ''') + + >>> write('recipes', 'setup.py', + ... ''' + ... import setuptools + ... setuptools.setup(name='recipes', + ... entry_points = { + ... 'zc.buildout': [ + ... 'clean = recipes:Clean', + ... 'evil_install = recipes:EvilInstall', + ... 'evil_update = recipes:EvilUpdate', + ... 'evil_uninstall = recipes:Clean', + ... ], + ... }, + ... ) + ... ''') + +Now let's look at 3 cases: + +1. We exit during installation after installing some other parts: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = p1 p2 p3 p4 + ... + ... [p1] + ... recipe = recipes:clean + ... + ... [p2] + ... recipe = recipes:clean + ... + ... [p3] + ... recipe = recipes:evil_install + ... + ... [p4] + ... recipe = recipes:clean + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing p1. + Installing p2. + Installing p3. + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Updating p1. + Updating p2. + Installing p3. + + >>> print system(buildout+' buildout:parts='), + Develop: '/sample-buildout/recipes' + Uninstalling p2. + Uninstalling p1. + +2. We exit while updating: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = p1 p2 p3 p4 + ... + ... [p1] + ... recipe = recipes:clean + ... + ... [p2] + ... recipe = recipes:clean + ... + ... [p3] + ... recipe = recipes:evil_update + ... + ... [p4] + ... recipe = recipes:clean + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing p1. + Installing p2. + Installing p3. + Installing p4. + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Updating p1. + Updating p2. + Updating p3. + + >>> print system(buildout+' buildout:parts='), + Develop: '/sample-buildout/recipes' + Uninstalling p2. + Uninstalling p1. + Uninstalling p4. + Uninstalling p3. + +3. We exit while installing or updating after uninstalling: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = p1 p2 p3 p4 + ... + ... [p1] + ... recipe = recipes:evil_update + ... + ... [p2] + ... recipe = recipes:clean + ... + ... [p3] + ... recipe = recipes:clean + ... + ... [p4] + ... recipe = recipes:clean + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Installing p1. + Installing p2. + Installing p3. + Installing p4. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = p1 p2 p3 p4 + ... + ... [p1] + ... recipe = recipes:evil_update + ... + ... [p2] + ... recipe = recipes:clean + ... + ... [p3] + ... recipe = recipes:clean + ... + ... [p4] + ... recipe = recipes:clean + ... x = 1 + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling p4. + Updating p1. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = p1 p2 p3 p4 + ... + ... [p1] + ... recipe = recipes:clean + ... + ... [p2] + ... recipe = recipes:clean + ... + ... [p3] + ... recipe = recipes:clean + ... + ... [p4] + ... recipe = recipes:clean + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/recipes' + Uninstalling p1. + Installing p1. + Updating p2. + Updating p3. + Installing p4. + + """ + +def install_source_dist_with_bad_py(): + """ + + >>> mkdir('badegg') + >>> mkdir('badegg', 'badegg') + >>> write('badegg', 'badegg', '__init__.py', '#\\n') + >>> mkdir('badegg', 'badegg', 'scripts') + >>> write('badegg', 'badegg', 'scripts', '__init__.py', '#\\n') + >>> write('badegg', 'badegg', 'scripts', 'one.py', + ... ''' + ... return 1 + ... ''') + + >>> write('badegg', 'setup.py', + ... ''' + ... from setuptools import setup, find_packages + ... setup( + ... name='badegg', + ... version='1', + ... packages = find_packages('.'), + ... zip_safe=False) + ... ''') + + >>> print system(buildout+' setup badegg sdist'), # doctest: +ELLIPSIS + Running setup script 'badegg/setup.py'. + ... + + >>> dist = join('badegg', 'dist') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs bo + ... find-links = %(dist)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = badegg + ... + ... [bo] + ... recipe = zc.recipe.egg + ... eggs = zc.buildout + ... scripts = buildout=bo + ... ''' % globals()) + + >>> print system(buildout);print 'X' # doctest: +ELLIPSIS + Installing eggs. + Getting distribution for 'badegg'. + Got badegg 1. + Installing bo. + ... + SyntaxError: ...'return' outside function... + ... + SyntaxError: ...'return' outside function... + ... + + >>> ls('eggs') # doctest: +ELLIPSIS + d badegg-1-py2.4.egg + ... + + >>> ls('bin') + - bo + - buildout + """ + +def version_requirements_in_build_honored(): + ''' + + >>> update_extdemo() + >>> dest = tmpdir('sample-install') + >>> mkdir('include') + >>> write('include', 'extdemo.h', + ... """ + ... #define EXTDEMO 42 + ... """) + + >>> zc.buildout.easy_install.build( + ... 'extdemo ==1.4', dest, + ... {'include-dirs': os.path.join(sample_buildout, 'include')}, + ... links=[link_server], index=link_server+'index/', + ... newest=False) + ['/sample-install/extdemo-1.4-py2.4-linux-i686.egg'] + + ''' + +def bug_105081_Specific_egg_versions_are_ignored_when_newer_eggs_are_around(): + """ + Buildout might ignore a specific egg requirement for a recipe: + + - Have a newer version of an egg in your eggs directory + - Use 'recipe==olderversion' in your buildout.cfg to request an + older version + + Buildout will go and fetch the older version, but it will *use* + the newer version when installing a part with this recipe. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = x + ... find-links = %(sample_eggs)s + ... + ... [x] + ... recipe = zc.recipe.egg + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout), + Installing x. + Getting distribution for 'demo'. + Got demo 0.4c1. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + Generated script '/sample-buildout/bin/demo'. + + >>> print system(join('bin', 'demo')), + 4 2 + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = x + ... find-links = %(sample_eggs)s + ... + ... [x] + ... recipe = zc.recipe.egg + ... eggs = demo ==0.1 + ... ''' % globals()) + + >>> print system(buildout), + Uninstalling x. + Installing x. + Getting distribution for 'demo==0.1'. + Got demo 0.1. + Generated script '/sample-buildout/bin/demo'. + + >>> print system(join('bin', 'demo')), + 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. ``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. + + >>> py_path = make_py_with_system_install(make_py, sample_eggs) + >>> print call_py( + ... py_path, + ... "import tellmy.version; print tellmy.version.__version__"), + 1.1 + +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( + ... ['tellmy.version'], example_dest, links=[sample_eggs], + ... executable=py_path, + ... index=None, + ... versions={'tellmy.version': '1.0'}) + >>> for dist in workingset: + ... res = str(dist) + ... if res.startswith('tellmy.version'): + ... print res + ... break + tellmy.version 1.0 + +Before the bugfix, the desired tellmy.version distribution would have +been blocked the one in site-packages. +""" + +def handle_namespace_package_in_both_site_packages_and_buildout_eggs(): + r""" +If you have the same namespace package in both site-packages and in +buildout, we need to be very careful that faux-Python-executables and +scripts generated by easy_install.sitepackage_safe_scripts correctly +combine the two. We show this with the local recipe that uses the +function, z3c.recipe.scripts. + +To demonstrate this, we will create three packages: tellmy.version 1.0, +tellmy.version 1.1, and tellmy.fortune 1.0. tellmy.version 1.1 is installed. + + >>> py_path = make_py_with_system_install(make_py, sample_eggs) + >>> print call_py( + ... py_path, + ... "import tellmy.version; print tellmy.version.__version__") + 1.1 + <BLANKLINE> + +Now we will create a buildout that creates a script and a faux-Python script. +We want to see that both can successfully import the specified versions of +tellmy.version and tellmy.fortune. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... + ... [primed_python] + ... executable = %(py_path)s + ... + ... [eggs] + ... recipe = z3c.recipe.scripts + ... python = primed_python + ... interpreter = py + ... include-site-packages = true + ... eggs = tellmy.version == 1.0 + ... tellmy.fortune == 1.0 + ... demo + ... script-initialization = + ... import tellmy.version + ... print tellmy.version.__version__ + ... import tellmy.fortune + ... print tellmy.fortune.__version__ + ... ''' % globals()) + + >>> print system(buildout) + Installing eggs. + Getting distribution for 'tellmy.version==1.0'. + Got tellmy.version 1.0. + Getting distribution for 'tellmy.fortune==1.0'. + Got tellmy.fortune 1.0. + Getting distribution for 'demo'. + Got demo 0.4c1. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + Generated script '/sample-buildout/bin/demo'. + Generated interpreter '/sample-buildout/bin/py'. + <BLANKLINE> + +Finally, we are ready to see if it worked. Prior to the bug fix that +this tests, the results of both calls below was the following:: + + 1.1 + Traceback (most recent call last): + ... + ImportError: No module named fortune + <BLANKLINE> + +In other words, we got the site-packages version of tellmy.version, and +we could not import tellmy.fortune at all. The following are the correct +results for the interpreter and for the script. + + >>> print call_py( + ... join('bin', 'py'), + ... "import tellmy.version; " + + ... "print tellmy.version.__version__; " + + ... "import tellmy.fortune; " + + ... "print tellmy.fortune.__version__") # doctest: +ELLIPSIS + 1.0 + 1.0... + + >>> print system(join('bin', 'demo')) + 1.0 + 1.0 + 4 2 + <BLANKLINE> + """ + +def handle_sys_path_version_hack(): + r""" +This is a test for a bugfix. + +If you use a Python that has a different version of one of your +dependencies, and the new package tries to do sys.path tricks in the +setup.py to get a __version__, and it uses namespace packages, the older +package will be loaded first, making the setup version the wrong number. +While very arguably packages simply shouldn't do this, some do, and we +don't want buildout to fall over when they do. + +To demonstrate this, we will need to create a distribution that has one of +these unpleasant tricks, and a Python that has an older version installed. + + >>> py_path, site_packages_path = make_py() + >>> for version in ('1.0', '1.1'): + ... tmp = tempfile.mkdtemp() + ... try: + ... write(tmp, 'README.txt', '') + ... mkdir(tmp, 'src') + ... mkdir(tmp, 'src', 'tellmy') + ... write(tmp, 'src', 'tellmy', '__init__.py', + ... "__import__(" + ... "'pkg_resources').declare_namespace(__name__)\n") + ... mkdir(tmp, 'src', 'tellmy', 'version') + ... write(tmp, 'src', 'tellmy', 'version', + ... '__init__.py', '__version__=%r\n' % version) + ... write( + ... tmp, 'setup.py', + ... "from setuptools import setup\n" + ... "import sys\n" + ... "sys.path.insert(0, 'src')\n" + ... "from tellmy.version import __version__\n" + ... "setup(\n" + ... " name='tellmy.version',\n" + ... " package_dir = {'': 'src'},\n" + ... " packages = ['tellmy', 'tellmy.version'],\n" + ... " install_requires = ['setuptools'],\n" + ... " namespace_packages=['tellmy'],\n" + ... " zip_safe=True, version=__version__,\n" + ... " author='bob', url='bob', author_email='bob')\n" + ... ) + ... zc.buildout.testing.sdist(tmp, sample_eggs) + ... if version == '1.0': + ... # We install the 1.0 version in site packages the way a + ... # system packaging system (debs, rpms) would do it. + ... zc.buildout.testing.sys_install(tmp, site_packages_path) + ... finally: + ... shutil.rmtree(tmp) + >>> print call_py( + ... py_path, + ... "import tellmy.version; print tellmy.version.__version__") + 1.0 + <BLANKLINE> + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(sample_eggs)s + ... + ... [primed_python] + ... executable = %(py_path)s + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... python = primed_python + ... eggs = tellmy.version == 1.1 + ... ''' % globals()) + +Before the bugfix, running this buildout would generate this error: + + Installing eggs. + Getting distribution for 'tellmy.version==1.1'. + Installing tellmy.version 1.1 + Caused installation of a distribution: + tellmy.version 1.0 + with a different version. + Got None. + While: + Installing eggs. + Error: There is a version conflict. + We already have: tellmy.version 1.0 + <BLANKLINE> + +You can see the copiously commented fix for this in easy_install.py (see +zc.buildout.easy_install.Installer._call_easy_install and particularly +the comment leading up to zc.buildout.easy_install._easy_install_cmd). +Now the install works correctly, as seen here. + + >>> print system(buildout) + Installing eggs. + Getting distribution for 'tellmy.version==1.1'. + Got tellmy.version 1.1. + <BLANKLINE> + + """ + +def isolated_include_site_packages(): + """ + +This is an isolated test of the include_site_packages functionality, passing +the argument directly to install, overriding a default. + +Our "py_path" has the "demoneeded" and "demo" packages available. We'll +simply be asking for "demoneeded" here. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + >>> zc.buildout.easy_install.include_site_packages(False) + True + + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, include_site_packages=True) + >>> [dist.project_name for dist in workingset] + ['demoneeded'] + +That worked fine. Let's try again with site packages not allowed (and +reversing the default). + + >>> zc.buildout.easy_install.include_site_packages(True) + False + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, include_site_packages=False) + Traceback (most recent call last): + ... + MissingDistribution: Couldn't find a distribution for 'demoneeded'. + +That's a failure, as expected. + +Now we explore an important edge case. + +Some system Pythons include setuptools (and other Python packages) in their +site-packages (or equivalent) using a .egg-info directory. The pkg_resources +module (from setuptools) considers a package installed using .egg-info to be a +develop egg. + +zc.buildout.buildout.Buildout.bootstrap will make setuptools and zc.buildout +available to the buildout via the eggs directory, for normal eggs; or the +develop-eggs directory, for develop-eggs. + +If setuptools or zc.buildout is found in site-packages and considered by +pkg_resources to be a develop egg, then the bootstrap code will use a .egg-link +in the local develop-eggs, pointing to site-packages, in its entirety. Because +develop-eggs must always be available for searching for distributions, this +indirectly brings site-packages back into the search path for distributions. + +Because of this, we have to take special care that we still exclude +site-packages even in this case. See the comments about site packages in the +Installer._satisfied and Installer._obtain methods for the implementation +(as of this writing). + +In this demonstration, we insert a link to the "demoneeded" distribution +in our develop-eggs, which would bring the package back in, except for +the special care we have taken to exclude it. + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> mkdir(example_dest, 'develop-eggs') + >>> write(example_dest, 'develop-eggs', 'demoneeded.egg-link', + ... site_packages_path) + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], + ... path=[join(example_dest, 'develop-eggs')], + ... executable=py_path, + ... index=None, include_site_packages=False) + Traceback (most recent call last): + ... + MissingDistribution: Couldn't find a distribution for 'demoneeded'. + +The MissingDistribution error shows that buildout correctly excluded the +"site-packages" source even though it was indirectly included in the path +via a .egg-link file. + + """ + +def include_site_packages_bug_623590(): + """ +As mentioned in isolated_include_site_packages, some system Pythons +include various Python packages in their site-packages (or equivalent) +using a .egg-info directory. The pkg_resources module (from setuptools) +considers a package installed using .egg-info to be a develop egg + +We generally prefer develop eggs when we are selecting dependencies, because +we expect them to be eggs that buildout has been told to develop. However, +we should not consider these site-packages eggs as develop eggs--they should +not have automatic precedence over eggs available elsewhere. + +We have specific code to handle this case, as identified in bug 623590. +See zc.buildout.easy_install.Installer._satisfied, as of this writing, +for the pertinent code. Here's the test for the bugfix. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + >>> zc.buildout.easy_install.include_site_packages(False) + True + + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demo'], example_dest, links=[sample_eggs], executable=py_path, + ... index=None, include_site_packages=True, prefer_final=False) + >>> [(dist.project_name, dist.version) for dist in workingset] + [('demo', '0.4c1'), ('demoneeded', '1.2c1')] +""" + +def allowed_eggs_from_site_packages(): + """ +Sometimes you need or want to control what eggs from site-packages are used. +The allowed-eggs-from-site-packages option allows you to specify a whitelist of +project names that may be included from site-packages. You can use globs to +specify the value. It defaults to a single value of '*', indicating that any +package may come from site-packages. + +This option interacts with include-site-packages in the following ways. + +If include-site-packages is true, then allowed-eggs-from-site-packages filters +what eggs from site-packages may be chosen. If allowed-eggs-from-site-packages +is an empty list, then no eggs from site-packages are chosen, but site-packages +will still be included at the end of path lists. + +If include-site-packages is false, allowed-eggs-from-site-packages is +irrelevant. + +This test shows the interaction with the zc.buildout.easy_install API. Another +test below (allow_site_package_eggs_option) shows using it with a buildout.cfg. + +Our "py_path" has the "demoneeded" and "demo" packages available. We'll +simply be asking for "demoneeded" here. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, + ... allowed_eggs_from_site_packages=['demoneeded', 'other']) + >>> [dist.project_name for dist in workingset] + ['demoneeded'] + +That worked fine. It would work fine for a glob too. + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, + ... allowed_eggs_from_site_packages=['?emon*', 'other']) + >>> [dist.project_name for dist in workingset] + ['demoneeded'] + +But now let's try again with 'demoneeded' not allowed. + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, + ... allowed_eggs_from_site_packages=['demo']) + Traceback (most recent call last): + ... + MissingDistribution: Couldn't find a distribution for 'demoneeded'. + +Here's the same, but with an empty list. + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, + ... allowed_eggs_from_site_packages=[]) + Traceback (most recent call last): + ... + MissingDistribution: Couldn't find a distribution for 'demoneeded'. + +Of course, this doesn't stop us from getting a package from elsewhere. Here, +we add a link server. + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, executable=py_path, + ... links=[link_server], index=link_server+'index/', + ... allowed_eggs_from_site_packages=['other']) + >>> [dist.project_name for dist in workingset] + ['demoneeded'] + >>> [dist.location for dist in workingset] + ['/site-packages-example-install/demoneeded-1.1-py2.6.egg'] + +Finally, here's an example of an interaction: we say that it is OK to +allow the "demoneeded" egg to come from site-packages, but we don't +include-site-packages. + + >>> zc.buildout.easy_install.clear_index_cache() + >>> rmdir(example_dest) + >>> example_dest = tmpdir('site-packages-example-install') + >>> workingset = zc.buildout.easy_install.install( + ... ['demoneeded'], example_dest, links=[], executable=py_path, + ... index=None, include_site_packages=False, + ... allowed_eggs_from_site_packages=['demoneeded']) + Traceback (most recent call last): + ... + MissingDistribution: Couldn't find a distribution for 'demoneeded'. + + """ + +def allowed_eggs_from_site_packages_dependencies_bugfix(): + """ +If you specify that a package with a dependency may come from site-packages, +that doesn't mean that the dependency may come from site-packages. This +is a test for a bug fix to verify that this is true. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + >>> interpreter_dir = tmpdir('interpreter') + >>> interpreter_parts_dir = os.path.join( + ... interpreter_dir, 'parts', 'interpreter') + >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin') + >>> mkdir(interpreter_bin_dir) + >>> mkdir(interpreter_dir, 'eggs') + >>> mkdir(interpreter_dir, 'parts') + >>> mkdir(interpreter_parts_dir) + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], join(interpreter_dir, 'eggs'), executable=py_path, + ... links=[link_server], index=link_server+'index/', + ... allowed_eggs_from_site_packages=['demo']) + >>> [dist.project_name for dist in ws] + ['demo', 'demoneeded'] + >>> from pprint import pprint + >>> pprint([dist.location for dist in ws]) + ['/executable_buildout/site-packages', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] + + """ + +def allowed_eggs_from_site_packages_bug_592524(): + """ +When we use allowed_eggs_from_site_packages, we need to make sure that the +site-packages paths are not inserted with the normal egg paths. They already +included at the end, and including them along with the normal egg paths will +possibly mask subsequent egg paths. This affects interpreters and scripts +generated by sitepackage_safe_scripts. + +Our "py_path" has the "demoneeded" and "demo" packages available. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + >>> interpreter_dir = tmpdir('interpreter') + >>> interpreter_parts_dir = os.path.join( + ... interpreter_dir, 'parts', 'interpreter') + >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin') + >>> mkdir(interpreter_bin_dir) + >>> mkdir(interpreter_dir, 'eggs') + >>> mkdir(interpreter_dir, 'parts') + >>> mkdir(interpreter_parts_dir) + >>> ws = zc.buildout.easy_install.install( + ... ['demo', 'other'], join(interpreter_dir, 'eggs'), executable=py_path, + ... links=[link_server], index=link_server+'index/', + ... allowed_eggs_from_site_packages=['demo']) + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, py_path, interpreter_parts_dir, + ... interpreter='py', include_site_packages=True) + +Now we will look at the paths in the site.py we generated. Notice that the +site-packages are at the end. They were not before this bugfix. + + >>> test = 'import pprint, sys; pprint.pprint(sys.path[-4:])' + >>> print call_py(join(interpreter_bin_dir, 'py'), test) + ['/interpreter/eggs/other-1.0-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', + '/executable_buildout/eggs/setuptools-0.0-pyN.N.egg', + '/executable_buildout/site-packages'] + <BLANKLINE> + """ + +def subprocesses_have_same_environment_by_default(): + """ +The scripts generated by sitepackage_safe_scripts set the PYTHONPATH so that, +if the environment is maintained (the default behavior), subprocesses get +the same Python packages. + +First, we set up a script and an interpreter. + + >>> interpreter_dir = tmpdir('interpreter') + >>> interpreter_parts_dir = os.path.join( + ... interpreter_dir, 'parts', 'interpreter') + >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin') + >>> mkdir(interpreter_bin_dir) + >>> mkdir(interpreter_dir, 'eggs') + >>> mkdir(interpreter_dir, 'parts') + >>> mkdir(interpreter_parts_dir) + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], join(interpreter_dir, 'eggs'), links=[link_server], + ... index=link_server+'index/') + >>> test = ( + ... "import subprocess, sys; subprocess.call(" + ... "[sys.executable, '-c', " + ... "'import eggrecipedemo; print eggrecipedemo.x'])") + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... reqs=['demo'], interpreter='py', + ... script_initialization=test + '; sys.exit(0)') + +This works for the script. + + >>> print system(join(interpreter_bin_dir, 'demo')) + 3 + <BLANKLINE> + +This also works for the generated interpreter. + + >>> print call_py(join(interpreter_bin_dir, 'py'), test) + 3 + <BLANKLINE> + +If you have a PYTHONPATH in your environment, it will be honored, after +the buildout-generated path. + + >>> original_pythonpath = os.environ.get('PYTHONPATH') + >>> os.environ['PYTHONPATH'] = 'foo' + >>> test = ( + ... "import subprocess, sys; subprocess.call(" + ... "[sys.executable, '-c', " + ... "'import sys, pprint; pprint.pprint(sys.path)'])") + >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( + ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, + ... reqs=['demo'], interpreter='py', + ... script_initialization=test + '; sys.exit(0)') + +This works for the script. As you can see, /sample_buildout/foo is included +right after the "parts" directory that contains site.py and sitecustomize.py. +You can also see, actually more easily than in the other example, that we +have the desired eggs available. + + >>> print system(join(interpreter_bin_dir, 'demo')), # doctest: +ELLIPSIS + ['', + '/interpreter/parts/interpreter', + '/sample-buildout/foo', + ... + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] + +This also works for the generated interpreter, with identical results. + + >>> print call_py(join(interpreter_bin_dir, 'py'), test), + ... # doctest: +ELLIPSIS + ['', + '/interpreter/parts/interpreter', + '/sample-buildout/foo', + ... + '/interpreter/eggs/demo-0.3-pyN.N.egg', + '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] + + >>> # Cleanup + >>> if original_pythonpath: + ... os.environ['PYTHONPATH'] = original_pythonpath + ... else: + ... del os.environ['PYTHONPATH'] + ... + + """ + +def bootstrap_makes_buildout_that_works_with_system_python(): + r""" +In order to work smoothly with a system Python, bootstrapping creates +the buildout script with +zc.buildout.easy_install.sitepackage_safe_scripts. If it did not, a +variety of problems might happen. For instance, if another version of +buildout or setuptools is installed in the site-packages than is +desired, it may cause a problem. + +A problem actually experienced in the field is when +a recipe wants a different version of a dependency that is installed in +site-packages. We will create a similar situation, and show that it is now +handled. + +First let's write a dummy recipe. + + >>> mkdir(sample_buildout, 'recipes') + >>> write(sample_buildout, 'recipes', 'dummy.py', + ... ''' + ... import logging, os, zc.buildout + ... class Dummy: + ... def __init__(self, buildout, name, options): + ... pass + ... def install(self): + ... return () + ... def update(self): + ... pass + ... ''') + >>> write(sample_buildout, 'recipes', 'setup.py', + ... ''' + ... from setuptools import setup + ... + ... setup( + ... name = "recipes", + ... entry_points = {'zc.buildout': ['dummy = dummy:Dummy']}, + ... install_requires = 'demoneeded==1.2c1', + ... ) + ... ''') + >>> write(sample_buildout, 'recipes', 'README.txt', " ") + +Now we'll try to use it with a Python that has a different version of +demoneeded installed. + + >>> py_path, site_packages_path = make_py() + >>> create_sample_sys_install(site_packages_path) + >>> rmdir('develop-eggs') + >>> from zc.buildout.testing import make_buildout + >>> make_buildout(executable=py_path) + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = recipes + ... parts = dummy + ... find-links = %(link_server)s + ... executable = %(py_path)s + ... + ... [dummy] + ... recipe = recipes:dummy + ... ''' % globals()) + +Now we actually run the buildout. Before the change, we got the following +error: + + Develop: '/sample-buildout/recipes' + While: + Installing. + Getting section dummy. + Initializing section dummy. + Installing recipe recipes. + Error: There is a version conflict. + We already have: demoneeded 1.1 + but recipes 0.0.0 requires 'demoneeded==1.2c1'. + +Now, it is handled smoothly. + + >>> print system(buildout) + Develop: '/sample-buildout/recipes' + Getting distribution for 'demoneeded==1.2c1'. + Got demoneeded 1.2c1. + Installing dummy. + <BLANKLINE> + +Here's the same story with a namespace package, which has some additional +complications behind the scenes. First, a recipe, in the "tellmy" namespace. + + >>> mkdir(sample_buildout, 'ns') + >>> mkdir(sample_buildout, 'ns', 'tellmy') + >>> write(sample_buildout, 'ns', 'tellmy', '__init__.py', + ... "__import__('pkg_resources').declare_namespace(__name__)\n") + >>> mkdir(sample_buildout, 'ns', 'tellmy', 'recipes') + >>> write(sample_buildout, 'ns', 'tellmy', 'recipes', '__init__.py', ' ') + >>> write(sample_buildout, 'ns', 'tellmy', 'recipes', 'dummy.py', + ... ''' + ... import logging, os, zc.buildout + ... class Dummy: + ... def __init__(self, buildout, name, options): + ... pass + ... def install(self): + ... return () + ... def update(self): + ... pass + ... ''') + >>> write(sample_buildout, 'ns', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup( + ... name="tellmy.recipes", + ... packages=['tellmy', 'tellmy.recipes'], + ... install_requires=['setuptools'], + ... namespace_packages=['tellmy'], + ... entry_points = {'zc.buildout': + ... ['dummy = tellmy.recipes.dummy:Dummy']}, + ... ) + ... ''') + +Now, a buildout that uses it. + + >>> create_sample_namespace_eggs(sample_eggs, site_packages_path) + >>> rmdir('develop-eggs') + >>> from zc.buildout.testing import make_buildout + >>> make_buildout(executable=py_path) + >>> write(sample_buildout, 'buildout.cfg', + ... ''' + ... [buildout] + ... develop = ns + ... recipes + ... parts = dummy + ... find-links = %(link_server)s + ... executable = %(py_path)s + ... + ... [dummy] + ... recipe = tellmy.recipes:dummy + ... ''' % globals()) + +Now we actually run the buildout. + + >>> print system(buildout) + Develop: '/sample-buildout/ns' + Develop: '/sample-buildout/recipes' + Uninstalling dummy. + Installing dummy. + <BLANKLINE> + + """ + +if sys.version_info > (2, 4): + def test_exit_codes(): + """ + >>> import subprocess + >>> def call(s): + ... p = subprocess.Popen(s, stdin=subprocess.PIPE, + ... stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + ... p.stdin.close() + ... print p.stdout.read() + ... print 'Exit:', bool(p.wait()) + + >>> call(buildout) + <BLANKLINE> + Exit: False + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = x + ... ''') + + >>> call(buildout) # doctest: +NORMALIZE_WHITESPACE + While: + Installing. + Getting section x. + Error: The referenced section, 'x', was not defined. + <BLANKLINE> + Exit: True + + >>> write('setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='zc.buildout.testexit', entry_points={ + ... 'zc.buildout': ['default = testexitrecipe:x']}) + ... ''') + + >>> write('testexitrecipe.py', + ... ''' + ... x y + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = x + ... develop = . + ... + ... [x] + ... recipe = zc.buildout.testexit + ... ''') + + >>> call(buildout) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + Develop: '/sample-buildout/.' + While: + Installing. + Getting section x. + Initializing section x. + Loading zc.buildout recipe entry zc.buildout.testexit:default. + <BLANKLINE> + An internal error occurred due to a bug in either zc.buildout or in a + recipe being used: + Traceback (most recent call last): + ... + x y + ^ + SyntaxError: invalid syntax + <BLANKLINE> + Exit: True + """ + +def bug_59270_recipes_always_start_in_buildout_dir(): + """ + Recipes can rely on running from buildout directory + + >>> mkdir('bad_start') + >>> write('bad_recipe.py', + ... ''' + ... import os + ... class Bad: + ... def __init__(self, *_): + ... print os.getcwd() + ... def install(self): + ... print os.getcwd() + ... os.chdir('bad_start') + ... print os.getcwd() + ... return () + ... ''') + + >>> write('setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='bad.test', + ... entry_points={'zc.buildout': ['default=bad_recipe:Bad']},) + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = . + ... parts = b1 b2 + ... [b1] + ... recipe = bad.test + ... [b2] + ... recipe = bad.test + ... ''') + + >>> os.chdir('bad_start') + >>> print system(join(sample_buildout, 'bin', 'buildout') + ... +' -c '+join(sample_buildout, 'buildout.cfg')), + Develop: '/sample-buildout/.' + /sample-buildout + /sample-buildout + Installing b1. + /sample-buildout + /sample-buildout/bad_start + Installing b2. + /sample-buildout + /sample-buildout/bad_start + + """ + +def bug_61890_file_urls_dont_seem_to_work_in_find_dash_links(): + """ + + This bug arises from the fact that setuptools is overly restrictive + about file urls, requiring that file urls pointing at directories + must end in a slash. + + >>> dest = tmpdir('sample-install') + >>> import zc.buildout.easy_install + >>> sample_eggs = sample_eggs.replace(os.path.sep, '/') + >>> ws = zc.buildout.easy_install.install( + ... ['demo==0.2'], dest, + ... links=['file://'+sample_eggs], index=link_server+'index/') + + + >>> for dist in ws: + ... print dist + demo 0.2 + demoneeded 1.1 + + >>> ls(dest) + - demo-0.2-py2.4.egg + - demoneeded-1.1-py2.4.egg + + """ + +def bug_75607_buildout_should_not_run_if_it_creates_an_empty_buildout_cfg(): + """ + >>> remove('buildout.cfg') + >>> print system(buildout), + While: + Initializing. + Error: Couldn't open /sample-buildout/buildout.cfg + + + + """ + +def dealing_with_extremely_insane_dependencies(): + r""" + + There was a problem with analysis of dependencies taking a long + time, in part because the analysis would get repeated every time a + package was encountered in a dependency list. Now, we don't do + the analysis any more: + + >>> import os + >>> for i in range(5): + ... p = 'pack%s' % i + ... deps = [('pack%s' % j) for j in range(5) if j is not i] + ... if i == 4: + ... deps.append('pack5') + ... mkdir(p) + ... write(p, 'setup.py', + ... 'from setuptools import setup\n' + ... 'setup(name=%r, install_requires=%r,\n' + ... ' url="u", author="a", author_email="e")\n' + ... % (p, deps)) + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = pack0 pack1 pack2 pack3 pack4 + ... parts = pack1 + ... + ... [pack1] + ... recipe = zc.recipe.egg:eggs + ... eggs = pack0 + ... ''') + + >>> print system(buildout), + Develop: '/sample-buildout/pack0' + Develop: '/sample-buildout/pack1' + Develop: '/sample-buildout/pack2' + Develop: '/sample-buildout/pack3' + Develop: '/sample-buildout/pack4' + Installing pack1. + Couldn't find index page for 'pack5' (maybe misspelled?) + Getting distribution for 'pack5'. + While: + Installing pack1. + Getting distribution for 'pack5'. + Error: Couldn't find a distribution for 'pack5'. + + However, if we run in verbose mode, we can see why packages were included: + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + We have a develop egg: zc.buildout 1.0.0 + We have the best distribution that satisfies 'setuptools'. + Picked: setuptools = 0.6 + Develop: '/sample-buildout/pack0' + Develop: '/sample-buildout/pack1' + Develop: '/sample-buildout/pack2' + Develop: '/sample-buildout/pack3' + Develop: '/sample-buildout/pack4' + ...Installing pack1. + Installing 'pack0'. + We have a develop egg: pack0 0.0.0 + Getting required 'pack4' + required by pack0 0.0.0. + We have a develop egg: pack4 0.0.0 + Getting required 'pack3' + required by pack0 0.0.0. + required by pack4 0.0.0. + We have a develop egg: pack3 0.0.0 + Getting required 'pack2' + required by pack0 0.0.0. + required by pack3 0.0.0. + required by pack4 0.0.0. + We have a develop egg: pack2 0.0.0 + Getting required 'pack1' + required by pack0 0.0.0. + required by pack2 0.0.0. + required by pack3 0.0.0. + required by pack4 0.0.0. + We have a develop egg: pack1 0.0.0 + Getting required 'pack5' + required by pack4 0.0.0. + We have no distributions for pack5 that satisfies 'pack5'. + Couldn't find index page for 'pack5' (maybe misspelled?) + Getting distribution for 'pack5'. + While: + Installing pack1. + Getting distribution for 'pack5'. + Error: Couldn't find a distribution for 'pack5'. + """ + +def read_find_links_to_load_extensions(): + """ +We'll create a wacky buildout extension that is just another name for http: + + >>> src = tmpdir('src') + >>> write(src, 'wacky_handler.py', + ... ''' + ... import urllib2 + ... class Wacky(urllib2.HTTPHandler): + ... wacky_open = urllib2.HTTPHandler.http_open + ... def install(buildout=None): + ... urllib2.install_opener(urllib2.build_opener(Wacky)) + ... ''') + >>> write(src, 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='wackyextension', version='1', + ... py_modules=['wacky_handler'], + ... entry_points = {'zc.buildout.extension': + ... ['default = wacky_handler:install'] + ... }, + ... ) + ... ''') + >>> print system(buildout+' setup '+src+' bdist_egg'), + ... # doctest: +ELLIPSIS + Running setup ... + creating 'dist/wackyextension-1-... + +Now we'll create a buildout that uses this extension to load other packages: + + >>> wacky_server = link_server.replace('http', 'wacky') + >>> dist = 'file://' + join(src, 'dist').replace(os.path.sep, '/') + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = demo + ... extensions = wackyextension + ... find-links = %(wacky_server)s/demoneeded-1.0.zip + ... %(dist)s + ... [demo] + ... recipe = zc.recipe.egg + ... eggs = demoneeded + ... ''' % globals()) + +When we run the buildout. it will load the extension from the dist +directory and then use the wacky extension to load the demo package + + >>> print system(buildout), + Getting distribution for 'wackyextension'. + Got wackyextension 1. + Installing demo. + Getting distribution for 'demoneeded'. + Got demoneeded 1.0. + + """ + +def distributions_from_local_find_links_make_it_to_download_cache(): + """ + +If we specify a local directory in find links, distors found there +need to make it to the download cache. + + >>> mkdir('test') + >>> write('test', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='foo') + ... ''') + + >>> print system(buildout+' setup test bdist_egg'), # doctest: +ELLIPSIS + Running setup script 'test/setup.py'. + ... + + + >>> mkdir('cache') + >>> old_cache = zc.buildout.easy_install.download_cache('cache') + >>> list(zc.buildout.easy_install.install(['foo'], 'eggs', + ... links=[join('test', 'dist')])) # doctest: +ELLIPSIS + [foo 0.0.0 ... + + >>> ls('cache') + - foo-0.0.0-py2.4.egg + + >>> _ = zc.buildout.easy_install.download_cache(old_cache) + + """ + +def create_egg(name, version, dest, install_requires=None, + dependency_links=None): + d = tempfile.mkdtemp() + if dest=='available': + extras = dict(x=['x']) + else: + extras = {} + if dependency_links: + links = 'dependency_links = %s, ' % dependency_links + else: + links = '' + if install_requires: + requires = 'install_requires = %s, ' % install_requires + else: + requires = '' + try: + open(os.path.join(d, 'setup.py'), 'w').write( + 'from setuptools import setup\n' + 'setup(name=%r, version=%r, extras_require=%r, zip_safe=True,\n' + ' %s %s py_modules=["setup"]\n)' + % (name, str(version), extras, requires, links) + ) + zc.buildout.testing.bdist_egg(d, sys.executable, os.path.abspath(dest)) + finally: + shutil.rmtree(d) + +def prefer_final_permutation(existing, available): + for d in ('existing', 'available'): + if os.path.exists(d): + shutil.rmtree(d) + os.mkdir(d) + for version in existing: + create_egg('spam', version, 'existing') + for version in available: + create_egg('spam', version, 'available') + + zc.buildout.easy_install.clear_index_cache() + [dist] = list( + zc.buildout.easy_install.install(['spam'], 'existing', ['available'], + always_unzip=True) + ) + + if dist.extras: + print 'downloaded', dist.version + else: + print 'had', dist.version + sys.path_importer_cache.clear() + +def prefer_final(): + """ +This test tests several permutations: + +Using different version numbers to work around zip importer cache problems. :( + +- With prefer final: + + - no existing and newer dev available + >>> prefer_final_permutation((), [1, '2a1']) + downloaded 1 + + - no existing and only dev available + >>> prefer_final_permutation((), ['3a1']) + downloaded 3a1 + + - final existing and only dev acailable + >>> prefer_final_permutation([4], ['5a1']) + had 4 + + - final existing and newer final available + >>> prefer_final_permutation([6], [7]) + downloaded 7 + + - final existing and same final available + >>> prefer_final_permutation([8], [8]) + had 8 + + - final existing and older final available + >>> prefer_final_permutation([10], [9]) + had 10 + + - only dev existing and final available + >>> prefer_final_permutation(['12a1'], [11]) + downloaded 11 + + - only dev existing and no final available newer dev available + >>> prefer_final_permutation(['13a1'], ['13a2']) + downloaded 13a2 + + - only dev existing and no final available older dev available + >>> prefer_final_permutation(['15a1'], ['14a1']) + had 15a1 + + - only dev existing and no final available same dev available + >>> prefer_final_permutation(['16a1'], ['16a1']) + had 16a1 + +- Without prefer final: + + >>> _ = zc.buildout.easy_install.prefer_final(False) + + - no existing and newer dev available + >>> prefer_final_permutation((), [18, '19a1']) + downloaded 19a1 + + - no existing and only dev available + >>> prefer_final_permutation((), ['20a1']) + downloaded 20a1 + + - final existing and only dev acailable + >>> prefer_final_permutation([21], ['22a1']) + downloaded 22a1 + + - final existing and newer final available + >>> prefer_final_permutation([23], [24]) + downloaded 24 + + - final existing and same final available + >>> prefer_final_permutation([25], [25]) + had 25 + + - final existing and older final available + >>> prefer_final_permutation([27], [26]) + had 27 + + - only dev existing and final available + >>> prefer_final_permutation(['29a1'], [28]) + had 29a1 + + - only dev existing and no final available newer dev available + >>> prefer_final_permutation(['30a1'], ['30a2']) + downloaded 30a2 + + - only dev existing and no final available older dev available + >>> prefer_final_permutation(['32a1'], ['31a1']) + had 32a1 + + - only dev existing and no final available same dev available + >>> prefer_final_permutation(['33a1'], ['33a1']) + had 33a1 + + >>> _ = zc.buildout.easy_install.prefer_final(True) + + """ + +def buildout_prefer_final_option(): + """ +The prefer-final buildout option can be used for override the default +preference for newer distributions. + +The default is prefer-final = false: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + ... + Picked: demo = 0.4c1 + ... + Picked: demoneeded = 1.2c1 + +Here we see that the final versions of demo and demoneeded are used. +We get the same behavior if we add prefer-final = false + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... prefer-final = false + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + ... + Picked: demo = 0.4c1 + ... + Picked: demoneeded = 1.2c1 + +If we specify prefer-final = true, we'll get the newest +distributions: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... prefer-final = true + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing 'zc.buildout', 'setuptools'. + ... + Picked: demo = 0.3 + ... + Picked: demoneeded = 1.1 + +We get an error if we specify anything but true or false: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... prefer-final = no + ... + ... [eggs] + ... recipe = zc.recipe.egg:eggs + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + While: + Initializing. + Error: Invalid value for prefer-final option: no + + """ + +def buildout_prefer_final_build_system_option(): + """ +The accept-buildout-test-releases buildout option can be used for overriding +the default preference for final distributions for recipes, buildout +extensions, and buildout itself. It is usually controlled via the bootstrap +script rather than in the configuration file, but we will test the machinery +using the file. + +Set up. This creates sdists for demorecipe 1.0 and 1.1b1, and for +demoextension 1.0 and 1.1b1. + + >>> create_sample_recipe_sdists(sample_eggs) + >>> create_sample_extension_sdists(sample_eggs) + +The default is accept-buildout-test-releases = false: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = demo + ... find-links = %(link_server)s + ... extensions = demoextension + ... + ... [demo] + ... recipe = demorecipe + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing ... + Picked: demoextension = 1.0 + ... + Picked: demorecipe = 1.0 + ... + +Here we see that the final versions of demorecipe and demoextension were used. + +We get the same behavior if we explicitly state that +accept-buildout-test-releases = false. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = demo + ... find-links = %(link_server)s + ... extensions = demoextension + ... accept-buildout-test-releases = false + ... + ... [demo] + ... recipe = demorecipe + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing ... + Picked: demoextension = 1.0 + ... + Picked: demorecipe = 1.0 + ... + +If we specify accept-buildout-test-releases = true, we'll get the newest +distributions in the build system: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = demo + ... find-links = %(link_server)s + ... extensions = demoextension + ... accept-buildout-test-releases = true + ... + ... [demo] + ... recipe = demorecipe + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + Installing ... + Picked: demoextension = 1.1b1 + ... + Picked: demorecipe = 1.1b1 + ... + +We get an error if we specify anything but true or false: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = demo + ... find-links = %(link_server)s + ... extensions = demoextension + ... accept-buildout-test-releases = no + ... + ... [demo] + ... recipe = demorecipe + ... ''' % globals()) + + >>> print system(buildout+' -v'), # doctest: +ELLIPSIS + While: + Initializing. + Error: Invalid value for accept-buildout-test-releases option: no + + """ + +def develop_with_modules(): + """ +Distribution setup scripts can import modules in the distribution directory: + + >>> mkdir('foo') + >>> write('foo', 'bar.py', + ... '''# empty + ... ''') + + >>> write('foo', 'setup.py', + ... ''' + ... import bar + ... from setuptools import setup + ... setup(name="foo") + ... ''') + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... develop = foo + ... parts = + ... ''') + + >>> print system(join('bin', 'buildout')), + Develop: '/sample-buildout/foo' + + >>> ls('develop-eggs') + - foo.egg-link + - z3c.recipe.scripts.egg-link + - zc.recipe.egg.egg-link + + """ + +def dont_pick_setuptools_if_version_is_specified_when_required_by_src_dist(): + """ +When installing a source distribution, we got setuptools without +honoring our version specification. + + >>> mkdir('dist') + >>> write('setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='foo', version='1', py_modules=['foo'], zip_safe=True) + ... ''') + >>> write('foo.py', '') + >>> _ = system(buildout+' setup . sdist') + + >>> if zc.buildout.easy_install.is_distribute: + ... distribute_version = 'distribute = %s' % ( + ... pkg_resources.working_set.find( + ... pkg_resources.Requirement.parse('distribute')).version,) + ... else: + ... distribute_version = '' + ... + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = dist + ... versions = versions + ... allow-picked-versions = false + ... + ... [versions] + ... setuptools = %s + ... foo = 1 + ... %s + ... + ... [foo] + ... recipe = zc.recipe.egg + ... eggs = foo + ... ''' % (pkg_resources.working_set.find( + ... pkg_resources.Requirement.parse('setuptools')).version, + ... distribute_version)) + + >>> print system(buildout), + Installing foo. + Getting distribution for 'foo==1'. + Got foo 1. + + """ + +def pyc_and_pyo_files_have_correct_paths(): + r""" + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... unzip = true + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = demo + ... interpreter = py + ... ''' % globals()) + + >>> _ = system(buildout) + + >>> write('t.py', + ... ''' + ... import eggrecipedemo, eggrecipedemoneeded + ... print eggrecipedemo.main.func_code.co_filename + ... print eggrecipedemoneeded.f.func_code.co_filename + ... ''') + + >>> print system(join('bin', 'py')+ ' t.py'), + /sample-buildout/eggs/demo-0.4c1-py2.4.egg/eggrecipedemo.py + /sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg/eggrecipedemoneeded.py + + >>> import os + >>> for name in os.listdir('eggs'): + ... if name.startswith('demoneeded'): + ... ls('eggs', name) + d EGG-INFO + - eggrecipedemoneeded.py + - eggrecipedemoneeded.pyc + - eggrecipedemoneeded.pyo + + """ + +def dont_mess_with_standard_dirs_with_variable_refs(): + """ + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... eggs-directory = ${buildout:directory}/develop-eggs + ... parts = + ... ''' % globals()) + >>> print system(buildout), + + """ + +def expand_shell_patterns_in_develop_paths(): + """ + Sometimes we want to include a number of eggs in some directory as + develop eggs, without explicitly listing all of them in our + buildout.cfg + + >>> make_dist_that_requires(sample_buildout, 'sampley') + >>> make_dist_that_requires(sample_buildout, 'samplez') + + Now, let's create a buildout that has a shell pattern that matches + both: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... develop = sample* + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = sampley + ... samplez + ... ''' % globals()) + + We can see that both eggs were found: + + >>> print system(buildout), + Develop: '/sample-buildout/sampley' + Develop: '/sample-buildout/samplez' + Installing eggs. + + """ + +def warn_users_when_expanding_shell_patterns_yields_no_results(): + """ + Sometimes shell patterns do not match anything, so we want to warn + our users about it... + + >>> make_dist_that_requires(sample_buildout, 'samplea') + + So if we have 2 patterns, one that has a matching directory, and + another one that does not + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... develop = samplea grumble* + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = samplea + ... ''' % globals()) + + We should get one of the eggs, and a warning for the pattern that + did not match anything. + + >>> print system(buildout), + Develop: '/sample-buildout/samplea' + Couldn't develop '/sample-buildout/grumble*' (not found) + Installing eggs. + + """ + +def make_sure_versions_dont_cancel_extras(): + """ + There was a bug that caused extras in requirements to be lost. + + >>> open('setup.py', 'w').write(''' + ... from setuptools import setup + ... setup(name='extraversiondemo', version='1.0', + ... url='x', author='x', author_email='x', + ... extras_require=dict(foo=['demo']), py_modules=['t']) + ... ''') + >>> open('README', 'w').close() + >>> open('t.py', 'w').close() + + >>> sdist('.', sample_eggs) + >>> mkdir('dest') + >>> ws = zc.buildout.easy_install.install( + ... ['extraversiondemo[foo]'], 'dest', links=[sample_eggs], + ... versions = dict(extraversiondemo='1.0') + ... ) + >>> sorted(dist.key for dist in ws) + ['demo', 'demoneeded', 'extraversiondemo'] + """ + +def increment_buildout_options(): + r""" + >>> write('b1.cfg', ''' + ... [buildout] + ... parts = p1 + ... x = 1 + ... y = a + ... b + ... + ... [p1] + ... recipe = zc.buildout:debug + ... foo = ${buildout:x} ${buildout:y} + ... ''') + + >>> write('buildout.cfg', ''' + ... [buildout] + ... extends = b1.cfg + ... parts += p2 + ... x += 2 + ... y -= a + ... + ... [p2] + ... <= p1 + ... ''') + + >>> print system(buildout), + Installing p1. + foo='1\n2 b' + recipe='zc.buildout:debug' + Installing p2. + foo='1\n2 b' + recipe='zc.buildout:debug' + """ + +def increment_buildout_with_multiple_extended_files_421022(): + r""" + >>> write('foo.cfg', ''' + ... [buildout] + ... foo-option = foo + ... [other] + ... foo-option = foo + ... ''') + >>> write('bar.cfg', ''' + ... [buildout] + ... bar-option = bar + ... [other] + ... bar-option = bar + ... ''') + >>> write('buildout.cfg', ''' + ... [buildout] + ... parts = p other + ... extends = bar.cfg foo.cfg + ... bar-option += baz + ... foo-option += ham + ... + ... [other] + ... recipe = zc.buildout:debug + ... bar-option += baz + ... foo-option += ham + ... + ... [p] + ... recipe = zc.buildout:debug + ... x = ${buildout:bar-option} ${buildout:foo-option} + ... ''') + + >>> print system(buildout), + Installing p. + recipe='zc.buildout:debug' + x='bar\nbaz foo\nham' + Installing other. + bar-option='bar\nbaz' + foo-option='foo\nham' + recipe='zc.buildout:debug' + """ + +def increment_on_command_line(): + r""" + >>> write('buildout.cfg', ''' + ... [buildout] + ... parts = p1 + ... x = 1 + ... y = a + ... b + ... + ... [p1] + ... recipe = zc.buildout:debug + ... foo = ${buildout:x} ${buildout:y} + ... + ... [p2] + ... <= p1 + ... ''') + + >>> print system(buildout+' buildout:parts+=p2 p1:foo+=bar'), + Installing p1. + foo='1 a\nb\nbar' + recipe='zc.buildout:debug' + Installing p2. + foo='1 a\nb\nbar' + recipe='zc.buildout:debug' + """ + +###################################################################### + +def make_py_with_system_install(make_py, sample_eggs): + py_path, site_packages_path = make_py() + create_sample_namespace_eggs(sample_eggs, site_packages_path) + return py_path + +def create_sample_namespace_eggs(dest, site_packages_path=None): + from zc.buildout.testing import write, mkdir + for pkg, version in (('version', '1.0'), ('version', '1.1'), + ('fortune', '1.0')): + tmp = tempfile.mkdtemp() + try: + write(tmp, 'README.txt', '') + mkdir(tmp, 'src') + mkdir(tmp, 'src', 'tellmy') + write(tmp, 'src', 'tellmy', '__init__.py', + "__import__(" + "'pkg_resources').declare_namespace(__name__)\n") + mkdir(tmp, 'src', 'tellmy', pkg) + write(tmp, 'src', 'tellmy', pkg, + '__init__.py', '__version__=%r\n' % version) + write( + tmp, 'setup.py', + "from setuptools import setup\n" + "setup(\n" + " name='tellmy.%(pkg)s',\n" + " package_dir = {'': 'src'},\n" + " packages = ['tellmy', 'tellmy.%(pkg)s'],\n" + " install_requires = ['setuptools'],\n" + " namespace_packages=['tellmy'],\n" + " zip_safe=True, version=%(version)r,\n" + " author='bob', url='bob', author_email='bob')\n" + % locals() + ) + zc.buildout.testing.sdist(tmp, dest) + if (site_packages_path and pkg == 'version' and version == '1.1'): + # We install the 1.1 version in site packages the way a + # system packaging system (debs, rpms) would do it. + zc.buildout.testing.sys_install(tmp, site_packages_path) + finally: + shutil.rmtree(tmp) + +def create_sample_extension_sdists(dest): + from zc.buildout.testing import write, mkdir + name = 'demoextension' + for version in ('1.0', '1.1b1'): + tmp = tempfile.mkdtemp() + try: + write(tmp, 'README.txt', '') + write(tmp, name + '.py', + "def ext(buildout):\n" + " pass\n" + "def unload(buildout):\n" + " pass\n" + % locals()) + write(tmp, 'setup.py', + "from setuptools import setup\n" + "setup(\n" + " name = %(name)r,\n" + " py_modules = [%(name)r],\n" + " entry_points = {\n" + " 'zc.buildout.extension': " + "['ext = %(name)s:ext'],\n" + " 'zc.buildout.unloadextension': " + "['ext = %(name)s:unload'],\n" + " },\n" + " zip_safe=True, version=%(version)r,\n" + " author='bob', url='bob', author_email='bob')\n" + % locals()) + zc.buildout.testing.sdist(tmp, dest) + finally: + shutil.rmtree(tmp) + +def create_sample_recipe_sdists(dest): + from zc.buildout.testing import write, mkdir + name = 'demorecipe' + for version in ('1.0', '1.1b1'): + tmp = tempfile.mkdtemp() + try: + write(tmp, 'README.txt', '') + write(tmp, name + '.py', + "import logging, os, zc.buildout\n" + "class Demorecipe:\n" + " def __init__(self, buildout, name, options):\n" + " self.name, self.options = name, options\n" + " def install(self):\n" + " return ()\n" + " def update(self):\n" + " pass\n" + % locals()) + write(tmp, 'setup.py', + "from setuptools import setup\n" + "setup(\n" + " name = %(name)r,\n" + " py_modules = [%(name)r],\n" + " entry_points = {'zc.buildout': " + "['default = %(name)s:Demorecipe']},\n" + " zip_safe=True, version=%(version)r,\n" + " author='bob', url='bob', author_email='bob')\n" + % locals()) + zc.buildout.testing.sdist(tmp, dest) + finally: + shutil.rmtree(tmp) + +def _write_eggrecipedemoneeded(tmp, minor_version, suffix=''): + from zc.buildout.testing import write + write(tmp, 'README.txt', '') + write(tmp, 'eggrecipedemoneeded.py', + 'y=%s\ndef f():\n pass' % minor_version) + write( + tmp, 'setup.py', + "from setuptools import setup\n" + "setup(name='demoneeded', py_modules=['eggrecipedemoneeded']," + " zip_safe=True, version='1.%s%s', author='bob', url='bob', " + "author_email='bob')\n" + % (minor_version, suffix) + ) + +def _write_eggrecipedemo(tmp, minor_version, suffix=''): + from zc.buildout.testing import write + write(tmp, 'README.txt', '') + write( + tmp, 'eggrecipedemo.py', + 'import eggrecipedemoneeded\n' + 'x=%s\n' + 'def main(): print x, eggrecipedemoneeded.y\n' + % minor_version) + write( + tmp, 'setup.py', + "from setuptools import setup\n" + "setup(name='demo', py_modules=['eggrecipedemo']," + " install_requires = 'demoneeded'," + " entry_points={'console_scripts': " + "['demo = eggrecipedemo:main']}," + " zip_safe=True, version='0.%s%s')\n" % (minor_version, suffix) + ) + +def create_sample_sys_install(site_packages_path): + for creator, minor_version in ( + (_write_eggrecipedemoneeded, 1), + (_write_eggrecipedemo, 3)): + # Write the files and install in site_packages_path. + tmp = tempfile.mkdtemp() + try: + creator(tmp, minor_version) + zc.buildout.testing.sys_install(tmp, site_packages_path) + finally: + shutil.rmtree(tmp) + +def create_sample_eggs(test, executable=sys.executable): + from zc.buildout.testing import write + dest = test.globs['sample_eggs'] + tmp = tempfile.mkdtemp() + try: + for i in (0, 1, 2): + suffix = i==2 and 'c1' or '' + _write_eggrecipedemoneeded(tmp, i, suffix) + zc.buildout.testing.sdist(tmp, dest) + + write( + tmp, 'setup.py', + "from setuptools import setup\n" + "setup(name='other', zip_safe=False, version='1.0', " + "py_modules=['eggrecipedemoneeded'])\n" + ) + zc.buildout.testing.bdist_egg(tmp, executable, dest) + + os.remove(os.path.join(tmp, 'eggrecipedemoneeded.py')) + + for i in (1, 2, 3, 4): + suffix = i==4 and 'c1' or '' + _write_eggrecipedemo(tmp, i, suffix) + zc.buildout.testing.bdist_egg(tmp, executable, dest) + + write(tmp, 'eggrecipebigdemo.py', 'import eggrecipedemo') + write( + tmp, 'setup.py', + "from setuptools import setup\n" + "setup(name='bigdemo', " + " install_requires = 'demo'," + " py_modules=['eggrecipebigdemo'], " + " zip_safe=True, version='0.1')\n" + ) + zc.buildout.testing.bdist_egg(tmp, executable, dest) + + finally: + shutil.rmtree(tmp) + +extdemo_c = """ +#include <Python.h> +#include <extdemo.h> + +static PyMethodDef methods[] = {{NULL}}; + +PyMODINIT_FUNC +initextdemo(void) +{ + PyObject *m; + m = Py_InitModule3("extdemo", methods, ""); +#ifdef TWO + PyModule_AddObject(m, "val", PyInt_FromLong(2)); +#else + PyModule_AddObject(m, "val", PyInt_FromLong(EXTDEMO)); +#endif +} +""" + +extdemo_setup_py = """ +import os +from distutils.core import setup, Extension + +if os.environ.get('test-variable'): + print "Have environment test-variable:", os.environ['test-variable'] + +setup(name = "extdemo", version = "%s", url="http://www.zope.org", + author="Demo", author_email="demo@demo.com", + ext_modules = [Extension('extdemo', ['extdemo.c'])], + ) +""" + +def add_source_dist(test, version=1.4): + + if 'extdemo' not in test.globs: + test.globs['extdemo'] = test.globs['tmpdir']('extdemo') + + tmp = test.globs['extdemo'] + write = test.globs['write'] + try: + write(tmp, 'extdemo.c', extdemo_c); + write(tmp, 'setup.py', extdemo_setup_py % version); + write(tmp, 'README', ""); + write(tmp, 'MANIFEST.in', "include *.c\n"); + test.globs['sdist'](tmp, test.globs['sample_eggs']) + except: + shutil.rmtree(tmp) + +def easy_install_SetUp(test): + zc.buildout.testing.buildoutSetUp(test) + sample_eggs = test.globs['tmpdir']('sample_eggs') + test.globs['sample_eggs'] = sample_eggs + os.mkdir(os.path.join(sample_eggs, 'index')) + create_sample_eggs(test) + add_source_dist(test) + test.globs['link_server'] = test.globs['start_server']( + test.globs['sample_eggs']) + test.globs['update_extdemo'] = lambda : add_source_dist(test, 1.5) + zc.buildout.testing.install_develop('zc.recipe.egg', test) + zc.buildout.testing.install_develop('z3c.recipe.scripts', test) + +egg_parse = re.compile('([0-9a-zA-Z_.]+)-([0-9a-zA-Z_.]+)-py(\d[.]\d).egg$' + ).match +def makeNewRelease(project, ws, dest, version='99.99'): + 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-%s-py%s.egg" % (eggname, version, 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: %s" % version) + ) + zip.close() + else: + shutil.copytree(dist.location, dest) + info_path = os.path.join(dest, 'EGG-INFO', 'PKG-INFO') + info = open(info_path).read().replace("Version: %s" % oldver, + "Version: %s" % version) + open(info_path, 'w').write(info) + +def getWorkingSetWithBuildoutEgg(test): + sample_buildout = test.globs['sample_buildout'] + eggs = os.path.join(sample_buildout, 'eggs') + + # If the zc.buildout dist is a develop 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, + zc.buildout.easy_install._safe_arg(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 + return ws + +def updateSetup(test): + zc.buildout.testing.buildoutSetUp(test) + new_releases = test.globs['tmpdir']('new_releases') + test.globs['new_releases'] = new_releases + ws = getWorkingSetWithBuildoutEgg(test) + # now let's make the new releases + makeNewRelease('zc.buildout', ws, new_releases) + makeNewRelease('zc.buildout', ws, new_releases, '100.0b1') + os.mkdir(os.path.join(new_releases, 'zc.buildout')) + if zc.buildout.easy_install.is_distribute: + makeNewRelease('distribute', ws, new_releases) + os.mkdir(os.path.join(new_releases, 'distribute')) + else: + makeNewRelease('setuptools', ws, new_releases) + os.mkdir(os.path.join(new_releases, 'setuptools')) + +def bootstrapSetup(test): + easy_install_SetUp(test) + sample_eggs = test.globs['sample_eggs'] + ws = getWorkingSetWithBuildoutEgg(test) + makeNewRelease('zc.buildout', ws, sample_eggs) + makeNewRelease('zc.buildout', ws, sample_eggs, '100.0b1') + os.environ['bootstrap-testing-find-links'] = test.globs['link_server'] + +normalize_bang = ( + re.compile(re.escape('#!'+ + zc.buildout.easy_install._safe_arg(sys.executable))), + '#!/usr/local/bin/python2.4', + ) + +hide_distribute_additions = (re.compile('install_dir .+\n'), '') +hide_zip_safe_message = ( + # This comes in a different place in the output in Python 2.7. It's not + # important to our tests. Hide it. + re.compile( + '((?<=\n)\n)?zip_safe flag not set; analyzing archive contents...\n'), + '') +hide_first_index_page_message = ( + # This comes in a different place in the output in Python 2.7. It's not + # important to our tests. Hide it. + re.compile( + "Couldn't find index page for '[^']+' \(maybe misspelled\?\)\n"), + '') +def test_suite(): + test_suite = [ + doctest.DocFileSuite( + 'buildout.txt', 'runsetup.txt', 'repeatable.txt', 'setup.txt', + setUp=zc.buildout.testing.buildoutSetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + zc.buildout.tests.hide_distribute_additions, + hide_zip_safe_message, + (re.compile('__buildout_signature__ = recipes-\S+'), + '__buildout_signature__ = recipes-SSSSSSSSSSS'), + (re.compile('executable = [\S ]+python\S*', re.I), + 'executable = python'), + (re.compile('[-d] (setuptools|distribute)-\S+[.]egg'), + 'setuptools.egg'), + (re.compile('zc.buildout(-\S+)?[.]egg(-link)?'), + 'zc.buildout.egg'), + (re.compile('creating \S*setup.cfg'), 'creating setup.cfg'), + (re.compile('hello\%ssetup' % os.path.sep), 'hello/setup'), + (re.compile('Picked: (\S+) = \S+'), + 'Picked: \\1 = V.V'), + (re.compile(r'We have a develop egg: zc.buildout (\S+)'), + 'We have a develop egg: zc.buildout X.X.'), + (re.compile(r'\\[\\]?'), '/'), + (re.compile('WindowsError'), 'OSError'), + (re.compile(r'\[Error \d+\] Cannot create a file ' + r'when that file already exists: '), + '[Errno 17] File exists: ' + ), + (re.compile('distribute'), 'setuptools'), + ]) + ), + doctest.DocFileSuite( + 'debugging.txt', + setUp=zc.buildout.testing.buildoutSetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.tests.hide_distribute_additions, + (re.compile(r'\S+buildout.py'), 'buildout.py'), + (re.compile(r'line \d+'), 'line NNN'), + (re.compile(r'py\(\d+\)'), 'py(NNN)'), + ]) + ), + + doctest.DocFileSuite( + 'update.txt', + setUp=updateSetup, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + normalize_bang, + zc.buildout.tests.hide_distribute_additions, + (re.compile('99[.]99'), 'NINETYNINE.NINETYNINE'), + (re.compile('(zc.buildout|setuptools)-\d+[.]\d+\S*' + '-py\d.\d.egg'), + '\\1.egg'), + (re.compile('distribute-\d+[.]\d+\S*' + '-py\d.\d.egg'), + 'setuptools.egg'), + (re.compile('(zc.buildout|setuptools)( version)? \d+[.]\d+\S*'), + '\\1 V.V'), + (re.compile('distribute( version)? \d+[.]\d+\S*'), + 'setuptools V.V'), + (re.compile('[-d] (setuptools|distribute)'), '- setuptools'), + (re.compile('distribute'), 'setuptools'), + (re.compile("\nUnused options for buildout: " + "'(distribute|setuptools)\-version'\."), + '') + ]) + ), + + doctest.DocFileSuite( + 'easy_install.txt', 'downloadcache.txt', 'dependencylinks.txt', + 'allowhosts.txt', 'unzip.txt', 'upgrading_distribute.txt', + setUp=easy_install_SetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + normalize_bang, + hide_first_index_page_message, + zc.buildout.tests.hide_distribute_additions, + (re.compile('extdemo[.]pyd'), 'extdemo.so'), + (re.compile('[-d] (setuptools|distribute)-\S+[.]egg'), + 'setuptools.egg'), + (re.compile(r'\\[\\]?'), '/'), + (re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'), + # Normalize generate_script's Windows interpreter to UNIX: + (re.compile(r'\nimport subprocess\n'), '\n'), + (re.compile('subprocess\\.call\\(argv, env=environ\\)'), + 'os.execve(sys.executable, argv, environ)'), + (re.compile('distribute'), 'setuptools'), + # Distribute unzips eggs by default. + (re.compile('\- demoneeded'), 'd demoneeded'), + ]+(sys.version_info < (2, 5) and [ + (re.compile('.*No module named runpy.*', re.S), ''), + (re.compile('.*usage: pdb.py scriptfile .*', re.S), ''), + (re.compile('.*Error: what does not exist.*', re.S), ''), + ] or [])), + ), + + doctest.DocFileSuite( + 'download.txt', 'extends-cache.txt', + setUp=easy_install_SetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS, + checker=renormalizing.RENormalizing([ + (re.compile(' at -?0x[^>]+'), '<MEM ADDRESS>'), + (re.compile('http://localhost:[0-9]{4,5}/'), + 'http://localhost/'), + (re.compile('[0-9a-f]{32}'), '<MD5 CHECKSUM>'), + zc.buildout.testing.normalize_path, + ]), + ), + + doctest.DocTestSuite( + setUp=easy_install_SetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + zc.buildout.tests.hide_distribute_additions, + hide_first_index_page_message, + (re.compile("buildout: Running \S*setup.py"), + 'buildout: Running setup.py'), + (re.compile('(setuptools|distribute)-\S+-'), + 'setuptools.egg'), + (re.compile('zc.buildout-\S+-'), + 'zc.buildout.egg'), + (re.compile('File "\S+one.py"'), + 'File "one.py"'), + (re.compile(r'We have a develop egg: (\S+) (\S+)'), + r'We have a develop egg: \1 V'), + (re.compile('Picked: (setuptools|distribute) = \S+'), + 'Picked: setuptools = V'), + (re.compile(r'\\[\\]?'), '/'), + (re.compile( + '-q develop -mxN -d "/sample-buildout/develop-eggs'), + '-q develop -mxN -d /sample-buildout/develop-eggs' + ), + (re.compile(r'^[*]...'), '...'), + # for bug_92891_bootstrap_crashes_with_egg_recipe_in_buildout_section + (re.compile(r"Unused options for buildout: 'eggs' 'scripts'\."), + "Unused options for buildout: 'scripts' 'eggs'."), + (re.compile('distribute'), 'setuptools'), + # Distribute unzips eggs by default. + (re.compile('\- demoneeded'), 'd demoneeded'), + ]), + ), + zc.buildout.testselectingpython.test_suite(), + zc.buildout.rmtree.test_suite(), + doctest.DocFileSuite( + 'windows.txt', + setUp=zc.buildout.testing.buildoutSetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + zc.buildout.tests.hide_distribute_additions, + (re.compile('__buildout_signature__ = recipes-\S+'), + '__buildout_signature__ = recipes-SSSSSSSSSSS'), + (re.compile('[-d] setuptools-\S+[.]egg'), 'setuptools.egg'), + (re.compile('zc.buildout(-\S+)?[.]egg(-link)?'), + 'zc.buildout.egg'), + (re.compile('creating \S*setup.cfg'), 'creating setup.cfg'), + (re.compile('hello\%ssetup' % os.path.sep), 'hello/setup'), + (re.compile('Picked: (\S+) = \S+'), + 'Picked: \\1 = V.V'), + (re.compile(r'We have a develop egg: zc.buildout (\S+)'), + 'We have a develop egg: zc.buildout X.X.'), + (re.compile(r'\\[\\]?'), '/'), + (re.compile('WindowsError'), 'OSError'), + (re.compile(r'\[Error 17\] Cannot create a file ' + r'when that file already exists: '), + '[Errno 17] File exists: ' + ), + ]) + ), + doctest.DocFileSuite( + 'testing_bugfix.txt', + checker=renormalizing.RENormalizing([ + # Python 2.7 + (re.compile( + re.escape( + 'testrunner.logsupport.NullHandler instance at')), + 'testrunner.logsupport.NullHandler object at'), + (re.compile(re.escape('logging.StreamHandler instance at')), + 'logging.StreamHandler object at'), + ]) + ), + ] + + # adding bootstrap.txt doctest to the suite + # only if bootstrap.py is present + bootstrap_py = os.path.join( + os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.dirname(zc.buildout.__file__) + ) + ) + ), + 'bootstrap', 'bootstrap.py') + + if os.path.exists(bootstrap_py): + test_suite.append(doctest.DocFileSuite( + 'bootstrap.txt', + setUp=bootstrapSetup, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + normalize_bang, + (re.compile('Downloading.*setuptools.*egg\n'), ''), + (re.compile('options:'), 'Options:'), + (re.compile('usage:'), 'Usage:'), + ]), + )) + test_suite.append(doctest.DocFileSuite( + 'virtualenv.txt', + setUp=easy_install_SetUp, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + zc.buildout.testing.normalize_path, + zc.buildout.testing.normalize_endings, + zc.buildout.testing.normalize_script, + zc.buildout.testing.normalize_egg_py, + zc.buildout.tests.hide_distribute_additions, + (re.compile('(setuptools|distribute)-\S+-'), + 'setuptools.egg'), + (re.compile('zc.buildout-\S+-'), + 'zc.buildout.egg'), + (re.compile(re.escape('#!"/executable_buildout/bin/py"')), + '#!/executable_buildout/bin/py'), # Windows. + (re.compile(re.escape('/broken_s/')), + '/broken_S/'), # Windows. + ]), + )) + + return unittest.TestSuite(test_suite) diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testselectingpython.py b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testselectingpython.py new file mode 100644 index 0000000..542e253 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/testselectingpython.py @@ -0,0 +1,96 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +import os, re, subprocess, sys, textwrap, unittest, doctest +from zope.testing import renormalizing +import zc.buildout.tests +import zc.buildout.testing + +if sys.version_info[:2] == (2, 4): + other_version = "2.5" +else: + other_version = "2.4" + +__test__ = dict( + test_selecting_python_via_easy_install= + """\ + +We can specify a specific Python executable. + + >>> dest = tmpdir('sample-install') + >>> ws = zc.buildout.easy_install.install( + ... ['demo'], dest, links=[link_server], + ... index='http://www.python.org/pypi/', + ... always_unzip=True, executable=other_executable) + + >>> ls(dest) + d demo-0.3-py%(other_version)s.egg + d demoneeded-1.1-py%(other_version)s.egg + +""" % dict(other_version=other_version) + ) + +def multi_python(test): + other_executable = zc.buildout.testing.find_python(other_version) + command = textwrap.dedent('''\ + try: + import setuptools + except ImportError: + import sys + sys.exit(1) + ''') + env = dict(os.environ) + env.pop('PYTHONPATH', None) + if subprocess.call([other_executable, '-c', command], env=env): + # the other executable does not have setuptools. Get setuptools. + # We will do this using the same tools we are testing, for better or + # worse. Alternatively, we could try using bootstrap. + executable_dir = test.globs['tmpdir']('executable_dir') + executable_parts = os.path.join(executable_dir, 'parts') + test.globs['mkdir'](executable_parts) + ws = zc.buildout.easy_install.install( + ['setuptools'], executable_dir, + index='http://www.python.org/pypi/', + always_unzip=True, executable=other_executable) + zc.buildout.easy_install.sitepackage_safe_scripts( + executable_dir, ws, other_executable, executable_parts, + reqs=['setuptools'], interpreter='py') + original_executable = other_executable + other_executable = os.path.join(executable_dir, 'py') + assert not subprocess.call( + [other_executable, '-c', command], env=env), ( + 'test set up failed') + sample_eggs = test.globs['tmpdir']('sample_eggs') + os.mkdir(os.path.join(sample_eggs, 'index')) + test.globs['sample_eggs'] = sample_eggs + zc.buildout.tests.create_sample_eggs(test, executable=other_executable) + test.globs['other_executable'] = other_executable + + +def setup(test): + zc.buildout.testing.buildoutSetUp(test) + multi_python(test) + zc.buildout.tests.add_source_dist(test) + test.globs['link_server'] = test.globs['start_server']( + test.globs['sample_eggs']) + + +def test_suite(): + return doctest.DocTestSuite( + setUp=setup, + tearDown=zc.buildout.testing.buildoutTearDown, + checker=renormalizing.RENormalizing([ + (re.compile('setuptools-\S+-py%s.egg' % other_version), + 'setuptools-V-py%s.egg' % other_version), + ]), + ) diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/unzip.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/unzip.txt new file mode 100644 index 0000000..9f55f3f --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/unzip.txt @@ -0,0 +1,54 @@ +Always unzipping eggs +===================== + +By default, zc.buildout doesn't unzip zip-safe eggs. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = demo + ... ''' % globals()) + + >>> _ = system(buildout) + >>> ls('eggs') + - demo-0.4c1-py2.4.egg + - demoneeded-1.2c1-py2.4.egg + d setuptools-0.6c8-py2.4.egg + - zc.buildout.egg-link + +This follows the policy followed by setuptools itself. Experience shows +this policy to to be inconvenient. Zipped eggs make debugging more +difficult and often import more slowly. + +You can include an unzip option in the buildout section to change the +default unzipping policy. + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... unzip = true + ... + ... [eggs] + ... recipe = zc.recipe.egg + ... eggs = demo + ... ''' % globals()) + + + >>> import os + >>> for name in os.listdir('eggs'): + ... if name.startswith('demo'): + ... remove('eggs', name) + + >>> _ = system(buildout) + >>> ls('eggs') + d demo-0.4c1-py2.4.egg + d demoneeded-1.2c1-py2.4.egg + d setuptools-0.6c8-py2.4.egg + - zc.buildout.egg-link diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/update.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/update.txt new file mode 100644 index 0000000..cab1610 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/update.txt @@ -0,0 +1,263 @@ +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-100.0b1-pyN.N.egg + - 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 + ... + ... 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), + Getting distribution for 'zc.buildout'. + 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 + +Notice that, even though we have a newer beta version of zc.buildout +available, the final "99.99" was selected. If you want to get non-final +versions, specify a specific version in your buildout's versions +section, you typically want to use the --accept-buildout-test-releases +option to the bootstrap script, which internally uses the +``accept-buildout-test-releases = true`` discussed below. + +Our buildout script's site.py has been updated to use the new eggs: + + >>> cat(sample_buildout, 'parts', 'buildout', 'site.py') + ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + "... + def addsitepackages(known_paths): + """Add site packages, as determined by zc.buildout. + <BLANKLINE> + See original_addsitepackages, below, for the original version.""" + setuptools_path = '/sample-buildout/eggs/setuptools-99.99-pyN.N.egg' + sys.path.append(setuptools_path) + known_paths.add(os.path.normcase(setuptools_path)) + import pkg_resources + buildout_paths = [ + '/sample-buildout/eggs/zc.buildout-99.99-pyN.N.egg', + '/sample-buildout/eggs/setuptools-99.99-pyN.N.egg' + ] + for path in buildout_paths: + sitedir, sitedircase = makepath(path) + if not sitedircase in known_paths and os.path.exists(sitedir): + sys.path.append(sitedir) + known_paths.add(sitedircase) + pkg_resources.working_set.add_entry(sitedir) + sys.__egginsert = len(buildout_paths) # Support setuptools. + original_paths = [ + ... + ] + for path in original_paths: + if path == setuptools_path or path not in known_paths: + addsitedir(path, known_paths) + return known_paths + ... + +Now, let's recreate the sample buildout. If we specify constraints on +the versions of zc.buildout and setuptools (or distribute) 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 + ... zc.buildout-version = < 99 + ... setuptools-version = < 99 + ... distribute-version = < 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), + Upgraded: + zc.buildout version 1.0.0, + setuptools version 0.6; + restarting. + 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'), + Develop: '/sample-buildout/showversions' + Updating show-versions. + zc.buildout 1.0.0 + setuptools 0.6 + +Or in non-newest mode: + + >>> print system(buildout+' -N'), + 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 +buildout's 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), + 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'. + Got zc.buildout 99.99. + Getting distribution for 'setuptools'. + Got setuptools 99.99. + Not upgrading because not running a local buildout command. + + >>> ls('bin') + +As mentioned above, the ``accept-buildout-test-releases = true`` means that +newer non-final versions of these dependencies are preferred. Typically +users are not expected to actually manipulate this value. Instead, the +bootstrap script creates a buildout buildout script that passes in the +value as a command line override. This then results in the buildout +script being rewritten to remember the decision. + +We'll mimic this by passing the argument actually in the command line. + + >>> cd(sample_buildout) + >>> 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 + + ... ' buildout:accept-buildout-test-releases=true'), + ... # doctest: +NORMALIZE_WHITESPACE + Getting distribution for 'zc.buildout'. + Got zc.buildout 100.0b1. + Upgraded: + zc.buildout version 100.0b1, + setuptools version 99.99; + restarting. + Generated script '/sample-buildout/bin/buildout'. + NOTE: Accepting early releases of build system packages. Rerun bootstrap + without --accept-buildout-test-releases (-t) to return to default + behavior. + Develop: '/sample-buildout/showversions' + Updating show-versions. + zc.buildout 100.0b1 + setuptools 99.99 + +The buildout script shows the change. + + >>> buildout_script = join(sample_buildout, 'bin', 'buildout') + >>> import sys + >>> if sys.platform.startswith('win'): + ... buildout_script += '-script.py' + >>> print open(buildout_script).read() # doctest: +ELLIPSIS + #... + sys.argv.insert(1, 'buildout:accept-buildout-test-releases=true') + print ('NOTE: Accepting early releases of build system packages. Rerun ' + 'bootstrap without --accept-buildout-test-releases (-t) to return to ' + 'default behavior.') + ... diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/upgrading_distribute.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/upgrading_distribute.txt new file mode 100644 index 0000000..e45b54b --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/upgrading_distribute.txt @@ -0,0 +1,56 @@ +Installing setuptools/distribute +-------------------------------- + +Some initial test setup: + + >>> import sys + >>> import zc.buildout + >>> dest = tmpdir('sample-install') + +Setuptools (0.6something) is packaged as an ``.egg``. So when installing it, +the egg is downloaded and used. Distribute is packaged as a tarball, which +makes an easy_install call necessary. In older versions of buildout, the +``_call_easy_install()`` method would call ``_get_dist()`` to get hold of the +setuptools path for calling easy_install. When an updated "distribute" was +found, this would try an install again, leading to an infinite recursion. + +The solution is to just use the setuptools location found at import time, like +happens with the buildout and setuptools location that is inserted in scripts' +paths. + +We test this corner case by patching the ``_get_dist()`` call: + + >>> def mock_get_dist(requirement, ws, always_unzip): + ... raise RuntimeError("We should not get called") + +When installing setuptools itself, we expect the "Getting dist" message not to +be printed. We call ``_call_easy_install()`` directly and get an error +because of a non-existing tarball, but that's the OK for this corner case +test: we only want to test that ``_get_dist()`` isn't getting called: + + >>> class MockDist(object): + ... def __str__(self): + ... return 'nonexisting.tgz' + ... @property + ... def project_name(self): + ... # Testing corner case: there *is* actually + ... # a newer setuptools package on pypi than we + ... # are running with, so it really installs it + ... # and compares project_name. We're past the + ... # point that we're testing, so we just raise + ... # the normally expected error. + ... raise zc.buildout.UserError( + ... "Couldn't install: nonexisting.tgz") + >>> dist = MockDist() + + >>> installer = zc.buildout.easy_install.Installer( + ... dest=dest, + ... links=[link_server], + ... index=link_server+'index/', + ... executable=sys.executable, + ... always_unzip=True) + >>> installer._get_dist = mock_get_dist + >>> installer._call_easy_install('setuptools', None, dest, dist) + Traceback (most recent call last): + ... + UserError: Couldn't install: nonexisting.tgz diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/virtualenv.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/virtualenv.txt new file mode 100644 index 0000000..c6c127f --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/virtualenv.txt @@ -0,0 +1,257 @@ +Version 1.5.0 of buildout (and higher) provides the ability to use +buildout directly with a system Python if you use z3c.recipe.scripts or +other isolation-aware recipes that use the sitepackage_safe_scripts function. + +Some people use virtualenv to provide similar functionality. +Unfortunately, a problem with the virtualenv executable as of this +writing means that -S will not work properly with it (see +https://bugs.launchpad.net/virtualenv/+bug/572545). This breaks +buildout's approach to providing isolation. + +Because of this, if buildout detects an executable with a broken -S +option, it will revert to its pre-1.5.0 behavior. If buildout has been +asked to provide isolation, it will warn the user that isolation will +not be provided by buildout, but proceed. This should give full +backwards compatibility to virtualenv users. + +The only minor annoyance in the future may be recipes that explicitly +use the new buildout functionality to provide isolation: as described +above, the builds will proceed, but users will receive warnings that +buildout is not providing isolation itself. The warnings themselves can +be squelched when running bin/buildout with the ``-s`` option or with a +lower verbosity than usual (e.g., one or more ``-q`` options). + +For tests, then, we can examine several things. We'll focus on four. + +- Running bootstrap with an executable broken in this way will not try to do + any -S tricks. + +- Running sitepackage_safe_scripts with a virtualenv will create an + old-style script. This will affect the bin/buildout script that is + created, for instance. If the sitepackage_safe_scripts function is asked + to provide isolation under these circumstances, it will warn that isolation + will not be available, but still create the desired script. + +- Using the easy_install Installer or install or build functions and trying + to request isolation will generate a warning and then the isolation request + will be ignored as it proceeds. + +- Passing -s (or -q) to the bin/buildout script will squelch warnings. + +Testing these involves first creating a Python that exhibits the same +behavior as the problematic one we care about from virtualenv. Let's do that +first. + + >>> import os, sys + >>> from zc.buildout.easy_install import _safe_arg + >>> py_path, site_packages_path = make_py() + >>> if sys.platform == 'win32': + ... py_script_path = py_path + '-script.py' + ... else: + ... py_script_path = py_path + ... + >>> py_file = open(py_script_path) + >>> py_lines = py_file.readlines() + >>> py_file.close() + >>> py_file = open(py_script_path, 'w') + >>> extra = '''\ + ... new_argv = argv[:1] + ... for ix, val in enumerate(argv[1:]): + ... if val.startswith('--'): + ... new_argv.append(val) + ... if val.startswith('-') and len(val) > 1: + ... if 'S' in val: + ... val = val.replace('S', '') + ... environ['BROKEN_DASH_S'] = 'Y' + ... if val != '-': + ... new_argv.append(val) + ... if 'c' in val: + ... new_argv.extend(argv[ix+2:]) + ... break + ... else: + ... new_argv.extend(argv[ix+1:]) + ... argv = new_argv + ... ''' + >>> for line in py_lines: + ... py_file.write(line) + ... if line.startswith('environ = os.environ.copy()'): + ... py_file.write(extra) + ... print 'Rewritten.' + ... + Rewritten. + >>> py_file.close() + >>> sitecustomize_path = join(os.path.dirname(site_packages_path), + ... 'parts', 'py', 'sitecustomize.py') + >>> sitecustomize_file = open(sitecustomize_path, 'a') + >>> sitecustomize_file.write(''' + ... import os, sys + ... sys.executable = %r + ... if 'BROKEN_DASH_S' in os.environ: + ... class ImportHook: + ... site = None + ... + ... @classmethod + ... def find_module(klass, fullname, path=None): + ... if klass.site is None and 'site' in sys.modules: + ... # Pop site out of sys.modules. This will be a + ... # close-enough approximation of site not being + ... # loaded for our tests--it lets us provoke the + ... # right errors when the fixes are absent, and + ... # works well enough when the fixes are present. + ... klass.site = sys.modules.pop('site') + ... if fullname == 'ConfigParser': + ... raise ImportError(fullname) + ... elif fullname == 'site': + ... # Keep the site module from being processed twice. + ... return klass + ... + ... @classmethod + ... def load_module(klass, fullname): + ... if fullname == 'site': + ... return klass.site + ... raise ImportError(fullname) + ... + ... sys.meta_path.append(ImportHook) + ... ''' % (py_path,)) + >>> sitecustomize_file.close() + >>> print call_py( + ... _safe_arg(py_path), + ... "import ConfigParser") + <BLANKLINE> + >>> print 'X'; print call_py( + ... _safe_arg(py_path), + ... "import ConfigParser", + ... '-S') # doctest: +ELLIPSIS + X...Traceback (most recent call last): + ... + ImportError: No module named ConfigParser + <BLANKLINE> + >>> from zc.buildout.easy_install import _has_broken_dash_S + >>> _has_broken_dash_S(py_path) + True + +Well, that was ugly, but it seems to have done the trick. The +executable represented by py_path has the same problematic +characteristic as the virtualenv one: -S results in a Python that does +not allow the import of some packages from the standard library. We'll +test with this. + +First, let's try running bootstrap. + + >>> from os.path import dirname, join + >>> import zc.buildout + >>> bootstrap_py = join( + ... dirname( + ... dirname( + ... dirname( + ... dirname(zc.buildout.__file__) + ... ) + ... ) + ... ), + ... 'bootstrap', 'bootstrap.py') + >>> broken_S_buildout = tmpdir('broken_S') + >>> os.chdir(broken_S_buildout) + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = + ... ''') + >>> write('bootstrap.py', open(bootstrap_py).read()) + >>> print 'X'; print system( + ... _safe_arg(py_path)+' '+ + ... 'bootstrap.py'); print 'X' # doctest: +ELLIPSIS + X... + Generated script '/broken_S/bin/buildout'. + ... + +If bootstrap didn't look out for a broken -S, that would have failed. Moreover, +take a look at bin/buildout: + + >>> cat('bin', 'buildout') # doctest: +NORMALIZE_WHITESPACE + #!/executable_buildout/bin/py + <BLANKLINE> + import sys + sys.path[0:0] = [ + '/broken_S/eggs/setuptools-0.0-pyN.N.egg', + '/broken_S/eggs/zc.buildout-0.0-pyN.N.egg', + ] + <BLANKLINE> + import zc.buildout.buildout + <BLANKLINE> + if __name__ == '__main__': + zc.buildout.buildout.main() + +That's the old-style buildout script: no changes for users with this issue. + +Of course, they don't get the new features either, presumably because +they don't need or want them. This means that if they use a recipe that +tries to use a new feature, the behavior needs to degrade gracefully. + +Here's an example. We'll switch to another buildout in which it is easier to +use local dev versions of zc.buildout and z3c.recipe.scripts. + + >>> os.chdir(dirname(dirname(buildout))) + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = eggs + ... find-links = %(link_server)s + ... include-site-packages = false + ... + ... [primed_python] + ... executable = %(py_path)s + ... + ... [eggs] + ... recipe = z3c.recipe.scripts + ... python = primed_python + ... interpreter = py + ... eggs = demo + ... ''' % globals()) + + >>> print system(buildout), # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS + Installing eggs. + Getting distribution for 'demo'. + Got demo 0.4c1. + Getting distribution for 'demoneeded'. + Got demoneeded 1.2c1. + Generated script '/sample-buildout/bin/demo'. + Generated interpreter '/sample-buildout/bin/py'. + ...UserWarning: Buildout has been asked to exclude or limit site-packages + so that builds can be repeatable when using a system Python. However, + the chosen Python executable has a broken implementation of -S (see + https://bugs.launchpad.net/virtualenv/+bug/572545 for an example + problem) and this breaks buildout's ability to isolate site-packages. + If the executable already has a clean site-packages (e.g., using + virtualenv's ``--no-site-packages`` option) you may be getting + equivalent repeatability. To silence this warning, use the -s argument + to the buildout script. Alternatively, use a Python executable with a + working -S (such as a standard Python binary). + warnings.warn(BROKEN_DASH_S_WARNING) + +So, it did what we asked as best it could, but gave a big warning. If +you don't want those warnings for those particular recipes that use the +new features, you can use the "-s" option to squelch the warnings. + + >>> print system(buildout + ' -s'), + Updating eggs. + +A lower verbosity (one or more -q options) also quiets the warning. + + >>> print system(buildout + ' -q'), + +Notice that, as we saw before with bin/buildout, the generated scripts +are old-style, because the new-style feature gracefully degrades to the +previous implementation when it encounters an executable with a broken +dash-S. + + >>> print 'X'; cat('bin', 'py') # doctest: +ELLIPSIS + X... + <BLANKLINE> + import sys + <BLANKLINE> + sys.path[0:0] = [ + '/sample-buildout/eggs/demo-0.4c1-pyN.N.egg', + '/sample-buildout/eggs/demoneeded-1.2c1-pyN.N.egg', + ] + ... + diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/windows.txt b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/windows.txt new file mode 100644 index 0000000..3f6b1d1 --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/windows.txt @@ -0,0 +1,66 @@ +zc.buildout on MS-Windows +========================= + +Certain aspects of every software project are dependent on the +operating system used. +The same - of course - applies to zc.buildout. + +To test that Windows doesn't get in the way, we'll test some system +dependent aspects. +The following recipe will create a read-only file which shutil.rmtree +can't delete. + + >>> mkdir('recipe') + >>> write('recipe', 'recipe.py', + ... ''' + ... import os + ... class Recipe: + ... def __init__(self, buildout, name, options): + ... self.location = os.path.join( + ... buildout['buildout']['parts-directory'], + ... name) + ... + ... def install(self): + ... print "can't remove read only files" + ... if not os.path.exists (self.location): + ... os.makedirs (self.location) + ... + ... name = os.path.join (self.location, 'readonly.txt') + ... open (name, 'w').write ('this is a read only file') + ... os.chmod (name, 0400) + ... return () + ... + ... update = install + ... ''') + + >>> write('recipe', 'setup.py', + ... ''' + ... from setuptools import setup + ... setup(name='spam', version='1', py_modules=['recipe'], + ... entry_points={'zc.buildout': ['default = recipe:Recipe']}, + ... ) + ... ''') + + >>> write('recipe', 'README', '') + + >>> print system(buildout+' setup recipe bdist_egg'), # doctest: +ELLIPSIS + Running setup script 'recipe/setup.py'. + ... + +and we'll configure a buildout to use it: + + >>> write('buildout.cfg', + ... ''' + ... [buildout] + ... parts = foo + ... find-links = %s + ... + ... [foo] + ... recipe = spam + ... ''' % join('recipe', 'dist')) + + >>> print system(buildout), + Getting distribution for 'spam'. + Got spam 1. + Installing foo. + can't remove read only files |