Merge lp:~james-w/pkgme-service-python/buildout into lp:pkgme-service-python

Proposed by James Westby
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
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

To post a comment you must log in.
18. By James Westby

Switch to distribute and add a note about site-wide buildout.

Revision history for this message
James Westby (james-w) wrote :

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

19. By James Westby

Symlink the eggs by default.

Revision history for this message
James Westby (james-w) wrote :

and yes we can share eggs (link-external-sourcecode does this).

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

Revision history for this message
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://launchpad.readthedocs.org/en/latest/buildout.html

Thanks,
jml

review: Needs Information
Revision history for this message
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

review: Approve
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.

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches