diff options
Diffstat (limited to 'eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/PKG-INFO')
-rw-r--r-- | eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/PKG-INFO | 8235 |
1 files changed, 8235 insertions, 0 deletions
diff --git a/eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/PKG-INFO b/eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/PKG-INFO new file mode 100644 index 0000000..ba54ddc --- /dev/null +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,8235 @@ +Metadata-Version: 1.0 +Name: zc.buildout +Version: 1.5.2 +Summary: System for managing development buildouts +Home-page: http://pypi.python.org/pypi/zc.buildout +Author: Jim Fulton +Author-email: jim@zope.com +License: ZPL 2.1 +Description: ******** + Buildout + ******** + + .. contents:: + + The Buildout project provides support for creating applications, + especially Python applications. It provides tools for assembling + applications from multiple parts, Python or otherwise. An application + may actually contain multiple programs, processes, and configuration + settings. + + 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". + + To get a feel for some of the things you might use buildouts for, see + the `Buildout examples`_. + + To lean more about using buildouts, see `Detailed Documentation`_. + + To see screencasts, talks, useful links and more documentation, visit + the `Buildout website <http://www.buildout.org>`_. + + Recipes + ******* + + Existing recipes include: + + `zc.recipe.egg <http://pypi.python.org/pypi/zc.recipe.egg>`_ + The egg recipe installes one or more eggs, with their + dependencies. It installs their console-script entry points with + the needed eggs included in their paths. It is suitable for use with + a "clean" Python: one without packages installed in site-packages. + + `z3c.recipe.scripts <http://pypi.python.org/pypi/z3c.recipe.scripts>`_ + Like zc.recipe.egg, this recipe builds interpreter scripts and entry + point scripts based on eggs. It can be used with a Python that has + packages installed in site-packages, such as a system Python. The + interpreter also has more features than the one offered by + zc.recipe.egg. + + `zc.recipe.testrunner <http://pypi.python.org/pypi/zc.recipe.testrunner>`_ + The testrunner egg creates a test runner script for one or + more eggs. + + `zc.recipe.zope3checkout <http://pypi.python.org/pypi/zc.recipe.zope3checkout>`_ + The zope3checkout recipe installs a Zope 3 checkout into a + buildout. + + `zc.recipe.zope3instance <http://pypi.python.org/pypi/zc.recipe.zope3instance>`_ + The zope3instance recipe sets up a Zope 3 instance. + + `zc.recipe.filestorage <http://pypi.python.org/pypi/zc.recipe.filestorage>`_ + The filestorage recipe sets up a ZODB file storage for use in a + Zope 3 instance created by the zope3instance recipe. + + Buildout examples + ***************** + + Here are a few examples of what you can do with buildouts. We'll + present these as a set of use cases. + + Try out an egg + ============== + + Sometimes you want to try an egg (or eggs) that someone has released. + You'd like to get a Python interpreter that lets you try things + interactively or run sample scripts without having to do path + manipulations. If you can and don't mind modifying your Python + installation, you could use easy_install, otherwise, you could create + a directory somewhere and create a buildout.cfg file in that directory + containing:: + + [buildout] + parts = mypython + + [mypython] + recipe = zc.recipe.egg + interpreter = mypython + eggs = theegg + + where theegg is the name of the egg you want to try out. + + Run buildout in this directory. It will create a bin subdirectory + that includes a mypython script. If you run mypython without any + arguments you'll get an interactive interpreter with the egg in the + path. If you run it with a script and script arguments, the script + will run with the egg in its path. Of course, you can specify as many + eggs as you want in the eggs option. + + If the egg provides any scripts (console_scripts entry points), those + will be installed in your bin directory too. + + Work on a package + ================= + + I often work on packages that are managed separately. They don't have + scripts to be installed, but I want to be able to run their tests + using the `zope.testing test runner + <http://www.python.org/pypi/zope.testing>`_. In this kind of + application, the program to be installed is the test runner. A good + example of this is `zc.ngi <http://svn.zope.org/zc.ngi/trunk/>`_. + + Here I have a subversion project for the zc.ngi package. The software + is in the src directory. The configuration file is very simple:: + + [buildout] + develop = . + parts = test + + [test] + recipe = zc.recipe.testrunner + eggs = zc.ngi + + I use the develop option to create a develop egg based on the current + directory. I request a test script named "test" using the + zc.recipe.testrunner recipe. In the section for the test script, I + specify that I want to run the tests in the zc.ngi package. + + When I check out this project into a new sandbox, I run bootstrap.py + to get setuptools and zc.buildout and to create bin/buildout. I run + bin/buildout, which installs the test script, bin/test, which I can + then use to run the tests. + + This is probably the most common type of buildout. + + If I need to run a previous version of zc.buildout, I use the + `--version` option of the bootstrap.py script:: + + $ python bootstrap.py --version 1.1.3 + + The `zc.buildout project <http://svn.zope.org/zc.buildout/trunk>`_ + is a slightly more complex example of this type of buildout. + + Install egg-based scripts + ========================= + + A variation of the `Try out an egg`_ use case is to install scripts + into your ~/bin directory (on Unix, of course). My ~/bin directory is + a buildout with a configuration file that looks like:: + + + [buildout] + parts = foo bar + bin-directory = . + + [foo] + ... + + where foo and bar are packages with scripts that I want available. As + I need new scripts, I can add additional sections. The bin-directory + option specified that scripts should be installed into the current + directory. + + Multi-program multi-machine systems + =================================== + + Using an older prototype version of the buildout, we've build a number + of systems involving multiple programs, databases, and machines. One + typical example consists of: + + - Multiple Zope instances + + - Multiple ZEO servers + + - An LDAP server + + - Cache-invalidation and Mail delivery servers + + - Dozens of add-on packages + + - Multiple test runners + + - Multiple deployment modes, including dev, stage, and prod, + with prod deployment over multiple servers + + Parts installed include: + + - Application software installs, including Zope, ZEO and LDAP + software + + - Add-on packages + + - Bundles of configuration that define Zope, ZEO and LDAP instances + + - Utility scripts such as test runners, server-control + scripts, cron jobs. + + Questions and Bug Reporting + *************************** + + Please send questions and comments to the + `distutils SIG mailing list <mailto://distutils-sig@python.org>`_. + + Report bugs using the `zc.buildout Launchpad Bug Tracker + <https://launchpad.net/zc.buildout/+bugs>`_. + + System Python and zc.buildout 1.5 + ********************************* + + The 1.5 line of zc.buildout introduced a number of changes. + + Problems + ======== + + As usual, please send questions and comments to the `distutils SIG + mailing list <mailto://distutils-sig@python.org>`_. Report bugs using + the `zc.buildout Launchpad Bug Tracker + <https://launchpad.net/zc.buildout/+bugs>`_. + + If problems are keeping you from your work, here's an easy way to + revert to the old code temporarily: switch to a custom "emergency" + bootstrap script, available from + http://svn.zope.org/repos/main/zc.buildout/branches/1.4/bootstrap/bootstrap.py . + + This customized script will select zc.buildout 1.4.4 by default. + zc.buildout 1.4.4 will not upgrade itself unless you explicitly specify + a new version. It will also prefer older versions of zc.recipe.egg and + some other common recipes. If you have trouble with other recipes, + consider using a standard buildout "versions" section to specify older + versions of these, as described in the Buildout documentation + (http://pypi.python.org/pypi/zc.buildout#repeatable-buildouts-controlling-eggs-used). + + Working with a System Python + ============================ + + While there are a number of new features available in zc.buildout 1.5, + the biggest is that Buildout itself supports usage with a system Python. + This can work if you follow a couple of simple rules. + + 1. Use the new bootstrap.py (available from + svn://svn.zope.org/repos/main/zc.buildout/trunk/bootstrap/bootstrap.py). + + 2. Use buildout recipes that have been upgraded to work with zc.buildout 1.5 + and higher. Specifically, they should use + ``zc.buildout.easy_install.sitepackage_safe_scripts`` to generate + their scripts, if any, rather than ``zc.buildout.easy_install.scripts``. + See the `Recipes That Support a System Python`_ section below for more + details on recipes that are available as of this writing, and + `Updating Recipes to Support a System Python`_ for instructions on + how to update a recipe. Note that you should generally only need to + update recipes that generate scripts. + + You can then use ``include-site-packages = false`` and + ``exec-sitecustomize = false`` buildout options to eliminate access to + your Python's site packages and not execute its sitecustomize file, if + it exists, respectively. + + Alternately, you can use the ``allowed-eggs-from-site-packages`` buildout + option as a glob-aware whitelist of eggs that may come from site-packages. + This value defaults to "*", accepting all eggs. + + It's important to note that recipes not upgraded for zc.buildout 1.5.0 + should continue to work--just without internal support for a system Python. + + Using a system Python is inherently fragile. Using a clean, + freshly-installed Python without customization in site-packages is more + robust and repeatable. See some of the regression tests added to the + 1.5.0 line for the kinds of issues that you can encounter with a system + Python, and see + http://pypi.python.org/pypi/z3c.recipe.scripts#including-site-packages-and-sitecustomize + for more discussion. + + However, using a system Python can be very convenient, and the + zc.buildout code for this feature has been tested by many users already. + Moreover, it has automated tests to exercise the problems that have been + encountered and fixed. Many people rely on it. + + Recipes That Support a System Python + ==================================== + + zc.recipe.egg continues to generate old-style scripts that are not safe + for use with a system Python. This was done for backwards + compatibility, because it is integral to so many buildouts and used as a + dependency of so many other recipes. + + If you want to generate new-style scripts that do support system Python + usage, use z3c.recipe.scripts instead + (http://pypi.python.org/pypi/z3c.recipe.scripts). z3c.recipe.scripts has + the same script and interpreter generation options as zc.recipe.egg, + plus a few more for the new features mentioned above. In the simplest + case, you should be able to simply change ``recipe = zc.recipe.egg`` to + ``recipe = z3c.recipe.scripts`` in the pertinent sections of your + buildout configuration and your generated scripts will work with a system + Python. + + Other updated recipes include zc.recipe.testrunner 1.4.0 and + z3c.recipe.tag 0.4.0. Others should be updated soon: see their change + documents for details, or see `Updating Recipes to Support a System + Python`_ for instructions on how to update recipes yourself. + + Templates for creating Python scripts with the z3c.recipe.filetemplate + recipe can be easily changed to support a system Python. + + - If you don't care about supporting relative paths, simply using a + generated interpreter with the eggs you want should be sufficient, as + it was before. For instance, if the interpreter is named "py", use + ``#!${buildout:bin-directory/py}`` or ``#!/usr/bin/env + ${buildout:bin-directory/py}``). + + - If you do care about relative paths, (``relative-paths = true`` in + your buildout configuration), then z3c.recipe.scripts does require a + bit more changes, as is usual for the relative path support in that + package. First, use z3c.recipe.scripts to generate a script or + interpreter with the dependencies you want. This will create a + directory in ``parts`` that has a site.py and sitecustomize.py. Then, + begin your script as in the snippet below. The example assumes that + the z3c.recipe.scripts generated were from a Buildout configuration + section labeled "scripts": adjust accordingly. + + :: + + #!${buildout:executable} -S + ${python-relative-path-setup} + import sys + sys.path.insert(0, ${scripts:parts-directory|path-repr}) + import site + + Updating Recipes to Support a System Python + =========================================== + + You should generally only need to update recipes that generate scripts. + These recipes need to change from using ``zc.buildout.easy_install.scripts`` + to be using ``zc.buildout.easy_install.sitepackage_safe_scripts``. + The signatures of the two functions are different. Please compare:: + + def scripts( + reqs, working_set, executable, dest, + scripts=None, + extra_paths=(), + arguments='', + interpreter=None, + initialization='', + relative_paths=False, + ): + + 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='', + ): + + In most cases, the arguments are merely reordered. The ``reqs`` + argument is no longer required in order to make it easier to generate an + interpreter alone. The ``arguments`` argument was renamed to + ``script_arguments`` to clarify that it did not affect interpreter + generation. + + The only new required argument is ``site_py_dest``. It must be the path + to a directory in which the customized site.py and sitecustomize.py + files will be written. A typical generation in a recipe will look like + this. + + (In the recipe's __init__ method...) + + :: + + self.options = options + b_options = buildout['buildout'] + options['parts-directory'] = os.path.join( + b_options['parts-directory'], self.name) + + (In the recipe's install method...) + + :: + + options = self.options + generated = [] + if not os.path.exists(options['parts-directory']): + os.mkdir(options['parts-directory']) + generated.append(options['parts-directory']) + + Then ``options['parts-directory']`` can be used for the ``site_py_dest`` + value. + + If you want to support the other arguments (``include_site_packages``, + ``exec_sitecustomize``, ``script_initialization``, as well as the + ``allowed-eggs-from-site-packages`` option), you might want to look at + some of the code in + svn://svn.zope.org/repos/main/zc.buildout/trunk/z3c.recipe.scripts\_/src/z3c/recipe/scripts/scripts.py . + You might even be able to adopt some of it by subclassing or delegating. + The Scripts class in that file is the closest to what you might be used + to from zc.recipe.egg. + + Important note for recipe authors: As of buildout 1.5.2, the code in + recipes is *always run with the access to the site-packages as + configured in the buildout section*. + + virtualenv + ========== + + Using virtualenv (http://pypi.python.org/pypi/virtualenv) with the + --no-site-packages option already provided a simple way of using a + system Python. This is intended to continue to work, and some automated + tests exist to demonstrate this. + + However, it is only supported to the degree that people have found it to + work in the past. The existing Buildout tests for virtualenv are only + for problems encountered previously. They are very far from + comprehensive. + + Using Buildout with a system python has at least three advantages over + using Buildout in conjunction with virtualenv. They may or may not be + pertinent to your desired usage. + + - Unlike ``virtualenv --no-site-packages``, Buildout's support allows you + to choose to let packages from your system Python be available to your + software (see ``include-site-packages`` in + http://pypi.python.org/pypi/z3c.recipe.scripts). + + You can even specify which eggs installed in your system Python can be + allowed to fulfill some of your packages' dependencies (see + ``allowed-eggs-from-site-packages`` in + http://pypi.python.org/pypi/z3c.recipe.scripts). + + At the expense of some repeatability and platform dependency, this + flexibility means that, for instance, you can rely on + difficult-to-build eggs like lxml coming from your system Python. + + - Buildout's implementation has a full set of automated tests. + + - An integral Buildout implementation means fewer steps and fewer dependencies + to work with a system Python. + + Detailed Documentation + ********************** + + 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>`_. + + 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 + + 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')) + + 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 + + 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'. + + 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 + + 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'. + ... + + 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.') + ... + + 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: + + 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. + + 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 + + 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 + + + + Change History + ************** + + 1.5.2 (2010-10-11) + ================== + + - changed metadata 'url' to pypi.python.org in order to solve + a temporary outage of buildout.org + + - IMPORTANT: For better backwards compatibility with the pre-1.5 line, + this release has two big changes from 1.5.0 and 1.5.1. + + - Buildout defaults to including site packages. + + - Buildout loads recipes and extensions with the same constraints to + site-packages that it builds eggs, instead of never allowing access + to site-packages. + + This means that the default configuration should better support + pre-existing use of system Python in recipes or builds. + + - To make it easier to detect the fact that buildout has set the PYTHONPATH, + BUILDOUT_ORIGINAL_PYTHONPATH is always set in the environment, even if + PYTHONPATH was not originally set. BUILDOUT_ORIGINAL_PYTHONPATH will + be an empty string if PYTHONPATH was not set. + + 1.5.1 (2010-08-29) + ================== + + New features: + + - Scripts store the old PYTHONPATH in BUILDOUT_ORIGINAL_PYTHONPATH if it + existed, and store nothing in the value if it did not exist. This allows + code that does not want subprocesses to have the system-Python-protected + site.py to set the environment of the subprocess as it was originally. + + Bugs fixed: + + - https://bugs.launchpad.net/bugs/623590 : If include-site-packages were + true and versions were not set explicitly, system eggs were preferred + over newer released eggs. Fixed. + + 1.5.0 (2010-08-23) + ================== + + New features: + + - zc.buildout supports Python 2.7. + + - By default, Buildout and the bootstrap script now prefer final versions of + Buildout, recipes, and extensions. This can be changed by using the + --accept-buildout-test-releases flag (or -t for short) when calling + bootstrap. This will hopefully allow beta releases of these items to + be more easily and safely made in the future. + + NOTE: dependencies of your own software are not affected by this new + behavior. Buildout continues to choose the newest available versions + of your dependencies regardless of whether they are final releases. To + prevent this, use the pre-existing switch ``prefer-final = true`` in + the [buildout] section of your configuration file (see + http://pypi.python.org/pypi/zc.buildout#preferring-final-releases) or + pin your versions using a versions section (see + http://pypi.python.org/pypi/zc.buildout#repeatable-buildouts-controlling-eggs-used). + + Bugs fixed: + + - You can now again use virtualenv with Buildout. The new features to let + buildout be used with a system Python are disabled in this configuration, + and the previous script generation behavior (1.4.3) is used, even if + the new function ``zc.buildout.easy_install.sitepackage_safe_scripts`` + is used. + + 1.5.0b2 (2010-04-29) + ==================== + + This was a re-release of 1.4.3 in order to keep 1.5.0b1 release from hurting + workflows that combined virtualenv with zc.buildout. + + 1.5.0b1 (2010-04-29) + ==================== + + New Features: + + - Added buildout:socket-timout option so that socket timeout can be configured + both from command line and from config files. (gotcha) + + - Buildout can be safely used with a system Python (or any Python with code + in site-packages), as long as you use (1) A fresh checkout, (2) the + new bootstrap.py, and (3) recipes that use the new + ``zc.buildout.easy_install.sitepackage_safe_scripts`` function to generate + scripts and interpreters. Many recipes will need to be updated to use + this new function. The scripts and interpreters generated by + ``zc.recipe.egg`` will continue to use the older function, not safe + with system Pythons. Use the ``z3c.recipe.scripts`` as a replacement. + + zc.recipe.egg is still a fully supported, and simpler, way of + generating scripts and interpreters if you are using a "clean" Python, + without code installed in site-packages. It keeps its previous behavior in + order to provide backwards compatibility. + + The z3c.recipe.scripts recipe allows you to control how you use the + code in site-packages. You can exclude it entirely (preferred); allow + eggs in it to fulfill package dependencies declared in setup.py and + buildout configuration; allow it to be available but not used to + fulfill dependencies declared in setup.py or buildout configuration; + or only allow certain eggs in site-packages to fulfill dependencies. + + - Added new function, ``zc.buildout.easy_install.sitepackage_safe_scripts``, + to generate scripts and interpreter. It produces a full-featured + interpreter (all command-line options supported) and the ability to + safely let scripts include site packages, such as with a system + Python. The ``z3c.recipe.scripts`` recipe uses this new function. + + - Improve bootstrap. + + * New options let you specify where to find ez_setup.py and where to find + a download cache. These options can keep bootstrap from going over the + network. + + * Another new option lets you specify where to put generated eggs. + + * The buildout script generated by bootstrap honors more of the settings + in the designated configuration file (e.g., buildout.cfg). + + * Correctly handle systems where pkg_resources is present but the rest of + setuptools is missing (like Ubuntu installs). + https://bugs.launchpad.net/zc.buildout/+bug/410528 + + - You can develop zc.buildout using Distribute instead of Setuptools. Use + the --distribute option on the dev.py script. (Releases should be tested + with both Distribute and Setuptools.) The tests for zc.buildout pass + with Setuptools and Python 2.4, 2.5, 2.6, and 2.7; and with Distribute and + Python 2.5, 2.6, and 2.7. Using zc.buildout with Distribute and Python 2.4 + is not recommended. + + - The ``distribute-version`` now works in the [buildout] section, mirroring + the ``setuptools-version`` option (this is for consistency; using the + general-purpose ``versions`` option is preferred). + + Bugs fixed: + + - Using Distribute with the ``allow-picked-versions = false`` buildout + option no longer causes an error. + + - The handling and documenting of default buildout options was normalized. + This means, among other things, that ``bin/buildout -vv`` and + ``bin/buildout annotate`` correctly list more of the options. + + - Installing a namespace package using a Python that already has a package + in the same namespace (e.g., in the Python's site-packages) failed in + some cases. It is now handled correctly. + + - Another variation of this 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. This is also now + handled correctly. + + 1.4.3 (2009-12-10) + ================== + + Bugs fixed: + + - Using pre-detected setuptools version for easy_installing tgz files. This + prevents a recursion error when easy_installing an upgraded "distribute" + tgz. Note that setuptools did not have this recursion problem solely + because it was packaged as an ``.egg``, which does not have to go through + the easy_install step. + + + 1.4.2 (2009-11-01) + ================== + + New Feature: + + - Added a --distribute option to the bootstrap script, in order + to use Distribute rather than Setuptools. By default, Setuptools + is used. + + Bugs fixed: + + - While checking for new versions of setuptools and buildout itself, + compare requirement locations instead of requirement objects. + + - Incrementing didn't work properly when extending multiple files. + https://bugs.launchpad.net/zc.buildout/+bug/421022 + + - The download API computed MD5 checksums of text files wrong on Windows. + + 1.4.1 (2009-08-27) + ================== + + New Feature: + + - Added a debug built-in recipe to make writing some tests easier. + + Bugs fixed: + + - (introduced in 1.4.0) option incrementing (-=) and decrementing (-=) + didn't work in the buildout section. + https://bugs.launchpad.net/zc.buildout/+bug/420463 + + - Option incrementing and decrementing didn't work for options + specified on the command line. + + - Scripts generated with relative-paths enabled couldn't be + symbolically linked to other locations and still work. + + - Scripts run using generated interpreters didn't have __file__ set correctly. + + - The standard Python -m option didn't work for custom interpreters. + + 1.4.0 (2009-08-26) + ================== + + - When doing variable substitutions, you can omit the section name to + refer to a variable in the same section (e.g. ${:foo}). + + - When doing variable substitution, you can use the special option, + ``_buildout_section_name_`` to get the section name. This is most handy + for getting the current section name (e.g. ${:_buildout_section_name_}). + + - A new special option, ``<`` allows sections to be used as macros. + + - Added annotate command for annotated sections. Displays sections + key-value pairs along with the value origin. + + - Added a download API that handles the download cache, offline mode etc and + is meant to be reused by recipes. + + - Used the download API to allow caching of base configurations (specified by + the buildout section's 'extends' option). + + 1.3.1 (2009-08-12) + ================== + + - Bug fixed: extras were ignored in some cases when versions were specified. + + 1.3.0 (2009-06-22) + ================== + + - Better Windows compatibility in test infrastructure. + + - Now the bootstrap.py has an optional --version argument, + that can be used to force zc.buildout version to use. + + - ``zc.buildout.testing.buildoutSetUp`` installs a new handler in the + python root logging facility. This handler is now removed during + tear down as it might disturb other packages reusing buildout's + testing infrastructure. + + - fixed usage of 'relative_paths' keyword parameter on Windows + + - Added an unload entry point for extensions. + + - Fixed bug: when the relative paths option was used, relative paths + could be inserted into sys.path if a relative path was used to run + the generated script. + + 1.2.1 (2009-03-18) + ================== + + - Refactored generation of relative egg paths to generate simpler code. + + 1.2.0 (2009-03-17) + ================== + + - Added a relative_paths option to zc.buildout.easy_install.script to + generate egg paths relative to the script they're used in. + + 1.1.2 (2009-03-16) + ================== + + - Added Python 2.6 support. Removed Python 2.3 support. + + - Fixed remaining deprecation warnings under Python 2.6, both when running + our tests and when using the package. + + - Switched from using os.popen* to subprocess.Popen, to avoid a deprecation + warning in Python 2.6. See: + + http://docs.python.org/library/subprocess.html#replacing-os-popen-os-popen2-os-popen3 + + - Made sure the 'redo_pyc' function and the doctest checkers work with Python + executable paths containing spaces. + + - Expand shell patterns when processing the list of paths in `develop`, e.g:: + + [buildout] + develop = ./local-checkouts/* + + - Conditionally import and use hashlib.md5 when it's available instead + of md5 module, which is deprecated in Python 2.6. + + - Added Jython support for bootstrap, development bootstrap + and zc.buildout support on Jython + + - Fixed a bug that would cause buildout to break while computing a + directory hash if it found a broken symlink (Launchpad #250573) + + 1.1.1 (2008-07-28) + ================== + + - Fixed a bug that caused buildouts to fail when variable + substitutions are used to name standard directories, as in:: + + [buildout] + eggs-directory = ${buildout:directory}/develop-eggs + + 1.1.0 (2008-07-19) + ================== + + - Added a buildout-level unzip option tp change the default policy for + unzipping zip-safe eggs. + + - Tracebacks are now printed for internal errors (as opposed to user + errors) even without the -D option. + + - pyc and pyo files are regenerated for installed eggs so that the + stored path in code objects matches the the install location. + + 1.0.6 (2008-06-13) + ================== + + - Manually reverted the changeset for the fix for + https://bugs.launchpad.net/zc.buildout/+bug/239212 to verify thet the test + actually fails with the changeset: + http://svn.zope.org/zc.buildout/trunk/src/zc/buildout/buildout.py?rev=87309&r1=87277&r2=87309 + Thanks tarek for pointing this out. (seletz) + + - fixed the test for the += -= syntax in buildout.txt as the test + was actually wronng. The original implementation did a split/join + on whitespace, and later on that was corrected to respect the original + EOL setting, the test was not updated, though. (seletz) + + - added a test to verify against https://bugs.launchpad.net/zc.buildout/+bug/239212 + in allowhosts.txt (seletz) + + - further fixes for """AttributeError: Buildout instance has no + attribute '_logger'""" by providing reasonable defaults + within the Buildout constructor (related to the new 'allow-hosts' option) + (patch by Gottfried Ganssauge) (ajung) + + + 1.0.5 (2008-06-10) + ================== + + - Fixed wrong split when using the += and -= syntax (mustapha) + + 1.0.4 (2008-06-10) + ================== + + - Added the `allow-hosts` option (tarek) + + - Quote the 'executable' argument when trying to detect the python + version using popen4. (sidnei) + + - Quote the 'spec' argument, as in the case of installing an egg from + the buildout-cache, if the filename contains spaces it would fail (sidnei) + + - Extended configuration syntax to allow -= and += operators (malthe, mustapha). + + 1.0.3 (2008-06-01) + ================== + + - fix for """AttributeError: Buildout instance has no attribute '_logger'""" + by providing reasonable defaults within the Buildout constructor. + (patch by Gottfried Ganssauge) (ajung) + + 1.0.2 (2008-05-13) + ================== + + - More fixes for Windows. A quoted sh-bang is now used on Windows to make the + .exe files work with a Python executable in 'program files'. + + - Added "-t <timeout_in_seconds>" option for specifying the socket timeout. + (ajung) + + 1.0.1 (2008-04-02) + ================== + + - Made easy_install.py's _get_version accept non-final releases of Python, + like 2.4.4c0. (hannosch) + + - Applied various patches for Windows (patch by Gottfried Ganssauge). (ajung) + + - Applied patch fixing rmtree issues on Windows (patch by + Gottfried Ganssauge). (ajung) + + 1.0.0 (2008-01-13) + ================== + + - Added a French translation of the buildout tutorial. + + 1.0.0b31 (2007-11-01) + ===================== + + Feature Changes + --------------- + + - Added a configuration option that allows buildouts to ignore + dependency_links metadata specified in setup. By default + dependency_links in setup are used in addition to buildout specified + find-links. This can make it hard to control where eggs come + from. Here's how to tell buildout to ignore URLs in + dependency_links:: + + [buildout] + use-dependency-links = false + + By default use-dependency-links is true, which matches the behavior + of previous versions of buildout. + + - Added a configuration option that causes buildout to error if a + version is picked. This is a nice safety belt when fixing all + versions is intended, especially when creating releases. + + Bugs Fixed + ---------- + + - 151820: Develop failed if the setup.py script imported modules in + the distribution directory. + + - Verbose logging of the develop command was omitting detailed + output. + + - The setup command wasn't documented. + + - The setup command failed if run in a directory without specifying a + configuration file. + + - The setup command raised a stupid exception if run without arguments. + + - When using a local find links or index, distributions weren't copied + to the download cache. + + - When installing from source releases, a version specification (via a + buildout versions section) for setuptools was ignored when deciding + which setuptools to use to build an egg from the source release. + + 1.0.0b30 (2007-08-20) + ===================== + + Feature Changes + --------------- + + - Changed the default policy back to what it was to avoid breakage in + existing buildouts. Use:: + + [buildout] + prefer-final = true + + to get the new policy. The new policy will go into effect in + buildout 2. + + 1.0.0b29 (2007-08-20) + ===================== + + Feature Changes + --------------- + + - Now, final distributions are prefered over non-final versions. If + both final and non-final versions satisfy a requirement, then the + final version will be used even if it is older. The normal way to + override this for specific packages is to specifically require a + non-final version, either specifically or via a lower bound. + + - There is a buildout prefer-final version that can be used with a + value of "false":: + + prefer-final = false + + To prefer newer versions, regardless of whether or not they are + final, buildout-wide. + + - The new simple Python index, http://cheeseshop.python.org/simple, is + used as the default index. This will provide better performance + than the human package index interface, + http://pypi.python.org/pypi. More importantly, it lists hidden + distributions, so buildouts with fixed distribution versions will be + able to find old distributions even if the distributions have been + hidden in the human PyPI interface. + + Bugs Fixed + ---------- + + - 126441: Look for default.cfg in the right place on Windows. + + 1.0.0b28 (2007-07-05) + ===================== + + Bugs Fixed + ---------- + + - When requiring a specific version, buildout looked for new versions + even if that single version was already installed. + + 1.0.0b27 (2007-06-20) + ===================== + + Bugs Fixed + ---------- + + - Scripts were generated incorrectly on Windows. This included the + buildout script itself, making buildout completely unusable. + + 1.0.0b26 (2007-06-19) + ===================== + + Feature Changes + --------------- + + - Thanks to recent fixes in setuptools, I was able to change buildout + to use find-link and index information when searching extensions. + + Sadly, this work, especially the timing, was motivated my the need + to use alternate indexes due to performance problems in the cheese + shop (http://www.python.org/pypi/). I really home we can address + these performance problems soon. + + 1.0.0b25 (2007-05-31) + ===================== + + Feature Changes + --------------- + + - buildout now changes to the buildout directory before running recipe + install and update methods. + + - Added a new init command for creating a new buildout. This creates + an empty configuration file and then bootstraps. + + - Except when using the new init command, it is now an error to run + buildout without a configuration file. + + - In verbose mode, when adding distributions to fulful requirements of + already-added distributions, we now show why the new distributions + are being added. + + - Changed the logging format to exclude the logger name for the + zc.buildout logger. This reduces noise in the output. + + - Clean up lots of messages, adding missing periods and adding quotes around + requirement strings and file paths. + + Bugs Fixed + ---------- + + - 114614: Buildouts could take a very long time if there were + dependency problems in large sets of pathologically interdependent + packages. + + - 59270: Buggy recipes can cause failures in later recipes via chdir + + - 61890: file:// urls don't seem to work in find-links + + setuptools requires that file urls that point to directories must + end in a "/". Added a workaround. + + - 75607: buildout should not run if it creates an empty buildout.cfg + + 1.0.0b24 (2007-05-09) + ===================== + + Feature Changes + --------------- + + - Improved error reporting by showing which packages require other + packages that can't be found or that cause version conflicts. + + - Added an API for use by recipe writers to clean up created files + when recipe errors occur. + + - Log installed scripts. + + Bugs Fixed + ---------- + + - 92891: bootstrap crashes with recipe option in buildout section. + + - 113085: Buildout exited with a zero exist status when internal errors + occurred. + + + 1.0.0b23 (2007-03-19) + ===================== + + Feature Changes + --------------- + + - Added support for download caches. A buildout can specify a cache + for distribution downloads. The cache can be shared among buildouts + to reduce network access and to support creating source + distributions for applications allowing install without network + access. + + - Log scripts created, as suggested in: + https://bugs.launchpad.net/zc.buildout/+bug/71353 + + Bugs Fixed + ---------- + + - It wasn't possible to give options on the command line for sections + not defined in a configuration file. + + 1.0.0b22 (2007-03-15) + ===================== + + Feature Changes + --------------- + + - Improved error reporting and debugging support: + + - Added "logical tracebacks" that show functionally what the buildout + was doing when an error occurs. Don't show a Python traceback + unless the -D option is used. + + - Added a -D option that causes the buildout to print a traceback and + start the pdb post-mortem debugger when an error occurs. + + - Warnings are printed for unused options in the buildout section and + installed-part sections. This should make it easier to catch option + misspellings. + + - Changed the way the installed database (.installed.cfg) is handled + to avoid database corruption when a user breaks out of a buildout + with control-c. + + - Don't save an installed database if there are no installed parts or + develop egg links. + + 1.0.0b21 (2007-03-06) + ===================== + + Feature Changes + --------------- + + - Added support for repeatable buildouts by allowing egg versions to + be specified in a versions section. + + - The easy_install module install and build functions now accept a + versions argument that supplied to mapping from project name to + version numbers. This can be used to fix version numbers for + required distributions and their depenencies. + + When a version isn't fixed, using either a versions option or using + a fixed version number in a requirement, then a debug log message is + emitted indicating the version picked. This is useful for setting + versions options. + + A default_versions function can be used to set a default value for + this option. + + - Adjusted the output for verbosity levels. Using a single -v option + no longer causes voluminous setuptools output. Uisng -vv and -vvv + now triggers extra setuptools output. + + - Added a remove testing helper function that removes files or directories. + + 1.0.0b20 (2007-02-08) + ===================== + + Feature Changes + --------------- + + - Added a buildout newest option, to control whether the newest + distributions should be sought to meet requirements. This might + also provide a hint to recipes that don't deal with + distributions. For example, a recipe that manages subversion + checkouts might not update a checkout if newest is set to "false". + + - Added a *newest* keyword parameter to the + zc.buildout.easy_install.install and zc.buildout.easy_install.build + functions to control whether the newest distributions that meed + given requirements should be sought. If a false value is provided + for this parameter and already installed eggs meet the given + requirements, then no attempt will be made to search for newer + distributions. + + - The recipe-testing support setUp function now adds the name + *buildout* to the test namespace with a value that is the path to + the buildout script in the sample buildout. This allows tests to + use + + >>> print system(buildout), + + rather than: + + >>> print system(join('bin', 'buildout')), + + + Bugs Fixed + ---------- + + - Paths returned from update methods replaced lists of installed files + rather than augmenting them. + + 1.0.0b19 (2007-01-24) + ===================== + + Bugs Fixed + ---------- + + - Explicitly specifying a Python executable failed if the output of + running Python with the -V option included a 2-digit (rather than a + 3-digit) version number. + + 1.0.0b18 (2007-01-22) + ===================== + + Feature Changes + --------------- + + - Added documentation for some previously undocumented features of the + easy_install APIs. + + - By popular demand, added a -o command-line option that is a short + hand for the assignment buildout:offline=true. + + Bugs Fixed + ---------- + + - When deciding whether recipe develop eggs had changed, buildout + incorrectly considered files in .svn and CVS directories. + + 1.0.0b17 (2006-12-07) + ===================== + + Feature Changes + --------------- + + - Configuration files can now be loaded from URLs. + + Bugs Fixed + ---------- + + - https://bugs.launchpad.net/products/zc.buildout/+bug/71246 + + Buildout extensions installed as eggs couldn't be loaded in offline + mode. + + + 1.0.0b16 (2006-12-07) + ===================== + + Feature Changes + --------------- + + - A new command-line argument, -U, suppresses reading user defaults. + + - You can now suppress use of an installed-part database + (e.g. .installed.cfg) by sprifying an empty value for the buildout + installed option. + + Bugs Fixed + ---------- + + - When the install command is used with a list of parts, only + those parts are supposed to be installed, but the buildout was also + building parts that those parts depended on. + + 1.0.0b15 (2006-12-06) + ===================== + + Bugs Fixed + ---------- + + - Uninstall recipes weren't loaded correctly in cases where + no parts in the (new) configuration used the recipe egg. + + 1.0.0b14 (2006-12-05) + ===================== + + Feature Changes + --------------- + + - Added uninstall recipes for dealing with complex uninstallation + scenarios. + + Bugs Fixed + ---------- + + - Automatic upgrades weren't performed on Windows due to a bug that + caused buildout to incorrectly determine that it wasn't running + locally in a buildout. + + - Fixed some spurious test failures on Windows. + + 1.0.0b13 (2006-12-04) + ===================== + + Feature Changes + --------------- + + - Variable substitutions now reflect option data written by recipes. + + - A part referenced by a part in a parts list is now added to the parts + list before the referencing part. This means that you can omit + parts from the parts list if they are referenced by other parts. + + - Added a develop function to the easy_install module to aid in + creating develop eggs with custom build_ext options. + + - The build and develop functions in the easy_install module now + return the path of the egg or egg link created. + + - Removed the limitation that parts named in the install command can + only name configured parts. + + - Removed support ConfigParser-style variable substitutions + (e.g. %(foo)s). Only the string-template style of variable + (e.g. ${section:option}) substitutions will be supported. + Supporting both violates "there's only one way to do it". + + - Deprecated the buildout-section extendedBy option. + + Bugs Fixed + ---------- + + - We treat setuptools as a dependency of any distribution that + (declares that it) uses namespace packages, whether it declares + setuptools as a dependency or not. This wasn't working for eggs + intalled by virtue of being dependencies. + + + 1.0.0b12 (2006-10-24) + ===================== + + Feature Changes + --------------- + + - Added an initialization argument to the + zc.buildout.easy_install.scripts function to include initialization + code in generated scripts. + + 1.0.0b11 (2006-10-24) + ===================== + + Bugs Fixed + ---------- + + `67737 <https://launchpad.net/products/zc.buildout/+bug/67737>`_ + Verbose and quite output options caused errors when the + develop buildout option was used to create develop eggs. + + `67871 <https://launchpad.net/products/zc.buildout/+bug/67871>`_ + Installation failed if the source was a (local) unzipped + egg. + + `67873 <https://launchpad.net/products/zc.buildout/+bug/67873>`_ + There was an error in producing an error message when part names + passed to the install command weren't included in the + configuration. + + 1.0.0b10 (2006-10-16) + ===================== + + Feature Changes + --------------- + + - Renamed the runsetup command to setup. (The old name still works.) + + - Added a recipe update method. Now install is only called when a part + is installed for the first time, or after an uninstall. Otherwise, + update is called. For backward compatibility, recipes that don't + define update methiods are still supported. + + - If a distribution defines namespace packages but fails to declare + setuptools as one of its dependencies, we now treat setuptools as an + implicit dependency. We generate a warning if the distribution + is a develop egg. + + - You can now create develop eggs for setup scripts that don't use setuptools. + + Bugs Fixed + ---------- + + - Egg links weren't removed when corresponding entries were removed + from develop sections. + + - Running a non-local buildout command (one not installed in the + buildout) ket to a hang if there were new versions of zc.buildout or + setuptools were available. Now we issue a warning and don't + upgrade. + + - When installing zip-safe eggs from local directories, the eggs were + moved, rather than copied, removing them from the source directory. + + 1.0.0b9 (2006-10-02) + ==================== + + Bugs Fixed + ---------- + + Non-zip-safe eggs were not unzipped when they were installed. + + 1.0.0b8 (2006-10-01) + ==================== + + Bugs Fixed + ---------- + + - Installing source distributions failed when using alternate Python + versions (depending on the versions of Python used.) + + - Installing eggs wasn't handled as efficiently as possible due to a + bug in egg URL parsing. + + - Fixed a bug in runsetup that caused setup scripts that introspected + __file__ to fail. + + 1.0.0b7 + ======= + + Added a documented testing framework for use by recipes. Refactored + the buildout tests to use it. + + Added a runsetup command run a setup script. This is handy if, like + me, you don't install setuptools in your system Python. + + 1.0.0b6 + ======= + + Fixed https://launchpad.net/products/zc.buildout/+bug/60582 + Use of extension options caused bootstrapping to fail if the eggs + directory didn't already exist. We no longer use extensions for + bootstrapping. There really isn't any reason to anyway. + + + 1.0.0b5 + ======= + + Refactored to do more work in buildout and less work in easy_install. + This makes things go a little faster, makes errors a little easier to + handle, and allows extensions (like the sftp extension) to influence + more of the process. This was done to fix a problem in using the sftp + support. + + 1.0.0b4 + ======= + + - Added an **experimental** extensions mechanism, mainly to support + adding sftp support to buildouts that need it. + + - Fixed buildout self-updating on Windows. + + 1.0.0b3 + ======= + + - Added a help option (-h, --help) + + - Increased the default level of verbosity. + + - Buildouts now automatically update themselves to new versions of + zc.buildout and setuptools. + + - Added Windows support. + + - Added a recipe API for generating user errors. + + - No-longer generate a py_zc.buildout script. + + - Fixed some bugs in variable substitutions. + + The characters "-", "." and " ", weren't allowed in section or + option names. + + Substitutions with invalid names were ignored, which caused + missleading failures downstream. + + - Improved error handling. No longer show tracebacks for user errors. + + - Now require a recipe option (and therefore a section) for every part. + + - Expanded the easy_install module API to: + + - Allow extra paths to be provided + + - Specify explicit entry points + + - Specify entry-point arguments + + 1.0.0b2 + ======= + + Added support for specifying some build_ext options when installing eggs + from source distributions. + + 1.0.0b1 + ======= + + - Changed the bootstrapping code to only install setuptools and + zc.buildout. The bootstrap code no-longer runs the buildout itself. + This was to fix a bug that caused parts to be recreated + unnecessarily because the recipe signature in the initial buildout + reflected temporary locations for setuptools and zc.buildout. + + - Now create a minimal setup.py if it doesn't exist and issue a + warning that it is being created. + + - Fixed bug in saving installed configuration data. %'s and extra + spaces weren't quoted. + + 1.0.0a1 + ======= + + Initial public version + + Download + ********************** + +Keywords: development build +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Topic :: Software Development :: Build Tools +Classifier: Topic :: Software Development :: Libraries :: Python Modules |