Source code for bob.buildout.extension

"""A monkey patch to zc.buildout.easy_install.develop that takes into
consideration eggs installed at both development and deployment directories."""

import os
import sys
import shutil
import tempfile
import subprocess
import pkg_resources
import zc.buildout.easy_install

from . import tools
from .envwrapper import EnvironmentWrapper

import logging
logger = logging.getLogger(__name__)

runsetup_template = """
import os
import sys
for k in %(paths)r.split(os.pathsep): sys.path.insert(0, k)
sys.path.insert(0, %(setupdir)r)

import os, setuptools

__file__ = %(__file__)r

os.chdir(%(setupdir)r)
sys.argv[0] = %(setup)r

exec(compile(open(%(setup)r).read(), %(setup)r, 'exec'))
"""

[docs]class Installer: def __init__(self, buildout): self.buildout = buildout['buildout'] self.verbose = tools.verbose(self.buildout) # finally builds the environment wrapper self.prefixes = tools.get_prefixes(self.buildout) self.envwrapper = EnvironmentWrapper(logger, tools.debug(self.buildout), self.prefixes, buildout.get('environ', {}), ) self.find_links = buildout.get('find_links', '') def __call__(self, spec, ws, dest, dist): """We will replace the default easy_install call by this one""" # set the environment self.envwrapper.set() # satisfy all package requirements before installing the package itself tools.satisfy_requirements(self.buildout, spec, ws) tmp = tempfile.mkdtemp(dir=dest) try: args = [sys.executable, '-c', zc.buildout.easy_install._easy_install_cmd, '-mZUNxd', tmp] if self.verbose: args.append('-v') else: args.append('-q') links = self.buildout.get('find-links', '') if links: args.extend(['-f', links]) args.append(spec) if logger.getEffectiveLevel() <= logging.DEBUG: logger.debug('Running easy_install:\n"%s"\npath=%s\n', '" "'.join(args), os.pathsep.join(tools.get_pythonpath(ws, self.buildout, self.prefixes)), ) sys.stdout.flush() # We want any pending output first exit_code = subprocess.call(list(args), env=dict( os.environ, PYTHONPATH=os.pathsep.join(tools.get_pythonpath(ws, self.buildout, self.prefixes)), ), ) dists = [] env = pkg_resources.Environment([tmp]) for project in env: dists.extend(env[project]) if exit_code: logger.error( "An error occurred when trying to install %s. " "Look above this message for any errors that " "were output by easy_install.", dist) if not dists: raise zc.buildout.UserError("Couldn't install: %s" % dist) if len(dists) > 1: logger.warn("Installing %s\n" "caused multiple distributions to be installed:\n" "%s\n", dist, '\n'.join(map(str, dists))) else: d = dists[0] if d.project_name != dist.project_name: logger.warn("Installing %s\n" "Caused installation of a distribution:\n" "%s\n" "with a different project name.", dist, d) if d.version != dist.version: logger.warn("Installing %s\n" "Caused installation of a distribution:\n" "%s\n" "with a different version.", dist, d) result = [] for d in dists: newloc = os.path.join(dest, os.path.basename(d.location)) if os.path.exists(newloc): if os.path.isdir(newloc): shutil.rmtree(newloc) else: os.remove(newloc) os.rename(d.location, newloc) [d] = pkg_resources.Environment([newloc])[d.project_name] result.append(d) return result finally: shutil.rmtree(tmp) self.envwrapper.unset()
[docs]class Extension: def __init__(self, buildout): self.buildout = buildout['buildout'] # shall we be verbose? self.verbose = tools.verbose(self.buildout) # replace zc.buildout's installer by our modified version, it will be # called indirectly by this extension, via zc.buildout self.installer = Installer(buildout)
[docs] def develop(self, setup, dest, build_ext=None, executable=sys.executable): assert executable == sys.executable, (executable, sys.executable) if os.path.isdir(setup): directory = setup setup = os.path.join(directory, 'setup.py') else: directory = os.path.dirname(setup) working_set = tools.working_set(self.buildout) tools.satisfy_requirements(self.buildout, directory, working_set) self.installer.envwrapper.set() undo = [] undo.append(self.installer.envwrapper.unset) try: if build_ext: setup_cfg = os.path.join(directory, 'setup.cfg') if os.path.exists(setup_cfg): os.rename(setup_cfg, setup_cfg+'-develop-aside') def restore_old_setup(): if os.path.exists(setup_cfg): os.remove(setup_cfg) os.rename(setup_cfg+'-develop-aside', setup_cfg) undo.append(restore_old_setup) else: open(setup_cfg, 'w') undo.append(lambda: os.remove(setup_cfg)) setuptools.command.setopt.edit_config( setup_cfg, dict(build_ext=build_ext)) fd, tsetup = tempfile.mkstemp() undo.append(lambda: os.remove(tsetup)) undo.append(lambda: os.close(fd)) os.write(fd, (runsetup_template % dict( # we reverse the order because we want the user paths to be inserted last ([::-1]). paths=os.pathsep.join(tools.get_pythonpath(working_set, self.buildout, self.installer.prefixes)[::-1]), setup=setup, setupdir=directory, __file__ = setup, )).encode()) tmp3 = tempfile.mkdtemp('build', dir=dest) undo.append(lambda : shutil.rmtree(tmp3)) args = [executable, tsetup, '-q', 'develop', '-mxN', '-d', tmp3] if self.verbose: args[2] = '-v' logger.debug("in: %r\n%s", directory, ' '.join(args)) zc.buildout.easy_install.call_subprocess(args) return zc.buildout.easy_install._copyeggs(tmp3, dest, '.egg-link', undo) finally: undo.reverse() [f() for f in undo]
def _dists_sig(dists): '''Override of zc.buildout.buildout._dists_sig() to avoid excessive directory hashing on "normal" distributions''' seen = set() result = [] for dist in dists: if dist in seen: continue seen.add(dist) location = dist.location result.append(os.path.basename(location)) return result
[docs]def extension(buildout): """Monkey patches zc.buildout.easy_install.develop""" ext = Extension(buildout) zc.buildout.easy_install.develop = ext.develop zc.buildout.easy_install.Installer._call_easy_install = ext.installer zc.buildout.buildout._dists_sig = _dists_sig