Merge lp://staging/~gary/zc.buildout/support-system-python into lp://staging/~gary/zc.buildout/oldtrunk

Proposed by Gary Poster
Status: Needs review
Proposed branch: lp://staging/~gary/zc.buildout/support-system-python
Merge into: lp://staging/~gary/zc.buildout/oldtrunk
Diff against target: None lines
To merge this branch: bzr merge lp://staging/~gary/zc.buildout/support-system-python
Reviewer Review Type Date Requested Status
Francis J. Lacoste (community) Approve
Jim Fulton Pending
Review via email: mp+8614@code.staging.launchpad.net
To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

I have made a number of changes to zc.buildout in the gary-support-system-python branch in zope.org's svn repository. They all support Launchpad or Landscape's use of Buildout with a system Python, directly or indirectly.

The following is a list of changes taken from my CHANGES.txt additions:

- Support and bugfixes for using a system Python with buildout.

  In all of these descriptions, "site-packages" is an imprecise term for a
  precise definition: the packages that are added by the Python's site.py.
  Practically, this is the difference of the set of paths normally used by
  Python minus those used when Python is started with the -S flag.

  * A new boolean option, 'include-site-packages', includes or excludes site
    packages from finding requirements, and from generated scripts.
    zc.buildout's own buildout.cfg dogfoods this option.

  * Script generation pushes dependency paths that are in site-packages to
    the end of the dependency paths in sys.path (but, as before, these are
    still before extra paths, the standard library, and the rest of the
    site-package paths).

  * Fix an error 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.

- The generated interpreter scripts now have a few more similarities to a
  real Python interpreter.

  * __file__ is set correctly for executed files.

  * -c incorporates all subsequent arguments as part of the command.

  * -V returns the version.

  * -S causes the script to not modify the standard path (for tests)

- 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).

I failed to implement one feature I had wanted: a way to cherrypick parts of a system Python's site-packages. I proposed an approach, but it ugly, fragile, and risky; and Jim proposed an approach, but it does not work for a modern Ubuntu.

Review requests for small changes to zc.recipe.testrunner and z3c.recipe.filetemplate, based on some of these changes, are also forthcoming.

495. By Gary Poster

merge fro svn branch: corrects bootstrap for Windows, hopefully.

Revision history for this message
Gary Poster (gary) wrote :

I effectively reverted a change I had made to bootstrap.py that tried to get rid of the is_jython code. I had thought we could rely on subprocess generally, if it exists, but I'm told (by Sidnei, thanks to him) that my version of bootstrap failed on Windows, but changing it back to use spawnle worked. Therefore, I've changed the branch back to use spawnle unless is_jython. I still suspect that this could be done more elegantly, but solving that is not a goal I want for this branch.

The following is the incremental diff for this change.

Thanks.

Gary

=== modified file 'bootstrap/bootstrap.py'
--- bootstrap/bootstrap.py 2009-07-09 23:55:19 +0000
+++ bootstrap/bootstrap.py 2009-07-13 14:03:35 +0000
@@ -139,16 +139,17 @@
     os.environ,
     PYTHONPATH=ws.find(pkg_resources.Requirement.parse('setuptools')).location)

-try:
+is_jython = sys.platform.startswith('java')
+if is_jython:
     import subprocess
-except ImportError:
+ exitcode = subprocess.Popen(cmd, env=env).wait()
+else: # Windows needs this, apparently; otherwise we would prefer subprocess
     exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
-else:
- # Jython can use subprocess but not spawn. We prefer it generally.
- exitcode = subprocess.Popen(cmd, env=env).wait()
 if exitcode != 0:
- # we shouldn't need an error message because a failure
- # should have generated a visible traceback in the subprocess.
+ sys.flush()
+ print ("An error occured when trying to install zc.buildout. "
+ "Look above this message for any errors that "
+ "were output by easy_install.")
     sys.exit(exitcode)

 ws.add_entry(configuration['--eggs'])

=== modified file 'src/zc/buildout/easy_install.py'
--- src/zc/buildout/easy_install.py 2009-07-11 15:49:15 +0000
+++ src/zc/buildout/easy_install.py 2009-07-13 14:03:35 +0000
@@ -419,8 +419,8 @@

             if exit_code:
                 logger.error(
- "An error occured when trying to install %s."
- "Look above this message for any errors that"
+ "An error occured when trying to install %s. "
+ "Look above this message for any errors that "
                     "were output by easy_install.",
                     dist)

Revision history for this message
Francis J. Lacoste (flacoste) wrote :
Download full text (10.5 KiB)

On July 11, 2009, Gary Poster wrote:
> Gary Poster has proposed merging lp:~gary/zc.buildout/support-system-python
> into lp:~gary/zc.buildout/trunk.
>
> Requested reviews:
> Francis J. Lacoste (flacoste)
>
> I have made a number of changes to zc.buildout in the
> gary-support-system-python branch in zope.org's svn repository. They all
> support Launchpad or Landscape's use of Buildout with a system Python,
> directly or indirectly.
>

Wow, this was a lot of changes!

From my cursory understanding, these changes looks good. I have a bunch of
questions / suggestions. So from the Launchpad point of view, it seems to
satisfy our use case.

Thanks a lot, this will make zc.buildout a lot more robust for our use-case.

> +# We have to manually parse our options rather than using one of the stdlib
> +# tools because we want to pass the ones we don't recognize along to
> +# zc.buildout.buildout.main.

Wouldn't it have been possible to use a '--' marker to delimit the bootstrap
options from the buildout ones:

  ./bootstrap.py --eggs eggs -- ZC.buildout options follows.

That's a common idiom I think.

> +match_equals = re.compile(r'(%s)=(\S*)' % ('|'.join(configuration),)).match

By using this regex, you preclude any paths with spaces in them. I know many
people avoid them because a lot of UNIX programs break on them, but I'm sure
you'll encounter a lot of Mac OS X users that have spaces in some of their
paths :-)

> +# defaults
> +tmpeggs = None

The above comment doesn't make sense to me.

> +if (configuration['--download-base'] and
> + not configuration['--download-base'].endswith('/')):
> + # download base needs a trailing slash to make the world happy
> + configuration['--download-base'] += '/'
> +

I know this is something specific to our coding conventions, but comments are
sentences needing a capital and a period ;-)

> +if configuration['--version']:
> + configuration['--version'] = '==' + configuration['--version']

I don't understand the reason for that code? I'm guessing it's because you are
going to pass it over to setuptools? Maybe add a comment?

> === modified file 'src/zc/buildout/buildout.py'
> - prefer_final = options.get('prefer-final', 'false')
> + prefer_final = options.setdefault('prefer-final', 'false')

Is there any reason for the switch to setdefault? If it's because you are
reusing options in other places, then I guess that's fine. Otherwise, I know
some people find using setdefault() with it's return value a little confusing
to read.

> + options = self['buildout']
> +
> + # Get a base working set for our distributions that corresponds to
the
> + # stated desires in the configuration.
> + distributions = ['setuptools', 'zc.buildout']

You have trailing white space on that previous line.

> === modified file 'src/zc/buildout/buildout.txt'

I like the merging of the sections into the general option list. It might make
them harder to find from the table of contents though.

> === modified file 'src/zc/buildout/easy_install.py'
> +
> +def _get_system_packages(executable):
> + """return a pair of the standard lib and site packages for the
executable.
...

Revision history for this message
Gary Poster (gary) wrote :
Download full text (27.5 KiB)

On Jul 13, 2009, at 5:06 PM, Francis J. Lacoste wrote:

> On July 11, 2009, Gary Poster wrote:
>> Gary Poster has proposed merging lp:~gary/zc.buildout/support-
>> system-python
>> into lp:~gary/zc.buildout/trunk.
>>
>> Requested reviews:
>> Francis J. Lacoste (flacoste)
>>
>> I have made a number of changes to zc.buildout in the
>> gary-support-system-python branch in zope.org's svn repository.
>> They all
>> support Launchpad or Landscape's use of Buildout with a system
>> Python,
>> directly or indirectly.
>>
>
> Wow, this was a lot of changes!

Yeah, sorry.

>> From my cursory understanding, these changes looks good. I have a
>> bunch of
> questions / suggestions. So from the Launchpad point of view, it
> seems to
> satisfy our use case.
>
> Thanks a lot, this will make zc.buildout a lot more robust for our
> use-case.
>
>> +# We have to manually parse our options rather than using one of
>> the stdlib
>> +# tools because we want to pass the ones we don't recognize along to
>> +# zc.buildout.buildout.main.
>
> Wouldn't it have been possible to use a '--' marker to delimit the
> bootstrap
> options from the buildout ones:
>
> ./bootstrap.py --eggs eggs -- ZC.buildout options follows.
>
> That's a common idiom I think.

It is, but it would break backwards compatibility of the usage of this
script. This was a design decision from before I came a long. If Jim
were OK with breaking backwards compatibility I'd be happy to go this
way.

>> +match_equals = re.compile(r'(%s)=(\S*)' %
>> ('|'.join(configuration),)).match
>
> By using this regex, you preclude any paths with spaces in them. I
> know many
> people avoid them because a lot of UNIX programs break on them, but
> I'm sure
> you'll encounter a lot of Mac OS X users that have spaces in some of
> their
> paths :-)

Yes, that was silly, thanks. (.*) should be sufficient. See
incremental diff.

>
>> +# defaults
>> +tmpeggs = None
>
> The above comment doesn't make sense to me.

Agreed. It was a bit of a legacy comment--at one point in development
I had set a lot of default values there. Now there is only one. I
removed "default" and added a more pertinent comment. See incremental
diff.

>
>> +if (configuration['--download-base'] and
>> + not configuration['--download-base'].endswith('/')):
>> + # download base needs a trailing slash to make the world happy
>> + configuration['--download-base'] += '/'
>> +
>
> I know this is something specific to our coding conventions, but
> comments are
> sentences needing a capital and a period ;-)

Ack, see diff.

>
>> +if configuration['--version']:
>> + configuration['--version'] = '==' + configuration['--version']
>
> I don't understand the reason for that code? I'm guessing it's
> because you are
> going to pass it over to setuptools? Maybe add a comment?

This is legacy code to some degree, but yes. I changed this approach
entirely to try to clarify, and added a comment.

> === modified file 'src/zc/buildout/buildout.py'
>> - prefer_final = options.get('prefer-final', 'false')
>> + prefer_final = options.setdefault('prefer-final', 'false')
>
> Is there any reason for th...

496. By Gary Poster

changes from flacosate review, merged from svn

Revision history for this message
Francis J. Lacoste (flacoste) wrote :
Download full text (4.2 KiB)

On July 13, 2009, Gary Poster wrote:
> On Jul 13, 2009, at 5:06 PM, Francis J. Lacoste wrote:
> > On July 11, 2009, Gary Poster wrote:
> >> Gary Poster has proposed merging lp:~gary/zc.buildout/support-
> >> system-python
> >> into lp:~gary/zc.buildout/trunk.
> >>
> >> Requested reviews:
> >> Francis J. Lacoste (flacoste)
> >>
> >> I have made a number of changes to zc.buildout in the
> >> gary-support-system-python branch in zope.org's svn repository.
> >> They all
> >> support Launchpad or Landscape's use of Buildout with a system
> >> Python,
> >> directly or indirectly.
> >
> > Wow, this was a lot of changes!
>
> Yeah, sorry.
>
> >> From my cursory understanding, these changes looks good. I have a
> >> bunch of
> >
> > questions / suggestions. So from the Launchpad point of view, it
> > seems to
> > satisfy our use case.
> >
> > Thanks a lot, this will make zc.buildout a lot more robust for our
> > use-case.
> >
> >> +# We have to manually parse our options rather than using one of
> >> the stdlib
> >> +# tools because we want to pass the ones we don't recognize along to
> >> +# zc.buildout.buildout.main.
> >
> > Wouldn't it have been possible to use a '--' marker to delimit the
> > bootstrap
> > options from the buildout ones:
> >
> > ./bootstrap.py --eggs eggs -- ZC.buildout options follows.
> >
> > That's a common idiom I think.
>
> It is, but it would break backwards compatibility of the usage of this
> script. This was a design decision from before I came a long. If Jim
> were OK with breaking backwards compatibility I'd be happy to go this
> way.
>

I see, it's fine like it is then.

> > === modified file 'src/zc/buildout/buildout.py'
> >
> >> - prefer_final = options.get('prefer-final', 'false')
> >> + prefer_final = options.setdefault('prefer-final', 'false')
> >
> > Is there any reason for the switch to setdefault? If it's because
> > you are
> > reusing options in other places, then I guess that's fine.
> > Otherwise, I know
> > some people find using setdefault() with it's return value a little
> > confusing
> > to read.
>
> It is because when buildout uses -vv, it is supposed to show you all
> configuration values, as shown in buildout.txt in the "Predefined
> buildout options" section. To appear in this output, they need to be
> in the options dict. Therefore, when appropriate, I switched
> from .get to .setdefault so that the options would get the default.
> It seemed like a reasonable, concise, and idiomatic way of
> accomplishing this. Otherwise, we'd need something like this.
>
> prefer_final = options.get('prefer-final', 'false')
> options['prefer-final'] = prefer_final
>
> I prefer the setdefault approach, but I'm happy to change.

No, setdefault is fine then.

> >
> >> + # one minus the set of the ones in ``python -S`` is the set of
> >> packages
> >> + # that are effectively site-packages.
> >> + def get_sys_path(clean=False):
> >> + cmd = [executable, "-c",
> >> + "import sys, os;"
> >> + "print repr([os.path.normpath(p) for p in
> >> sys.path])"]
> >> + if clean:
> >> + cmd.insert(1, '-S')
> >> + _proc = subprocess.Popen(
> >...

Read more...

review: Approve
497. By Gary Poster

merge from svn

498. By Gary Poster

merge from svn branch

Revision history for this message
Gary Poster (gary) wrote :

So, I have a, um, 1500+ line additional diff. This further breaks our review policies, which might mean we should think further about how to do these upstream-project reviews in the future. But meanwhile...

https://pastebin.canonical.com/20796/

It was developed to prepare some previous bugfixes for upstream review by adding tests, and to address and give diagnostic tools for bug 407408 ( https://bugs.launchpad.net/launchpad-foundations/+bug/407408 ).

This diff does the following:

- Add new option and tests: ``allowed-eggs-from-site-packages`` allows you
  to specify a glob-aware whitelist of project names that may come from
  site-packages. This defaults to '*', meaning there is no filtering.
- Add feature and test to let you see what eggs have been selected from
  site-packages: "Egg from site-packages: ..." in the output of
  ``bin/buildout -v``.
- Add new option and tests: ``include-site-packages-for-buildout`` allows
  you to control if site-packages are used for the bin/buildout script.
  This defaults to "false," which is a change in behavior! (Rationale is in
  docs.)
- Standardize on one of the several competing approaches for default options
  in the [buildout] section. This removes my previous use of setdefault,
  discussed with flacoste.
- Make generated script paths prettier by eliminating duplicate paths.
- Add comment about recipes using buildout options, not buildout attributes.
  Practice what I preach in zc.recipe.egg.
- Standardize on one approach for handling bool options in zc.recipe.egg. Not
  sure I like it but it is preexisting, and now it is consistent, and it
  behaves well in terms of letting buildout be configured before it looks too
  hard at it.
- Normalize tests.py function docstrings to begin at col 0
- Correct some typos

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On August 5, 2009, Gary Poster wrote:
> So, I have a, um, 1500+ line additional diff. This further breaks our
> review policies, which might mean we should think further about how to do
> these upstream-project reviews in the future. But meanwhile...
>
> https://pastebin.canonical.com/20796/

Hi Gary,

Thanks for fixing this. Hopefully, this is the last changes we need to support
our use case properly. A good way to split a branch like this is to start by
doing all the clean-ups you want to do first (option changes, reformmating,
etc) and then do your actual changes. Keep that in mind for another time.

I don't have much comments, so feel free to land this.

> === modified file 'CHANGES.txt'
> --- CHANGES.txt 2009-07-11 17:15:31 +0000
> +++ CHANGES.txt 2009-08-04 23:57:12 +0000
> @@ -13,13 +13,28 @@
>
> * A new boolean option, 'include-site-packages', includes or excludes
site
> packages from finding requirements, and from generated scripts.
> - zc.buildout's own buildout.cfg dogfoods this option.
> + zc.buildout's own buildout.cfg dogfoods this option. This defaults
> + to 'true', which is very similar to buildout's previous behavior.
> +
> + * A new boolean option, 'include-site-packages-for-buildout', does the
> + same thing but only for the bin/buildout script. This can be important
> + for getting recipes and their dependencies without conflicts. This
> + defaults to 'false', which is different from buildout's previous
behavior.
> +
> + * Another new option, 'allowed-eggs-from-site-packages', lets you specify
> + a whitelist of project names of eggs that are allowed to come from
> + your Python's site-packages. This lets you more tightly control your
use
> + of site-packages.
>

You should explain how this option interacts with include-site-packages. If
include-site-packages is False, does it overrides it? Or, is it only used if
include-site-packages is True?

I see that you explained this in the documentation, although you didn't
explain how it interacted with the buildout variant.

> === modified file 'src/zc/buildout/tests.py'
> +def allowed_eggs_from_site_packages_option():
> + """
> +As introduced in the previous test, the allowed-eggs-from-site-packages
option
> +allows you to specify a whitelist of project names that may be included
from
> +site-packages.
> +
> +This test shows the option being used in a buildout.
> +
> +The buildout defaults to a whitelist of ('*',), or any project name.
> +

Is it really necessary here to duplicate all the options you already tested
directly using the API?

--
Francis J. Lacoste
<email address hidden>

Unmerged revisions

498. By Gary Poster

merge from svn branch

497. By Gary Poster

merge from svn

496. By Gary Poster

changes from flacosate review, merged from svn

495. By Gary Poster

merge fro svn branch: corrects bootstrap for Windows, hopefully.

494. By Gary Poster

merge from trunk

493. By gary

make bootstrap produce prettier bin/buildout that honors more of the configuration settings.

492. By gary

Make buildout's cfg (for zc.buildout developers) set include_site_packages = false.

When I did that, I discovered that the testselectingpython.py test was pretty
fragile. I made it less so, though my fix makes the test setup conditionally
rely on the same code that the test itself is supposed to demonstrate... :-/

491. By gary

Make the interpreter script a bit more like a Python interpreter.

In this case, now I can use it with setup.py files that refer to their
__file__.

Also try to put in some diagnostics for when getting the site packages doesn't
work. This is hopefully only a convenience for developers of zc.buildout
itself.

490. By gary

try to explain how the two kinds of bootstrap options work a bit better

489. By gary

switch to version numbers I can actually use elsewhere

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CHANGES.txt'
2--- CHANGES.txt 2009-06-22 16:41:40 +0000
3+++ CHANGES.txt 2009-07-11 17:15:31 +0000
4@@ -1,6 +1,53 @@
5 Change History
6 **************
7
8+1.4.0 (unreleased)
9+==================
10+
11+- Support and bugfixes for using a system Python with buildout.
12+
13+ In all of these descriptions, "site-packages" is an imprecise term for a
14+ precise definition: the packages that are added by the Python's site.py.
15+ Practically, this is the difference of the set of paths normally used by
16+ Python minus those used when Python is started with the -S flag.
17+
18+ * A new boolean option, 'include-site-packages', includes or excludes site
19+ packages from finding requirements, and from generated scripts.
20+ zc.buildout's own buildout.cfg dogfoods this option.
21+
22+ * Script generation pushes dependency paths that are in site-packages to
23+ the end of the dependency paths in sys.path (but, as before, these are
24+ still before extra paths, the standard library, and the rest of the
25+ site-package paths).
26+
27+ * Fix an error when at least two dependencies were in a shared location like
28+ site-packages, and the first one met the "versions" setting. The first
29+ dependency would be added, but subsequent dependencies from the same
30+ location (e.g., site-packages) would use the version of the package found
31+ in the shared location, ignoring the version setting.
32+
33+- The generated interpreter scripts now have a few more similarities to a
34+ real Python interpreter.
35+
36+ * __file__ is set correctly for executed files.
37+
38+ * -c incorporates all subsequent arguments as part of the command.
39+
40+ * -V returns the version.
41+
42+ * -S causes the script to not modify the standard path (for tests)
43+
44+- Improve bootstrap.
45+
46+ * New options let you specify where to find ez_setup.py and where to find
47+ a download cache. These options can keep bootstrap from going over the
48+ network.
49+
50+ * Another new option lets you specify where to put generated eggs.
51+
52+ * The buildout script generated by bootstrap honors more of the settings
53+ in the designated configuration file (e.g., buildout.cfg).
54+
55 1.3.0 (2009-06-22)
56 ==================
57
58
59=== modified file 'bootstrap/bootstrap.py'
60--- bootstrap/bootstrap.py 2009-06-22 13:05:44 +0000
61+++ bootstrap/bootstrap.py 2009-07-09 23:55:19 +0000
62@@ -20,19 +20,97 @@
63 $Id$
64 """
65
66-import os, shutil, sys, tempfile, urllib2
67-
68-tmpeggs = tempfile.mkdtemp()
69-
70-is_jython = sys.platform.startswith('java')
71+import os, re, shutil, sys, tempfile, textwrap, urllib, urllib2
72+
73+# We have to manually parse our options rather than using one of the stdlib
74+# tools because we want to pass the ones we don't recognize along to
75+# zc.buildout.buildout.main.
76+
77+configuration = {
78+ '--ez_setup-source': 'http://peak.telecommunity.com/dist/ez_setup.py',
79+ '--version': '',
80+ '--download-base': None,
81+ '--eggs': None}
82+
83+helpstring = __doc__ + textwrap.dedent('''
84+ This script recognizes the following options itself. The first option it
85+ encounters that is not one of these will cause the script to stop parsing
86+ options and pass the rest on to buildout. Therefore, if you want to use
87+ any of the following options *and* buildout command-line options like
88+ -c, first use the following options, and then use the buildout options.
89+
90+ Options:
91+ --version=ZC_BUILDOUT_VERSION
92+ Specify a version number of the zc.buildout to use
93+ --ez_setup-source=URL_OR_FILE
94+ Specify a URL or file location for the ez_setup file.
95+ Defaults to
96+ %(--ez_setup-source)s
97+ --download-base=URL_OR_DIRECTORY
98+ Specify a URL or directory for downloading setuptools and
99+ zc.buildout. Defaults to PyPI.
100+ --eggs=DIRECTORY
101+ Specify a directory for storing eggs. Defaults to a temporary
102+ directory that is deleted when the bootstrap script completes.
103+
104+ By using --ez_setup-source and --download-base to point to local resources,
105+ you can keep this script from going over the network.
106+ ''' % configuration)
107+match_equals = re.compile(r'(%s)=(\S*)' % ('|'.join(configuration),)).match
108+args = sys.argv[1:]
109+if args == ['--help']:
110+ print helpstring
111+ sys.exit(0)
112+
113+# defaults
114+tmpeggs = None
115+
116+while args:
117+ val = args[0]
118+ if val in configuration:
119+ del args[0]
120+ if not args or args[0].startswith('-'):
121+ print "ERROR: %s requires an argument."
122+ print helpstring
123+ sys.exit(1)
124+ configuration[val] = args[0]
125+ else:
126+ match = match_equals(val)
127+ if match and match.group(1) in configuration:
128+ configuration[match.group(1)] = match.group(2)
129+ else:
130+ break
131+ del args[0]
132+
133+for name in ('--ez_setup-source', '--download-base'):
134+ val = configuration[name]
135+ if val is not None and '://' not in val: # we're being lazy.
136+ configuration[name] = 'file://%s' % (
137+ urllib.pathname2url(os.path.abspath(os.path.expanduser(val))),)
138+
139+if (configuration['--download-base'] and
140+ not configuration['--download-base'].endswith('/')):
141+ # download base needs a trailing slash to make the world happy
142+ configuration['--download-base'] += '/'
143+
144+if not configuration['--eggs']:
145+ configuration['--eggs'] = tmpeggs = tempfile.mkdtemp()
146+else:
147+ configuration['--eggs'] = os.path.abspath(
148+ os.path.expanduser(configuration['--eggs']))
149+
150+if configuration['--version']:
151+ configuration['--version'] = '==' + configuration['--version']
152
153 try:
154 import pkg_resources
155 except ImportError:
156 ez = {}
157- exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
158- ).read() in ez
159- ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
160+ exec urllib2.urlopen(configuration['--ez_setup-source']).read() in ez
161+ setuptools_args = dict(to_dir=configuration['--eggs'], download_delay=0)
162+ if configuration['--download-base']:
163+ setuptools_args['download_base'] = configuration['--download-base']
164+ ez['use_setuptools'](**setuptools_args)
165
166 import pkg_resources
167
168@@ -45,40 +123,38 @@
169 else:
170 def quote (c):
171 return c
172-
173-cmd = 'from setuptools.command.easy_install import main; main()'
174-ws = pkg_resources.working_set
175-
176-if len(sys.argv) > 2 and sys.argv[1] == '--version':
177- VERSION = '==%s' % sys.argv[2]
178- args = sys.argv[3:] + ['bootstrap']
179-else:
180- VERSION = ''
181- args = sys.argv[1:] + ['bootstrap']
182-
183-if is_jython:
184+cmd = [quote(sys.executable),
185+ '-c',
186+ quote('from setuptools.command.easy_install import main; main()'),
187+ '-mqNxd',
188+ quote(configuration['--eggs'])]
189+
190+if configuration['--download-base']:
191+ cmd.extend(['-f', quote(configuration['--download-base'])])
192+
193+cmd.append('zc.buildout' + configuration['--version'])
194+
195+ws = pkg_resources.working_set
196+env = dict(
197+ os.environ,
198+ PYTHONPATH=ws.find(pkg_resources.Requirement.parse('setuptools')).location)
199+
200+try:
201 import subprocess
202-
203- assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
204- quote(tmpeggs), 'zc.buildout' + VERSION],
205- env=dict(os.environ,
206- PYTHONPATH=
207- ws.find(pkg_resources.Requirement.parse('setuptools')).location
208- ),
209- ).wait() == 0
210-
211+except ImportError:
212+ exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
213 else:
214- assert os.spawnle(
215- os.P_WAIT, sys.executable, quote (sys.executable),
216- '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
217- dict(os.environ,
218- PYTHONPATH=
219- ws.find(pkg_resources.Requirement.parse('setuptools')).location
220- ),
221- ) == 0
222+ # Jython can use subprocess but not spawn. We prefer it generally.
223+ exitcode = subprocess.Popen(cmd, env=env).wait()
224+if exitcode != 0:
225+ # we shouldn't need an error message because a failure
226+ # should have generated a visible traceback in the subprocess.
227+ sys.exit(exitcode)
228
229-ws.add_entry(tmpeggs)
230-ws.require('zc.buildout' + VERSION)
231+ws.add_entry(configuration['--eggs'])
232+ws.require('zc.buildout' + configuration['--version'])
233 import zc.buildout.buildout
234+args.append('bootstrap')
235 zc.buildout.buildout.main(args)
236-shutil.rmtree(tmpeggs)
237+if tmpeggs is not None:
238+ shutil.rmtree(tmpeggs)
239
240=== modified file 'buildout.cfg'
241--- buildout.cfg 2009-03-10 20:13:57 +0000
242+++ buildout.cfg 2009-07-10 00:02:25 +0000
243@@ -1,6 +1,7 @@
244 [buildout]
245 develop = zc.recipe.egg_ .
246 parts = test oltest py
247+include-site-packages = false
248
249 [py]
250 recipe = zc.recipe.egg
251
252=== modified file 'setup.py'
253--- setup.py 2009-06-22 17:48:51 +0000
254+++ setup.py 2009-07-09 18:17:00 +0000
255@@ -12,7 +12,7 @@
256 #
257 ##############################################################################
258
259-version = "1.3.1dev"
260+version = "1.4.0dev"
261
262 import os
263 from setuptools import setup, find_packages
264
265=== modified file 'src/zc/buildout/bootstrap.txt'
266--- src/zc/buildout/bootstrap.txt 2009-06-22 13:05:44 +0000
267+++ src/zc/buildout/bootstrap.txt 2009-07-09 23:55:19 +0000
268@@ -120,4 +120,74 @@
269 zc.buildout.buildout.main()
270 <BLANKLINE>
271
272+You can specify a location of ez_setup.py, so you can rely on a local or remote
273+location. We'll write our own ez_setup.py that we will also use to test some
274+other bootstrap options.
275+
276+ >>> write('ez_setup.py', '''\
277+ ... def use_setuptools(**kwargs):
278+ ... import sys, pprint
279+ ... pprint.pprint(kwargs)
280+ ... sys.exit()
281+ ... ''')
282+ >>> print system(
283+ ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+
284+ ... 'bootstrap.py --ez_setup-source=./ez_setup.py')
285+ ... # doctest: +ELLIPSIS
286+ {'download_delay': 0,
287+ 'to_dir': '...'}
288+ <BLANKLINE>
289+
290+You can also pass a download-cache, and a place in which eggs should be stored
291+(they are normally stored in a temporary directory).
292+
293+ >>> print system(
294+ ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+
295+ ... 'bootstrap.py --ez_setup-source=./ez_setup.py '+
296+ ... '--download-base=./download-cache --eggs=eggs')
297+ ... # doctest: +ELLIPSIS
298+ {'download_base': '/sample/download-cache/',
299+ 'download_delay': 0,
300+ 'to_dir': '/sample/eggs'}
301+ <BLANKLINE>
302+
303+Here's the entire help text.
304+
305+ >>> print system(
306+ ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+
307+ ... 'bootstrap.py --help')
308+ ... # doctest: +ELLIPSIS
309+ Bootstrap a buildout-based project
310+ <BLANKLINE>
311+ Simply run this script in a directory containing a buildout.cfg.
312+ The script accepts buildout command-line options, so you can
313+ use the -c option to specify an alternate configuration file.
314+ <BLANKLINE>
315+ ...
316+ <BLANKLINE>
317+ This script recognizes the following options itself. The first option it
318+ encounters that is not one of these will cause the script to stop parsing
319+ options and pass the rest on to buildout. Therefore, if you want to use
320+ any of the following options *and* buildout command-line options like
321+ -c, first use the following options, and then use the buildout options.
322+ <BLANKLINE>
323+ Options:
324+ --version=ZC_BUILDOUT_VERSION
325+ Specify a version number of the zc.buildout to use
326+ --ez_setup-source=URL_OR_FILE
327+ Specify a URL or file location for the ez_setup file.
328+ Defaults to
329+ http://peak.telecommunity.com/dist/ez_setup.py
330+ --download-base=URL_OR_DIRECTORY
331+ Specify a URL or directory for downloading setuptools and
332+ zc.buildout. Defaults to PyPI.
333+ --eggs=DIRECTORY
334+ Specify a directory for storing eggs. Defaults to a temporary
335+ directory that is deleted when the bootstrap script completes.
336+ <BLANKLINE>
337+ By using --ez_setup-source and --download-base to point to local resources,
338+ you can keep this script from going over the network.
339+ <BLANKLINE>
340+ <BLANKLINE>
341+
342
343
344=== modified file 'src/zc/buildout/buildout.py'
345--- src/zc/buildout/buildout.py 2009-06-19 19:56:53 +0000
346+++ src/zc/buildout/buildout.py 2009-07-10 01:37:32 +0000
347@@ -59,7 +59,7 @@
348 """
349
350 class MissingSection(zc.buildout.UserError, KeyError):
351- """A required section is missinh
352+ """A required section is missing
353 """
354
355 def __str__(self):
356@@ -211,20 +211,30 @@
357 if versions:
358 zc.buildout.easy_install.default_versions(dict(self[versions]))
359
360- prefer_final = options.get('prefer-final', 'false')
361+ prefer_final = options.setdefault('prefer-final', 'false')
362 if prefer_final not in ('true', 'false'):
363 self._error('Invalid value for prefer-final option: %s',
364 prefer_final)
365 zc.buildout.easy_install.prefer_final(prefer_final=='true')
366
367- use_dependency_links = options.get('use-dependency-links', 'true')
368+ include_site_packages = options.setdefault(
369+ 'include-site-packages', 'true')
370+ if include_site_packages not in ('true', 'false'):
371+ self._error('Invalid value for include-site-packages option: %s',
372+ include_site_packages)
373+ zc.buildout.easy_install.include_site_packages(
374+ include_site_packages=='true')
375+
376+ use_dependency_links = options.setdefault(
377+ 'use-dependency-links', 'true')
378 if use_dependency_links not in ('true', 'false'):
379 self._error('Invalid value for use-dependency-links option: %s',
380 use_dependency_links)
381 zc.buildout.easy_install.use_dependency_links(
382- use_dependency_links == 'true')
383+ use_dependency_links=='true')
384
385- allow_picked_versions = options.get('allow-picked-versions', 'true')
386+ allow_picked_versions = options.setdefault(
387+ 'allow-picked-versions', 'true')
388 if allow_picked_versions not in ('true', 'false'):
389 self._error('Invalid value for allow-picked-versions option: %s',
390 allow_picked_versions)
391@@ -246,22 +256,19 @@
392
393 zc.buildout.easy_install.download_cache(download_cache)
394
395- install_from_cache = options.get('install-from-cache')
396- if install_from_cache:
397- if install_from_cache not in ('true', 'false'):
398- self._error('Invalid value for install-from-cache option: %s',
399- install_from_cache)
400- if install_from_cache == 'true':
401- zc.buildout.easy_install.install_from_cache(True)
402-
403-
404- always_unzip = options.get('unzip')
405- if always_unzip:
406- if always_unzip not in ('true', 'false'):
407- self._error('Invalid value for unzip option: %s',
408- always_unzip)
409- if always_unzip == 'true':
410- zc.buildout.easy_install.always_unzip(True)
411+ install_from_cache = options.setdefault(
412+ 'install-from-cache', 'false')
413+ if install_from_cache not in ('true', 'false'):
414+ self._error('Invalid value for install-from-cache option: %s',
415+ install_from_cache)
416+ zc.buildout.easy_install.install_from_cache(
417+ install_from_cache=='true')
418+
419+ always_unzip = options.setdefault('unzip', 'false')
420+ if always_unzip not in ('true', 'false'):
421+ self._error('Invalid value for unzip option: %s',
422+ always_unzip)
423+ zc.buildout.easy_install.always_unzip(always_unzip=='true')
424
425 # "Use" each of the defaults so they aren't reported as unused options.
426 for name in _buildout_default_options:
427@@ -275,15 +282,35 @@
428 return os.path.join(self._buildout_dir, name)
429
430 def bootstrap(self, args):
431- __doing__ = 'Bootstraping.'
432+ __doing__ = 'Bootstrapping.'
433
434 self._setup_directories()
435
436- # Now copy buildout and setuptools eggs, amd record destination eggs:
437+ options = self['buildout']
438+
439+ # Get a base working set for our distributions that corresponds to the
440+ # stated desires in the configuration.
441+ distributions = ['setuptools', 'zc.buildout']
442+ if options.get('offline') == 'true':
443+ ws = zc.buildout.easy_install.working_set(
444+ distributions, options['executable'],
445+ [options['develop-eggs-directory'], options['eggs-directory']]
446+ )
447+ else:
448+ ws = zc.buildout.easy_install.install(
449+ distributions, options['eggs-directory'],
450+ links=self._links,
451+ index=options.get('index'),
452+ executable=options['executable'],
453+ path=[options['develop-eggs-directory']],
454+ newest=self.newest,
455+ allow_hosts=self._allow_hosts,)
456+
457+ # Now copy buildout and setuptools eggs, and record destination eggs:
458 entries = []
459 for name in 'setuptools', 'zc.buildout':
460 r = pkg_resources.Requirement.parse(name)
461- dist = pkg_resources.working_set.find(r)
462+ dist = ws.find(r)
463 if dist.precedence == pkg_resources.DEVELOP_DIST:
464 dest = os.path.join(self['buildout']['develop-eggs-directory'],
465 name+'.egg-link')
466@@ -303,8 +330,10 @@
467 ws = pkg_resources.WorkingSet(entries)
468 ws.require('zc.buildout')
469 zc.buildout.easy_install.scripts(
470- ['zc.buildout'], ws, sys.executable,
471- self['buildout']['bin-directory'])
472+ ['zc.buildout'], ws, options['executable'],
473+ options['bin-directory'],
474+ include_site_packages=
475+ zc.buildout.easy_install.include_site_packages())
476
477 init = bootstrap
478
479
480=== modified file 'src/zc/buildout/buildout.txt'
481--- src/zc/buildout/buildout.txt 2009-06-19 19:56:53 +0000
482+++ src/zc/buildout/buildout.txt 2009-07-07 17:17:40 +0000
483@@ -2029,11 +2029,14 @@
484 <BLANKLINE>
485 Configuration data:
486 [buildout]
487+ allow-picked-versions = true
488 bin-directory = /sample-buildout/bin
489 develop-eggs-directory = /sample-buildout/develop-eggs
490 directory = /sample-buildout
491 eggs-directory = /sample-buildout/eggs
492 executable = /usr/local/bin/python2.3
493+ include-site-packages = true
494+ install-from-cache = false
495 installed = /sample-buildout/.installed.cfg
496 log-format =
497 log-level = INFO
498@@ -2041,7 +2044,10 @@
499 offline = false
500 parts =
501 parts-directory = /sample-buildout/parts
502+ prefer-final = false
503 python = buildout
504+ unzip = false
505+ use-dependency-links = true
506 verbosity = 20
507 <BLANKLINE>
508
509@@ -2049,6 +2055,14 @@
510 command-line assignments. We've discussed most of these options
511 already, but let's review them and touch on some we haven't discussed:
512
513+allow-picked-versions
514+ By default, the buildout will choose the best match for a given requirement
515+ if the requirement is not specified precisely (for instance, using the
516+ "versions" option. This behavior corresponds to the
517+ "allow-picked-versions" being set to its default value, "true". If
518+ "allow-picked-versions" is "false," instead of picking the best match,
519+ buildout will raise an error. This helps enforce repeatability.
520+
521 bin-directory
522 The directory path where scripts are written. This can be a
523 relative path, which is interpreted relative to the directory
524@@ -2073,6 +2087,24 @@
525 The Python executable used to run the buildout. See the python
526 option below.
527
528+include-site-packages
529+ By default, buildout will look for dependencies in the system's
530+ site-packages. For this purpose, paths outside of Python's standard
531+ library--or more precisely, those that are not included when Python is
532+ started with the -S argument--are loosely referred to as "site-packages"
533+ here. The include-site-packages buildout option can be used to override
534+ the default behavior of using site packages
535+ ("include-site-packages = false").
536+
537+install-from-cache
538+ A download cache can be used as the basis of application source releases.
539+ In an application source release, we want to distribute an application that
540+ can be built without making any network accesses. In this case, we
541+ distribute a buildout with download cache and tell the buildout to install
542+ from the download cache only, without making network accesses. The
543+ buildout install-from-cache option can be used to signal that packages
544+ should be installed only from the download cache.
545+
546 installed
547 The file path where information about the results of the previous
548 buildout run is written. This can be a relative path, which is
549@@ -2086,12 +2118,51 @@
550 log-level
551 The log level before verbosity adjustment
552
553+newest
554+ By default buildout and recipes will try to find the newest versions of
555+ distributions needed to satisfy requirements. This can be very time
556+ consuming, especially when incrementally working on setting up a buildout
557+ or working on a recipe. The buildout "newest" option can be used to to
558+ suppress this. If the "newest" option is set to false, then new
559+ distributions won't be sought if an installed distribution meets
560+ requirements. The "newest" option can also be set to false using the -N
561+ command-line option. See also the "offline" option.
562+
563+offline
564+ The "offline" option goes a bit further than the "newest" option. If the
565+ buildout "offline" option is given a value of "true", the buildout and
566+ recipes that are aware of the option will avoid doing network access. This
567+ is handy when running the buildout when not connected to the internet. It
568+ also makes buildouts run much faster. This option is typically set using
569+ the buildout -o option.
570+
571 parts
572 A white space separated list of parts to be installed.
573
574 parts-directory
575 A working directory that parts can used to store data.
576
577+prefer-final
578+ Currently, when searching for new releases, the newest available
579+ release is used. This isn't usually ideal, as you may get a
580+ development release or alpha releases not ready to be widely used.
581+ You can request that final releases be preferred using the prefer
582+ final option in the buildout section::
583+
584+ [buildout]
585+ ...
586+ prefer-final = true
587+
588+ When the prefer-final option is set to true, then when searching for
589+ new releases, final releases are preferred. If there are final
590+ releases that satisfy distribution requirements, then those releases
591+ are used even if newer non-final releases are available. The buildout
592+ prefer-final option can be used to override this behavior.
593+
594+ In buildout version 2, final releases will be preferred by default.
595+ You will then need to use a false value for prefer-final to get the
596+ newest releases.
597+
598 python
599 The name of a section containing information about the default
600 Python interpreter. Recipes that need a installation
601@@ -2102,6 +2173,26 @@
602 Python executable. By default, the buildout section defines the
603 default Python as the Python used to run the buildout.
604
605+unzip
606+ By default, zc.buildout doesn't unzip zip-safe eggs ("unzip = false").
607+ This follows the policy followed by setuptools itself. Experience shows
608+ this policy to to be inconvenient. Zipped eggs make debugging more
609+ difficult and often import more slowly. You can include an unzip option in
610+ the buildout section to change the default unzipping policy ("unzip =
611+ true").
612+
613+use-dependency-links
614+ By default buildout will obey the setuptools dependency_links metadata
615+ when it looks for dependencies. This behavior can be controlled with
616+ the use-dependency-links buildout option::
617+
618+ [buildout]
619+ ...
620+ use-dependency-links = false
621+
622+ The option defaults to true. If you set it to false, then dependency
623+ links are only looked for in the locations specified by find-links.
624+
625 verbosity
626 A log-level adjustment. Typically, this is set via the -q and -v
627 command-line options.
628@@ -2180,48 +2271,6 @@
629 Generated script '/sample-bootstrapped2/bin/buildout'.
630
631
632-Newest and Offline Modes
633-------------------------
634-
635-By default buildout and recipes will try to find the newest versions
636-of distributions needed to satisfy requirements. This can be very
637-time consuming, especially when incrementally working on setting up a
638-buildout or working on a recipe. The buildout newest option can be
639-used to to suppress this. If the newest option is set to false, then
640-new distributions won't be sought if an installed distribution meets
641-requirements. The newest option can be set to false using the -N
642-command-line option.
643-
644-The offline option goes a bit further. If the buildout offline option
645-is given a value of "true", the buildout and recipes that are aware of
646-the option will avoid doing network access. This is handy when
647-running the buildout when not connected to the internet. It also
648-makes buildouts run much faster. This option is typically set using
649-the buildout -o option.
650-
651-Preferring Final Releases
652--------------------------
653-
654-Currently, when searching for new releases, the newest available
655-release is used. This isn't usually ideal, as you may get a
656-development release or alpha releases not ready to be widely used.
657-You can request that final releases be preferred using the prefer
658-final option in the buildout section::
659-
660- [buildout]
661- ...
662- prefer-final = true
663-
664-When the prefer-final option is set to true, then when searching for
665-new releases, final releases are preferred. If there are final
666-releases that satisfy distribution requirements, then those releases
667-are used even if newer non-final releases are available. The buildout
668-prefer-final option can be used to override this behavior.
669-
670-In buildout version 2, final releases will be preferred by default.
671-You will then need to use a false value for prefer-final to get the
672-newest releases.
673-
674 Finding distributions
675 ---------------------
676
677@@ -2270,19 +2319,6 @@
678 /some/otherpath
679 /some/path/someegg-1.0.0-py2.3.egg
680
681-Dependency links
682-----------------
683-
684-By default buildout will obey the setuptools dependency_links metadata
685-when it looks for dependencies. This behavior can be controlled with
686-the use-dependency-links buildout option::
687-
688- [buildout]
689- ...
690- use-dependency-links = false
691-
692-The option defaults to true. If you set it to false, then dependency
693-links are only looked for in the locations specified by find-links.
694
695 Controlling the installation database
696 -------------------------------------
697
698=== modified file 'src/zc/buildout/easy_install.py'
699--- src/zc/buildout/easy_install.py 2009-06-22 16:41:40 +0000
700+++ src/zc/buildout/easy_install.py 2009-07-11 15:49:15 +0000
701@@ -64,12 +64,54 @@
702 pkg_resources.Requirement.parse('setuptools')
703 ).location
704
705-# Include buildout and setuptools eggs in paths
706-buildout_and_setuptools_path = [
707- setuptools_loc,
708- pkg_resources.working_set.find(
709- pkg_resources.Requirement.parse('zc.buildout')).location,
710- ]
711+# Include buildout and setuptools eggs in paths. We prevent dupes just to
712+# keep from duplicating any log messages about them.
713+buildout_loc = pkg_resources.working_set.find(
714+ pkg_resources.Requirement.parse('zc.buildout')).location
715+buildout_and_setuptools_path = [setuptools_loc]
716+if os.path.normpath(setuptools_loc) != os.path.normpath(buildout_loc):
717+ buildout_and_setuptools_path.append(buildout_loc)
718+
719+def _get_system_packages(executable):
720+ """return a pair of the standard lib and site packages for the executable.
721+ """
722+ # We want to get a list of the site packages, which is not easy. The
723+ # canonical way to do this is to use distutils.sysconfig.get_python_lib(),
724+ # but that only returns a single path, which does not reflect reality for
725+ # many system Pythons, which have multiple additions. Instead, we start
726+ # Python with -S, which does not import site.py and set up the extra paths
727+ # like site-packages or (Ubuntu/Debian) dist-packages and python-support.
728+ # We then compare that sys.path with the normal one. The set of the normal
729+ # one minus the set of the ones in ``python -S`` is the set of packages
730+ # that are effectively site-packages.
731+ def get_sys_path(clean=False):
732+ cmd = [executable, "-c",
733+ "import sys, os;"
734+ "print repr([os.path.normpath(p) for p in sys.path])"]
735+ if clean:
736+ cmd.insert(1, '-S')
737+ _proc = subprocess.Popen(
738+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
739+ stdout, stderr = _proc.communicate();
740+ if _proc.returncode:
741+ raise RuntimeError(
742+ 'error trying to get system packages:\n%s' % (stderr,))
743+ res = eval(stdout)
744+ try:
745+ res.remove('.')
746+ except ValueError:
747+ pass
748+ return res
749+ stdlib = get_sys_path(clean=True)
750+ # The given executable might not be the current executable, so it is
751+ # appropriate to do another subprocess to figure out what the additional
752+ # site-package paths are. Moreover, even if this executable *is* the
753+ # current executable, this code might be run in the context of code that
754+ # has manipulated the sys.path--for instance, to add local zc.buildout or
755+ # setuptools eggs.
756+ site_packages = [p for p in get_sys_path() if p not in stdlib]
757+ return (stdlib, site_packages)
758+
759
760 class IncompatibleVersionError(zc.buildout.UserError):
761 """A specified version is incompatible with a given requirement.
762@@ -99,6 +141,7 @@
763
764 FILE_SCHEME = re.compile('file://', re.I).match
765
766+
767 class AllowHostsPackageIndex(setuptools.package_index.PackageIndex):
768 """Will allow urls that are local to the system.
769
770@@ -111,7 +154,12 @@
771
772
773 _indexes = {}
774-def _get_index(executable, index_url, find_links, allow_hosts=('*',)):
775+def _get_index(executable, index_url, find_links, allow_hosts=('*',),
776+ path=None):
777+ # If path is None, the index will use sys.path. If you provide an empty
778+ # path ([]), it will complain uselessly about missing index pages for
779+ # packages found in the paths that you expect to use. Therefore, this path
780+ # is always the same as the _env path in the Installer.
781 key = executable, index_url, tuple(find_links)
782 index = _indexes.get(key)
783 if index is not None:
784@@ -120,7 +168,8 @@
785 if index_url is None:
786 index_url = default_index_url
787 index = AllowHostsPackageIndex(
788- index_url, hosts=allow_hosts, python=_get_version(executable)
789+ index_url, hosts=allow_hosts, search_path=path,
790+ python=_get_version(executable)
791 )
792
793 if find_links:
794@@ -152,6 +201,7 @@
795 _use_dependency_links = True
796 _allow_picked_versions = True
797 _always_unzip = False
798+ _include_site_packages = True
799
800 def __init__(self,
801 dest=None,
802@@ -163,6 +213,7 @@
803 newest=True,
804 versions=None,
805 use_dependency_links=None,
806+ include_site_packages=None,
807 allow_hosts=('*',)
808 ):
809 self._dest = dest
810@@ -185,7 +236,16 @@
811 self._executable = executable
812 if always_unzip is not None:
813 self._always_unzip = always_unzip
814- path = (path and path[:] or []) + buildout_and_setuptools_path
815+ path = (path and path[:] or [])
816+ if include_site_packages is not None:
817+ self._include_site_packages = include_site_packages
818+ stdlib, self._site_packages = _get_system_packages(executable)
819+ if self._include_site_packages:
820+ path.extend(buildout_and_setuptools_path)
821+ path.extend(self._site_packages)
822+ # else we could try to still include the buildout_and_setuptools_path
823+ # if the elements are not in site_packages, but we're not bothering
824+ # with this optimization for now, in the name of code simplicity.
825 if dest is not None and dest not in path:
826 path.insert(0, dest)
827 self._path = path
828@@ -194,13 +254,26 @@
829 self._newest = newest
830 self._env = pkg_resources.Environment(path,
831 python=_get_version(executable))
832- self._index = _get_index(executable, index, links, self._allow_hosts)
833+ self._index = _get_index(executable, index, links, self._allow_hosts,
834+ self._path)
835
836 if versions is not None:
837 self._versions = versions
838
839 def _satisfied(self, req, source=None):
840- dists = [dist for dist in self._env[req.project_name] if dist in req]
841+ # We get all distributions that match the given requirement. If we
842+ # are not supposed to include site-packages, we also filter those out.
843+ # We need to do the filtering here even though we have exluded
844+ # site packages from the _env's paths (see Installer.__init__) because
845+ # an .egg-link, such as one for setuptools or zc.buildout installed
846+ # by zc.buildout.buildout.Buildout.bootstrap, can indirectly include
847+ # a path in our _site_packages.
848+ dists = [dist for dist in self._env[req.project_name] if (
849+ dist in req and (
850+ self._include_site_packages or
851+ dist.location not in self._site_packages)
852+ )
853+ ]
854 if not dists:
855 logger.debug('We have no distributions for %s that satisfies %r.',
856 req.project_name, str(req))
857@@ -404,10 +477,19 @@
858 # Nothing is available.
859 return None
860
861- # Filter the available dists for the requirement and source flag
862+ # Filter the available dists for the requirement and source flag. If we
863+ # are not supposed to include site-packages, we also filter those out.
864+ # We need to do the filtering here even though we have exluded site
865+ # packages from the _index's paths (see Installer.__init__) because an
866+ # .egg-link, such as one for setuptools or zc.buildout installed by
867+ # zc.buildout.buildout.Buildout.bootstrap, can indirectly include a
868+ # path in our _site_packages.
869 dists = [dist for dist in index[requirement.project_name]
870 if ((dist in requirement)
871 and
872+ (self._include_site_packages or
873+ dist.location not in self._site_packages)
874+ and
875 ((not source) or
876 (dist.precedence == pkg_resources.SOURCE_DIST)
877 )
878@@ -571,7 +653,7 @@
879 self._links.append(link)
880 self._index = _get_index(self._executable,
881 self._index_url, self._links,
882- self._allow_hosts)
883+ self._allow_hosts, self._path)
884
885 for dist in dists:
886 # Check whether we picked a version and, if we did, report it:
887@@ -650,34 +732,52 @@
888 self._maybe_add_setuptools(ws, dist)
889
890 # OK, we have the requested distributions and they're in the working
891- # set, but they may have unmet requirements. We'll simply keep
892- # trying to resolve requirements, adding missing requirements as they
893- # are reported.
894- #
895- # Note that we don't pass in the environment, because we want
896+ # set, but they may have unmet requirements. We'll resolve these
897+ # requirements. This is code modified from
898+ # pkg_resources.WorkingSet.resolve. We can't reuse that code directly
899+ # because we have to constrain our requirements (see
900+ # versions_section_ignored_for_dependency_in_favor_of_site_packages in
901+ # zc.buildout.tests).
902+ #
903+ requirements.reverse() # set up the stack
904+ processed = {} # set of processed requirements
905+ best = {} # key -> dist
906+ #
907+ # Note that we don't use the existing environment, because we want
908 # to look for new eggs unless what we have is the best that
909 # matches the requirement.
910- while 1:
911- try:
912- ws.resolve(requirements)
913- except pkg_resources.DistributionNotFound, err:
914- [requirement] = err
915- requirement = self._constrain(requirement)
916- if dest:
917- logger.debug('Getting required %r', str(requirement))
918- else:
919- logger.debug('Adding required %r', str(requirement))
920- _log_requirement(ws, requirement)
921-
922- for dist in self._get_dist(requirement, ws, self._always_unzip
923- ):
924-
925- ws.add(dist)
926- self._maybe_add_setuptools(ws, dist)
927- except pkg_resources.VersionConflict, err:
928- raise VersionConflict(err, ws)
929- else:
930- break
931+ env = pkg_resources.Environment(ws.entries)
932+ while requirements:
933+ # process dependencies breadth-first
934+ req = self._constrain(requirements.pop(0))
935+ if req in processed:
936+ # Ignore cyclic or redundant dependencies
937+ continue
938+ dist = best.get(req.key)
939+ if dist is None:
940+ # Find the best distribution and add it to the map
941+ dist = ws.by_key.get(req.key)
942+ if dist is None:
943+ try:
944+ dist = best[req.key] = env.best_match(req, ws)
945+ except pkg_resources.VersionConflict, err:
946+ raise VersionConflict(err, ws)
947+ if dist is None:
948+ if dest:
949+ logger.debug('Getting required %r', str(req))
950+ else:
951+ logger.debug('Adding required %r', str(req))
952+ _log_requirement(ws, req)
953+ for dist in self._get_dist(req,
954+ ws, self._always_unzip):
955+ ws.add(dist)
956+ self._maybe_add_setuptools(ws, dist)
957+ if dist not in req:
958+ # Oops, the "best" so far conflicts with a dependency
959+ raise VersionConflict(
960+ pkg_resources.VersionConflict(dist, req), ws)
961+ requirements.extend(dist.requires(req.extras)[::-1])
962+ processed[req] = True
963
964 return ws
965
966@@ -773,6 +873,12 @@
967 Installer._prefer_final = bool(setting)
968 return old
969
970+def include_site_packages(setting=None):
971+ old = Installer._include_site_packages
972+ if setting is not None:
973+ Installer._include_site_packages = bool(setting)
974+ return old
975+
976 def use_dependency_links(setting=None):
977 old = Installer._use_dependency_links
978 if setting is not None:
979@@ -795,19 +901,21 @@
980 links=(), index=None,
981 executable=sys.executable, always_unzip=None,
982 path=None, working_set=None, newest=True, versions=None,
983- use_dependency_links=None, allow_hosts=('*',)):
984+ use_dependency_links=None, include_site_packages=None,
985+ allow_hosts=('*',)):
986 installer = Installer(dest, links, index, executable, always_unzip, path,
987 newest, versions, use_dependency_links,
988- allow_hosts=allow_hosts)
989+ include_site_packages, allow_hosts=allow_hosts)
990 return installer.install(specs, working_set)
991
992
993 def build(spec, dest, build_ext,
994 links=(), index=None,
995 executable=sys.executable,
996- path=None, newest=True, versions=None, allow_hosts=('*',)):
997+ path=None, newest=True, versions=None, include_site_packages=None,
998+ allow_hosts=('*',)):
999 installer = Installer(dest, links, index, executable, True, path, newest,
1000- versions, allow_hosts=allow_hosts)
1001+ versions, include_site_packages, allow_hosts=allow_hosts)
1002 return installer.build(spec, build_ext)
1003
1004
1005@@ -903,8 +1011,54 @@
1006 [f() for f in undo]
1007
1008
1009-def working_set(specs, executable, path):
1010- return install(specs, None, executable=executable, path=path)
1011+def working_set(specs, executable, path, include_site_packages=None):
1012+ return install(
1013+ specs, None, executable=executable, path=path,
1014+ include_site_packages=include_site_packages)
1015+
1016+def get_path(working_set, executable, extra_paths=(),
1017+ include_site_packages=True):
1018+ """Given working set and path to executable, return value for sys.path.
1019+
1020+ Distribution locations from the working set come first in the list. Within
1021+ that collection, this function pushes site-packages-based distribution
1022+ locations to the end of the list, so that they don't mask eggs.
1023+
1024+ This expects that the working_set has already been created to honor a
1025+ include_site_packages setting. That is, if include_site_packages is False,
1026+ this function does *not* verify that the working_set's distributions are
1027+ not in site packages.
1028+
1029+ However, it does explicitly include site packages if include_site_packages
1030+ is True.
1031+
1032+ The standard library (defined as what the given Python executable has on
1033+ the path before its site.py is run) is always included.
1034+ """
1035+ stdlib, site_packages = _get_system_packages(executable)
1036+ postponed = []
1037+ path = []
1038+ for dist in working_set:
1039+ location = os.path.normpath(dist.location)
1040+ if location in path:
1041+ path.remove(location)
1042+ postponed.append(location)
1043+ elif location in site_packages:
1044+ postponed.append(location)
1045+ elif location not in postponed:
1046+ path.append(location)
1047+ path.extend(postponed)
1048+ path.extend(extra_paths)
1049+ # now we add in all paths
1050+ if include_site_packages:
1051+ # err a bit on the side of cleanliness, avoiding dupes just to look
1052+ # pretty.
1053+ for location in site_packages:
1054+ if location not in path:
1055+ path.append(location)
1056+ path.extend(stdlib)
1057+ path = map(realpath, path)
1058+ return path
1059
1060 def scripts(reqs, working_set, executable, dest,
1061 scripts=None,
1062@@ -912,13 +1066,11 @@
1063 arguments='',
1064 interpreter=None,
1065 initialization='',
1066- relative_paths=False,
1067+ include_site_packages=True,
1068+ relative_paths=False
1069 ):
1070-
1071- path = [dist.location for dist in working_set]
1072- path.extend(extra_paths)
1073- path = map(realpath, path)
1074-
1075+ path = get_path(
1076+ working_set, executable, extra_paths, include_site_packages)
1077 generated = []
1078
1079 if isinstance(reqs, str):
1080@@ -969,7 +1121,7 @@
1081 if relative_paths:
1082 relative_paths = os.path.normcase(relative_paths)
1083 sname = os.path.normcase(os.path.abspath(sname))
1084- spath = ',\n '.join(
1085+ spath = ',\n '.join(
1086 [_relativitize(os.path.normcase(path_item), sname, relative_paths)
1087 for path_item in path]
1088 )
1089@@ -977,7 +1129,7 @@
1090 for i in range(_relative_depth(relative_paths, sname)):
1091 rpsetup += "base = os.path.dirname(base)\n"
1092 else:
1093- spath = repr(path)[1:-1].replace(', ', ',\n ')
1094+ spath = repr(path)[1:-1].replace(', ', ',\n ')
1095 rpsetup = ''
1096 return spath, rpsetup
1097
1098@@ -1075,9 +1227,9 @@
1099
1100 %(relative_paths_setup)s
1101 import sys
1102-sys.path[0:0] = [
1103- %(path)s,
1104- ]
1105+sys.path[:] = [
1106+ %(path)s,
1107+ ]
1108 %(initialization)s
1109 import %(module_name)s
1110
1111@@ -1120,31 +1272,60 @@
1112
1113 py_script_template = script_header + '''\
1114
1115+globs = globals().copy() # get a clean copy early
1116+
1117 %(relative_paths_setup)s
1118 import sys
1119
1120-sys.path[0:0] = [
1121- %(path)s,
1122- ]
1123-
1124-_interactive = True
1125-if len(sys.argv) > 1:
1126- import getopt
1127- _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
1128+_set_path = _interactive = True
1129+_force_interactive = False
1130+
1131+_command = None
1132+_args = sys.argv[1:]
1133+
1134+while _args:
1135+ if _args[0].startswith('-'):
1136+ _arg = _args.pop(0)
1137+ for _ix, _opt in enumerate(_arg[1:]):
1138+ if _opt == 'i':
1139+ _force_interactive = True
1140+ elif _opt == 'c':
1141+ _interactive = False
1142+ _command = _args.pop(0) # Argument expected for the -c option
1143+ _args.insert(0, '-c')
1144+ break
1145+ elif _opt == 'S':
1146+ # We'll approximate this. It is mostly convenient for tests.
1147+ _set_path = False
1148+ elif _opt == 'V':
1149+ print 'Python ' + sys.version.split()[0]
1150+ _interactive = False
1151+ break
1152+ else:
1153+ continue
1154+ break
1155+ else:
1156+ break
1157+
1158+if _set_path:
1159+ sys.path[:] = [
1160+ %(path)s,
1161+ ]
1162+sys.path.insert(0, '.')
1163+
1164+sys.argv[:] = _args
1165+
1166+if _command:
1167+ exec _command
1168+elif _args:
1169 _interactive = False
1170- for (_opt, _val) in _options:
1171- if _opt == '-i':
1172- _interactive = True
1173- elif _opt == '-c':
1174- exec _val
1175-
1176- if _args:
1177- sys.argv[:] = _args
1178- execfile(sys.argv[0])
1179-
1180-if _interactive:
1181+ globs['__file__'] = sys.argv[0]
1182+ execfile(sys.argv[0], globs)
1183+
1184+if _interactive or _force_interactive:
1185 import code
1186- code.interact(banner="", local=globals())
1187+ del globs['__file__']
1188+ code.interact(banner="", local=globs)
1189 '''
1190
1191 runsetup_template = """
1192
1193=== modified file 'src/zc/buildout/easy_install.txt'
1194--- src/zc/buildout/easy_install.txt 2009-06-22 16:41:40 +0000
1195+++ src/zc/buildout/easy_install.txt 2009-07-10 00:00:06 +0000
1196@@ -89,6 +89,14 @@
1197 for using dependency_links in preference to other
1198 locations. Defaults to true.
1199
1200+include_site_packages
1201+ A flag indicating whether Python's non-standard-library packages should
1202+ be available for finding dependencies. Defaults to true.
1203+
1204+ Paths outside of Python's standard library--or more precisely, those that
1205+ are not included when Python is started with the -S argument--are loosely
1206+ referred to as "site-packages" here.
1207+
1208 relative_paths
1209 Adjust egg paths so they are relative to the script path. This
1210 allows scripts to work when scripts and eggs are moved, as long as
1211@@ -399,6 +407,65 @@
1212 >>> [d.version for d in ws]
1213 ['0.3', '1.1']
1214
1215+Dependencies in Site Packages
1216+-----------------------------
1217+
1218+Paths outside of Python's standard library--or more precisely, those that are
1219+not included when Python is started with the -S argument--are loosely referred
1220+to as "site-packages" here. These site-packages are searched by default for
1221+distributions. This can be disabled, so that, for instance, a system Python
1222+can be used with buildout, cleaned of any packages installed by a user or
1223+system package manager.
1224+
1225+The default behavior can be controlled and introspected using
1226+zc.buildout.easy_install.include_site_packages.
1227+
1228+ >>> zc.buildout.easy_install.include_site_packages()
1229+ True
1230+
1231+Here's an example of using a Python executable that includes our dependencies.
1232+
1233+Our "primed_executable" has the "demoneeded," "other," and "setuptools"
1234+packages available. We'll simply be asking for "other" here.
1235+
1236+ >>> primed_executable = get_executable_with_site_packages()
1237+
1238+ >>> example_dest = tmpdir('site-packages-example-install')
1239+ >>> workingset = zc.buildout.easy_install.install(
1240+ ... ['other'], example_dest, links=[], executable=primed_executable,
1241+ ... index=None)
1242+ >>> [dist.project_name for dist in workingset]
1243+ ['other']
1244+
1245+That worked fine. Let's try again with site packages not allowed. We'll
1246+change the policy by changing the default. Notice that the function for
1247+changing the default value returns the previous value.
1248+
1249+ >>> zc.buildout.easy_install.include_site_packages(False)
1250+ True
1251+
1252+ >>> zc.buildout.easy_install.include_site_packages()
1253+ False
1254+
1255+ >>> zc.buildout.easy_install.clear_index_cache()
1256+ >>> rmdir(example_dest)
1257+ >>> example_dest = tmpdir('site-packages-example-install')
1258+ >>> workingset = zc.buildout.easy_install.install(
1259+ ... ['other'], example_dest, links=[], executable=primed_executable,
1260+ ... index=None)
1261+ Traceback (most recent call last):
1262+ ...
1263+ MissingDistribution: Couldn't find a distribution for 'other'.
1264+ >>> zc.buildout.easy_install.clear_index_cache()
1265+
1266+Now we'll reset the default.
1267+
1268+ >>> zc.buildout.easy_install.include_site_packages(True)
1269+ False
1270+
1271+ >>> zc.buildout.easy_install.include_site_packages()
1272+ True
1273+
1274 Dependency links
1275 ----------------
1276
1277@@ -580,14 +647,15 @@
1278
1279 The demo script run the entry point defined in the demo egg:
1280
1281- >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE
1282+ >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1283 #!/usr/local/bin/python2.4
1284 <BLANKLINE>
1285 import sys
1286- sys.path[0:0] = [
1287- '/sample-install/demo-0.3-py2.4.egg',
1288- '/sample-install/demoneeded-1.1-py2.4.egg',
1289- ]
1290+ sys.path[:] = [
1291+ '/sample-install/demo-0.3-py2.4.egg',
1292+ '/sample-install/demoneeded-1.1-py2.4.egg',
1293+ ...
1294+ ]
1295 <BLANKLINE>
1296 import eggrecipedemo
1297 <BLANKLINE>
1298@@ -596,7 +664,8 @@
1299
1300 Some things to note:
1301
1302-- The demo and demoneeded eggs are added to the beginning of sys.path.
1303+- The demo and demoneeded eggs are at the beginning of sys.path. The script
1304+ controls the entire path.
1305
1306 - The module for the script entry point is imported and the entry
1307 point, in this case, 'main', is run.
1308@@ -617,14 +686,15 @@
1309 ... [('demo', 'eggrecipedemo', 'main')],
1310 ... ws, sys.executable, bin)
1311
1312- >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE
1313+ >>> cat(bin, 'demo') # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1314 #!/usr/local/bin/python2.4
1315 <BLANKLINE>
1316 import sys
1317- sys.path[0:0] = [
1318- '/sample-install/demo-0.3-py2.4.egg',
1319- '/sample-install/demoneeded-1.1-py2.4.egg',
1320- ]
1321+ sys.path[:] = [
1322+ '/sample-install/demo-0.3-py2.4.egg',
1323+ '/sample-install/demoneeded-1.1-py2.4.egg',
1324+ ...
1325+ ]
1326 <BLANKLINE>
1327 import eggrecipedemo
1328 <BLANKLINE>
1329@@ -661,33 +731,63 @@
1330 The py script simply runs the Python interactive interpreter with
1331 the path set:
1332
1333- >>> cat(bin, 'py') # doctest: +NORMALIZE_WHITESPACE
1334+ >>> cat(bin, 'py') # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1335 #!/usr/local/bin/python2.4
1336+ globs = globals().copy() # get a clean copy early
1337+ <BLANKLINE>
1338 import sys
1339 <BLANKLINE>
1340- sys.path[0:0] = [
1341- '/sample-install/demo-0.3-py2.4.egg',
1342- '/sample-install/demoneeded-1.1-py2.4.egg',
1343- ]
1344- <BLANKLINE>
1345- _interactive = True
1346- if len(sys.argv) > 1:
1347- import getopt
1348- _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
1349+ _set_path = _interactive = True
1350+ _force_interactive = False
1351+ <BLANKLINE>
1352+ _command = None
1353+ _args = sys.argv[1:]
1354+ <BLANKLINE>
1355+ while _args:
1356+ if _args[0].startswith('-'):
1357+ _arg = _args.pop(0)
1358+ for _ix, _opt in enumerate(_arg[1:]):
1359+ if _opt == 'i':
1360+ _force_interactive = True
1361+ elif _opt == 'c':
1362+ _interactive = False
1363+ _command = _args.pop(0) # Argument expected for the -c option
1364+ _args.insert(0, '-c')
1365+ break
1366+ elif _opt == 'S':
1367+ # We'll approximate this. It is mostly convenient for tests.
1368+ _set_path = False
1369+ elif _opt == 'V':
1370+ print 'Python ' + sys.version.split()[0]
1371+ _interactive = False
1372+ break
1373+ else:
1374+ continue
1375+ break
1376+ else:
1377+ break
1378+ <BLANKLINE>
1379+ if _set_path:
1380+ sys.path[:] = [
1381+ '/sample-install/demo-0.3-pyN.N.egg',
1382+ '/sample-install/demoneeded-1.1-pyN.N.egg',
1383+ ...
1384+ ]
1385+ sys.path.insert(0, '.')
1386+ <BLANKLINE>
1387+ sys.argv[:] = _args
1388+ <BLANKLINE>
1389+ if _command:
1390+ exec _command
1391+ elif _args:
1392 _interactive = False
1393- for (_opt, _val) in _options:
1394- if _opt == '-i':
1395- _interactive = True
1396- elif _opt == '-c':
1397- exec _val
1398- <BLANKLINE>
1399- if _args:
1400- sys.argv[:] = _args
1401- execfile(sys.argv[0])
1402- <BLANKLINE>
1403- if _interactive:
1404+ globs['__file__'] = sys.argv[0]
1405+ execfile(sys.argv[0], globs)
1406+ <BLANKLINE>
1407+ if _interactive or _force_interactive:
1408 import code
1409- code.interact(banner="", local=globals())
1410+ del globs['__file__']
1411+ code.interact(banner="", local=globs)
1412
1413 If invoked with a script name and arguments, it will run that script, instead.
1414
1415@@ -722,14 +822,15 @@
1416 ... ['demo'], ws, sys.executable, bin, dict(demo='run'),
1417 ... extra_paths=[foo])
1418
1419- >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
1420+ >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1421 #!/usr/local/bin/python2.4
1422 <BLANKLINE>
1423 import sys
1424- sys.path[0:0] = [
1425+ sys.path[:] = [
1426 '/sample-install/demo-0.3-py2.4.egg',
1427 '/sample-install/demoneeded-1.1-py2.4.egg',
1428 '/foo',
1429+ ...
1430 ]
1431 <BLANKLINE>
1432 import eggrecipedemo
1433@@ -737,6 +838,68 @@
1434 if __name__ == '__main__':
1435 eggrecipedemo.main()
1436
1437+Ordering paths
1438+--------------
1439+
1440+We have already seen that we have a precise definition for a loose term:
1441+"site-packages". Paths outside of Python's standard library--or more
1442+precisely, those that are not included when Python is started with the -S
1443+argument--are loosely referred to as "site-packages" here.
1444+
1445+When generating scripts, paths that come from the site-packages are ordered
1446+after the other specific dependencies generated from the working set. This
1447+is so that directories such as "site-packages" that can contain multiple
1448+dependencies come after the more specific distributions found by setuptools,
1449+reducing the chance of the distributions being masked by the system folders.
1450+
1451+This is controlled by the ``get_path`` function, which is available for
1452+other script recipes to use.
1453+
1454+As a demonstration, we will have create a working set that has dependencies
1455+on "bigdemo" and "other". In our first case, we will use a clean Python
1456+without any of these dependencies installed.
1457+
1458+ >>> dest1 = tmpdir('path-install-1')
1459+ >>> ws1 = zc.buildout.easy_install.install(
1460+ ... ['other', 'bigdemo'], dest1,
1461+ ... links=[link_server], index=link_server+'index/')
1462+ >>> path1 = zc.buildout.easy_install.get_path(ws1, sys.executable)
1463+
1464+ >>> import pprint
1465+ >>> pprint.pprint(path1) # doctest: +ELLIPSIS
1466+ ['.../path-install-1/other-1.0-py...egg',
1467+ '.../path-install-1/bigdemo-0.1-py...egg',
1468+ '.../path-install-1/demo-0.3-py...egg',
1469+ '.../path-install-1/demoneeded-1.1-py...egg',
1470+ ...]
1471+
1472+We will now compare the results using a Python that has bigdemo's indirect
1473+dependency available, "demoneeded," and "other," but not "demo" or "bigdemo".
1474+
1475+ >>> dest2 = tmpdir('path-install-2')
1476+ >>> ws2 = zc.buildout.easy_install.install(
1477+ ... ['other', 'bigdemo'], dest2,
1478+ ... links=[link_server], index=link_server+'index/',
1479+ ... executable=primed_executable)
1480+ >>> path2 = zc.buildout.easy_install.get_path(ws2, primed_executable)
1481+ >>> pprint.pprint(path2) # doctest: +ELLIPSIS
1482+ ['.../path-install-2/bigdemo-0.1-py...egg',
1483+ '.../path-install-2/demo-0.3-py...egg',
1484+ '.../executable/eggs/other-1.0-py...egg',
1485+ '.../executable/eggs/demoneeded-1.1-py...egg',
1486+ '.../executable/eggs/setuptools-0.6c9-py...egg',
1487+ '.../executable/bin',
1488+ ...]
1489+ >>> zc.buildout.easy_install.clear_index_cache() # clean up
1490+
1491+Notice that the paths from the executable come after the ones for this
1492+buildout. This is most evident in the change of order for the "other" egg.
1493+
1494+In fact, this ordering is not important in this example, because the
1495+executable's paths all are individual packages; but if a path were a directory
1496+that shared many packages, like a classic "site-packages" directory, its shared
1497+packages would not mask those selected by the buildout.
1498+
1499 Providing script arguments
1500 --------------------------
1501
1502@@ -748,12 +911,13 @@
1503 ... ['demo'], ws, sys.executable, bin, dict(demo='run'),
1504 ... arguments='1, 2')
1505
1506- >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
1507+ >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1508 #!/usr/local/bin/python2.4
1509 import sys
1510- sys.path[0:0] = [
1511+ sys.path[:] = [
1512 '/sample-install/demo-0.3-py2.4.egg',
1513 '/sample-install/demoneeded-1.1-py2.4.egg',
1514+ ...
1515 ]
1516 <BLANKLINE>
1517 import eggrecipedemo
1518@@ -771,13 +935,14 @@
1519 ... arguments='1, 2',
1520 ... initialization='import os\nos.chdir("foo")')
1521
1522- >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE
1523+ >>> cat(bin, 'run') # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
1524 #!/usr/local/bin/python2.4
1525 import sys
1526- sys.path[0:0] = [
1527- '/sample-install/demo-0.3-py2.4.egg',
1528- '/sample-install/demoneeded-1.1-py2.4.egg',
1529- ]
1530+ sys.path[:] = [
1531+ '/sample-install/demo-0.3-py2.4.egg',
1532+ '/sample-install/demoneeded-1.1-py2.4.egg',
1533+ ...
1534+ ]
1535 <BLANKLINE>
1536 import os
1537 os.chdir("foo")
1538@@ -811,7 +976,7 @@
1539 ... interpreter='py',
1540 ... relative_paths=bo)
1541
1542- >>> cat(bo, 'bin', 'run')
1543+ >>> cat(bo, 'bin', 'run') # doctest: +ELLIPSIS
1544 #!/usr/local/bin/python2.4
1545 <BLANKLINE>
1546 import os
1547@@ -821,12 +986,13 @@
1548 base = os.path.dirname(base)
1549 <BLANKLINE>
1550 import sys
1551- sys.path[0:0] = [
1552- join(base, 'eggs/demo-0.3-pyN.N.egg'),
1553- join(base, 'eggs/demoneeded-1.1-pyN.N.egg'),
1554- '/ba',
1555- join(base, 'bar'),
1556- ]
1557+ sys.path[:] = [
1558+ join(base, 'eggs/demo-0.3-pyN.N.egg'),
1559+ join(base, 'eggs/demoneeded-1.1-pyN.N.egg'),
1560+ '/ba',
1561+ join(base, 'bar'),
1562+ ...
1563+ ]
1564 <BLANKLINE>
1565 import eggrecipedemo
1566 <BLANKLINE>
1567@@ -843,8 +1009,10 @@
1568
1569 We specified an interpreter and its paths are adjusted too:
1570
1571- >>> cat(bo, 'bin', 'py')
1572+ >>> cat(bo, 'bin', 'py') # doctest: +ELLIPSIS
1573 #!/usr/local/bin/python2.4
1574+ globs = globals().copy() # get a clean copy early
1575+ <BLANKLINE>
1576 <BLANKLINE>
1577 import os
1578 <BLANKLINE>
1579@@ -854,32 +1022,59 @@
1580 <BLANKLINE>
1581 import sys
1582 <BLANKLINE>
1583- sys.path[0:0] = [
1584- join(base, 'eggs/demo-0.3-pyN.N.egg'),
1585- join(base, 'eggs/demoneeded-1.1-pyN.N.egg'),
1586- '/ba',
1587- join(base, 'bar'),
1588- ]
1589- <BLANKLINE>
1590- _interactive = True
1591- if len(sys.argv) > 1:
1592- import getopt
1593- _options, _args = getopt.getopt(sys.argv[1:], 'ic:')
1594+ _set_path = _interactive = True
1595+ _force_interactive = False
1596+ <BLANKLINE>
1597+ _command = None
1598+ _args = sys.argv[1:]
1599+ <BLANKLINE>
1600+ while _args:
1601+ if _args[0].startswith('-'):
1602+ _arg = _args.pop(0)
1603+ for _ix, _opt in enumerate(_arg[1:]):
1604+ if _opt == 'i':
1605+ _force_interactive = True
1606+ elif _opt == 'c':
1607+ _interactive = False
1608+ _command = _args.pop(0) # Argument expected for the -c option
1609+ _args.insert(0, '-c')
1610+ break
1611+ elif _opt == 'S':
1612+ # We'll approximate this. It is mostly convenient for tests.
1613+ _set_path = False
1614+ elif _opt == 'V':
1615+ print 'Python ' + sys.version.split()[0]
1616+ _interactive = False
1617+ break
1618+ else:
1619+ continue
1620+ break
1621+ else:
1622+ break
1623+ <BLANKLINE>
1624+ if _set_path:
1625+ sys.path[:] = [
1626+ join(base, 'eggs/demo-0.3-pyN.N.egg'),
1627+ join(base, 'eggs/demoneeded-1.1-pyN.N.egg'),
1628+ '/ba',
1629+ join(base, 'bar'),
1630+ ...,
1631+ ]
1632+ sys.path.insert(0, '.')
1633+ <BLANKLINE>
1634+ sys.argv[:] = _args
1635+ <BLANKLINE>
1636+ if _command:
1637+ exec _command
1638+ elif _args:
1639 _interactive = False
1640- for (_opt, _val) in _options:
1641- if _opt == '-i':
1642- _interactive = True
1643- elif _opt == '-c':
1644- exec _val
1645- <BLANKLINE>
1646- if _args:
1647- sys.argv[:] = _args
1648- execfile(sys.argv[0])
1649- <BLANKLINE>
1650- if _interactive:
1651+ globs['__file__'] = sys.argv[0]
1652+ execfile(sys.argv[0], globs)
1653+ <BLANKLINE>
1654+ if _interactive or _force_interactive:
1655 import code
1656- code.interact(banner="", local=globals())
1657-
1658+ del globs['__file__']
1659+ code.interact(banner="", local=globs)
1660
1661 Handling custom build options for extensions provided in source distributions
1662 -----------------------------------------------------------------------------
1663
1664=== modified file 'src/zc/buildout/testing.py'
1665--- src/zc/buildout/testing.py 2009-06-18 09:54:24 +0000
1666+++ src/zc/buildout/testing.py 2009-07-11 15:49:15 +0000
1667@@ -116,12 +116,19 @@
1668 args = [zc.buildout.easy_install._safe_arg(arg)
1669 for arg in args]
1670 args.insert(0, '-q')
1671- args.append(dict(os.environ, PYTHONPATH=setuptools_location))
1672+
1673+ env = dict(os.environ)
1674+ if executable == sys.executable:
1675+ env['PYTHONPATH'] = setuptools_location
1676+ # else pass an executable that has setuptools! See testselectingpython.py.
1677+ args.append(env)
1678
1679 here = os.getcwd()
1680 try:
1681 os.chdir(d)
1682- os.spawnle(os.P_WAIT, executable, zc.buildout.easy_install._safe_arg (executable), setup, *args)
1683+ os.spawnle(os.P_WAIT, executable,
1684+ zc.buildout.easy_install._safe_arg(executable),
1685+ setup, *args)
1686 if os.path.exists('build'):
1687 rmtree('build')
1688 finally:
1689@@ -133,6 +140,11 @@
1690 def bdist_egg(setup, executable, dest):
1691 _runsetup(setup, executable, 'bdist_egg', '-d', dest)
1692
1693+def sys_install(setup, dest):
1694+ _runsetup(setup, sys.executable, 'install', '--home', dest,
1695+ '--single-version-externally-managed',
1696+ '--record', os.path.join(dest, 'added'))
1697+
1698 def find_python(version):
1699 e = os.environ.get('PYTHON%s' % version)
1700 if e is not None:
1701@@ -272,7 +284,7 @@
1702
1703
1704 # Create the develop-eggs dir, which didn't get created the usual
1705- # way due to thr trick above:
1706+ # way due to the trick above:
1707 os.mkdir('develop-eggs')
1708
1709 def start_server(path):
1710
1711=== modified file 'src/zc/buildout/tests.py'
1712--- src/zc/buildout/tests.py 2009-06-19 19:56:53 +0000
1713+++ src/zc/buildout/tests.py 2009-07-11 15:49:15 +0000
1714@@ -16,7 +16,7 @@
1715 $Id$
1716 """
1717
1718-import os, re, shutil, sys, tempfile, unittest, zipfile
1719+import os, re, shutil, sys, tempfile, textwrap, unittest, zipfile
1720 from zope.testing import doctest, renormalizing
1721 import pkg_resources
1722 import zc.buildout.testing, zc.buildout.easy_install
1723@@ -1768,6 +1768,91 @@
1724 1 2
1725 """
1726
1727+def versions_section_ignored_for_dependency_in_favor_of_site_packages():
1728+ r"""
1729+This is a test for a bugfix.
1730+
1731+The error showed itself when at least two dependencies were in a shared
1732+location like site-packages, and the first one met the "versions" setting. The
1733+first dependency would be added, but subsequent dependencies from the same
1734+location (e.g., site-packages) would use the version of the package found in
1735+the shared location, ignoring the version setting.
1736+
1737+We begin with a Python that has demoneeded version 1.1 installed and a demo
1738+version 0.3, all in a site-packages-like shared directory. We need to create
1739+this.
1740+
1741+ >>> executable_buildout = tmpdir('executable_buildout')
1742+ >>> old_wd = os.getcwd()
1743+ >>> os.chdir(executable_buildout)
1744+ >>> import textwrap
1745+ >>> write('buildout.cfg', textwrap.dedent(
1746+ ... '''
1747+ ... [buildout]
1748+ ... parts = interpreter
1749+ ...
1750+ ... [interpreter]
1751+ ... recipe = zc.recipe.egg
1752+ ... scripts = py
1753+ ... interpreter = py
1754+ ... extra-paths = %(site-packages)s
1755+ ... include-site-packages = false
1756+ ... eggs = setuptools
1757+ ... ''') % {'site-packages': join(site_packages, 'lib', 'python')})
1758+ >>> zc.buildout.buildout.Buildout(
1759+ ... 'buildout.cfg',
1760+ ... [('buildout', 'log-level', 'WARNING'),
1761+ ... # trick bootstrap into putting the buildout develop egg
1762+ ... # in the eggs dir.
1763+ ... ('buildout', 'develop-eggs-directory', 'eggs'),
1764+ ... ]
1765+ ... ).bootstrap([])
1766+ >>> os.mkdir('develop-eggs')
1767+ >>> zc.buildout.testing.install_develop(
1768+ ... 'zc.recipe.egg',
1769+ ... os.path.join(executable_buildout, 'develop-eggs'))
1770+ >>> print system(
1771+ ... os.path.join(executable_buildout, 'bin', 'buildout'))
1772+ Installing interpreter.
1773+ Generated interpreter '/executable_buildout/bin/py'.
1774+ <BLANKLINE>
1775+ >>> os.chdir(old_wd)
1776+ >>> primed_executable = os.path.join(executable_buildout, 'bin', 'py')
1777+
1778+``eggrecipedemo.main()`` shows the number after the dot (that is, ``X`` in
1779+``1.X``), for the demo package and the demoneeded package, so this demonstrates
1780+that our Python does in fact have demo version 0.3 and demoneeded version 1.1.
1781+
1782+ >>> print system(primed_executable+" -c "+
1783+ ... "'import eggrecipedemo; eggrecipedemo.main()'")
1784+ 3 1
1785+ <BLANKLINE>
1786+
1787+Now we will install bigdemo, specifying different versions of demo
1788+and demoneeded in a versions section. Before the bugfix, the demo version
1789+would be honored, but not the demoneeded.
1790+
1791+Now here's a setup that would expose the bug, using the
1792+zc.buildout.easy_install API.
1793+
1794+ >>> example_dest = tmpdir('example_dest')
1795+ >>> workingset = zc.buildout.easy_install.install(
1796+ ... ['bigdemo'], example_dest, links=[sample_eggs],
1797+ ... executable=primed_executable,
1798+ ... index=None, include_site_packages=True,
1799+ ... versions={'demoneeded': '1.2c1', 'demo': '0.3'})
1800+ >>> for dist in workingset:
1801+ ... print dist
1802+ bigdemo 0.1
1803+ demo 0.3
1804+ demoneeded 1.2c1
1805+
1806+Before the bugfix, the demoneeded distribution was not included in the working
1807+set, and the demoneeded in site-packages (of the wrong number) would have been
1808+used.
1809+
1810+ """
1811+
1812 if sys.version_info > (2, 4):
1813 def test_exit_codes():
1814 """
1815@@ -2156,7 +2241,7 @@
1816 """
1817 This test tests several permutations:
1818
1819-Using different version numbers to work around zip impporter cache problems. :(
1820+Using different version numbers to work around zip importer cache problems. :(
1821
1822 - With prefer final:
1823
1824@@ -2338,6 +2423,177 @@
1825
1826 """
1827
1828+def isolated_include_site_packages():
1829+ """
1830+
1831+This is an isolated test of the include_site_packages functionality, passing
1832+the argument directly to install, overriding a default.
1833+
1834+Our "primed_executable" has the "demoneeded," "other," and "setuptools"
1835+packages available. We'll simply be asking for "other" here.
1836+
1837+ >>> primed_executable = get_executable_with_site_packages()
1838+ >>> zc.buildout.easy_install.include_site_packages(False)
1839+ True
1840+
1841+ >>> example_dest = tmpdir('site-packages-example-install')
1842+ >>> workingset = zc.buildout.easy_install.install(
1843+ ... ['other'], example_dest, links=[], executable=primed_executable,
1844+ ... index=None, include_site_packages=True)
1845+ >>> [dist.project_name for dist in workingset]
1846+ ['other']
1847+
1848+That worked fine. Let's try again with site packages not allowed (and
1849+reversing the default).
1850+
1851+ >>> zc.buildout.easy_install.include_site_packages(True)
1852+ False
1853+
1854+ >>> zc.buildout.easy_install.clear_index_cache()
1855+ >>> rmdir(example_dest)
1856+ >>> example_dest = tmpdir('site-packages-example-install')
1857+ >>> workingset = zc.buildout.easy_install.install(
1858+ ... ['other'], example_dest, links=[], executable=primed_executable,
1859+ ... index=None, include_site_packages=False)
1860+ Traceback (most recent call last):
1861+ ...
1862+ MissingDistribution: Couldn't find a distribution for 'other'.
1863+
1864+That's a failure, as expected.
1865+
1866+Now we explore an important edge case.
1867+
1868+Some system Pythons include setuptools (and other Python packages) in their
1869+site-packages (or equivalent) using a .egg-info directory. The pkg_resources
1870+module (from setuptools) considers a package installed using .egg-info to be a
1871+develop egg.
1872+
1873+zc.buildout.buildout.Buildout.bootstrap will make setuptools and zc.buildout
1874+available to the buildout via the eggs directory, for normal eggs; or the
1875+develop-eggs directory, for develop-eggs.
1876+
1877+If setuptools or zc.buildout is found in site-packages and considered by
1878+pkg_resources to be a develop egg, then the bootstrap code will use a .egg-link
1879+in the local develop-eggs, pointing to site-packages, in its entirety. Because
1880+develop-eggs must always be available for searching for distributions, this
1881+indirectly brings site-packages back into the search path for distributions.
1882+
1883+Because of this, we have to take special care that we still exclude
1884+site-packages even in this case. See the comments about site packages in the
1885+Installer._satisfied and Installer._obtain methods for the implementation
1886+(as of this writing).
1887+
1888+In this demonstration, we insert a link to the "other" distribution in our
1889+develop-eggs, which would bring the package back in, except for the special
1890+care we have taken to exclude it.
1891+
1892+ >>> zc.buildout.easy_install.clear_index_cache()
1893+ >>> rmdir(example_dest)
1894+ >>> example_dest = tmpdir('site-packages-example-install')
1895+ >>> mkdir(example_dest, 'develop-eggs')
1896+ >>> stdlib, site_packages = (
1897+ ... zc.buildout.easy_install._get_system_packages(primed_executable))
1898+ >>> path_to_other = [p for p in site_packages if 'other' in p][0]
1899+ >>> write(example_dest, 'develop-eggs', 'other.egg-link', path_to_other)
1900+ >>> workingset = zc.buildout.easy_install.install(
1901+ ... ['other'], example_dest, links=[],
1902+ ... path=[join(example_dest, 'develop-eggs')],
1903+ ... executable=primed_executable,
1904+ ... index=None, include_site_packages=False)
1905+ Traceback (most recent call last):
1906+ ...
1907+ MissingDistribution: Couldn't find a distribution for 'other'.
1908+
1909+The MissingDistribution error shows that buildout correctly excluded the
1910+"site-packages" source even though it was indirectly included in the path
1911+via a .egg-link file.
1912+
1913+ """
1914+
1915+def buildout_include_site_packages_option():
1916+ """
1917+The include-site-packages buildout option can be used to override the default
1918+behavior of using site packages.
1919+
1920+The default is include-site-packages = true. As a demonstration, notice we do
1921+not set find-links, but the eggs are still found because they are in the
1922+executable's path.
1923+
1924+Our "primed_executable" has the "demoneeded," "other," and "setuptools"
1925+packages available. We'll simply be asking for "other" here.
1926+
1927+ >>> primed_executable = get_executable_with_site_packages()
1928+ >>> write('buildout.cfg',
1929+ ... '''
1930+ ... [buildout]
1931+ ... parts = eggs
1932+ ... find-links =
1933+ ...
1934+ ... [primed_python]
1935+ ... executable = %(primed_executable)s
1936+ ...
1937+ ... [eggs]
1938+ ... recipe = zc.recipe.egg:eggs
1939+ ... python = primed_python
1940+ ... eggs = other
1941+ ... ''' % globals())
1942+
1943+ >>> print system(primed_executable+" "+buildout)
1944+ Installing eggs.
1945+ <BLANKLINE>
1946+
1947+However, if we set include-site-packages to false, we get an error, because
1948+the packages are not available in any links, and they are not allowed to be
1949+obtained from the executable's site packages.
1950+
1951+ >>> zc.buildout.easy_install.clear_index_cache()
1952+ >>> write('buildout.cfg',
1953+ ... '''
1954+ ... [buildout]
1955+ ... parts = eggs
1956+ ... find-links =
1957+ ... include-site-packages = false
1958+ ...
1959+ ... [primed_python]
1960+ ... executable = %(primed_executable)s
1961+ ...
1962+ ... [eggs]
1963+ ... recipe = zc.recipe.egg:eggs
1964+ ... eggs = other
1965+ ... ''' % globals())
1966+ >>> print system(primed_executable+" "+buildout)
1967+ Uninstalling eggs.
1968+ Installing eggs.
1969+ Couldn't find index page for 'other' (maybe misspelled?)
1970+ Getting distribution for 'other'.
1971+ While:
1972+ Installing eggs.
1973+ Getting distribution for 'other'.
1974+ Error: Couldn't find a distribution for 'other'.
1975+ <BLANKLINE>
1976+
1977+We get an error if we specify anything but true or false:
1978+
1979+ >>> write('buildout.cfg',
1980+ ... '''
1981+ ... [buildout]
1982+ ... parts = eggs
1983+ ... find-links = %(link_server)s
1984+ ... include-site-packages = no
1985+ ...
1986+ ... [eggs]
1987+ ... recipe = zc.recipe.egg:eggs
1988+ ... eggs = other
1989+ ... ''' % globals())
1990+
1991+ >>> print system(primed_executable+" "+buildout)
1992+ While:
1993+ Initializing.
1994+ Error: Invalid value for include-site-packages option: no
1995+ <BLANKLINE>
1996+
1997+ """
1998+
1999 def develop_with_modules():
2000 """
2001 Distribution setup scripts can import modules in the distribution directory:
2002@@ -2533,6 +2789,8 @@
2003 def create_sample_eggs(test, executable=sys.executable):
2004 write = test.globs['write']
2005 dest = test.globs['sample_eggs']
2006+ site_packages = test.globs['tmpdir']('site_packages')
2007+ test.globs['site_packages'] = site_packages
2008 tmp = tempfile.mkdtemp()
2009 try:
2010 write(tmp, 'README.txt', '')
2011@@ -2549,6 +2807,8 @@
2012 % (i, c1)
2013 )
2014 zc.buildout.testing.sdist(tmp, dest)
2015+ if i==1:
2016+ zc.buildout.testing.sys_install(tmp, site_packages)
2017
2018 write(
2019 tmp, 'setup.py',
2020@@ -2578,6 +2838,8 @@
2021 " zip_safe=True, version='0.%s%s')\n" % (i, c1)
2022 )
2023 zc.buildout.testing.bdist_egg(tmp, executable, dest)
2024+ if i==3:
2025+ zc.buildout.testing.sys_install(tmp, site_packages)
2026
2027 write(tmp, 'eggrecipebigdemo.py', 'import eggrecipedemo')
2028 write(
2029@@ -2652,6 +2914,49 @@
2030 test.globs['sample_eggs'])
2031 test.globs['update_extdemo'] = lambda : add_source_dist(test, 1.5)
2032 zc.buildout.testing.install_develop('zc.recipe.egg', test)
2033+ # most tests don't need this set up, and it takes some time, so we just
2034+ # make it available as a convenience.
2035+ def get_executable_with_site_packages(requirements=None):
2036+ executable_buildout = test.globs['tmpdir']('executable')
2037+ old_wd = os.getcwd()
2038+ os.chdir(executable_buildout)
2039+ if requirements is None:
2040+ requirements = ['demoneeded', 'setuptools', 'other']
2041+ elif len([req for req in requirements
2042+ if req.startswith('setuptools')]) == 0:
2043+ requirements.append('setuptools') # you always need that.
2044+ requirements = '\n '.join(requirements)
2045+ test.globs['write']('buildout.cfg', textwrap.dedent(
2046+ '''
2047+ [buildout]
2048+ parts = interpreter
2049+ find-links = %(link_server)s
2050+ prefer-final = true
2051+
2052+ [interpreter]
2053+ recipe = zc.recipe.egg
2054+ interpreter = py
2055+ eggs = %(requirements)s
2056+ ''') % {'requirements': requirements,
2057+ 'link_server': test.globs['link_server']})
2058+ zc.buildout.buildout.Buildout(
2059+ 'buildout.cfg',
2060+ [('buildout', 'log-level', 'WARNING'),
2061+ # trick bootstrap into putting the buildout develop egg
2062+ # in the eggs dir.
2063+ ('buildout', 'develop-eggs-directory', 'eggs'),
2064+ ]
2065+ ).bootstrap([])
2066+ os.mkdir('develop-eggs')
2067+ zc.buildout.testing.install_develop(
2068+ 'zc.recipe.egg',
2069+ os.path.join(executable_buildout, 'develop-eggs'))
2070+ test.globs['system'](
2071+ os.path.join(executable_buildout, 'bin', 'buildout'))
2072+ os.chdir(old_wd)
2073+ return os.path.join(executable_buildout, 'bin', 'py')
2074+ test.globs['get_executable_with_site_packages'] = (
2075+ get_executable_with_site_packages)
2076
2077 egg_parse = re.compile('([0-9a-zA-Z_.]+)-([0-9a-zA-Z_.]+)-py(\d[.]\d).egg$'
2078 ).match
2079
2080=== modified file 'src/zc/buildout/testselectingpython.py'
2081--- src/zc/buildout/testselectingpython.py 2008-01-14 00:46:41 +0000
2082+++ src/zc/buildout/testselectingpython.py 2009-07-10 00:02:25 +0000
2083@@ -11,8 +11,9 @@
2084 # FOR A PARTICULAR PURPOSE.
2085 #
2086 ##############################################################################
2087-import os, re, sys, unittest
2088+import os, re, subprocess, sys, textwrap, unittest
2089 from zope.testing import doctest, renormalizing
2090+import zc.buildout.easy_install
2091 import zc.buildout.tests
2092 import zc.buildout.testing
2093
2094@@ -25,13 +26,14 @@
2095 test_selecting_python_via_easy_install=
2096 """\
2097
2098-We can specify an specific Python executable.
2099+We can specify a specific Python executable.
2100
2101 >>> dest = tmpdir('sample-install')
2102 >>> ws = zc.buildout.easy_install.install(
2103 ... ['demo'], dest, links=[link_server],
2104 ... index='http://www.python.org/pypi/',
2105- ... always_unzip=True, executable= other_executable)
2106+ ... always_unzip=True, executable= other_executable,
2107+ ... include_site_packages=False)
2108
2109 >>> ls(dest)
2110 d demo-0.3-py%(other_version)s.egg
2111@@ -43,6 +45,31 @@
2112
2113 def multi_python(test):
2114 other_executable = zc.buildout.testing.find_python(other_version)
2115+ command = textwrap.dedent('''\
2116+ try:
2117+ import setuptools
2118+ except ImportError:
2119+ import sys
2120+ sys.exit(1)
2121+ ''')
2122+ if subprocess.call([other_executable, '-c', command],
2123+ env=os.environ):
2124+ # the other executable does not have setuptools. Get setuptools.
2125+ # We will do this using the same tools we are testing, for better or
2126+ # worse. Alternatively, we could try using bootstrap.
2127+ executable_dir = test.globs['tmpdir']('executable_dir')
2128+ ws = zc.buildout.easy_install.install(
2129+ ['setuptools'], executable_dir,
2130+ index='http://www.python.org/pypi/',
2131+ always_unzip=True, executable=other_executable)
2132+ zc.buildout.easy_install.scripts(
2133+ ['setuptools'], ws, other_executable, executable_dir,
2134+ interpreter='py')
2135+ original_executable = other_executable
2136+ other_executable = os.path.join(executable_dir, 'py')
2137+ assert not subprocess.call(
2138+ [other_executable, '-c', command], env=os.environ), (
2139+ 'test set up failed')
2140 sample_eggs = test.globs['tmpdir']('sample_eggs')
2141 os.mkdir(os.path.join(sample_eggs, 'index'))
2142 test.globs['sample_eggs'] = sample_eggs
2143
2144=== modified file 'src/zc/buildout/unzip.txt'
2145--- src/zc/buildout/unzip.txt 2008-07-18 19:51:43 +0000
2146+++ src/zc/buildout/unzip.txt 2009-07-08 13:10:16 +0000
2147@@ -21,9 +21,8 @@
2148 d setuptools-0.6c8-py2.4.egg
2149 - zc.buildout.egg-link
2150
2151-This follows the
2152-policy followed by setuptools itself. Experience shows this policy
2153-to to be inconvenient. Zipped eggs make debugging more difficult and
2154+This follows the policy followed by setuptools itself. Experience shows this
2155+policy to to be inconvenient. Zipped eggs make debugging more difficult and
2156 often import more slowly.
2157
2158 You can include an unzip option in the buildout section to change the
2159
2160=== modified file 'src/zc/buildout/update.txt'
2161--- src/zc/buildout/update.txt 2008-07-15 22:01:29 +0000
2162+++ src/zc/buildout/update.txt 2009-07-07 14:40:47 +0000
2163@@ -80,14 +80,15 @@
2164
2165 Our buildout script has been updated to use the new eggs:
2166
2167- >>> cat(sample_buildout, 'bin', 'buildout')
2168+ >>> cat(sample_buildout, 'bin', 'buildout') # doctest: +ELLIPSIS
2169 #!/usr/local/bin/python2.4
2170 <BLANKLINE>
2171 import sys
2172- sys.path[0:0] = [
2173- '/sample-buildout/eggs/zc.buildout-99.99-py2.4.egg',
2174- '/sample-buildout/eggs/setuptools-99.99-py2.4.egg',
2175- ]
2176+ sys.path[:] = [
2177+ '/sample-buildout/eggs/zc.buildout-99.99-py2.4.egg',
2178+ '/sample-buildout/eggs/setuptools-99.99-py2.4.egg',
2179+ ...
2180+ ]
2181 <BLANKLINE>
2182 import zc.buildout.buildout
2183 <BLANKLINE>
2184
2185=== modified file 'zc.recipe.egg_/setup.py'
2186--- zc.recipe.egg_/setup.py 2009-03-18 18:57:12 +0000
2187+++ zc.recipe.egg_/setup.py 2009-07-09 18:17:00 +0000
2188@@ -16,7 +16,7 @@
2189 $Id$
2190 """
2191
2192-version = '0'
2193+version = '1.3.0dev'
2194
2195 import os
2196 from setuptools import setup, find_packages
2197@@ -66,7 +66,7 @@
2198 package_dir = {'':'src'},
2199 namespace_packages = ['zc', 'zc.recipe'],
2200 install_requires = [
2201- 'zc.buildout >=1.2.0',
2202+ 'zc.buildout >=1.4.0dev',
2203 'setuptools'],
2204 tests_require = ['zope.testing'],
2205 test_suite = name+'.tests.test_suite',
2206
2207=== modified file 'zc.recipe.egg_/src/zc/recipe/egg/README.txt'
2208--- zc.recipe.egg_/src/zc/recipe/egg/README.txt 2009-06-22 16:41:40 +0000
2209+++ zc.recipe.egg_/src/zc/recipe/egg/README.txt 2009-07-07 14:40:47 +0000
2210@@ -372,16 +372,18 @@
2211
2212 Let's look at the script that was generated:
2213
2214- >>> cat(sample_buildout, 'bin', 'foo') # doctest: +NORMALIZE_WHITESPACE
2215+ >>> cat(sample_buildout, 'bin', 'foo')
2216+ ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
2217 #!/usr/local/bin/python2.4
2218 <BLANKLINE>
2219 import sys
2220- sys.path[0:0] = [
2221- '/sample-buildout/eggs/demo-0.4c1-py2.4.egg',
2222- '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
2223- '/foo/bar',
2224- '/sample-buildout/spam',
2225- ]
2226+ sys.path[:] = [
2227+ '/sample-buildout/eggs/demo-0.4c1-py2.4.egg',
2228+ '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
2229+ '/foo/bar',
2230+ '/sample-buildout/spam',
2231+ ...
2232+ ]
2233 <BLANKLINE>
2234 import eggrecipedemo
2235 <BLANKLINE>
2236@@ -419,7 +421,8 @@
2237
2238 Let's look at the script that was generated:
2239
2240- >>> cat(sample_buildout, 'bin', 'foo') # doctest: +NORMALIZE_WHITESPACE
2241+ >>> cat(sample_buildout, 'bin', 'foo')
2242+ ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
2243 #!/usr/local/bin/python2.4
2244 <BLANKLINE>
2245 import os
2246@@ -429,12 +432,13 @@
2247 base = os.path.dirname(base)
2248 <BLANKLINE>
2249 import sys
2250- sys.path[0:0] = [
2251- join(base, 'eggs/demo-0.4c1-pyN.N.egg'),
2252- join(base, 'eggs/demoneeded-1.2c1-pyN.N.egg'),
2253- '/foo/bar',
2254- join(base, 'spam'),
2255- ]
2256+ sys.path[:] = [
2257+ join(base, 'eggs/demo-0.4c1-pyN.N.egg'),
2258+ join(base, 'eggs/demoneeded-1.2c1-pyN.N.egg'),
2259+ '/foo/bar',
2260+ join(base, 'spam'),
2261+ ...
2262+ ]
2263 <BLANKLINE>
2264 import eggrecipedemo
2265 <BLANKLINE>
2266@@ -466,7 +470,8 @@
2267 Installing demo.
2268 Generated script '/sample-buildout/bin/foo'.
2269
2270- >>> cat(sample_buildout, 'bin', 'foo') # doctest: +NORMALIZE_WHITESPACE
2271+ >>> cat(sample_buildout, 'bin', 'foo')
2272+ ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
2273 #!/usr/local/bin/python2.4
2274 <BLANKLINE>
2275 import os
2276@@ -476,12 +481,13 @@
2277 base = os.path.dirname(base)
2278 <BLANKLINE>
2279 import sys
2280- sys.path[0:0] = [
2281- join(base, 'eggs/demo-0.4c1-pyN.N.egg'),
2282- join(base, 'eggs/demoneeded-1.2c1-pyN.N.egg'),
2283- '/foo/bar',
2284- join(base, 'spam'),
2285- ]
2286+ sys.path[:] = [
2287+ join(base, 'eggs/demo-0.4c1-pyN.N.egg'),
2288+ join(base, 'eggs/demoneeded-1.2c1-pyN.N.egg'),
2289+ '/foo/bar',
2290+ join(base, 'spam'),
2291+ ...
2292+ ]
2293 <BLANKLINE>
2294 import eggrecipedemo
2295 <BLANKLINE>
2296@@ -519,16 +525,18 @@
2297 Installing demo.
2298 Generated script '/sample-buildout/bin/foo'.
2299
2300- >>> cat(sample_buildout, 'bin', 'foo') # doctest: +NORMALIZE_WHITESPACE
2301+ >>> cat(sample_buildout, 'bin', 'foo')
2302+ ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
2303 #!/usr/local/bin/python2.4
2304 <BLANKLINE>
2305 import sys
2306- sys.path[0:0] = [
2307- '/sample-buildout/eggs/demo-0.4c1-py2.4.egg',
2308- '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
2309- '/foo/bar',
2310- '/sample-buildout/spam',
2311- ]
2312+ sys.path[:] = [
2313+ '/sample-buildout/eggs/demo-0.4c1-py2.4.egg',
2314+ '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
2315+ '/foo/bar',
2316+ '/sample-buildout/spam',
2317+ ...
2318+ ]
2319 <BLANKLINE>
2320 a = (1, 2
2321 3, 4)
2322@@ -577,16 +585,17 @@
2323 - demo
2324 - other
2325
2326- >>> cat(sample_buildout, 'bin', 'other')
2327+ >>> cat(sample_buildout, 'bin', 'other') # doctest: +ELLIPSIS
2328 #!/usr/local/bin/python2.4
2329 <BLANKLINE>
2330 import sys
2331- sys.path[0:0] = [
2332- '/sample-buildout/eggs/demo-0.4c1-py2.4.egg',
2333- '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
2334- '/foo/bar',
2335- '/sample-buildout/spam',
2336- ]
2337+ sys.path[:] = [
2338+ '/sample-buildout/eggs/demo-0.4c1-py2.4.egg',
2339+ '/sample-buildout/eggs/demoneeded-1.2c1-py2.4.egg',
2340+ '/foo/bar',
2341+ '/sample-buildout/spam',
2342+ ...
2343+ ]
2344 <BLANKLINE>
2345 import foo.bar
2346 <BLANKLINE>
2347
2348=== modified file 'zc.recipe.egg_/src/zc/recipe/egg/egg.py'
2349--- zc.recipe.egg_/src/zc/recipe/egg/egg.py 2009-03-17 12:09:25 +0000
2350+++ zc.recipe.egg_/src/zc/recipe/egg/egg.py 2009-07-06 23:14:39 +0000
2351@@ -54,6 +54,13 @@
2352
2353 python = options.get('python', buildout['buildout']['python'])
2354 options['executable'] = buildout[python]['executable']
2355+ include_site_packages = self.options.get(
2356+ 'include-site-packages',
2357+ self.buildout['buildout'].get('include-site-packages', 'true'))
2358+ if include_site_packages not in ('true', 'false'):
2359+ self._error('Invalid value for include-site-packages option: %s',
2360+ include_site_packages)
2361+ self.include_site_packages = (include_site_packages=='true')
2362
2363 def working_set(self, extra=()):
2364 """Separate method to just get the working set
2365@@ -72,7 +79,8 @@
2366 if self.buildout['buildout'].get('offline') == 'true':
2367 ws = zc.buildout.easy_install.working_set(
2368 distributions, options['executable'],
2369- [options['develop-eggs-directory'], options['eggs-directory']]
2370+ [options['develop-eggs-directory'], options['eggs-directory']],
2371+ include_site_packages=self.include_site_packages
2372 )
2373 else:
2374 kw = {}
2375@@ -81,12 +89,13 @@
2376
2377 ws = zc.buildout.easy_install.install(
2378 distributions, options['eggs-directory'],
2379- links = self.links,
2380- index = self.index,
2381- executable = options['executable'],
2382+ links=self.links,
2383+ index=self.index,
2384+ executable=options['executable'],
2385 path=[options['develop-eggs-directory']],
2386 newest=self.buildout['buildout'].get('newest') == 'true',
2387 allow_hosts=self.allow_hosts,
2388+ include_site_packages=self.include_site_packages,
2389 **kw)
2390
2391 return orig_distributions, ws
2392@@ -166,7 +175,8 @@
2393 interpreter=options.get('interpreter'),
2394 initialization=options.get('initialization', ''),
2395 arguments=options.get('arguments', ''),
2396- relative_paths=self._relative_paths,
2397+ include_site_packages=self.include_site_packages,
2398+ relative_paths=self._relative_paths
2399 )
2400
2401 return ()

Subscribers

People subscribed via source and target branches

to all changes: