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:
parent
d668dea97d
commit
5881a03408
@ -3170,6 +3170,15 @@ def constrain(self, other, deps=True):
|
|||||||
raise UnsatisfiableArchitectureSpecError(sarch, oarch)
|
raise UnsatisfiableArchitectureSpecError(sarch, oarch)
|
||||||
|
|
||||||
changed = False
|
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:
|
if self.compiler is not None and other.compiler is not None:
|
||||||
changed |= self.compiler.constrain(other.compiler)
|
changed |= self.compiler.constrain(other.compiler)
|
||||||
elif self.compiler is None:
|
elif self.compiler is None:
|
||||||
|
@ -11,19 +11,6 @@
|
|||||||
from spack.spec import Spec
|
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):
|
class SpecList(object):
|
||||||
|
|
||||||
def __init__(self, name='specs', yaml_list=None, reference=None):
|
def __init__(self, name='specs', yaml_list=None, reference=None):
|
||||||
@ -177,30 +164,36 @@ def __getitem__(self, key):
|
|||||||
return self.specs[key]
|
return self.specs[key]
|
||||||
|
|
||||||
|
|
||||||
def _expand_matrix_constraints(object, specify=True):
|
def _expand_matrix_constraints(matrix_config):
|
||||||
# recurse so we can handle nexted matrices
|
# recurse so we can handle nested matrices
|
||||||
expanded_rows = []
|
expanded_rows = []
|
||||||
for row in object['matrix']:
|
for row in matrix_config['matrix']:
|
||||||
new_row = []
|
new_row = []
|
||||||
for r in row:
|
for r in row:
|
||||||
if isinstance(r, dict):
|
if isinstance(r, dict):
|
||||||
|
# Flatten the nested matrix into a single row of constraints
|
||||||
new_row.extend(
|
new_row.extend(
|
||||||
[[' '.join(c)]
|
[[' '.join([str(c) for c in expanded_constraint_list])]
|
||||||
for c in _expand_matrix_constraints(r, specify=False)])
|
for expanded_constraint_list in _expand_matrix_constraints(r)]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
new_row.append([r])
|
new_row.append([r])
|
||||||
expanded_rows.append(new_row)
|
expanded_rows.append(new_row)
|
||||||
|
|
||||||
excludes = object.get('exclude', []) # only compute once
|
excludes = matrix_config.get('exclude', []) # only compute once
|
||||||
sigil = object.get('sigil', '')
|
sigil = matrix_config.get('sigil', '')
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for combo in itertools.product(*expanded_rows):
|
for combo in itertools.product(*expanded_rows):
|
||||||
# Construct a combined spec to test against excludes
|
# Construct a combined spec to test against excludes
|
||||||
flat_combo = [constraint for list in combo for constraint in list]
|
flat_combo = [constraint for constraint_list in combo
|
||||||
ordered_combo = sorted(flat_combo, key=spec_ordering_key)
|
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
|
# Abstract variants don't have normal satisfaction semantics
|
||||||
# Convert all variants to concrete types.
|
# Convert all variants to concrete types.
|
||||||
# This method is best effort, so all existing variants will be
|
# 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):
|
if any(test_spec.satisfies(x) for x in excludes):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if sigil: # add sigil if necessary
|
if sigil:
|
||||||
ordered_combo[0] = sigil + ordered_combo[0]
|
flat_combo[0] = Spec(sigil + str(flat_combo[0]))
|
||||||
|
|
||||||
# Add to list of constraints
|
# Add to list of constraints
|
||||||
if specify:
|
results.append(flat_combo)
|
||||||
results.append([Spec(x) for x in ordered_combo])
|
|
||||||
else:
|
|
||||||
results.append(ordered_combo)
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,24 +45,34 @@ def test_spec_list_expansions(self):
|
|||||||
assert speclist.specs_as_constraints == self.default_constraints
|
assert speclist.specs_as_constraints == self.default_constraints
|
||||||
assert speclist.specs == self.default_specs
|
assert speclist.specs == self.default_specs
|
||||||
|
|
||||||
def test_spec_list_constraint_ordering(self):
|
@pytest.mark.regression('28749')
|
||||||
specs = [{'matrix': [
|
@pytest.mark.parametrize('specs,expected', [
|
||||||
|
# Constraints are ordered randomly
|
||||||
|
([{'matrix': [
|
||||||
['^zmpi'],
|
['^zmpi'],
|
||||||
['%gcc@4.5.0'],
|
['%gcc@4.5.0'],
|
||||||
['hypre', 'libelf'],
|
['hypre', 'libelf'],
|
||||||
['~shared'],
|
['~shared'],
|
||||||
['cflags=-O3', 'cflags="-g -O0"'],
|
['cflags=-O3', 'cflags="-g -O0"'],
|
||||||
['^foo']
|
['^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)
|
speclist = SpecList('specs', specs)
|
||||||
|
expected_specs = [Spec(x) for x in expected]
|
||||||
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'),
|
|
||||||
]
|
|
||||||
assert speclist.specs == expected_specs
|
assert speclist.specs == expected_specs
|
||||||
|
|
||||||
def test_spec_list_add(self):
|
def test_spec_list_add(self):
|
||||||
|
@ -1228,3 +1228,15 @@ def test_merge_abstract_anonymous_specs(specs, expected):
|
|||||||
specs = [Spec(x) for x in specs]
|
specs = [Spec(x) for x in specs]
|
||||||
result = spack.spec.merge_abstract_anonymous_specs(*specs)
|
result = spack.spec.merge_abstract_anonymous_specs(*specs)
|
||||||
assert result == Spec(expected)
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user