Use Spec.constrain to construct spec lists for stacks (#28783)

* stacks: add regression tests for matrix expansion

* Use constrain semantics to construct spec lists for stacks

* Fix semantics for constraining an anonymous spec. Add tests
This commit is contained in:
Massimiliano Culpo 2022-02-04 20:17:23 +01:00 committed by GitHub
parent d668dea97d
commit 5881a03408
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 62 additions and 40 deletions

View File

@ -3170,6 +3170,15 @@ def constrain(self, other, deps=True):
raise UnsatisfiableArchitectureSpecError(sarch, oarch)
changed = False
if not self.name and other.name:
self.name = other.name
changed = True
if not self.namespace and other.namespace:
self.namespace = other.namespace
changed = True
if self.compiler is not None and other.compiler is not None:
changed |= self.compiler.constrain(other.compiler)
elif self.compiler is None:

View File

@ -11,19 +11,6 @@
from spack.spec import Spec
def spec_ordering_key(s):
if s.startswith('^'):
return 5
elif s.startswith('/'):
return 4
elif s.startswith('%'):
return 3
elif any(s.startswith(c) for c in '~-+@') or '=' in s:
return 2
else:
return 1
class SpecList(object):
def __init__(self, name='specs', yaml_list=None, reference=None):
@ -177,30 +164,36 @@ def __getitem__(self, key):
return self.specs[key]
def _expand_matrix_constraints(object, specify=True):
# recurse so we can handle nexted matrices
def _expand_matrix_constraints(matrix_config):
# recurse so we can handle nested matrices
expanded_rows = []
for row in object['matrix']:
for row in matrix_config['matrix']:
new_row = []
for r in row:
if isinstance(r, dict):
# Flatten the nested matrix into a single row of constraints
new_row.extend(
[[' '.join(c)]
for c in _expand_matrix_constraints(r, specify=False)])
[[' '.join([str(c) for c in expanded_constraint_list])]
for expanded_constraint_list in _expand_matrix_constraints(r)]
)
else:
new_row.append([r])
expanded_rows.append(new_row)
excludes = object.get('exclude', []) # only compute once
sigil = object.get('sigil', '')
excludes = matrix_config.get('exclude', []) # only compute once
sigil = matrix_config.get('sigil', '')
results = []
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)
flat_combo = [constraint for constraint_list in combo
for constraint in constraint_list]
flat_combo = [Spec(x) for x in flat_combo]
test_spec = flat_combo[0].copy()
for constraint in flat_combo[1:]:
test_spec.constrain(constraint)
test_spec = Spec(' '.join(ordered_combo))
# Abstract variants don't have normal satisfaction semantics
# Convert all variants to concrete types.
# This method is best effort, so all existing variants will be
@ -214,14 +207,12 @@ def _expand_matrix_constraints(object, specify=True):
if any(test_spec.satisfies(x) for x in excludes):
continue
if sigil: # add sigil if necessary
ordered_combo[0] = sigil + ordered_combo[0]
if sigil:
flat_combo[0] = Spec(sigil + str(flat_combo[0]))
# Add to list of constraints
if specify:
results.append([Spec(x) for x in ordered_combo])
else:
results.append(ordered_combo)
results.append(flat_combo)
return results

View File

@ -45,24 +45,34 @@ def test_spec_list_expansions(self):
assert speclist.specs_as_constraints == self.default_constraints
assert speclist.specs == self.default_specs
def test_spec_list_constraint_ordering(self):
specs = [{'matrix': [
@pytest.mark.regression('28749')
@pytest.mark.parametrize('specs,expected', [
# Constraints are ordered randomly
([{'matrix': [
['^zmpi'],
['%gcc@4.5.0'],
['hypre', 'libelf'],
['~shared'],
['cflags=-O3', 'cflags="-g -O0"'],
['^foo']
]}]
]}], [
'hypre cflags=-O3 ~shared %gcc@4.5.0 ^foo ^zmpi',
'hypre cflags="-g -O0" ~shared %gcc@4.5.0 ^foo ^zmpi',
'libelf cflags=-O3 ~shared %gcc@4.5.0 ^foo ^zmpi',
'libelf cflags="-g -O0" ~shared %gcc@4.5.0 ^foo ^zmpi',
]),
# A constraint affects both the root and a dependency
([{'matrix': [
['gromacs'],
['%gcc'],
['+plumed ^plumed%gcc']
]}], [
'gromacs+plumed%gcc ^plumed%gcc'
])
])
def test_spec_list_constraint_ordering(self, specs, expected):
speclist = SpecList('specs', specs)
expected_specs = [
Spec('hypre cflags=-O3 ~shared %gcc@4.5.0 ^foo ^zmpi'),
Spec('hypre cflags="-g -O0" ~shared %gcc@4.5.0 ^foo ^zmpi'),
Spec('libelf cflags=-O3 ~shared %gcc@4.5.0 ^foo ^zmpi'),
Spec('libelf cflags="-g -O0" ~shared %gcc@4.5.0 ^foo ^zmpi'),
]
expected_specs = [Spec(x) for x in expected]
assert speclist.specs == expected_specs
def test_spec_list_add(self):

View File

@ -1228,3 +1228,15 @@ def test_merge_abstract_anonymous_specs(specs, expected):
specs = [Spec(x) for x in specs]
result = spack.spec.merge_abstract_anonymous_specs(*specs)
assert result == Spec(expected)
@pytest.mark.parametrize('anonymous,named,expected', [
('+plumed', 'gromacs', 'gromacs+plumed'),
('+plumed ^plumed%gcc', 'gromacs', 'gromacs+plumed ^plumed%gcc'),
('+plumed', 'builtin.gromacs', 'builtin.gromacs+plumed')
])
def test_merge_anonymous_spec_with_named_spec(anonymous, named, expected):
s = Spec(anonymous)
changed = s.constrain(named)
assert changed
assert s == Spec(expected)