Commit 8be81fe4 authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki Committed by Julien Muchembled

Add referred parts' hash strings in __buildout_signature__, that invokes...

Add referred parts' hash strings in __buildout_signature__, that invokes rebuild of a part when one of its (recursive) dependencies are modified.

Also remove duplicates and sort entries in __buildout_signature__.
parent b096b83a
...@@ -310,6 +310,7 @@ class Buildout(DictMixin): ...@@ -310,6 +310,7 @@ class Buildout(DictMixin):
self._raw = _unannotate(data) self._raw = _unannotate(data)
self._data = {} self._data = {}
self._parts = [] self._parts = []
self._initializing = []
# provide some defaults before options are parsed # provide some defaults before options are parsed
# because while parsing options those attributes might be # because while parsing options those attributes might be
...@@ -839,14 +840,29 @@ class Buildout(DictMixin): ...@@ -839,14 +840,29 @@ class Buildout(DictMixin):
"Unexpected entry, %r, in develop-eggs directory.", f) "Unexpected entry, %r, in develop-eggs directory.", f)
def _compute_part_signatures(self, parts): def _compute_part_signatures(self, parts):
# The same recipe may appear many times.
sig_cache= {}
# Compute recipe signature and add to options # Compute recipe signature and add to options
for part in parts: for part in parts:
options = self.get(part) options = self.get(part)
if options is None: if options is None:
options = self[part] = {} options = self[part] = {}
recipe, entry = _recipe(options) recipe, entry = _recipe(options)
req = pkg_resources.Requirement.parse(recipe)
sig = _dists_sig(pkg_resources.working_set.resolve([req])) try:
# Copy cached list, because sig.append is called later
sig = sig_cache[recipe][:]
except KeyError:
req = pkg_resources.Requirement.parse(recipe)
sig_result = sig_cache[recipe] = sorted(set(_dists_sig(pkg_resources.working_set.resolve([req]))))
sig = sig_result[:]
for dependency in sorted(options.depends):
m = md5()
for item in sorted(self[dependency].items()):
m.update(('%r\0%r\0' % item).encode())
sig.append('%s:%s' % (dependency, m.hexdigest()))
options['__buildout_signature__'] = ' '.join(sig) options['__buildout_signature__'] = ' '.join(sig)
def _read_installed_part_options(self): def _read_installed_part_options(self):
...@@ -1178,21 +1194,32 @@ class Buildout(DictMixin): ...@@ -1178,21 +1194,32 @@ class Buildout(DictMixin):
v = ' '+v v = ' '+v
print_("%s =%s" % (k, v)) print_("%s =%s" % (k, v))
def __getitem__(self, section): def initialize(self, options, reqs, entry):
__doing__ = 'Getting section %s.', section recipe_class = _install_and_load(reqs, 'zc.buildout', entry, self)
self._initializing.append(options)
try: try:
return self._data[section] return recipe_class(self, options.name, options)
except KeyError: finally:
pass del self._initializing[-1]
def __getitem__(self, section):
__doing__ = 'Getting section %s.', section
try: try:
data = self._raw[section] options = self._data[section]
except KeyError: except KeyError:
raise MissingSection(section) try:
data = self._raw[section]
options = self.Options(self, section, data) except KeyError:
self._data[section] = options raise MissingSection(section)
options._initialize()
options = self.Options(self, section, data)
self._data[section] = options
options._initialize()
if self._initializing:
caller = self._initializing[-1]
if 'buildout' != section != caller.name:
caller.depends.add(section)
return options return options
def __setitem__(self, name, data): def __setitem__(self, name, data):
...@@ -1275,6 +1302,7 @@ class Options(DictMixin): ...@@ -1275,6 +1302,7 @@ class Options(DictMixin):
self._raw = data self._raw = data
self._cooked = {} self._cooked = {}
self._data = {} self._data = {}
self.depends = set()
def _initialize(self): def _initialize(self):
name = self.name name = self.name
...@@ -1292,17 +1320,9 @@ class Options(DictMixin): ...@@ -1292,17 +1320,9 @@ class Options(DictMixin):
return # buildout section can never be a part return # buildout section can never be a part
if self.get('recipe'): if self.get('recipe'):
self.initialize() self.recipe = self.buildout.initialize(self, *_recipe(self._data))
self.buildout._parts.append(name) self.buildout._parts.append(name)
def initialize(self):
reqs, entry = _recipe(self._data)
buildout = self.buildout
recipe_class = _install_and_load(reqs, 'zc.buildout', entry, buildout)
name = self.name
self.recipe = recipe_class(buildout, name, self)
def _do_extend_raw(self, name, data, doing): def _do_extend_raw(self, name, data, doing):
if name == 'buildout': if name == 'buildout':
return data return data
...@@ -1409,7 +1429,14 @@ class Options(DictMixin): ...@@ -1409,7 +1429,14 @@ class Options(DictMixin):
section, option = s section, option = s
if not section: if not section:
section = self.name section = self.name
v = self.buildout[section].get(option, None, seen, last=last) options = self
else:
self.buildout._initializing.append(self)
try:
options = self.buildout[section]
finally:
del self.buildout._initializing[-1]
v = options.get(option, None, seen, last=last)
if v is None: if v is None:
if option == '_buildout_section_name_': if option == '_buildout_section_name_':
v = self.name v = self.name
......
...@@ -2544,7 +2544,7 @@ were created. ...@@ -2544,7 +2544,7 @@ were created.
The ``.installed.cfg`` is only updated for the recipes that ran:: The ``.installed.cfg`` is only updated for the recipes that ran::
>>> cat(sample_buildout, '.installed.cfg') >>> cat(sample_buildout, '.installed.cfg')
... # doctest: +NORMALIZE_WHITESPACE ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
[buildout] [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link installed_develop_eggs = /sample-buildout/develop-eggs/recipes.egg-link
parts = debug d1 d2 d3 d4 parts = debug d1 d2 d3 d4
...@@ -2574,7 +2574,7 @@ The ``.installed.cfg`` is only updated for the recipes that ran:: ...@@ -2574,7 +2574,7 @@ The ``.installed.cfg`` is only updated for the recipes that ran::
<BLANKLINE> <BLANKLINE>
[d4] [d4]
__buildout_installed__ = /sample-buildout/data2-extra __buildout_installed__ = /sample-buildout/data2-extra
__buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== __buildout_signature__ = recipes-PiIFiO8ny5yNZ1S3JfT0xg== d2:...
path = /sample-buildout/data2-extra path = /sample-buildout/data2-extra
recipe = recipes:mkdir recipe = recipes:mkdir
...@@ -2587,6 +2587,7 @@ Now, if we run the buildout without the install command:: ...@@ -2587,6 +2587,7 @@ Now, if we run the buildout without the install command::
>>> print_(system(buildout), end='') >>> print_(system(buildout), end='')
Develop: '/sample-buildout/recipes' Develop: '/sample-buildout/recipes'
Uninstalling d4.
Uninstalling d2. Uninstalling d2.
Uninstalling d1. Uninstalling d1.
Uninstalling debug. Uninstalling debug.
...@@ -2596,7 +2597,8 @@ Now, if we run the buildout without the install command:: ...@@ -2596,7 +2597,8 @@ Now, if we run the buildout without the install command::
Installing d2. Installing d2.
d2: Creating directory data2 d2: Creating directory data2
Updating d3. Updating d3.
Updating d4. Installing d4.
d4: Creating directory data2-extra
We see the output of the debug recipe, and that ``data2`` was created. We We see the output of the debug recipe, and that ``data2`` was created. We
also see that ``d1`` and ``d2`` have gone away:: also see that ``d1`` and ``d2`` have gone away::
......
...@@ -179,18 +179,14 @@ def wait_until(label, func, *args, **kw): ...@@ -179,18 +179,14 @@ def wait_until(label, func, *args, **kw):
time.sleep(0.01) time.sleep(0.01)
raise ValueError('Timed out waiting for: '+label) raise ValueError('Timed out waiting for: '+label)
class TestOptions(zc.buildout.buildout.Options):
def initialize(self):
pass
class Buildout(zc.buildout.buildout.Buildout): class Buildout(zc.buildout.buildout.Buildout):
def __init__(self): def __init__(self):
zc.buildout.buildout.Buildout.__init__( zc.buildout.buildout.Buildout.__init__(
self, '', [('buildout', 'directory', os.getcwd())]) self, '', [('buildout', 'directory', os.getcwd())])
Options = TestOptions def initialize(self, *args):
pass
def buildoutSetUp(test): def buildoutSetUp(test):
......
...@@ -2089,6 +2089,80 @@ def dealing_with_extremely_insane_dependencies(): ...@@ -2089,6 +2089,80 @@ def dealing_with_extremely_insane_dependencies():
Error: Couldn't find a distribution for 'pack5'. Error: Couldn't find a distribution for 'pack5'.
""" """
def test_part_pulled_by_recipe():
"""
>>> mkdir(sample_buildout, 'recipes')
>>> write(sample_buildout, 'recipes', 'test.py',
... '''
... class Recipe:
...
... def __init__(self, buildout, name, options):
... self.x = buildout[options['x']][name]
...
... def install(self):
... print self.x
... return ()
...
... update = install
... ''')
>>> write(sample_buildout, 'recipes', 'setup.py',
... '''
... from setuptools import setup
... setup(
... name = "recipes",
... entry_points = {'zc.buildout': ['test = test:Recipe']},
... )
... ''')
>>> write(sample_buildout, 'buildout.cfg',
... '''
... [buildout]
... develop = recipes
... parts = a
... [a]
... recipe = recipes:test
... x = b
... [b]
... <= a
... a = A
... b = B
... c = ${c:x}
... [c]
... x = c
... ''')
>>> os.chdir(sample_buildout)
>>> buildout = os.path.join(sample_buildout, 'bin', 'buildout')
>>> print_(system(buildout), end='')
Develop: '/sample-buildout/recipes'
Installing b.
B
Installing a.
A
>>> print_(system(buildout), end='')
Develop: '/sample-buildout/recipes'
Updating b.
B
Updating a.
A
>>> cat('.installed.cfg') # doctest: +ELLIPSIS
[buildout]
...
[b]
__buildout_installed__ =
__buildout_signature__ = recipes-... c:...
...
[a]
__buildout_installed__ =
__buildout_signature__ = recipes-... b:...
...
"""
def read_find_links_to_load_extensions(): def read_find_links_to_load_extensions():
r""" r"""
We'll create a wacky buildout extension that just announces itself when used: We'll create a wacky buildout extension that just announces itself when used:
......
...@@ -97,14 +97,14 @@ of extra requirements to be included in the working set. ...@@ -97,14 +97,14 @@ of extra requirements to be included in the working set.
We can see that the options were augmented with additional data We can see that the options were augmented with additional data
computed by the egg recipe by looking at .installed.cfg: computed by the egg recipe by looking at .installed.cfg:
>>> cat(sample_buildout, '.installed.cfg') >>> cat(sample_buildout, '.installed.cfg') # doctest: +ELLIPSIS
[buildout] [buildout]
installed_develop_eggs = /sample-buildout/develop-eggs/sample.egg-link installed_develop_eggs = /sample-buildout/develop-eggs/sample.egg-link
parts = sample-part parts = sample-part
<BLANKLINE> <BLANKLINE>
[sample-part] [sample-part]
__buildout_installed__ = __buildout_installed__ =
__buildout_signature__ = ... __buildout_signature__ = sample-... setuptools-...egg zc.buildout-... zc.recipe.egg-...
_b = /sample-buildout/bin _b = /sample-buildout/bin
_d = /sample-buildout/develop-eggs _d = /sample-buildout/develop-eggs
_e = /sample-buildout/eggs _e = /sample-buildout/eggs
......
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