Merge lp:~james-w/pkgme-service-python/buildout into lp:pkgme-service-python
- buildout
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Jonathan Lange |
Approved revision: | 21 |
Merged at revision: | 17 |
Proposed branch: | lp:~james-w/pkgme-service-python/buildout |
Merge into: | lp:pkgme-service-python |
Diff against target: |
1051 lines (+932/-37) 9 files modified
.bzrignore (+6/-4) HACKING (+28/-6) Makefile (+59/-17) bootstrap.py (+262/-0) buildout.cfg (+36/-0) distribute_setup.py (+515/-0) tarmac_tests.sh (+2/-5) test-dependencies.txt (+0/-5) versions.cfg (+24/-0) |
To merge this branch: | bzr merge lp:~james-w/pkgme-service-python/buildout |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jonathan Lange (community) | Approve | ||
Review via email: mp+116980@code.launchpad.net |
Commit message
Switch to buildout.
Description of the change
Hi,
Here's a stab at moving a project to buildout.
Some issues:
* buildout is very tempremental trying to bootstrap.
- If you have it installed system-wide then you can't use the
bootstrap.py. You can work around this by doing the bootstrap
in a virtualenv, but that's not particularly nice.
- This branch uses the system-wide one. I think the drawback of that
is that everyone will have to use exactly the same version.
- I can't really work out how LP is avoiding these issues. I suspect
they aren't.
* The Makefile dependencies are a little off, e.g. after a bootstrap
a check will do a quick buildout run, and subsequent runs won't.
* There's no depenencies branch to use yet.
* I don't know if we can speed things up with sharing the eggs dir between
branches. As it stands this branch is a few seconds slower to bootstrap
than trunk. I don't know if that will reverse on larger projects (I
suspect it will though.)
I'm mainly posting this for discussion at this point.
Thanks,
James
- 18. By James Westby
-
Switch to distribute and add a note about site-wide buildout.
James Westby (james-w) wrote : | # |
- 19. By James Westby
-
Symlink the eggs by default.
James Westby (james-w) wrote : | # |
and yes we can share eggs (link-external-
It speeds up subsequent bootstraps immensely, and there's little
risk of contamination.
I've taken a stab at doing it by default with no extra steps, but
I'm not sure if using the parent dir will be appreciated.
Thanks,
James
Jonathan Lange (jml) wrote : | # |
Looks good. Thanks for exploring this! I've pinged benji & gary_poster on IRC to take a look as well.
Is the shared eggs directory is inherited from the environment? ISTR buildout has a facility for this already, but I can't recall exactly?
Also, in case you haven't read it, http://
Thanks,
jml
Jonathan Lange (jml) wrote : | # |
* EGGS_DIR being set works.
* Works for me.
* Probably should add lp:ca-download-cache step to either Makefile or documentation
- 20. By James Westby
-
Checkout the download-cache too, and use more specific variable names.
- 21. By James Westby
-
Explain the env vars in the HACKING doc.
James Westby (james-w) wrote : | # |
On Mon, 30 Jul 2012 16:12:20 -0000, Jonathan Lange <email address hidden> wrote:
> * Probably should add lp:ca-download-cache step to either Makefile or documentation
Added to both. I also made the names of the env vars more specific.
Thanks,
James
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2012-07-19 16:09:51 +0000 |
3 | +++ .bzrignore 2012-07-30 20:14:17 +0000 |
4 | @@ -1,7 +1,9 @@ |
5 | -_trial_temp |
6 | -build |
7 | -virtualenv |
8 | -virtualenv.tarmac |
9 | log |
10 | pkgme_service_python.egg-info |
11 | credentials |
12 | +.installed.cfg |
13 | +bin |
14 | +develop-eggs |
15 | +download-cache |
16 | +eggs |
17 | +parts |
18 | |
19 | === modified file 'HACKING' |
20 | --- HACKING 2012-07-19 14:22:06 +0000 |
21 | +++ HACKING 2012-07-30 20:14:17 +0000 |
22 | @@ -14,7 +14,7 @@ |
23 | Running the tests |
24 | ================= |
25 | |
26 | -Development of pkgme-service-python is done within a virtualenv build |
27 | +Development of pkgme-service-python is done within a buildout build |
28 | environment. You will need to create one of these in order to run the tests. |
29 | |
30 | In a checkout of `lp:pkgme-service-python |
31 | @@ -23,9 +23,25 @@ |
32 | $ make bootstrap |
33 | $ make check |
34 | |
35 | -You may wish to run ``. virtualenv/bin/activate`` to be able to run e.g. |
36 | -``python`` and have the project and all dependencies importable. |
37 | - |
38 | +The bootstrap will fail if you have a system-wide install of buildout that |
39 | +is the same version as the one in use by this project. (You will see |
40 | +``DistributionNotFound: zc.buildout==<version>``). If you encounter |
41 | +that then you can either remove the site-wide install, or use a virtualenv |
42 | +to run the bootstrap step. |
43 | + |
44 | +You can get a shell to try code interactively by running ``./bin/py``. |
45 | + |
46 | +Buildout uses two directories as caches that can be shared between branches. |
47 | +The first is the ``download-cache`` directory. This contains all of the |
48 | +distributions of the Python dependencies. You can get this from |
49 | +``lp:ca-download-cache``, but the Makefile will grab it for you. |
50 | + |
51 | +The other directory is the ``eggs`` directory that holds built versions |
52 | +of the dependencies. |
53 | + |
54 | +The default for both of these is to symlink them from the parent directory, |
55 | +but if you wish to put them somewhere else you can set the locations with |
56 | +the ``CA_DOWNLOAD_CACHE_DIR`` and ``CA_EGGS_DIR`` environment variables. |
57 | |
58 | Bug tracker |
59 | =========== |
60 | @@ -94,8 +110,14 @@ |
61 | Dependencies are tracked in two different places: |
62 | |
63 | 1. ``setup.py`` lists the key dependencies of the package |
64 | -1. ``test-dependencies.txt`` lists dependencies that are only used by the |
65 | - suite. |
66 | +1. ``buildout.cfg`` lists dependencies that are only used by the |
67 | + test suite. |
68 | + |
69 | +The versions of all dependencies are chosen in ``versions.cfg``. |
70 | +To change the version in use you should |
71 | + |
72 | +1. Change the version in ``versions.cfg``. |
73 | +1. Run ``make update-deps``. |
74 | |
75 | |
76 | How to release |
77 | |
78 | === modified file 'Makefile' |
79 | --- Makefile 2012-07-11 12:02:57 +0000 |
80 | +++ Makefile 2012-07-30 20:14:17 +0000 |
81 | @@ -12,27 +12,69 @@ |
82 | # You should have received a copy of the GNU General Public License |
83 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
84 | |
85 | -VIRTUALENV=virtualenv |
86 | -VIRTUALENV_DIR?=virtualenv |
87 | -VIRTUALENV_PIP=$(VIRTUALENV_DIR)/bin/pip |
88 | -VIRTUALENV_PYTHON=$(VIRTUALENV_DIR)/bin/python |
89 | +EXTERNAL_PY?=python2.7 |
90 | +PY=./bin/py |
91 | + |
92 | +EXTERNAL_BUILDOUT?=$(EXTERNAL_PY) bootstrap.py |
93 | +BUILDOUT=./bin/buildout |
94 | + |
95 | +CA_EGGS_DIR?=../eggs |
96 | +CA_DOWNLOAD_CACHE_DIR?=../download-cache |
97 | |
98 | clean: |
99 | - [ ! -d $(VIRTUALENV_DIR) ] || rm -r $(VIRTUALENV_DIR) |
100 | - |
101 | -fetch-deps: $(VIRTUALENV_DIR) |
102 | - $(VIRTUALENV_PIP) install -r test-dependencies.txt |
103 | - $(VIRTUALENV_PYTHON) setup.py develop |
104 | - |
105 | -$(VIRTUALENV_DIR): |
106 | - $(VIRTUALENV) --no-site-packages $(VIRTUALENV_DIR) |
107 | - |
108 | -bootstrap: clean fetch-deps |
109 | - |
110 | -test: $(VIRTUALENV_DIR) |
111 | - $(VIRTUALENV_PYTHON) -m testtools.run discover pkgme_service_client |
112 | + $(RM) -r bin |
113 | + $(RM) -r develop-eggs |
114 | +ifeq ($(CA_EGGS_DIR),eggs) |
115 | + $(RM) -r eggs |
116 | +else |
117 | + $(RM) eggs |
118 | +endif |
119 | +ifeq ($(CA_DOWNLOAD_CACHE_DIR),download-cache) |
120 | + $(RM) -r download-cache |
121 | +else |
122 | + $(RM) download-cache |
123 | +endif |
124 | + $(RM) -r parts |
125 | + $(RM) -r .installed.cfg |
126 | + |
127 | +eggs: |
128 | +ifeq ($(CA_EGGS_DIR),$@) |
129 | + mkdir $@ |
130 | +else |
131 | + [ -d $(CA_EGGS_DIR) ] || mkdir $(CA_EGGS_DIR) |
132 | + ln -s $(CA_EGGS_DIR) $@ |
133 | +endif |
134 | + |
135 | +download-cache: |
136 | +ifeq ($(CA_DOWNLOAD_CACHE_DIR),$@) |
137 | + bzr checkout lp:ca-download-cache $@ |
138 | +else |
139 | + [ -d $(CA_DOWNLOAD_CACHE_DIR) ] || bzr checkout lp:ca-download-cache $(CA_DOWNLOAD_CACHE_DIR) |
140 | + ln -s $(CA_DOWNLOAD_CACHE_DIR) $@ |
141 | +endif |
142 | + |
143 | +$(BUILDOUT): bootstrap.py distribute_setup.py eggs download-cache |
144 | + $(EXTERNAL_BUILDOUT) --distribute --setup-source distribute_setup.py \ |
145 | + --download-base=download-cache/dist --eggs=eggs \ |
146 | + --version 1.5.1 |
147 | + touch --no-create $@ |
148 | + |
149 | +$(PY): $(BUILDOUT) |
150 | + $(BUILDOUT) |
151 | + touch --no-create $@ |
152 | + |
153 | +bootstrap: clean $(PY) |
154 | + |
155 | +test: $(PY) |
156 | + $(PY) -m testtools.run discover pkgme_service_client |
157 | |
158 | check: test |
159 | |
160 | +# Can be used to update the dependencies when you change something |
161 | +# in versions.cfg. |
162 | +update-deps: $(BUILDOUT) |
163 | + $(BUILDOUT) buildout:install-from-cache=false |
164 | + |
165 | |
166 | .PHONY: bootstrap test check clean fetch-deps |
167 | +.DEFAULT_GOAL := $(PY) |
168 | |
169 | === added file 'bootstrap.py' |
170 | --- bootstrap.py 1970-01-01 00:00:00 +0000 |
171 | +++ bootstrap.py 2012-07-30 20:14:17 +0000 |
172 | @@ -0,0 +1,262 @@ |
173 | +############################################################################## |
174 | +# |
175 | +# Copyright (c) 2006 Zope Foundation and Contributors. |
176 | +# All Rights Reserved. |
177 | +# |
178 | +# This software is subject to the provisions of the Zope Public License, |
179 | +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. |
180 | +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED |
181 | +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
182 | +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
183 | +# FOR A PARTICULAR PURPOSE. |
184 | +# |
185 | +############################################################################## |
186 | +"""Bootstrap a buildout-based project |
187 | + |
188 | +Simply run this script in a directory containing a buildout.cfg. |
189 | +The script accepts buildout command-line options, so you can |
190 | +use the -c option to specify an alternate configuration file. |
191 | +""" |
192 | + |
193 | +import os, shutil, sys, tempfile, urllib, urllib2, subprocess |
194 | +from optparse import OptionParser |
195 | + |
196 | +if sys.platform == 'win32': |
197 | + def quote(c): |
198 | + if ' ' in c: |
199 | + return '"%s"' % c # work around spawn lamosity on windows |
200 | + else: |
201 | + return c |
202 | +else: |
203 | + quote = str |
204 | + |
205 | +# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. |
206 | +stdout, stderr = subprocess.Popen( |
207 | + [sys.executable, '-Sc', |
208 | + 'try:\n' |
209 | + ' import ConfigParser\n' |
210 | + 'except ImportError:\n' |
211 | + ' print 1\n' |
212 | + 'else:\n' |
213 | + ' print 0\n'], |
214 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() |
215 | +has_broken_dash_S = bool(int(stdout.strip())) |
216 | + |
217 | +# In order to be more robust in the face of system Pythons, we want to |
218 | +# run without site-packages loaded. This is somewhat tricky, in |
219 | +# particular because Python 2.6's distutils imports site, so starting |
220 | +# with the -S flag is not sufficient. However, we'll start with that: |
221 | +if not has_broken_dash_S and 'site' in sys.modules: |
222 | + # We will restart with python -S. |
223 | + args = sys.argv[:] |
224 | + args[0:0] = [sys.executable, '-S'] |
225 | + args = map(quote, args) |
226 | + os.execv(sys.executable, args) |
227 | +# Now we are running with -S. We'll get the clean sys.path, import site |
228 | +# because distutils will do it later, and then reset the path and clean |
229 | +# out any namespace packages from site-packages that might have been |
230 | +# loaded by .pth files. |
231 | +clean_path = sys.path[:] |
232 | +import site # imported because of its side effects |
233 | +sys.path[:] = clean_path |
234 | +for k, v in sys.modules.items(): |
235 | + if k in ('setuptools', 'pkg_resources') or ( |
236 | + hasattr(v, '__path__') and |
237 | + len(v.__path__) == 1 and |
238 | + not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))): |
239 | + # This is a namespace package. Remove it. |
240 | + sys.modules.pop(k) |
241 | + |
242 | +is_jython = sys.platform.startswith('java') |
243 | + |
244 | +setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' |
245 | +distribute_source = 'http://python-distribute.org/distribute_setup.py' |
246 | + |
247 | + |
248 | +# parsing arguments |
249 | +def normalize_to_url(option, opt_str, value, parser): |
250 | + if value: |
251 | + if '://' not in value: # It doesn't smell like a URL. |
252 | + value = 'file://%s' % ( |
253 | + urllib.pathname2url( |
254 | + os.path.abspath(os.path.expanduser(value))),) |
255 | + if opt_str == '--download-base' and not value.endswith('/'): |
256 | + # Download base needs a trailing slash to make the world happy. |
257 | + value += '/' |
258 | + else: |
259 | + value = None |
260 | + name = opt_str[2:].replace('-', '_') |
261 | + setattr(parser.values, name, value) |
262 | + |
263 | +usage = '''\ |
264 | +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] |
265 | + |
266 | +Bootstraps a buildout-based project. |
267 | + |
268 | +Simply run this script in a directory containing a buildout.cfg, using the |
269 | +Python that you want bin/buildout to use. |
270 | + |
271 | +Note that by using --setup-source and --download-base to point to |
272 | +local resources, you can keep this script from going over the network. |
273 | +''' |
274 | + |
275 | +parser = OptionParser(usage=usage) |
276 | +parser.add_option("-v", "--version", dest="version", |
277 | + help="use a specific zc.buildout version") |
278 | +parser.add_option("-d", "--distribute", |
279 | + action="store_true", dest="use_distribute", default=False, |
280 | + help="Use Distribute rather than Setuptools.") |
281 | +parser.add_option("--setup-source", action="callback", dest="setup_source", |
282 | + callback=normalize_to_url, nargs=1, type="string", |
283 | + help=("Specify a URL or file location for the setup file. " |
284 | + "If you use Setuptools, this will default to " + |
285 | + setuptools_source + "; if you use Distribute, this " |
286 | + "will default to " + distribute_source + ".")) |
287 | +parser.add_option("--download-base", action="callback", dest="download_base", |
288 | + callback=normalize_to_url, nargs=1, type="string", |
289 | + help=("Specify a URL or directory for downloading " |
290 | + "zc.buildout and either Setuptools or Distribute. " |
291 | + "Defaults to PyPI.")) |
292 | +parser.add_option("--eggs", |
293 | + help=("Specify a directory for storing eggs. Defaults to " |
294 | + "a temporary directory that is deleted when the " |
295 | + "bootstrap script completes.")) |
296 | +parser.add_option("-t", "--accept-buildout-test-releases", |
297 | + dest='accept_buildout_test_releases', |
298 | + action="store_true", default=False, |
299 | + help=("Normally, if you do not specify a --version, the " |
300 | + "bootstrap script and buildout gets the newest " |
301 | + "*final* versions of zc.buildout and its recipes and " |
302 | + "extensions for you. If you use this flag, " |
303 | + "bootstrap and buildout will get the newest releases " |
304 | + "even if they are alphas or betas.")) |
305 | +parser.add_option("-c", None, action="store", dest="config_file", |
306 | + help=("Specify the path to the buildout configuration " |
307 | + "file to be used.")) |
308 | + |
309 | +options, args = parser.parse_args() |
310 | + |
311 | +# if -c was provided, we push it back into args for buildout's main function |
312 | +if options.config_file is not None: |
313 | + args += ['-c', options.config_file] |
314 | + |
315 | +if options.eggs: |
316 | + eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) |
317 | +else: |
318 | + eggs_dir = tempfile.mkdtemp() |
319 | + |
320 | +if options.setup_source is None: |
321 | + if options.use_distribute: |
322 | + options.setup_source = distribute_source |
323 | + else: |
324 | + options.setup_source = setuptools_source |
325 | + |
326 | +if options.accept_buildout_test_releases: |
327 | + args.append('buildout:accept-buildout-test-releases=true') |
328 | +args.append('bootstrap') |
329 | + |
330 | +try: |
331 | + import pkg_resources |
332 | + import setuptools # A flag. Sometimes pkg_resources is installed alone. |
333 | + if not hasattr(pkg_resources, '_distribute'): |
334 | + raise ImportError |
335 | +except ImportError: |
336 | + ez_code = urllib2.urlopen( |
337 | + options.setup_source).read().replace('\r\n', '\n') |
338 | + ez = {} |
339 | + exec ez_code in ez |
340 | + setup_args = dict(to_dir=eggs_dir, download_delay=0) |
341 | + if options.download_base: |
342 | + setup_args['download_base'] = options.download_base |
343 | + if options.use_distribute: |
344 | + setup_args['no_fake'] = True |
345 | + ez['use_setuptools'](**setup_args) |
346 | + if 'pkg_resources' in sys.modules: |
347 | + reload(sys.modules['pkg_resources']) |
348 | + import pkg_resources |
349 | + # This does not (always?) update the default working set. We will |
350 | + # do it. |
351 | + for path in sys.path: |
352 | + if path not in pkg_resources.working_set.entries: |
353 | + pkg_resources.working_set.add_entry(path) |
354 | + |
355 | +cmd = [quote(sys.executable), |
356 | + '-c', |
357 | + quote('from setuptools.command.easy_install import main; main()'), |
358 | + '-mqNxd', |
359 | + quote(eggs_dir)] |
360 | + |
361 | +if not has_broken_dash_S: |
362 | + cmd.insert(1, '-S') |
363 | + |
364 | +find_links = options.download_base |
365 | +if not find_links: |
366 | + find_links = os.environ.get('bootstrap-testing-find-links') |
367 | +if find_links: |
368 | + cmd.extend(['-f', quote(find_links)]) |
369 | + |
370 | +if options.use_distribute: |
371 | + setup_requirement = 'distribute' |
372 | +else: |
373 | + setup_requirement = 'setuptools' |
374 | +ws = pkg_resources.working_set |
375 | +setup_requirement_path = ws.find( |
376 | + pkg_resources.Requirement.parse(setup_requirement)).location |
377 | +env = dict( |
378 | + os.environ, |
379 | + PYTHONPATH=setup_requirement_path) |
380 | + |
381 | +requirement = 'zc.buildout' |
382 | +version = options.version |
383 | +if version is None and not options.accept_buildout_test_releases: |
384 | + # Figure out the most recent final version of zc.buildout. |
385 | + import setuptools.package_index |
386 | + _final_parts = '*final-', '*final' |
387 | + |
388 | + def _final_version(parsed_version): |
389 | + for part in parsed_version: |
390 | + if (part[:1] == '*') and (part not in _final_parts): |
391 | + return False |
392 | + return True |
393 | + index = setuptools.package_index.PackageIndex( |
394 | + search_path=[setup_requirement_path]) |
395 | + if find_links: |
396 | + index.add_find_links((find_links,)) |
397 | + req = pkg_resources.Requirement.parse(requirement) |
398 | + if index.obtain(req) is not None: |
399 | + best = [] |
400 | + bestv = None |
401 | + for dist in index[req.project_name]: |
402 | + distv = dist.parsed_version |
403 | + if _final_version(distv): |
404 | + if bestv is None or distv > bestv: |
405 | + best = [dist] |
406 | + bestv = distv |
407 | + elif distv == bestv: |
408 | + best.append(dist) |
409 | + if best: |
410 | + best.sort() |
411 | + version = best[-1].version |
412 | +if version: |
413 | + requirement = '=='.join((requirement, version)) |
414 | +cmd.append(requirement) |
415 | + |
416 | +if is_jython: |
417 | + import subprocess |
418 | + exitcode = subprocess.Popen(cmd, env=env).wait() |
419 | +else: # Windows prefers this, apparently; otherwise we would prefer subprocess |
420 | + exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) |
421 | +if exitcode != 0: |
422 | + sys.stdout.flush() |
423 | + sys.stderr.flush() |
424 | + print ("An error occurred when trying to install zc.buildout. " |
425 | + "Look above this message for any errors that " |
426 | + "were output by easy_install.") |
427 | + sys.exit(exitcode) |
428 | + |
429 | +ws.add_entry(eggs_dir) |
430 | +ws.require(requirement) |
431 | +import zc.buildout.buildout |
432 | +zc.buildout.buildout.main(args) |
433 | +if not options.eggs: # clean up temporary egg directory |
434 | + shutil.rmtree(eggs_dir) |
435 | |
436 | === added file 'buildout.cfg' |
437 | --- buildout.cfg 1970-01-01 00:00:00 +0000 |
438 | +++ buildout.cfg 2012-07-30 20:14:17 +0000 |
439 | @@ -0,0 +1,36 @@ |
440 | +# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
441 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
442 | + |
443 | +[buildout] |
444 | +parts = |
445 | + scripts |
446 | +unzip = true |
447 | +eggs-directory = eggs |
448 | +download-cache = download-cache |
449 | +relative-paths = true |
450 | + |
451 | +# Disable this option temporarily if you want buildout to find software |
452 | +# dependencies *other* than those in our download-cache. Once you have the |
453 | +# desired software, reenable this option (and check in the new software to |
454 | +# lp:ca-dependencies if this is going to be reviewed/merged/deployed.) |
455 | +install-from-cache = true |
456 | + |
457 | +# This also will need to be temporarily disabled or changed for package |
458 | +# upgrades. Newly-added packages should also add their desired version number |
459 | +# to versions.cfg. |
460 | +extends = versions.cfg |
461 | + |
462 | +allow-picked-versions = false |
463 | + |
464 | +prefer-final = true |
465 | + |
466 | +develop = . |
467 | + |
468 | +[scripts] |
469 | +recipe = z3c.recipe.scripts |
470 | +# Test dependencies get added here |
471 | +eggs = pkgme-service-python |
472 | + mock |
473 | + testtools |
474 | +include-site-packages = false |
475 | +interpreter = py |
476 | |
477 | === added file 'distribute_setup.py' |
478 | --- distribute_setup.py 1970-01-01 00:00:00 +0000 |
479 | +++ distribute_setup.py 2012-07-30 20:14:17 +0000 |
480 | @@ -0,0 +1,515 @@ |
481 | +#!python |
482 | +"""Bootstrap distribute installation |
483 | + |
484 | +If you want to use setuptools in your package's setup.py, just include this |
485 | +file in the same directory with it, and add this to the top of your setup.py:: |
486 | + |
487 | + from distribute_setup import use_setuptools |
488 | + use_setuptools() |
489 | + |
490 | +If you want to require a specific version of setuptools, set a download |
491 | +mirror, or use an alternate download directory, you can do so by supplying |
492 | +the appropriate options to ``use_setuptools()``. |
493 | + |
494 | +This file can also be run as a script to install or upgrade setuptools. |
495 | +""" |
496 | +import os |
497 | +import sys |
498 | +import time |
499 | +import fnmatch |
500 | +import tempfile |
501 | +import tarfile |
502 | +from distutils import log |
503 | + |
504 | +try: |
505 | + from site import USER_SITE |
506 | +except ImportError: |
507 | + USER_SITE = None |
508 | + |
509 | +try: |
510 | + import subprocess |
511 | + |
512 | + def _python_cmd(*args): |
513 | + args = (sys.executable,) + args |
514 | + return subprocess.call(args) == 0 |
515 | + |
516 | +except ImportError: |
517 | + # will be used for python 2.3 |
518 | + def _python_cmd(*args): |
519 | + args = (sys.executable,) + args |
520 | + # quoting arguments if windows |
521 | + if sys.platform == 'win32': |
522 | + def quote(arg): |
523 | + if ' ' in arg: |
524 | + return '"%s"' % arg |
525 | + return arg |
526 | + args = [quote(arg) for arg in args] |
527 | + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 |
528 | + |
529 | +DEFAULT_VERSION = "0.6.28" |
530 | +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" |
531 | +SETUPTOOLS_FAKED_VERSION = "0.6c11" |
532 | + |
533 | +SETUPTOOLS_PKG_INFO = """\ |
534 | +Metadata-Version: 1.0 |
535 | +Name: setuptools |
536 | +Version: %s |
537 | +Summary: xxxx |
538 | +Home-page: xxx |
539 | +Author: xxx |
540 | +Author-email: xxx |
541 | +License: xxx |
542 | +Description: xxx |
543 | +""" % SETUPTOOLS_FAKED_VERSION |
544 | + |
545 | + |
546 | +def _install(tarball, install_args=()): |
547 | + # extracting the tarball |
548 | + tmpdir = tempfile.mkdtemp() |
549 | + log.warn('Extracting in %s', tmpdir) |
550 | + old_wd = os.getcwd() |
551 | + try: |
552 | + os.chdir(tmpdir) |
553 | + tar = tarfile.open(tarball) |
554 | + _extractall(tar) |
555 | + tar.close() |
556 | + |
557 | + # going in the directory |
558 | + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
559 | + os.chdir(subdir) |
560 | + log.warn('Now working in %s', subdir) |
561 | + |
562 | + # installing |
563 | + log.warn('Installing Distribute') |
564 | + if not _python_cmd('setup.py', 'install', *install_args): |
565 | + log.warn('Something went wrong during the installation.') |
566 | + log.warn('See the error message above.') |
567 | + finally: |
568 | + os.chdir(old_wd) |
569 | + |
570 | + |
571 | +def _build_egg(egg, tarball, to_dir): |
572 | + # extracting the tarball |
573 | + tmpdir = tempfile.mkdtemp() |
574 | + log.warn('Extracting in %s', tmpdir) |
575 | + old_wd = os.getcwd() |
576 | + try: |
577 | + os.chdir(tmpdir) |
578 | + tar = tarfile.open(tarball) |
579 | + _extractall(tar) |
580 | + tar.close() |
581 | + |
582 | + # going in the directory |
583 | + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
584 | + os.chdir(subdir) |
585 | + log.warn('Now working in %s', subdir) |
586 | + |
587 | + # building an egg |
588 | + log.warn('Building a Distribute egg in %s', to_dir) |
589 | + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) |
590 | + |
591 | + finally: |
592 | + os.chdir(old_wd) |
593 | + # returning the result |
594 | + log.warn(egg) |
595 | + if not os.path.exists(egg): |
596 | + raise IOError('Could not build the egg.') |
597 | + |
598 | + |
599 | +def _do_download(version, download_base, to_dir, download_delay): |
600 | + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' |
601 | + % (version, sys.version_info[0], sys.version_info[1])) |
602 | + if not os.path.exists(egg): |
603 | + tarball = download_setuptools(version, download_base, |
604 | + to_dir, download_delay) |
605 | + _build_egg(egg, tarball, to_dir) |
606 | + sys.path.insert(0, egg) |
607 | + import setuptools |
608 | + setuptools.bootstrap_install_from = egg |
609 | + |
610 | + |
611 | +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
612 | + to_dir=os.curdir, download_delay=15, no_fake=True): |
613 | + # making sure we use the absolute path |
614 | + to_dir = os.path.abspath(to_dir) |
615 | + was_imported = 'pkg_resources' in sys.modules or \ |
616 | + 'setuptools' in sys.modules |
617 | + try: |
618 | + try: |
619 | + import pkg_resources |
620 | + if not hasattr(pkg_resources, '_distribute'): |
621 | + if not no_fake: |
622 | + _fake_setuptools() |
623 | + raise ImportError |
624 | + except ImportError: |
625 | + return _do_download(version, download_base, to_dir, download_delay) |
626 | + try: |
627 | + pkg_resources.require("distribute>=" + version) |
628 | + return |
629 | + except pkg_resources.VersionConflict: |
630 | + e = sys.exc_info()[1] |
631 | + if was_imported: |
632 | + sys.stderr.write( |
633 | + "The required version of distribute (>=%s) is not available,\n" |
634 | + "and can't be installed while this script is running. Please\n" |
635 | + "install a more recent version first, using\n" |
636 | + "'easy_install -U distribute'." |
637 | + "\n\n(Currently using %r)\n" % (version, e.args[0])) |
638 | + sys.exit(2) |
639 | + else: |
640 | + del pkg_resources, sys.modules['pkg_resources'] # reload ok |
641 | + return _do_download(version, download_base, to_dir, |
642 | + download_delay) |
643 | + except pkg_resources.DistributionNotFound: |
644 | + return _do_download(version, download_base, to_dir, |
645 | + download_delay) |
646 | + finally: |
647 | + if not no_fake: |
648 | + _create_fake_setuptools_pkg_info(to_dir) |
649 | + |
650 | + |
651 | +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
652 | + to_dir=os.curdir, delay=15): |
653 | + """Download distribute from a specified location and return its filename |
654 | + |
655 | + `version` should be a valid distribute version number that is available |
656 | + as an egg for download under the `download_base` URL (which should end |
657 | + with a '/'). `to_dir` is the directory where the egg will be downloaded. |
658 | + `delay` is the number of seconds to pause before an actual download |
659 | + attempt. |
660 | + """ |
661 | + # making sure we use the absolute path |
662 | + to_dir = os.path.abspath(to_dir) |
663 | + try: |
664 | + from urllib.request import urlopen |
665 | + except ImportError: |
666 | + from urllib2 import urlopen |
667 | + tgz_name = "distribute-%s.tar.gz" % version |
668 | + url = download_base + tgz_name |
669 | + saveto = os.path.join(to_dir, tgz_name) |
670 | + src = dst = None |
671 | + if not os.path.exists(saveto): # Avoid repeated downloads |
672 | + try: |
673 | + log.warn("Downloading %s", url) |
674 | + src = urlopen(url) |
675 | + # Read/write all in one block, so we don't create a corrupt file |
676 | + # if the download is interrupted. |
677 | + data = src.read() |
678 | + dst = open(saveto, "wb") |
679 | + dst.write(data) |
680 | + finally: |
681 | + if src: |
682 | + src.close() |
683 | + if dst: |
684 | + dst.close() |
685 | + return os.path.realpath(saveto) |
686 | + |
687 | + |
688 | +def _no_sandbox(function): |
689 | + def __no_sandbox(*args, **kw): |
690 | + try: |
691 | + from setuptools.sandbox import DirectorySandbox |
692 | + if not hasattr(DirectorySandbox, '_old'): |
693 | + def violation(*args): |
694 | + pass |
695 | + DirectorySandbox._old = DirectorySandbox._violation |
696 | + DirectorySandbox._violation = violation |
697 | + patched = True |
698 | + else: |
699 | + patched = False |
700 | + except ImportError: |
701 | + patched = False |
702 | + |
703 | + try: |
704 | + return function(*args, **kw) |
705 | + finally: |
706 | + if patched: |
707 | + DirectorySandbox._violation = DirectorySandbox._old |
708 | + del DirectorySandbox._old |
709 | + |
710 | + return __no_sandbox |
711 | + |
712 | + |
713 | +def _patch_file(path, content): |
714 | + """Will backup the file then patch it""" |
715 | + existing_content = open(path).read() |
716 | + if existing_content == content: |
717 | + # already patched |
718 | + log.warn('Already patched.') |
719 | + return False |
720 | + log.warn('Patching...') |
721 | + _rename_path(path) |
722 | + f = open(path, 'w') |
723 | + try: |
724 | + f.write(content) |
725 | + finally: |
726 | + f.close() |
727 | + return True |
728 | + |
729 | +_patch_file = _no_sandbox(_patch_file) |
730 | + |
731 | + |
732 | +def _same_content(path, content): |
733 | + return open(path).read() == content |
734 | + |
735 | + |
736 | +def _rename_path(path): |
737 | + new_name = path + '.OLD.%s' % time.time() |
738 | + log.warn('Renaming %s into %s', path, new_name) |
739 | + os.rename(path, new_name) |
740 | + return new_name |
741 | + |
742 | + |
743 | +def _remove_flat_installation(placeholder): |
744 | + if not os.path.isdir(placeholder): |
745 | + log.warn('Unkown installation at %s', placeholder) |
746 | + return False |
747 | + found = False |
748 | + for file in os.listdir(placeholder): |
749 | + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): |
750 | + found = True |
751 | + break |
752 | + if not found: |
753 | + log.warn('Could not locate setuptools*.egg-info') |
754 | + return |
755 | + |
756 | + log.warn('Removing elements out of the way...') |
757 | + pkg_info = os.path.join(placeholder, file) |
758 | + if os.path.isdir(pkg_info): |
759 | + patched = _patch_egg_dir(pkg_info) |
760 | + else: |
761 | + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) |
762 | + |
763 | + if not patched: |
764 | + log.warn('%s already patched.', pkg_info) |
765 | + return False |
766 | + # now let's move the files out of the way |
767 | + for element in ('setuptools', 'pkg_resources.py', 'site.py'): |
768 | + element = os.path.join(placeholder, element) |
769 | + if os.path.exists(element): |
770 | + _rename_path(element) |
771 | + else: |
772 | + log.warn('Could not find the %s element of the ' |
773 | + 'Setuptools distribution', element) |
774 | + return True |
775 | + |
776 | +_remove_flat_installation = _no_sandbox(_remove_flat_installation) |
777 | + |
778 | + |
779 | +def _after_install(dist): |
780 | + log.warn('After install bootstrap.') |
781 | + placeholder = dist.get_command_obj('install').install_purelib |
782 | + _create_fake_setuptools_pkg_info(placeholder) |
783 | + |
784 | + |
785 | +def _create_fake_setuptools_pkg_info(placeholder): |
786 | + if not placeholder or not os.path.exists(placeholder): |
787 | + log.warn('Could not find the install location') |
788 | + return |
789 | + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) |
790 | + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ |
791 | + (SETUPTOOLS_FAKED_VERSION, pyver) |
792 | + pkg_info = os.path.join(placeholder, setuptools_file) |
793 | + if os.path.exists(pkg_info): |
794 | + log.warn('%s already exists', pkg_info) |
795 | + return |
796 | + |
797 | + if not os.access(pkg_info, os.W_OK): |
798 | + log.warn("Don't have permissions to write %s, skipping", pkg_info) |
799 | + |
800 | + log.warn('Creating %s', pkg_info) |
801 | + f = open(pkg_info, 'w') |
802 | + try: |
803 | + f.write(SETUPTOOLS_PKG_INFO) |
804 | + finally: |
805 | + f.close() |
806 | + |
807 | + pth_file = os.path.join(placeholder, 'setuptools.pth') |
808 | + log.warn('Creating %s', pth_file) |
809 | + f = open(pth_file, 'w') |
810 | + try: |
811 | + f.write(os.path.join(os.curdir, setuptools_file)) |
812 | + finally: |
813 | + f.close() |
814 | + |
815 | +_create_fake_setuptools_pkg_info = _no_sandbox( |
816 | + _create_fake_setuptools_pkg_info |
817 | +) |
818 | + |
819 | + |
820 | +def _patch_egg_dir(path): |
821 | + # let's check if it's already patched |
822 | + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') |
823 | + if os.path.exists(pkg_info): |
824 | + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): |
825 | + log.warn('%s already patched.', pkg_info) |
826 | + return False |
827 | + _rename_path(path) |
828 | + os.mkdir(path) |
829 | + os.mkdir(os.path.join(path, 'EGG-INFO')) |
830 | + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') |
831 | + f = open(pkg_info, 'w') |
832 | + try: |
833 | + f.write(SETUPTOOLS_PKG_INFO) |
834 | + finally: |
835 | + f.close() |
836 | + return True |
837 | + |
838 | +_patch_egg_dir = _no_sandbox(_patch_egg_dir) |
839 | + |
840 | + |
841 | +def _before_install(): |
842 | + log.warn('Before install bootstrap.') |
843 | + _fake_setuptools() |
844 | + |
845 | + |
846 | +def _under_prefix(location): |
847 | + if 'install' not in sys.argv: |
848 | + return True |
849 | + args = sys.argv[sys.argv.index('install') + 1:] |
850 | + for index, arg in enumerate(args): |
851 | + for option in ('--root', '--prefix'): |
852 | + if arg.startswith('%s=' % option): |
853 | + top_dir = arg.split('root=')[-1] |
854 | + return location.startswith(top_dir) |
855 | + elif arg == option: |
856 | + if len(args) > index: |
857 | + top_dir = args[index + 1] |
858 | + return location.startswith(top_dir) |
859 | + if arg == '--user' and USER_SITE is not None: |
860 | + return location.startswith(USER_SITE) |
861 | + return True |
862 | + |
863 | + |
864 | +def _fake_setuptools(): |
865 | + log.warn('Scanning installed packages') |
866 | + try: |
867 | + import pkg_resources |
868 | + except ImportError: |
869 | + # we're cool |
870 | + log.warn('Setuptools or Distribute does not seem to be installed.') |
871 | + return |
872 | + ws = pkg_resources.working_set |
873 | + try: |
874 | + setuptools_dist = ws.find( |
875 | + pkg_resources.Requirement.parse('setuptools', replacement=False) |
876 | + ) |
877 | + except TypeError: |
878 | + # old distribute API |
879 | + setuptools_dist = ws.find( |
880 | + pkg_resources.Requirement.parse('setuptools') |
881 | + ) |
882 | + |
883 | + if setuptools_dist is None: |
884 | + log.warn('No setuptools distribution found') |
885 | + return |
886 | + # detecting if it was already faked |
887 | + setuptools_location = setuptools_dist.location |
888 | + log.warn('Setuptools installation detected at %s', setuptools_location) |
889 | + |
890 | + # if --root or --preix was provided, and if |
891 | + # setuptools is not located in them, we don't patch it |
892 | + if not _under_prefix(setuptools_location): |
893 | + log.warn('Not patching, --root or --prefix is installing Distribute' |
894 | + ' in another location') |
895 | + return |
896 | + |
897 | + # let's see if its an egg |
898 | + if not setuptools_location.endswith('.egg'): |
899 | + log.warn('Non-egg installation') |
900 | + res = _remove_flat_installation(setuptools_location) |
901 | + if not res: |
902 | + return |
903 | + else: |
904 | + log.warn('Egg installation') |
905 | + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') |
906 | + if (os.path.exists(pkg_info) and |
907 | + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): |
908 | + log.warn('Already patched.') |
909 | + return |
910 | + log.warn('Patching...') |
911 | + # let's create a fake egg replacing setuptools one |
912 | + res = _patch_egg_dir(setuptools_location) |
913 | + if not res: |
914 | + return |
915 | + log.warn('Patched done.') |
916 | + _relaunch() |
917 | + |
918 | + |
919 | +def _relaunch(): |
920 | + log.warn('Relaunching...') |
921 | + # we have to relaunch the process |
922 | + # pip marker to avoid a relaunch bug |
923 | + _cmd = ['-c', 'install', '--single-version-externally-managed'] |
924 | + if sys.argv[:3] == _cmd: |
925 | + sys.argv[0] = 'setup.py' |
926 | + args = [sys.executable] + sys.argv |
927 | + sys.exit(subprocess.call(args)) |
928 | + |
929 | + |
930 | +def _extractall(self, path=".", members=None): |
931 | + """Extract all members from the archive to the current working |
932 | + directory and set owner, modification time and permissions on |
933 | + directories afterwards. `path' specifies a different directory |
934 | + to extract to. `members' is optional and must be a subset of the |
935 | + list returned by getmembers(). |
936 | + """ |
937 | + import copy |
938 | + import operator |
939 | + from tarfile import ExtractError |
940 | + directories = [] |
941 | + |
942 | + if members is None: |
943 | + members = self |
944 | + |
945 | + for tarinfo in members: |
946 | + if tarinfo.isdir(): |
947 | + # Extract directories with a safe mode. |
948 | + directories.append(tarinfo) |
949 | + tarinfo = copy.copy(tarinfo) |
950 | + tarinfo.mode = 448 # decimal for oct 0700 |
951 | + self.extract(tarinfo, path) |
952 | + |
953 | + # Reverse sort directories. |
954 | + if sys.version_info < (2, 4): |
955 | + def sorter(dir1, dir2): |
956 | + return cmp(dir1.name, dir2.name) |
957 | + directories.sort(sorter) |
958 | + directories.reverse() |
959 | + else: |
960 | + directories.sort(key=operator.attrgetter('name'), reverse=True) |
961 | + |
962 | + # Set correct owner, mtime and filemode on directories. |
963 | + for tarinfo in directories: |
964 | + dirpath = os.path.join(path, tarinfo.name) |
965 | + try: |
966 | + self.chown(tarinfo, dirpath) |
967 | + self.utime(tarinfo, dirpath) |
968 | + self.chmod(tarinfo, dirpath) |
969 | + except ExtractError: |
970 | + e = sys.exc_info()[1] |
971 | + if self.errorlevel > 1: |
972 | + raise |
973 | + else: |
974 | + self._dbg(1, "tarfile: %s" % e) |
975 | + |
976 | + |
977 | +def _build_install_args(argv): |
978 | + install_args = [] |
979 | + user_install = '--user' in argv |
980 | + if user_install and sys.version_info < (2, 6): |
981 | + log.warn("--user requires Python 2.6 or later") |
982 | + raise SystemExit(1) |
983 | + if user_install: |
984 | + install_args.append('--user') |
985 | + return install_args |
986 | + |
987 | + |
988 | +def main(argv, version=DEFAULT_VERSION): |
989 | + """Install or upgrade setuptools and EasyInstall""" |
990 | + tarball = download_setuptools() |
991 | + _install(tarball, _build_install_args(argv)) |
992 | + |
993 | + |
994 | +if __name__ == '__main__': |
995 | + main(sys.argv[1:]) |
996 | |
997 | === modified file 'tarmac_tests.sh' |
998 | --- tarmac_tests.sh 2012-07-11 12:02:57 +0000 |
999 | +++ tarmac_tests.sh 2012-07-30 20:14:17 +0000 |
1000 | @@ -16,10 +16,7 @@ |
1001 | set -e |
1002 | |
1003 | LOG_FILE=log |
1004 | -VIRTUALENV_PATH=virtualenv.tarmac |
1005 | - |
1006 | -rm -rf $VIRTUALENV_PATH |
1007 | |
1008 | echo "Running pkgme-service-python tests in tarmac" > $LOG_FILE |
1009 | -make bootstrap VIRTUALENV_DIR=$VIRTUALENV_PATH >> $LOG_FILE |
1010 | -make test VIRTUALENV_DIR=$VIRTUALENV_PATH |
1011 | +make bootstrap >> $LOG_FILE |
1012 | +make test |
1013 | |
1014 | === removed file 'test-dependencies.txt' |
1015 | --- test-dependencies.txt 2012-07-19 18:00:03 +0000 |
1016 | +++ test-dependencies.txt 1970-01-01 00:00:00 +0000 |
1017 | @@ -1,5 +0,0 @@ |
1018 | -fixtures |
1019 | -mock==0.8 |
1020 | -testtools |
1021 | -# These dependencies are for tarmac's benefit |
1022 | -discover |
1023 | |
1024 | === added file 'versions.cfg' |
1025 | --- versions.cfg 1970-01-01 00:00:00 +0000 |
1026 | +++ versions.cfg 2012-07-30 20:14:17 +0000 |
1027 | @@ -0,0 +1,24 @@ |
1028 | +[buildout] |
1029 | +versions = versions |
1030 | + |
1031 | +# Update the version number here to upgrade to a new version. |
1032 | +# You'll likely want to run 'make update-deps' when you have |
1033 | +# changed something here. |
1034 | +# |
1035 | +# If you are forking a dependency then make sure you include a pointer |
1036 | +# to the branch where the fork is maintained. |
1037 | +# |
1038 | +# Alphabetical, case-insensitive, please! :-) |
1039 | +[versions] |
1040 | +fixtures = 0.3.9 |
1041 | +discover = 0.4 |
1042 | +distribute = 0.6.28 |
1043 | +httplib2 = 0.7.4 |
1044 | +oauth = 1.0.1 |
1045 | +mock = 0.8 |
1046 | +piston-mini-client = 0.7.2 |
1047 | +testtools = 0.9.15 |
1048 | +z3c.recipe.scripts = 1.0.1 |
1049 | +# Also upgrade the zc.buildout version in the Makefile's bin/buildout section. |
1050 | +zc.buildout = 1.5.1 |
1051 | +zc.recipe.egg = 1.3.2 |
I investigated the site-wide thing some more, and filed
https:/ /bugs.launchpad .net/zc. buildout/ +bug/1029715
The workaround I'm using for now is to require v1.5.1 of buildout,
so it works for precise users, even if they have buildout installed
via apt.
Thanks,
James