diff -r 5ff1fc726848 -r c6bca38c1cbf eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eggs/zc.buildout-1.5.2-py2.6.egg/zc/buildout/easy_install.txt Sat Jan 08 11:20:57 2011 +0530 @@ -0,0 +1,1985 @@ +Python API for egg and script installation +========================================== + +The easy_install module provides some functions to provide support for +egg and script installation. It provides functionality at the python +level that is similar to easy_install, with a few exceptions: + +- By default, we look for new packages *and* the packages that + they depend on. This is somewhat like (and uses) the --upgrade + option of easy_install, except that we also upgrade required + packages. + +- If the highest-revision package satisfying a specification is + already present, then we don't try to get another one. This saves a + lot of search time in the common case that packages are pegged to + specific versions. + +- If there is a develop egg that satisfies a requirement, we don't + look for additional distributions. We always give preference to + develop eggs. + +- Distutils options for building extensions can be passed. + +Distribution installation +------------------------- + +The easy_install module provides a function, install, for installing one +or more packages and their dependencies. The install function takes 2 +positional arguments: + +- An iterable of setuptools requirement strings for the distributions + to be installed, and + +- A destination directory to install to and to satisfy requirements + from. The destination directory can be None, in which case, no new + distributions are downloaded and there will be an error if the + needed distributions can't be found among those already installed. + +It supports a number of optional keyword arguments: + +links + A sequence of URLs, file names, or directories to look for + links to distributions. + +index + The URL of an index server, or almost any other valid URL. :) + + If not specified, the Python Package Index, + http://pypi.python.org/simple/, is used. You can specify an + alternate index with this option. If you use the links option and + if the links point to the needed distributions, then the index can + be anything and will be largely ignored. In the examples, here, + we'll just point to an empty directory on our link server. This + will make our examples run a little bit faster. + +executable + A path to a Python executable. Distributions will be installed + using this executable and will be for the matching Python version. + +path + A list of additional directories to search for locally-installed + distributions. + +always_unzip + A flag indicating that newly-downloaded distributions should be + directories even if they could be installed as zip files. + +working_set + An existing working set to be augmented with additional + distributions, if necessary to satisfy requirements. This allows + you to call install multiple times, if necessary, to gather + multiple sets of requirements. + +newest + A boolean value indicating whether to search for new distributions + when already-installed distributions meet the requirement. When + this is true, the default, and when the destination directory is + not None, then the install function will search for the newest + distributions that satisfy the requirements. + +versions + A dictionary mapping project names to version numbers to be used + when selecting distributions. This can be used to specify a set of + distribution versions independent of other requirements. + +use_dependency_links + A flag indicating whether to search for dependencies using the + setup dependency_links metadata or not. If true, links are searched + for using dependency_links in preference to other + locations. Defaults to true. + +include_site_packages + A flag indicating whether Python's non-standard-library packages should + be available for finding dependencies. Defaults to true. + + Paths outside of Python's standard library--or more precisely, those that + are not included when Python is started with the -S argument--are loosely + referred to as "site-packages" here. + +relative_paths + Adjust egg paths so they are relative to the script path. This + allows scripts to work when scripts and eggs are moved, as long as + they are both moved in the same way. + +The install method returns a working set containing the distributions +needed to meet the given requirements. + +We have a link server that has a number of eggs: + + >>> print get(link_server), + + bigdemo-0.1-py2.4.egg
+ demo-0.1-py2.4.egg
+ demo-0.2-py2.4.egg
+ demo-0.3-py2.4.egg
+ demo-0.4c1-py2.4.egg
+ demoneeded-1.0.zip
+ demoneeded-1.1.zip
+ demoneeded-1.2c1.zip
+ extdemo-1.4.zip
+ index/
+ other-1.0-py2.4.egg
+ + +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 + + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + ] + + import eggrecipedemo + + 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 + + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + ] + + import eggrecipedemo + + 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 + + import sys + + sys.path[0:0] = [ + '/sample-install/demo-0.3-pyN.N.egg', + '/sample-install/demoneeded-1.1-pyN.N.egg', + ] + + _interactive = True + if len(sys.argv) > 1: + _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:') + _interactive = False + for (_opt, _val) in _options: + if _opt == '-i': + _interactive = True + elif _opt == '-c': + exec _val + elif _opt == '-m': + sys.argv[1:] = _args + _args = [] + __import__("runpy").run_module( + _val, {}, "__main__", alter_sys=True) + + if _args: + sys.argv[:] = _args + __file__ = _args[0] + del _options, _args + execfile(__file__) + + if _interactive: + del _interactive + __import__("code").interact(banner="", local=globals()) + +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 + + import sys + sys.path[0:0] = [ + '/sample-install/demo-0.3-py2.4.egg', + '/sample-install/demoneeded-1.1-py2.4.egg', + '/foo', + ] + + import eggrecipedemo + + 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', + ] + + import eggrecipedemo + + 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', + ] + + import os + os.chdir("foo") + + import eggrecipedemo + + 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 + + import os + + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + + import sys + sys.path[0:0] = [ + join(base, 'eggs/demo-0.3-pyN.N.egg'), + join(base, 'eggs/demoneeded-1.1-pyN.N.egg'), + '/ba', + join(base, 'bar'), + ] + + import eggrecipedemo + + 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 + + import os + + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + + import sys + + sys.path[0:0] = [ + join(base, 'eggs/demo-0.3-pyN.N.egg'), + join(base, 'eggs/demoneeded-1.1-pyN.N.egg'), + '/ba', + join(base, 'bar'), + ] + + _interactive = True + if len(sys.argv) > 1: + _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:') + _interactive = False + for (_opt, _val) in _options: + if _opt == '-i': + _interactive = True + elif _opt == '-c': + exec _val + elif _opt == '-m': + sys.argv[1:] = _args + _args = [] + __import__("runpy").run_module( + _val, {}, "__main__", alter_sys=True) + + if _args: + sys.argv[:] = _args + __file__ = _args[0] + del _options, _args + execfile(__file__) + + if _interactive: + del _interactive + __import__("code").interact(banner="", local=globals()) + +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 + + 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. + + 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 + + def original_addsitepackages(known_paths):... + +Here are some examples of the interpreter in use. + + >>> print call_py(interpreter_path, "print 16+26") + 42 + + >>> 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'] + + >>> 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 + + +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 + + join = os.path.join + base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) + base = os.path.dirname(base) + + 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. + + 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'] + + +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. + + 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'] + + +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. + + 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 + + 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', + ...] + + +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. + + 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 + + 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. + + 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 + + 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 + + import sys + sys.path[0:0] = [ + '/interpreter/parts/interpreter', + ] + + + 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 eggrecipedemo + + 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 + + +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', + ] + + 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") + + import eggrecipedemo + + 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 + #include + + 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), + + bigdemo-0.1-py2.4.egg
+ demo-0.1-py2.4.egg
+ demo-0.2-py2.4.egg
+ demo-0.3-py2.4.egg
+ demo-0.4c1-py2.4.egg
+ demoneeded-1.0.zip
+ demoneeded-1.1.zip
+ demoneeded-1.2c1.zip
+ extdemo-1.4.zip
+ extdemo-1.5.zip
+ index/
+ other-1.0-py2.4.egg
+ + +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