ASP-based solver: model disjoint sets for multivalued variants (#22534)

* ASP-based solver: avoid adding values to variants when they're set

fixes #22533
fixes #21911

Added a rule that prevents any value to slip in a variant when the
variant is set explicitly. This is relevant for multi-valued variants,
in particular for those that have disjoint sets of values.

* Ensure disjoint sets have a clear semantics for external packages
This commit is contained in:
Massimiliano Culpo 2021-03-26 15:22:38 +01:00 committed by Todd Gamblin
parent b11dd0478a
commit e7494b627b
4 changed files with 64 additions and 2 deletions

View file

@ -593,11 +593,16 @@ def pkg_rules(self, pkg, tests):
values = [] values = []
elif isinstance(values, spack.variant.DisjointSetsOfValues): elif isinstance(values, spack.variant.DisjointSetsOfValues):
union = set() union = set()
for s in values.sets: # Encode the disjoint sets in the logic program
for sid, s in enumerate(values.sets):
for value in s:
self.gen.fact(fn.variant_value_from_disjoint_sets(
pkg.name, name, value, sid
))
union.update(s) union.update(s)
values = union values = union
# make sure that every variant has at least one posisble value # make sure that every variant has at least one possible value
if not values: if not values:
values = [variant.default] values = [variant.default]

View file

@ -344,6 +344,15 @@ variant_set(Package, Variant) :- variant_set(Package, Variant, _).
% A variant cannot have a value that is not also a possible value % A variant cannot have a value that is not also a possible value
:- variant_value(Package, Variant, Value), not variant_possible_value(Package, Variant, Value). :- variant_value(Package, Variant, Value), not variant_possible_value(Package, Variant, Value).
% Some multi valued variants accept multiple values from disjoint sets.
% Ensure that we respect that constraint and we don't pick values from more
% than one set at once
:- variant_value(Package, Variant, Value1),
variant_value(Package, Variant, Value2),
variant_value_from_disjoint_sets(Package, Variant, Value1, Set1),
variant_value_from_disjoint_sets(Package, Variant, Value2, Set2),
Set1 != Set2.
% variant_set is an explicitly set variant value. If it's not 'set', % variant_set is an explicitly set variant value. If it's not 'set',
% we revert to the default value. If it is set, we force the set value % we revert to the default value. If it is set, we force the set value
variant_value(Package, Variant, Value) variant_value(Package, Variant, Value)
@ -403,6 +412,7 @@ variant_single_value(Package, "dev_path")
#defined variant_possible_value/3. #defined variant_possible_value/3.
#defined variant_default_value_from_packages_yaml/3. #defined variant_default_value_from_packages_yaml/3.
#defined variant_default_value_from_package_py/3. #defined variant_default_value_from_package_py/3.
#defined variant_value_from_disjoint_sets/4.
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
% Platform semantics % Platform semantics

View file

@ -1110,3 +1110,35 @@ def test_error_message_for_inconsistent_variants(self, spec_str):
s = Spec(spec_str) s = Spec(spec_str)
with pytest.raises(RuntimeError, match='not found in package'): with pytest.raises(RuntimeError, match='not found in package'):
s.concretize() s.concretize()
@pytest.mark.regression('22533')
@pytest.mark.parametrize('spec_str,variant_name,expected_values', [
# Test the default value 'auto'
('mvapich2', 'file_systems', ('auto',)),
# Test setting a single value from the disjoint set
('mvapich2 file_systems=lustre', 'file_systems', ('lustre',)),
# Test setting multiple values from the disjoint set
('mvapich2 file_systems=lustre,gpfs', 'file_systems',
('lustre', 'gpfs')),
])
def test_mv_variants_disjoint_sets_from_spec(
self, spec_str, variant_name, expected_values
):
s = Spec(spec_str).concretized()
assert set(expected_values) == set(s.variants[variant_name].value)
@pytest.mark.regression('22533')
def test_mv_variants_disjoint_sets_from_packages_yaml(self):
external_mvapich2 = {
'mvapich2': {
'buildable': False,
'externals': [{
'spec': 'mvapich2@2.3.1 file_systems=nfs,ufs',
'prefix': '/usr'
}]
}
}
spack.config.set('packages', external_mvapich2)
s = Spec('mvapich2').concretized()
assert set(s.variants['file_systems'].value) == set(['ufs', 'nfs'])

View file

@ -0,0 +1,15 @@
# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
class Mvapich2(Package):
homepage = "http://www.homepage.org"
url = "http://www.someurl"
version('1.5', '9c5d5d4fe1e17dd12153f40bc5b6dbc0')
variant(
'file_systems',
description='List of the ROMIO file systems to activate',
values=auto_or_any_combination_of('lustre', 'gpfs', 'nfs', 'ufs'),
)