bugfix: nested matrices in spec lists (#12320)

* stack concretization: fix handling of variant names with dashes
* spec_list: bugfix for handling nested matrices
This commit is contained in:
Greg Becker 2019-08-08 20:53:31 -07:00 committed by Todd Gamblin
parent 9336a8be38
commit be6ac6ce6d
6 changed files with 88 additions and 27 deletions

View file

@ -1340,11 +1340,7 @@ def _concretize_from_constraints(spec_constraints):
try: try:
return s.concretized() return s.concretized()
except spack.spec.InvalidDependencyError as e: except spack.spec.InvalidDependencyError as e:
dep_index = e.message.index('depend on ') + len('depend on ') invalid_deps_string = ['^' + d for d in e.invalid_deps]
invalid_msg = e.message[dep_index:]
invalid_deps_string = ['^' + d.strip(',')
for d in invalid_msg.split()
if d != 'or']
invalid_deps = [c for c in spec_constraints invalid_deps = [c for c in spec_constraints
if any(c.satisfies(invd, strict=True) if any(c.satisfies(invd, strict=True)
for invd in invalid_deps_string)] for invd in invalid_deps_string)]
@ -1352,13 +1348,13 @@ def _concretize_from_constraints(spec_constraints):
raise e raise e
invalid_constraints.extend(invalid_deps) invalid_constraints.extend(invalid_deps)
except UnknownVariantError as e: except UnknownVariantError as e:
invalid_variants = re.findall(r"'(\w+)'", e.message) invalid_variants = e.unknown_variants
invalid_deps = [c for c in spec_constraints inv_variant_constraints = [c for c in spec_constraints
if any(name in c.variants if any(name in c.variants
for name in invalid_variants)] for name in invalid_variants)]
if len(invalid_deps) != len(invalid_variants): if len(inv_variant_constraints) != len(invalid_variants):
raise e raise e
invalid_constraints.extend(invalid_deps) invalid_constraints.extend(inv_variant_constraints)
def make_repo_path(root): def make_repo_path(root):

View file

@ -963,8 +963,7 @@ def get_dependency(self, name):
dep = self._dependencies.get(name) dep = self._dependencies.get(name)
if dep is not None: if dep is not None:
return dep return dep
raise InvalidDependencyError( raise InvalidDependencyError(self.name, name)
self.name + " does not depend on " + comma_or(name))
def _find_deps(self, where, deptype): def _find_deps(self, where, deptype):
deptype = dp.canonical_deptype(deptype) deptype = dp.canonical_deptype(deptype)
@ -2067,8 +2066,7 @@ def concretize(self, tests=False):
extra = set(user_spec_deps.keys()).difference(visited_user_specs) extra = set(user_spec_deps.keys()).difference(visited_user_specs)
if extra: if extra:
raise InvalidDependencyError( raise InvalidDependencyError(self.name, extra)
self.name + " does not depend on " + comma_or(extra))
# This dictionary will store object IDs rather than Specs as keys # This dictionary will store object IDs rather than Specs as keys
# since the Spec __hash__ will change as patches are added to them # since the Spec __hash__ will change as patches are added to them
@ -4156,6 +4154,10 @@ class InconsistentSpecError(SpecError):
class InvalidDependencyError(SpecError): class InvalidDependencyError(SpecError):
"""Raised when a dependency in a spec is not actually a dependency """Raised when a dependency in a spec is not actually a dependency
of the package.""" of the package."""
def __init__(self, pkg, deps):
self.invalid_deps = deps
super(InvalidDependencyError, self).__init__(
'Package {0} does not depend on {1}'.format(pkg, comma_or(deps)))
class NoProviderError(SpecError): class NoProviderError(SpecError):

View file

@ -54,16 +54,7 @@ def specs_as_constraints(self):
constraints = [] constraints = []
for item in self.specs_as_yaml_list: for item in self.specs_as_yaml_list:
if isinstance(item, dict): # matrix of specs if isinstance(item, dict): # matrix of specs
excludes = item.get('exclude', []) constraints.extend(_expand_matrix_constraints(item))
for combo in itertools.product(*(item['matrix'])):
# Test against the excludes using a single spec
ordered_combo = sorted(combo, key=spec_ordering_key)
test_spec = Spec(' '.join(ordered_combo))
if any(test_spec.satisfies(x) for x in excludes):
continue
# Add as list of constraints
constraints.append([Spec(x) for x in ordered_combo])
else: # individual spec else: # individual spec
constraints.append([Spec(item)]) constraints.append([Spec(item)])
self._constraints = constraints self._constraints = constraints
@ -161,6 +152,36 @@ def __getitem__(self, key):
return self.specs[key] return self.specs[key]
def _expand_matrix_constraints(object, specify=True):
# recurse so we can handle nexted matrices
expanded_rows = []
for row in object['matrix']:
new_row = []
for r in row:
if isinstance(r, dict):
new_row.extend(_expand_matrix_constraints(r, specify=False))
else:
new_row.append([r])
expanded_rows.append(new_row)
results = []
excludes = object.get('exclude', []) # only compute once
for combo in itertools.product(*expanded_rows):
# Construct a combined spec to test against excludes
flat_combo = [constraint for list in combo for constraint in list]
ordered_combo = sorted(flat_combo, key=spec_ordering_key)
test_spec = Spec(' '.join(ordered_combo))
if any(test_spec.satisfies(x) for x in excludes):
continue
# Add to list of constraints
if specify:
results.append([Spec(x) for x in ordered_combo])
else:
results.append(ordered_combo)
return results
class SpecListError(SpackError): class SpecListError(SpackError):
"""Error class for all errors related to SpecList objects.""" """Error class for all errors related to SpecList objects."""

View file

@ -1139,6 +1139,33 @@ def test_stack_concretize_extraneous_variants(tmpdir, config, mock_packages):
user.variants['shared'].value) user.variants['shared'].value)
def test_stack_concretize_extraneous_variants_with_dash(tmpdir, config,
mock_packages):
filename = str(tmpdir.join('spack.yaml'))
with open(filename, 'w') as f:
f.write("""\
env:
definitions:
- packages: [libelf, mpileaks]
- install:
- matrix:
- [$packages]
- ['shared=False', '+shared-libs']
specs:
- $install
""")
with tmpdir.as_cwd():
env('create', 'test', './spack.yaml')
with ev.read('test'):
concretize()
ev.read('test')
# Regression test for handling of variants with dashes in them
# will fail before this point if code regresses
assert True
def test_stack_definition_extension(tmpdir): def test_stack_definition_extension(tmpdir):
filename = str(tmpdir.join('spack.yaml')) filename = str(tmpdir.join('spack.yaml'))
with open(filename, 'w') as f: with open(filename, 'w') as f:

View file

@ -2,6 +2,7 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details. # Spack Project Developers. See the top-level COPYRIGHT file for details.
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import itertools
from spack.spec_list import SpecList from spack.spec_list import SpecList
from spack.spec import Spec from spack.spec import Spec
@ -143,3 +144,15 @@ def test_spec_list_extension(self):
otherlist.specs_as_yaml_list) otherlist.specs_as_yaml_list)
assert speclist.specs == self.default_specs + otherlist.specs assert speclist.specs == self.default_specs + otherlist.specs
assert speclist._reference is new_ref assert speclist._reference is new_ref
def test_spec_list_nested_matrices(self):
inner_matrix = [{'matrix': [['zlib', 'libelf'], ['%gcc', '%intel']]}]
outer_addition = ['+shared', '~shared']
outer_matrix = [{'matrix': [inner_matrix, outer_addition]}]
speclist = SpecList('specs', outer_matrix)
expected_components = itertools.product(['zlib', 'libelf'],
['%gcc', '%intel'],
['+shared', '~shared'])
expected = [Spec(' '.join(combo)) for combo in expected_components]
assert set(speclist.specs) == set(expected)

View file

@ -16,6 +16,7 @@
import llnl.util.tty.color import llnl.util.tty.color
import llnl.util.lang as lang import llnl.util.lang as lang
from spack.util.string import comma_or
import spack.directives import spack.directives
import spack.error as error import spack.error as error
@ -778,9 +779,10 @@ class DuplicateVariantError(error.SpecError):
class UnknownVariantError(error.SpecError): class UnknownVariantError(error.SpecError):
"""Raised when an unknown variant occurs in a spec.""" """Raised when an unknown variant occurs in a spec."""
def __init__(self, pkg, variant): def __init__(self, pkg, variants):
self.unknown_variants = variants
super(UnknownVariantError, self).__init__( super(UnknownVariantError, self).__init__(
'Package {0} has no variant {1}!'.format(pkg, variant) 'Package {0} has no variant {1}!'.format(pkg, comma_or(variants))
) )