Merge lp:~gary/zc.buildout/betafix1 into lp:zc.buildout

Proposed by Gary Poster
Status: Needs review
Proposed branch: lp:~gary/zc.buildout/betafix1
Merge into: lp:zc.buildout
Prerequisite: lp:~gary/zc.buildout/betafixstart
Diff against target: 359 lines (+91/-216)
3 files modified
bootstrap/bootstrap.py (+0/-206)
src/zc/buildout/easy_install.py (+22/-10)
src/zc/buildout/tests.py (+69/-0)
To merge this branch: bzr merge lp:~gary/zc.buildout/betafix1
Reviewer Review Type Date Requested Status
Francis J. Lacoste (community) Approve
Review via email: mp+27732@code.launchpad.net

Description of the change

This change addresses two problems identified with the allowed_eggs_from_site_packages feature, as partially identified by bug 592524. I think the tests pretty clearly describe the problems they are addressing.

The first non-whitespace change in easy_install.py addresses the "allowed_eggs_from_site_packages_dependencies_bugfix" test. Hopefully the comment explains what is going on.

The remaining changes in easy_install.py address the "allowed_eggs_from_site_packages_bug_592524" test. The key line is "path = [p for p in path if p not in site_paths]" and, as with the other fix, it is explained with a comment.

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

I'm not sure why the diff shows bootstrap as having been deleted.

betafix1$ bzr ls bootstrap/
bootstrap.py

Similarly, loggerhead shows the diff for the single revision that comprises the branch as not including a change to bootstrap:
http://bazaar.launchpad.net/~gary/zc.buildout/betafix1/revision/554

(Unfortunately, loggerhead is giving me an Internal Server Error when I try to go to the browse link on the branch page, http://bazaar.launchpad.net/~gary/zc.buildout/betafix1/files .)

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

Allow good here also.

review: Approve
Revision history for this message
Thomas Herve (therve) wrote :

This branch seems ready to land?

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

Thomas: Technically, yes. The problem is the fact that the buildout universe does not support betas well, and the last beta release for this branch caused a lot of problems. I have a plan to make the next release, and zc.buildout, better in this regard, but it involves more work.

Howvere, I can certainly make a local release easily, and now. If you would like me to move Launchpad to a new release so you all can use it too, I'm happy to: just let me know.

Gary

Unmerged revisions

554. By Gary Poster

Fix some problems with allowed_eggs_from_site_packages

553. By Gary Poster

get a baseline of zc.buildout with passing tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'bootstrap/bootstrap.py'
2--- bootstrap/bootstrap.py 2010-05-10 20:36:47 +0000
3+++ bootstrap/bootstrap.py 1970-01-01 00:00:00 +0000
4@@ -1,206 +0,0 @@
5-##############################################################################
6-#
7-# Copyright (c) 2006 Zope Foundation and Contributors.
8-# All Rights Reserved.
9-#
10-# This software is subject to the provisions of the Zope Public License,
11-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
12-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
13-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
15-# FOR A PARTICULAR PURPOSE.
16-#
17-##############################################################################
18-"""Bootstrap a buildout-based project
19-
20-Simply run this script in a directory containing a buildout.cfg.
21-The script accepts buildout command-line options, so you can
22-use the -c option to specify an alternate configuration file.
23-
24-$Id$
25-"""
26-
27-import os, shutil, sys, tempfile, textwrap, urllib, urllib2
28-from optparse import OptionParser
29-
30-if sys.platform == 'win32':
31- def quote(c):
32- if ' ' in c:
33- return '"%s"' % c # work around spawn lamosity on windows
34- else:
35- return c
36-else:
37- quote = str
38-
39-# In order to be more robust in the face of system Pythons, we want to
40-# run without site-packages loaded. This is somewhat tricky, in
41-# particular because Python 2.6's distutils imports site, so starting
42-# with the -S flag is not sufficient. However, we'll start with that:
43-if 'site' in sys.modules:
44- # We will restart with python -S.
45- args = sys.argv[:]
46- args[0:0] = [sys.executable, '-S']
47- args = map(quote, args)
48- os.execv(sys.executable, args)
49-# Now we are running with -S. We'll get the clean sys.path, import site
50-# because distutils will do it later, and then reset the path and clean
51-# out any namespace packages from site-packages that might have been
52-# loaded by .pth files.
53-clean_path = sys.path[:]
54-import site
55-sys.path[:] = clean_path
56-for k, v in sys.modules.items():
57- if (hasattr(v, '__path__') and
58- len(v.__path__)==1 and
59- not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
60- # This is a namespace package. Remove it.
61- sys.modules.pop(k)
62-
63-is_jython = sys.platform.startswith('java')
64-
65-setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
66-distribute_source = 'http://python-distribute.org/distribute_setup.py'
67-
68-# parsing arguments
69-def normalize_to_url(option, opt_str, value, parser):
70- if value:
71- if '://' not in value: # It doesn't smell like a URL.
72- value = 'file://%s' % (
73- urllib.pathname2url(
74- os.path.abspath(os.path.expanduser(value))),)
75- if opt_str == '--download-base' and not value.endswith('/'):
76- # Download base needs a trailing slash to make the world happy.
77- value += '/'
78- else:
79- value = None
80- name = opt_str[2:].replace('-', '_')
81- setattr(parser.values, name, value)
82-
83-usage = '''\
84-[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
85-
86-Bootstraps a buildout-based project.
87-
88-Simply run this script in a directory containing a buildout.cfg, using the
89-Python that you want bin/buildout to use.
90-
91-Note that by using --setup-source and --download-base to point to
92-local resources, you can keep this script from going over the network.
93-'''
94-
95-parser = OptionParser(usage=usage)
96-parser.add_option("-v", "--version", dest="version",
97- help="use a specific zc.buildout version")
98-parser.add_option("-d", "--distribute",
99- action="store_true", dest="use_distribute", default=False,
100- help="Use Distribute rather than Setuptools.")
101-parser.add_option("--setup-source", action="callback", dest="setup_source",
102- callback=normalize_to_url, nargs=1, type="string",
103- help=("Specify a URL or file location for the setup file. "
104- "If you use Setuptools, this will default to " +
105- setuptools_source + "; if you use Distribute, this "
106- "will default to " + distribute_source +"."))
107-parser.add_option("--download-base", action="callback", dest="download_base",
108- callback=normalize_to_url, nargs=1, type="string",
109- help=("Specify a URL or directory for downloading "
110- "zc.buildout and either Setuptools or Distribute. "
111- "Defaults to PyPI."))
112-parser.add_option("--eggs",
113- help=("Specify a directory for storing eggs. Defaults to "
114- "a temporary directory that is deleted when the "
115- "bootstrap script completes."))
116-parser.add_option("-c", None, action="store", dest="config_file",
117- help=("Specify the path to the buildout configuration "
118- "file to be used."))
119-
120-options, args = parser.parse_args()
121-
122-# if -c was provided, we push it back into args for buildout's main function
123-if options.config_file is not None:
124- args += ['-c', options.config_file]
125-
126-if options.eggs:
127- eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
128-else:
129- eggs_dir = tempfile.mkdtemp()
130-
131-if options.setup_source is None:
132- if options.use_distribute:
133- options.setup_source = distribute_source
134- else:
135- options.setup_source = setuptools_source
136-
137-args = args + ['bootstrap']
138-
139-
140-try:
141- to_reload = False
142- import pkg_resources
143- to_reload = True
144- if not hasattr(pkg_resources, '_distribute'):
145- raise ImportError
146- import setuptools # A flag. Sometimes pkg_resources is installed alone.
147-except ImportError:
148- ez_code = urllib2.urlopen(
149- options.setup_source).read().replace('\r\n', '\n')
150- ez = {}
151- exec ez_code in ez
152- setup_args = dict(to_dir=eggs_dir, download_delay=0)
153- if options.download_base:
154- setup_args['download_base'] = options.download_base
155- if options.use_distribute:
156- setup_args['no_fake'] = True
157- ez['use_setuptools'](**setup_args)
158- if to_reload:
159- reload(pkg_resources)
160- else:
161- import pkg_resources
162- # This does not (always?) update the default working set. We will
163- # do it.
164- for path in sys.path:
165- if path not in pkg_resources.working_set.entries:
166- pkg_resources.working_set.add_entry(path)
167-
168-cmd = [quote(sys.executable),
169- '-c',
170- quote('from setuptools.command.easy_install import main; main()'),
171- '-mqNxd',
172- quote(eggs_dir)]
173-
174-if options.download_base:
175- cmd.extend(['-f', quote(options.download_base)])
176-
177-requirement = 'zc.buildout'
178-if options.version:
179- requirement = '=='.join((requirement, options.version))
180-cmd.append(requirement)
181-
182-if options.use_distribute:
183- setup_requirement = 'distribute'
184-else:
185- setup_requirement = 'setuptools'
186-ws = pkg_resources.working_set
187-env = dict(
188- os.environ,
189- PYTHONPATH=ws.find(
190- pkg_resources.Requirement.parse(setup_requirement)).location)
191-
192-if is_jython:
193- import subprocess
194- exitcode = subprocess.Popen(cmd, env=env).wait()
195-else: # Windows prefers this, apparently; otherwise we would prefer subprocess
196- exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
197-if exitcode != 0:
198- sys.stdout.flush()
199- sys.stderr.flush()
200- print ("An error occured when trying to install zc.buildout. "
201- "Look above this message for any errors that "
202- "were output by easy_install.")
203- sys.exit(exitcode)
204-
205-ws.add_entry(eggs_dir)
206-ws.require(requirement)
207-import zc.buildout.buildout
208-zc.buildout.buildout.main(args)
209-if not options.eggs: # clean up temporary egg directory
210- shutil.rmtree(eggs_dir)
211
212=== modified file 'src/zc/buildout/easy_install.py'
213--- src/zc/buildout/easy_install.py 2010-04-29 18:11:45 +0000
214+++ src/zc/buildout/easy_install.py 2010-06-16 15:10:44 +0000
215@@ -58,7 +58,6 @@
216 import java.lang.System
217 jython_os_name = (java.lang.System.getProperties()['os.name']).lower()
218
219-
220 setuptools_loc = pkg_resources.working_set.find(
221 pkg_resources.Requirement.parse('setuptools')
222 ).location
223@@ -873,7 +872,13 @@
224 dist = best[req.key] = env.best_match(req, ws)
225 except pkg_resources.VersionConflict, err:
226 raise VersionConflict(err, ws)
227- if dist is None:
228+ if dist is None or (
229+ dist.location in self._site_packages and not
230+ self.allow_site_package_egg(dist.project_name)):
231+ # If we didn't find a distribution in the
232+ # environment, or what we found is from site
233+ # packages and not allowed to be there, try
234+ # again.
235 if destination:
236 logger.debug('Getting required %r', str(req))
237 else:
238@@ -1548,15 +1553,15 @@
239 """
240 path = _get_path(working_set, extra_paths)
241 site_path = os.path.join(dest, 'site.py')
242- egg_path_string, preamble = _relative_path_and_setup(
243- site_path, path, relative_paths, indent_level=2, omit_os_import=True)
244- if preamble:
245- preamble = '\n'.join(
246- [(line and ' %s' % (line,) or line)
247- for line in preamble.split('\n')])
248- original_path_setup = ''
249+ original_path_setup = preamble = ''
250 if include_site_packages:
251 stdlib, site_paths = _get_system_paths(executable)
252+ # We want to make sure that paths from site-packages, such as those
253+ # allowed by allowed_eggs_from_site_packages, always come last, or
254+ # else site-packages paths may include packages that mask the eggs we
255+ # really want.
256+ path = [p for p in path if p not in site_paths]
257+ # Now we set up the code we need.
258 original_path_setup = original_path_snippet % (
259 _format_paths((repr(p) for p in site_paths), 2),)
260 distribution = working_set.find(
261@@ -1570,10 +1575,17 @@
262 relative_paths)
263 else:
264 location = repr(distribution.location)
265- preamble += namespace_include_site_packages_setup % (location,)
266+ preamble = namespace_include_site_packages_setup % (location,)
267 original_path_setup = (
268 addsitedir_namespace_originalpackages_snippet +
269 original_path_setup)
270+ egg_path_string, relative_preamble = _relative_path_and_setup(
271+ site_path, path, relative_paths, indent_level=2, omit_os_import=True)
272+ if relative_preamble:
273+ relative_preamble = '\n'.join(
274+ [(line and ' %s' % (line,) or line)
275+ for line in relative_preamble.split('\n')])
276+ preamble = relative_preamble + preamble
277 addsitepackages_marker = 'def addsitepackages('
278 enableusersite_marker = 'ENABLE_USER_SITE = '
279 successful_rewrite = False
280
281=== modified file 'src/zc/buildout/tests.py'
282--- src/zc/buildout/tests.py 1970-01-01 00:00:00 +0000
283+++ src/zc/buildout/tests.py 2010-06-16 15:10:44 +0000
284@@ -2255,6 +2255,75 @@
285
286 """
287
288+def allowed_eggs_from_site_packages_dependencies_bugfix():
289+ """
290+If you specify that a package with a dependency may come from site-packages,
291+that doesn't mean that the dependency may come from site-packages. This
292+is a test for a bug fix to verify that this is true.
293+
294+ >>> py_path, site_packages_path = make_py()
295+ >>> create_sample_sys_install(site_packages_path)
296+ >>> interpreter_dir = tmpdir('interpreter')
297+ >>> interpreter_parts_dir = os.path.join(
298+ ... interpreter_dir, 'parts', 'interpreter')
299+ >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin')
300+ >>> mkdir(interpreter_bin_dir)
301+ >>> mkdir(interpreter_dir, 'eggs')
302+ >>> mkdir(interpreter_dir, 'parts')
303+ >>> mkdir(interpreter_parts_dir)
304+ >>> ws = zc.buildout.easy_install.install(
305+ ... ['demo'], join(interpreter_dir, 'eggs'), executable=py_path,
306+ ... links=[link_server], index=link_server+'index/',
307+ ... allowed_eggs_from_site_packages=['demo'])
308+ >>> [dist.project_name for dist in ws]
309+ ['demo', 'demoneeded']
310+ >>> from pprint import pprint
311+ >>> pprint([dist.location for dist in ws])
312+ ['/executable_buildout/site-packages',
313+ '/interpreter/eggs/demoneeded-1.1-pyN.N.egg']
314+
315+ """
316+
317+def allowed_eggs_from_site_packages_bug_592524():
318+ """
319+When we use allowed_eggs_from_site_packages, we need to make sure that the
320+site-packages paths are not inserted with the normal egg paths. They already
321+included at the end, and including them along with the normal egg paths will
322+possibly mask subsequent egg paths. This affects interpreters and scripts
323+generated by sitepackage_safe_scripts.
324+
325+Our "py_path" has the "demoneeded" and "demo" packages available.
326+
327+ >>> py_path, site_packages_path = make_py()
328+ >>> create_sample_sys_install(site_packages_path)
329+ >>> interpreter_dir = tmpdir('interpreter')
330+ >>> interpreter_parts_dir = os.path.join(
331+ ... interpreter_dir, 'parts', 'interpreter')
332+ >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin')
333+ >>> mkdir(interpreter_bin_dir)
334+ >>> mkdir(interpreter_dir, 'eggs')
335+ >>> mkdir(interpreter_dir, 'parts')
336+ >>> mkdir(interpreter_parts_dir)
337+ >>> ws = zc.buildout.easy_install.install(
338+ ... ['demo', 'other'], join(interpreter_dir, 'eggs'), executable=py_path,
339+ ... links=[link_server], index=link_server+'index/',
340+ ... allowed_eggs_from_site_packages=['demo'])
341+ >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts(
342+ ... interpreter_bin_dir, ws, py_path, interpreter_parts_dir,
343+ ... interpreter='py', include_site_packages=True)
344+
345+Now we will look at the paths in the site.py we generated. Notice that the
346+site-packages are at the end. They were not before this bugfix.
347+
348+ >>> test = 'import pprint, sys; pprint.pprint(sys.path[-4:])'
349+ >>> print call_py(join(interpreter_bin_dir, 'py'), test)
350+ ['/interpreter/eggs/other-1.0-pyN.N.egg',
351+ '/interpreter/eggs/demoneeded-1.1-pyN.N.egg',
352+ '/executable_buildout/eggs/setuptools-0.0-pyN.N.egg',
353+ '/executable_buildout/site-packages']
354+ <BLANKLINE>
355+ """
356+
357 def subprocesses_have_same_environment_by_default():
358 """
359 The scripts generated by sitepackage_safe_scripts set the PYTHONPATH so that,

Subscribers

People subscribed via source and target branches

to all changes: