Commit c5a5bc1a authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki Committed by Xavier Thompson

[feat] zc.recipe.egg: Support on the fly patches.

- 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 bc983725
...@@ -81,6 +81,9 @@ is_source_encoding_line = re.compile(r'coding[:=]\s*([-\w.]+)').search ...@@ -81,6 +81,9 @@ is_source_encoding_line = re.compile(r'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()
...@@ -464,6 +467,11 @@ class Installer(object): ...@@ -464,6 +467,11 @@ class Installer(object):
zc.buildout.rmtree.rmtree(tmp) zc.buildout.rmtree.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
...@@ -676,7 +684,7 @@ class Installer(object): ...@@ -676,7 +684,7 @@ class Installer(object):
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])
__doing__ = _doing_list, self._requirements_and_constraints __doing__ = _doing_list, self._requirements_and_constraints
...@@ -700,6 +708,9 @@ class Installer(object): ...@@ -700,6 +708,9 @@ class Installer(object):
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):
self._maybe_add_setuptools(ws, dist) self._maybe_add_setuptools(ws, dist)
...@@ -798,7 +809,7 @@ class Installer(object): ...@@ -798,7 +809,7 @@ class Installer(object):
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))
...@@ -849,12 +860,31 @@ class Installer(object): ...@@ -849,12 +860,31 @@ class Installer(object):
) )
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_pip_wheel(base, self._dest, dist) dists = self._call_pip_wheel(base, self._dest, dist)
...@@ -974,6 +1004,7 @@ def install(specs, dest, ...@@ -974,6 +1004,7 @@ def install(specs, dest,
allowed_eggs_from_site_packages=None, allowed_eggs_from_site_packages=None,
check_picked=True, check_picked=True,
allow_unknown_extras=False, allow_unknown_extras=False,
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
...@@ -985,18 +1016,19 @@ def install(specs, dest, ...@@ -985,18 +1016,19 @@ def install(specs, dest,
allow_hosts=allow_hosts, allow_hosts=allow_hosts,
check_picked=check_picked, check_picked=check_picked,
allow_unknown_extras=allow_unknown_extras) allow_unknown_extras=allow_unknown_extras)
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
...@@ -70,6 +71,30 @@ class Base: ...@@ -70,6 +71,30 @@ class Base:
except KeyError: except KeyError:
pass pass
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):
...@@ -127,10 +152,11 @@ class Custom(Base): ...@@ -127,10 +152,11 @@ class Custom(Base):
extra_path = os.pathsep.join(ws.entries) extra_path = os.pathsep.join(ws.entries)
self.environment['PYTHONEXTRAPATH'] = os.environ['PYTHONEXTRAPATH'] = extra_path self.environment['PYTHONEXTRAPATH'] = os.environ['PYTHONEXTRAPATH'] = extra_path
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,
) )
......
...@@ -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.
......
...@@ -57,6 +57,34 @@ class Eggs(object): ...@@ -57,6 +57,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
...@@ -134,6 +162,7 @@ class Eggs(object): ...@@ -134,6 +162,7 @@ class Eggs(object):
[develop_eggs_dir, eggs_dir] [develop_eggs_dir, eggs_dir]
) )
else: else:
patch_dict = self._get_patch_dict(self.options, distributions)
ws = zc.buildout.easy_install.install( ws = zc.buildout.easy_install.install(
distributions, eggs_dir, distributions, eggs_dir,
links=links, links=links,
...@@ -141,7 +170,8 @@ class Eggs(object): ...@@ -141,7 +170,8 @@ class Eggs(object):
path=[develop_eggs_dir], path=[develop_eggs_dir],
newest=newest, newest=newest,
allow_hosts=allow_hosts, allow_hosts=allow_hosts,
allow_unknown_extras=allow_unknown_extras) allow_unknown_extras=allow_unknown_extras,
patch_dict=patch_dict)
ws = zc.buildout.easy_install.sort_working_set( ws = zc.buildout.easy_install.sort_working_set(
ws, buildout_dir, eggs_dir, develop_eggs_dir ws, buildout_dir, eggs_dir, develop_eggs_dir
) )
......
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