Commit e1b51d63 authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki

zc.recipe.egg: Support on the fly pathces.

- Support on the fly patches in zc.recipe.egg by ``EGGNAME-patches``,
  ``EGGNAME-patch-options``, ``EGGNAME-patch-binary`` (or
  ``patch-binary``) and ``EGGNAME-patch-revision`` options.

- Support on the fly patches in zc.recipe.egg:custom by ``patches``,
  ``patch-options``, ``patch-binary`` and ``patch-revision`` options.
  (options ``EGGNAME-*`` are also supported as well).
parent 1f17de1c
...@@ -58,6 +58,9 @@ is_source_encoding_line = re.compile('coding[:=]\s*([-\w.]+)').search ...@@ -58,6 +58,9 @@ is_source_encoding_line = re.compile('coding[:=]\s*([-\w.]+)').search
is_win32 = sys.platform == 'win32' is_win32 = sys.platform == 'win32'
is_jython = sys.platform.startswith('java') is_jython = sys.platform.startswith('java')
PATCH_MARKER = 'SlapOSPatched'
orig_versions_re = re.compile(r'[+\-]%s\d+' % PATCH_MARKER)
if is_jython: if is_jython:
import java.lang.System import java.lang.System
jython_os_name = (java.lang.System.getProperties()['os.name']).lower() jython_os_name = (java.lang.System.getProperties()['os.name']).lower()
...@@ -418,6 +421,11 @@ class Installer: ...@@ -418,6 +421,11 @@ class Installer:
shutil.rmtree(tmp) shutil.rmtree(tmp)
def _obtain(self, requirement, source=None): def _obtain(self, requirement, source=None):
# get the non-patched version
req = str(requirement)
if PATCH_MARKER in req:
requirement = pkg_resources.Requirement.parse(re.sub(orig_versions_re, '', req))
# initialize out index for this project: # initialize out index for this project:
index = self._index index = self._index
...@@ -642,7 +650,7 @@ class Installer: ...@@ -642,7 +650,7 @@ class Installer:
return requirement return requirement
def install(self, specs, working_set=None): def install(self, specs, working_set=None, patch_dict=None):
logger.debug('Installing %s.', repr(specs)[1:-1]) logger.debug('Installing %s.', repr(specs)[1:-1])
self._requirements_and_constraints.append( self._requirements_and_constraints.append(
...@@ -663,6 +671,9 @@ class Installer: ...@@ -663,6 +671,9 @@ class Installer:
ws = working_set ws = working_set
for requirement in requirements: for requirement in requirements:
if patch_dict and requirement.project_name in patch_dict:
self._env.scan(
self.build(str(requirement), {}, patch_dict=patch_dict))
for dist in self._get_dist(requirement, ws, for dist in self._get_dist(requirement, ws,
for_buildout_run=for_buildout_run): for_buildout_run=for_buildout_run):
ws.add(dist) ws.add(dist)
...@@ -733,7 +744,7 @@ class Installer: ...@@ -733,7 +744,7 @@ class Installer:
processed[req] = True processed[req] = True
return ws return ws
def build(self, spec, build_ext): def build(self, spec, build_ext, patch_dict=None):
requirement = self._constrain(pkg_resources.Requirement.parse(spec)) requirement = self._constrain(pkg_resources.Requirement.parse(spec))
...@@ -784,12 +795,31 @@ class Installer: ...@@ -784,12 +795,31 @@ class Installer:
) )
base = os.path.dirname(setups[0]) base = os.path.dirname(setups[0])
setup_cfg_dict = {'build_ext':build_ext}
patch_dict = (patch_dict or {}).get(re.sub('[<>=].*', '', spec))
if patch_dict:
setup_cfg_dict.update(
{'egg_info':{'tag_build':'+%s%03d' % (PATCH_MARKER,
patch_dict['patch_revision'])}})
for i, patch in enumerate(patch_dict['patches']):
url, md5sum = (patch.strip().split('#', 1) + [''])[:2]
download = zc.buildout.download.Download()
path, is_temp = download(url, md5sum=md5sum or None,
path=os.path.join(tmp, 'patch.%s' % i))
args = [patch_dict['patch_binary']] + patch_dict['patch_options']
kwargs = {'cwd':base,
'stdin':open(path)}
popen = subprocess.Popen(args, **kwargs)
popen.communicate()
if popen.returncode != 0:
raise subprocess.CalledProcessError(
popen.returncode, ' '.join(args))
setup_cfg = os.path.join(base, 'setup.cfg') setup_cfg = os.path.join(base, 'setup.cfg')
if not os.path.exists(setup_cfg): if not os.path.exists(setup_cfg):
f = open(setup_cfg, 'w') f = open(setup_cfg, 'w')
f.close() f.close()
setuptools.command.setopt.edit_config( setuptools.command.setopt.edit_config(
setup_cfg, dict(build_ext=build_ext)) setup_cfg, setup_cfg_dict)
dists = self._call_easy_install( dists = self._call_easy_install(
base, pkg_resources.WorkingSet(), base, pkg_resources.WorkingSet(),
...@@ -874,6 +904,7 @@ def install(specs, dest, ...@@ -874,6 +904,7 @@ def install(specs, dest,
use_dependency_links=None, allow_hosts=('*',), use_dependency_links=None, allow_hosts=('*',),
include_site_packages=None, include_site_packages=None,
allowed_eggs_from_site_packages=None, allowed_eggs_from_site_packages=None,
patch_dict=None,
): ):
assert executable == sys.executable, (executable, sys.executable) assert executable == sys.executable, (executable, sys.executable)
assert include_site_packages is None assert include_site_packages is None
...@@ -883,18 +914,19 @@ def install(specs, dest, ...@@ -883,18 +914,19 @@ def install(specs, dest,
always_unzip, path, always_unzip, path,
newest, versions, use_dependency_links, newest, versions, use_dependency_links,
allow_hosts=allow_hosts) allow_hosts=allow_hosts)
return installer.install(specs, working_set) return installer.install(specs, working_set, patch_dict=patch_dict)
def build(spec, dest, build_ext, def build(spec, dest, build_ext,
links=(), index=None, links=(), index=None,
executable=sys.executable, executable=sys.executable,
path=None, newest=True, versions=None, allow_hosts=('*',)): path=None, newest=True, versions=None, allow_hosts=('*',),
patch_dict=None):
assert executable == sys.executable, (executable, sys.executable) assert executable == sys.executable, (executable, sys.executable)
installer = Installer(dest, links, index, executable, installer = Installer(dest, links, index, executable,
True, path, newest, True, path, newest,
versions, allow_hosts=allow_hosts) versions, allow_hosts=allow_hosts)
return installer.build(spec, build_ext) return installer.build(spec, build_ext, patch_dict=patch_dict)
def _rm(*paths): def _rm(*paths):
......
...@@ -9,6 +9,19 @@ eggs ...@@ -9,6 +9,19 @@ eggs
requirement strings. Each string must be given on a separate requirement strings. Each string must be given on a separate
line. line.
patch-binary
The path to the patch executable.
EGGNAME-patches
A new-line separated list of patchs to apply when building.
EGGNAME-patch-options
Options to give to the patch program when applying patches.
EGGNAME-patch-revision
An integer to specify the revision (default is the number of
patches).
find-links find-links
A list of URLs, files, or directories to search for distributions. A list of URLs, files, or directories to search for distributions.
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
""" """
import logging import logging
import os import os
import re
import sys import sys
import zc.buildout.easy_install import zc.buildout.easy_install
...@@ -34,6 +35,29 @@ class Base: ...@@ -34,6 +35,29 @@ class Base:
def update(self): def update(self):
return self.install() return self.install()
def _get_patch_dict(self, options, distribution):
patch_dict = {}
global_patch_binary = options.get('patch-binary', 'patch')
def get_option(egg, key, default):
return options.get('%s-%s' % (egg, key),
options.get(key, default))
egg = re.sub('[<>=].*', '', distribution)
patches = filter(lambda x:x,
map(lambda x:x.strip(),
get_option(egg, 'patches', '').splitlines()))
patches = list(patches)
if not patches:
return patch_dict
patch_options = get_option(egg, 'patch-options', '-p0').split()
patch_binary = get_option(egg, 'patch-binary', global_patch_binary)
patch_revision = int(get_option(egg, 'patch-revision', len(patches)))
patch_dict[egg] = {
'patches':patches,
'patch_options':patch_options,
'patch_binary':patch_binary,
'patch_revision':patch_revision,
}
return patch_dict
class Custom(Base): class Custom(Base):
...@@ -102,10 +126,11 @@ class Custom(Base): ...@@ -102,10 +126,11 @@ class Custom(Base):
self._set_environment() self._set_environment()
try: try:
patch_dict = self._get_patch_dict(options, distribution)
return zc.buildout.easy_install.build( return zc.buildout.easy_install.build(
distribution, options['_d'], self.build_ext, distribution, options['_d'], self.build_ext,
self.links, self.index, sys.executable, self.links, self.index, sys.executable,
[options['_e']], newest=self.newest, [options['_e']], newest=self.newest, patch_dict=patch_dict,
) )
finally: finally:
self._restore_environment() self._restore_environment()
......
...@@ -24,6 +24,19 @@ setup-eggs ...@@ -24,6 +24,19 @@ setup-eggs
A new-line separated list of eggs that need to be installed A new-line separated list of eggs that need to be installed
beforehand. It is useful to meet the `setup_requires` requirement. beforehand. It is useful to meet the `setup_requires` requirement.
patch-binary
The path to the patch executable.
patches
A new-line separated list of patchs to apply when building.
patch-options
Options to give to the patch program when applying patches.
patch-revision
An integer to specify the revision (default is the number of
patches).
define define
A comma-separated list of names of C preprocessor variables to A comma-separated list of names of C preprocessor variables to
define. define.
......
...@@ -51,6 +51,34 @@ class Eggs(object): ...@@ -51,6 +51,34 @@ class Eggs(object):
options['develop-eggs-directory'] = b_options['develop-eggs-directory'] options['develop-eggs-directory'] = b_options['develop-eggs-directory']
options['_d'] = options['develop-eggs-directory'] # backward compat. options['_d'] = options['develop-eggs-directory'] # backward compat.
def _get_patch_dict(self, options, distribution_list):
patch_dict = {}
global_patch_binary = options.get('patch-binary', 'patch')
def get_option(egg, key, default):
if len(distribution_list) == 1:
return options.get('%s-%s' % (egg, key),
options.get(key, default))
else:
return options.get('%s-%s' % (egg, key), default)
for distribution in distribution_list:
egg = re.sub('[<>=].*', '', distribution)
patches = filter(lambda x:x,
map(lambda x:x.strip(),
get_option(egg, 'patches', '').splitlines()))
patches = list(patches)
if not patches:
continue
patch_options = get_option(egg, 'patch-options', '-p0').split()
patch_binary = get_option(egg, 'patch-binary', global_patch_binary)
patch_revision = int(get_option(egg, 'patch-revision', len(patches)))
patch_dict[egg] = {
'patches':patches,
'patch_options':patch_options,
'patch_binary':patch_binary,
'patch_revision':patch_revision,
}
return patch_dict
def working_set(self, extra=()): def working_set(self, extra=()):
"""Separate method to just get the working set """Separate method to just get the working set
...@@ -75,13 +103,15 @@ class Eggs(object): ...@@ -75,13 +103,15 @@ class Eggs(object):
[options['develop-eggs-directory'], options['eggs-directory']] [options['develop-eggs-directory'], options['eggs-directory']]
) )
else: else:
patch_dict = self._get_patch_dict(options, distributions)
ws = zc.buildout.easy_install.install( ws = zc.buildout.easy_install.install(
distributions, options['eggs-directory'], distributions, options['eggs-directory'],
links=self.links, links=self.links,
index=self.index, index=self.index,
path=[options['develop-eggs-directory']], path=[options['develop-eggs-directory']],
newest=self.buildout['buildout'].get('newest') == 'true', newest=self.buildout['buildout'].get('newest') == 'true',
allow_hosts=self.allow_hosts) allow_hosts=self.allow_hosts,
patch_dict=patch_dict)
return orig_distributions, ws return orig_distributions, ws
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment