Update concretize to check for more changes and iterate further.

This commit is contained in:
Todd Gamblin 2015-07-16 01:12:11 -07:00
parent 9087f26537
commit e097696390
4 changed files with 82 additions and 29 deletions

View File

@ -63,9 +63,9 @@ def concretize_version(self, spec):
""" """
# return if already concrete. # return if already concrete.
if spec.versions.concrete: if spec.versions.concrete:
return return False
# If there are known avaialble versions, return the most recent # If there are known available versions, return the most recent
# version that satisfies the spec # version that satisfies the spec
pkg = spec.package pkg = spec.package
valid_versions = sorted( valid_versions = sorted(
@ -93,6 +93,8 @@ def concretize_version(self, spec):
else: else:
spec.versions = ver([last]) spec.versions = ver([last])
return True # Things changed
def concretize_architecture(self, spec): def concretize_architecture(self, spec):
"""If the spec already had an architecture, return. Otherwise if """If the spec already had an architecture, return. Otherwise if
@ -109,22 +111,27 @@ def concretize_architecture(self, spec):
they're not explicit. they're not explicit.
""" """
if spec.architecture is not None: if spec.architecture is not None:
return return False
if spec.root.architecture: if spec.root.architecture:
spec.architecture = spec.root.architecture spec.architecture = spec.root.architecture
else: else:
spec.architecture = spack.architecture.sys_type() spec.architecture = spack.architecture.sys_type()
assert(spec.architecture is not None)
return True # changed
def concretize_variants(self, spec): def concretize_variants(self, spec):
"""If the spec already has variants filled in, return. Otherwise, add """If the spec already has variants filled in, return. Otherwise, add
the default variants from the package specification. the default variants from the package specification.
""" """
changed = False
for name, variant in spec.package.variants.items(): for name, variant in spec.package.variants.items():
if name not in spec.variants: if name not in spec.variants:
spec.variants[name] = spack.spec.VariantSpec( spec.variants[name] = spack.spec.VariantSpec(name, variant.default)
name, variant.default) changed = True
return changed
def concretize_compiler(self, spec): def concretize_compiler(self, spec):
@ -144,7 +151,7 @@ def concretize_compiler(self, spec):
if (spec.compiler and if (spec.compiler and
spec.compiler.concrete and spec.compiler.concrete and
spec.compiler in all_compilers): spec.compiler in all_compilers):
return return False
try: try:
nearest = next(p for p in spec.traverse(direction='parents') nearest = next(p for p in spec.traverse(direction='parents')
@ -165,6 +172,8 @@ def concretize_compiler(self, spec):
except StopIteration: except StopIteration:
spec.compiler = spack.compilers.default_compiler().copy() spec.compiler = spack.compilers.default_compiler().copy()
return True # things changed.
def choose_provider(self, spec, providers): def choose_provider(self, spec, providers):
"""This is invoked for virtual specs. Given a spec with a virtual name, """This is invoked for virtual specs. Given a spec with a virtual name,

View File

@ -736,26 +736,31 @@ def _concretize_helper(self, presets=None, visited=None):
if visited is None: visited = set() if visited is None: visited = set()
if self.name in visited: if self.name in visited:
return return False
changed = False
# Concretize deps first -- this is a bottom-up process. # Concretize deps first -- this is a bottom-up process.
for name in sorted(self.dependencies.keys()): for name in sorted(self.dependencies.keys()):
self.dependencies[name]._concretize_helper(presets, visited) changed |= self.dependencies[name]._concretize_helper(presets, visited)
if self.name in presets: if self.name in presets:
self.constrain(presets[self.name]) changed |= self.constrain(presets[self.name])
else: else:
# Concretize virtual dependencies last. Because they're added # Concretize virtual dependencies last. Because they're added
# to presets below, their constraints will all be merged, but we'll # to presets below, their constraints will all be merged, but we'll
# still need to select a concrete package later. # still need to select a concrete package later.
if not self.virtual: if not self.virtual:
spack.concretizer.concretize_architecture(self) changed |= any(
spack.concretizer.concretize_compiler(self) (spack.concretizer.concretize_architecture(self),
spack.concretizer.concretize_version(self) spack.concretizer.concretize_compiler(self),
spack.concretizer.concretize_variants(self) spack.concretizer.concretize_version(self),
spack.concretizer.concretize_variants(self)))
presets[self.name] = self presets[self.name] = self
visited.add(self.name) visited.add(self.name)
return changed
def _replace_with(self, concrete): def _replace_with(self, concrete):
@ -783,20 +788,22 @@ def _expand_virtual_packages(self):
this are infrequent, but should implement this before it is this are infrequent, but should implement this before it is
a problem. a problem.
""" """
changed = False
while True: while True:
virtuals =[v for v in self.traverse() if v.virtual] virtuals =[v for v in self.traverse() if v.virtual]
if not virtuals: if not virtuals:
return return changed
for spec in virtuals: for spec in virtuals:
providers = spack.db.providers_for(spec) providers = spack.db.providers_for(spec)
concrete = spack.concretizer.choose_provider(spec, providers) concrete = spack.concretizer.choose_provider(spec, providers)
concrete = concrete.copy() concrete = concrete.copy()
spec._replace_with(concrete) spec._replace_with(concrete)
changed = True
# If there are duplicate providers or duplicate provider deps, this # If there are duplicate providers or duplicate provider deps, this
# consolidates them and merge constraints. # consolidates them and merge constraints.
self.normalize(force=True) changed |= self.normalize(force=True)
def concretize(self): def concretize(self):
@ -814,9 +821,16 @@ def concretize(self):
if self._concrete: if self._concrete:
return return
self.normalize() changed = True
self._expand_virtual_packages() force = False
self._concretize_helper()
while changed:
changes = (self.normalize(force=force),
self._expand_virtual_packages(),
self._concretize_helper())
changed = any(changes)
force=True
self._concrete = True self._concrete = True
@ -982,6 +996,7 @@ def _merge_dependency(self, dep, visited, spec_deps, provider_index):
# it from the package description. # it from the package description.
if dep.name not in spec_deps: if dep.name not in spec_deps:
spec_deps[dep.name] = dep.copy() spec_deps[dep.name] = dep.copy()
changed = True
# Constrain package information with spec info # Constrain package information with spec info
try: try:
@ -998,7 +1013,6 @@ def _merge_dependency(self, dep, visited, spec_deps, provider_index):
dependency = spec_deps[dep.name] dependency = spec_deps[dep.name]
if dep.name not in self.dependencies: if dep.name not in self.dependencies:
self._add_dependency(dependency) self._add_dependency(dependency)
changed = True
changed |= dependency._normalize_helper(visited, spec_deps, provider_index) changed |= dependency._normalize_helper(visited, spec_deps, provider_index)
return changed return changed
@ -1031,13 +1045,12 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
if pkg_dep: if pkg_dep:
changed |= self._merge_dependency( changed |= self._merge_dependency(
pkg_dep, visited, spec_deps, provider_index) pkg_dep, visited, spec_deps, provider_index)
any_change |= changed any_change |= changed
return any_change return any_change
def normalize(self, **kwargs): def normalize(self, force=False):
"""When specs are parsed, any dependencies specified are hanging off """When specs are parsed, any dependencies specified are hanging off
the root, and ONLY the ones that were explicitly provided are there. the root, and ONLY the ones that were explicitly provided are there.
Normalization turns a partial flat spec into a DAG, where: Normalization turns a partial flat spec into a DAG, where:
@ -1050,13 +1063,13 @@ def normalize(self, **kwargs):
package that provides a virtual package that is in the spec, package that provides a virtual package that is in the spec,
then we replace the virtual package with the non-virtual one. then we replace the virtual package with the non-virtual one.
4. The spec DAG matches package DAG. 4. The spec DAG matches package DAG, including default variant values.
TODO: normalize should probably implement some form of cycle detection, TODO: normalize should probably implement some form of cycle detection,
to ensure that the spec is actually a DAG. to ensure that the spec is actually a DAG.
""" """
if self._normal and not kwargs.get('force', False): if self._normal and not force:
return return False
# Ensure first that all packages & compilers in the DAG exist. # Ensure first that all packages & compilers in the DAG exist.
self.validate_names() self.validate_names()
@ -1070,19 +1083,18 @@ def normalize(self, **kwargs):
# traverse the package DAG and fill out dependencies according # traverse the package DAG and fill out dependencies according
# to package files & their 'when' specs # to package files & their 'when' specs
visited = set() visited = set()
self._normalize_helper(visited, spec_deps, index) any_change = self._normalize_helper(visited, spec_deps, index)
# If there are deps specified but not visited, they're not # If there are deps specified but not visited, they're not
# actually deps of this package. Raise an error. # actually deps of this package. Raise an error.
extra = set(spec_deps.keys()).difference(visited) extra = set(spec_deps.keys()).difference(visited)
# Anything left over is not a valid part of the spec.
if extra: if extra:
raise InvalidDependencyException( raise InvalidDependencyException(
self.name + " does not depend on " + comma_or(extra)) self.name + " does not depend on " + comma_or(extra))
# Mark the spec as normal once done. # Mark the spec as normal once done.
self._normal = True self._normal = True
return any_change
def normalized(self): def normalized(self):

View File

@ -86,9 +86,24 @@ def test_chained_mpi(self):
Spec('mpi')))) Spec('mpi'))))
def test_default_variant(self):
spec = Spec('optional-dep-test-3')
spec.concretize()
self.assertTrue('a' in spec)
spec = Spec('optional-dep-test-3~var')
spec.concretize()
self.assertTrue('a' in spec)
spec = Spec('optional-dep-test-3+var')
spec.concretize()
self.assertTrue('b' in spec)
def test_transitive_chain(self): def test_transitive_chain(self):
# Each of these dependencies comes from a conditional # Each of these dependencies comes from a conditional
# dependency on another. This requires iterating to evaluate # dependency on another. This requires iterating to evaluate
# the whole chain. # the whole chain.
self.check_normalize('optional-dep-test+f', self.check_normalize(
'optional-dep-test+f',
Spec('optional-dep-test+f', Spec('f'), Spec('g'), Spec('mpi'))) Spec('optional-dep-test+f', Spec('f'), Spec('g'), Spec('mpi')))

View File

@ -0,0 +1,17 @@
from spack import *
class OptionalDepTest3(Package):
"""Depends on the optional-dep-test package"""
homepage = "http://www.example.com"
url = "http://www.example.com/optional-dep-test-3-1.0.tar.gz"
version('1.0', '0123456789abcdef0123456789abcdef')
variant('var', default=False)
depends_on('a', when='~var')
depends_on('b', when='+var')
def install(self, spec, prefix):
pass