solver: support concrete multivalued variants (#50325)
The solves now supports key:=val syntax for multivalued variants in specs originating from input, externals, requirements, directives and when conditions Signed-off-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
This commit is contained in:
parent
3ed6736b2c
commit
acd47147a5
@ -2512,7 +2512,22 @@ def _spec_clauses(
|
|||||||
if self.pkg_class(spec.name).has_variant(vname):
|
if self.pkg_class(spec.name).has_variant(vname):
|
||||||
clauses.append(f.variant_value(spec.name, vname, value))
|
clauses.append(f.variant_value(spec.name, vname, value))
|
||||||
else:
|
else:
|
||||||
clauses.append(f.variant_value(spec.name, vname, value))
|
variant_clause = f.variant_value(spec.name, vname, value)
|
||||||
|
if (
|
||||||
|
variant.concrete
|
||||||
|
and variant.type == vt.VariantType.MULTI
|
||||||
|
and not spec.concrete
|
||||||
|
):
|
||||||
|
if body is False:
|
||||||
|
variant_clause.args = (
|
||||||
|
f"concrete_{variant_clause.args[0]}",
|
||||||
|
*variant_clause.args[1:],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
clauses.append(
|
||||||
|
fn.attr("concrete_variant_request", spec.name, vname, value)
|
||||||
|
)
|
||||||
|
clauses.append(variant_clause)
|
||||||
|
|
||||||
# compiler flags
|
# compiler flags
|
||||||
source = context.source if context else "none"
|
source = context.source if context else "none"
|
||||||
|
@ -159,10 +159,12 @@ unification_set(SetID, VirtualNode)
|
|||||||
|
|
||||||
% TODO: literals, at the moment, can only influence the "root" unification set. This needs to be extended later.
|
% TODO: literals, at the moment, can only influence the "root" unification set. This needs to be extended later.
|
||||||
|
|
||||||
% Node attributes that have multiple node arguments (usually, only the first argument is a node)
|
% Node attributes that need custom rules in ASP, e.g. because they involve multiple nodes
|
||||||
multiple_nodes_attribute("depends_on").
|
node_attributes_with_custom_rules("depends_on").
|
||||||
multiple_nodes_attribute("virtual_on_edge").
|
node_attributes_with_custom_rules("virtual_on_edge").
|
||||||
multiple_nodes_attribute("provider_set").
|
node_attributes_with_custom_rules("provider_set").
|
||||||
|
node_attributes_with_custom_rules("concrete_variant_set").
|
||||||
|
node_attributes_with_custom_rules("concrete_variant_request").
|
||||||
|
|
||||||
trigger_condition_holds(TriggerID, node(min_dupe_id, Package)) :-
|
trigger_condition_holds(TriggerID, node(min_dupe_id, Package)) :-
|
||||||
solve_literal(TriggerID),
|
solve_literal(TriggerID),
|
||||||
@ -397,12 +399,26 @@ trigger_condition_holds(ID, RequestorNode) :-
|
|||||||
trigger_node(ID, PackageNode, RequestorNode);
|
trigger_node(ID, PackageNode, RequestorNode);
|
||||||
attr(Name, node(X, A1)) : condition_requirement(ID, Name, A1), condition_nodes(ID, PackageNode, node(X, A1));
|
attr(Name, node(X, A1)) : condition_requirement(ID, Name, A1), condition_nodes(ID, PackageNode, node(X, A1));
|
||||||
attr(Name, node(X, A1), A2) : condition_requirement(ID, Name, A1, A2), condition_nodes(ID, PackageNode, node(X, A1));
|
attr(Name, node(X, A1), A2) : condition_requirement(ID, Name, A1, A2), condition_nodes(ID, PackageNode, node(X, A1));
|
||||||
attr(Name, node(X, A1), A2, A3) : condition_requirement(ID, Name, A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name);
|
attr(Name, node(X, A1), A2, A3) : condition_requirement(ID, Name, A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), not node_attributes_with_custom_rules(Name);
|
||||||
attr(Name, node(X, A1), A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4), condition_nodes(ID, PackageNode, node(X, A1));
|
attr(Name, node(X, A1), A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4), condition_nodes(ID, PackageNode, node(X, A1));
|
||||||
% Special cases
|
% Special cases
|
||||||
attr("depends_on", node(X, A1), node(Y, A2), A3) : condition_requirement(ID, "depends_on", A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), condition_nodes(ID, PackageNode, node(Y, A2));
|
attr("depends_on", node(X, A1), node(Y, A2), A3) : condition_requirement(ID, "depends_on", A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), condition_nodes(ID, PackageNode, node(Y, A2));
|
||||||
not cannot_hold(ID, PackageNode).
|
not cannot_hold(ID, PackageNode).
|
||||||
|
|
||||||
|
condition_with_concrete_variant(ID, Package, Variant) :- condition_requirement(ID, "concrete_variant_request", Package, Variant, _).
|
||||||
|
|
||||||
|
cannot_hold(ID, PackageNode) :-
|
||||||
|
not attr("variant_value", node(X, A1), Variant, Value),
|
||||||
|
condition_with_concrete_variant(ID, A1, Variant),
|
||||||
|
condition_requirement(ID, "concrete_variant_request", A1, Variant, Value),
|
||||||
|
condition_nodes(ID, PackageNode, node(X, A1)).
|
||||||
|
|
||||||
|
cannot_hold(ID, PackageNode) :-
|
||||||
|
attr("variant_value", node(X, A1), Variant, Value),
|
||||||
|
condition_with_concrete_variant(ID, A1, Variant),
|
||||||
|
not condition_requirement(ID, "concrete_variant_request", A1, Variant, Value),
|
||||||
|
condition_nodes(ID, PackageNode, node(X, A1)).
|
||||||
|
|
||||||
condition_holds(ConditionID, node(X, Package))
|
condition_holds(ConditionID, node(X, Package))
|
||||||
:- pkg_fact(Package, condition_trigger(ConditionID, TriggerID)),
|
:- pkg_fact(Package, condition_trigger(ConditionID, TriggerID)),
|
||||||
trigger_condition_holds(TriggerID, node(X, Package)).
|
trigger_condition_holds(TriggerID, node(X, Package)).
|
||||||
@ -449,8 +465,8 @@ imposed_nodes(ConditionID, PackageNode, node(X, A1))
|
|||||||
|
|
||||||
% Conditions that hold impose may impose constraints on other specs
|
% Conditions that hold impose may impose constraints on other specs
|
||||||
attr(Name, node(X, A1)) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1), imposed_nodes(ID, PackageNode, node(X, A1)).
|
attr(Name, node(X, A1)) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1), imposed_nodes(ID, PackageNode, node(X, A1)).
|
||||||
attr(Name, node(X, A1), A2) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2), imposed_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name).
|
attr(Name, node(X, A1), A2) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2), imposed_nodes(ID, PackageNode, node(X, A1)), not node_attributes_with_custom_rules(Name).
|
||||||
attr(Name, node(X, A1), A2, A3) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3), imposed_nodes(ID, PackageNode, node(X, A1)), not multiple_nodes_attribute(Name).
|
attr(Name, node(X, A1), A2, A3) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3), imposed_nodes(ID, PackageNode, node(X, A1)), not node_attributes_with_custom_rules(Name).
|
||||||
attr(Name, node(X, A1), A2, A3, A4) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3, A4), imposed_nodes(ID, PackageNode, node(X, A1)).
|
attr(Name, node(X, A1), A2, A3, A4) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3, A4), imposed_nodes(ID, PackageNode, node(X, A1)).
|
||||||
|
|
||||||
% Provider set is relevant only for literals, since it's the only place where `^[virtuals=foo] bar`
|
% Provider set is relevant only for literals, since it's the only place where `^[virtuals=foo] bar`
|
||||||
@ -471,6 +487,15 @@ provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, Virtua
|
|||||||
imposed_constraint(ID, "depends_on", A1, A2, A3),
|
imposed_constraint(ID, "depends_on", A1, A2, A3),
|
||||||
internal_error("Build deps must land in exactly one duplicate").
|
internal_error("Build deps must land in exactly one duplicate").
|
||||||
|
|
||||||
|
% For := we must keep track of the origin of the fact, since we need to check
|
||||||
|
% each condition separately, i.e. foo:=a,b in one place and foo:=c in another
|
||||||
|
% should not make foo:=a,b,c possible
|
||||||
|
attr("concrete_variant_set", node(X, A1), Variant, Value, ID)
|
||||||
|
:- impose(ID, PackageNode),
|
||||||
|
imposed_nodes(ID, PackageNode, node(X, A1)),
|
||||||
|
imposed_constraint(ID, "concrete_variant_set", A1, Variant, Value).
|
||||||
|
|
||||||
|
|
||||||
% The rule below accounts for expressions like:
|
% The rule below accounts for expressions like:
|
||||||
%
|
%
|
||||||
% root ^dep %compiler
|
% root ^dep %compiler
|
||||||
@ -1149,6 +1174,22 @@ error(100, "No valid value for variant '{1}' of package '{0}'", Package, Variant
|
|||||||
% if a variant is set to anything, it is considered 'set'.
|
% if a variant is set to anything, it is considered 'set'.
|
||||||
attr("variant_set", PackageNode, Variant) :- attr("variant_set", PackageNode, Variant, _).
|
attr("variant_set", PackageNode, Variant) :- attr("variant_set", PackageNode, Variant, _).
|
||||||
|
|
||||||
|
% Setting a concrete variant implies setting a variant
|
||||||
|
concrete_variant_value(PackageNode, Variant, Value, Origin) :- attr("concrete_variant_set", PackageNode, Variant, Value, Origin).
|
||||||
|
|
||||||
|
attr("variant_set", PackageNode, Variant, Value) :- attr("concrete_variant_set", PackageNode, Variant, Value, _).
|
||||||
|
|
||||||
|
% Concrete variant values must be in the answer set
|
||||||
|
:- concrete_variant_value(PackageNode, Variant, Value, _), not attr("variant_value", PackageNode, Variant, Value).
|
||||||
|
|
||||||
|
% Extra variant values are not allowed, if the variant is concrete
|
||||||
|
variant_is_concrete(PackageNode, Variant, Origin) :- concrete_variant_value(PackageNode, Variant, _, Origin).
|
||||||
|
|
||||||
|
error(100, "The variant {0} in package {1} specified as := has the extra value {2}", Variant, PackageNode, Value)
|
||||||
|
:- variant_is_concrete(PackageNode, Variant, Origin),
|
||||||
|
attr("variant_value", PackageNode, Variant, Value),
|
||||||
|
not concrete_variant_value(PackageNode, Variant, Value, Origin).
|
||||||
|
|
||||||
% 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
|
||||||
% This only applies to packages we need to build -- concrete packages may
|
% This only applies to packages we need to build -- concrete packages may
|
||||||
% have been built w/different variants from older/different package versions.
|
% have been built w/different variants from older/different package versions.
|
||||||
|
@ -4670,6 +4670,9 @@ def substitute_abstract_variants(spec: Spec):
|
|||||||
# in $spack/lib/spack/spack/spec_list.py
|
# in $spack/lib/spack/spack/spec_list.py
|
||||||
unknown = []
|
unknown = []
|
||||||
for name, v in spec.variants.items():
|
for name, v in spec.variants.items():
|
||||||
|
if v.concrete and v.type == vt.VariantType.MULTI:
|
||||||
|
continue
|
||||||
|
|
||||||
if name == "dev_path":
|
if name == "dev_path":
|
||||||
v.type = vt.VariantType.SINGLE
|
v.type = vt.VariantType.SINGLE
|
||||||
v.concrete = True
|
v.concrete = True
|
||||||
|
@ -3470,3 +3470,99 @@ def test_installed_compiler_and_better_external(
|
|||||||
with spack.config.override("concretizer:reuse", False):
|
with spack.config.override("concretizer:reuse", False):
|
||||||
mpileaks = spack.concretize.concretize_one("mpileaks")
|
mpileaks = spack.concretize.concretize_one("mpileaks")
|
||||||
assert mpileaks.satisfies("%gcc@10")
|
assert mpileaks.satisfies("%gcc@10")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.regression("50006")
|
||||||
|
def test_concrete_multi_valued_variants_in_externals(mutable_config, mock_packages, tmp_path):
|
||||||
|
"""Tests that concrete multivalued variants in externals cannot be extended with additional
|
||||||
|
values when concretizing.
|
||||||
|
"""
|
||||||
|
packages_yaml = syaml.load_config(
|
||||||
|
f"""
|
||||||
|
packages:
|
||||||
|
gcc:
|
||||||
|
buildable: false
|
||||||
|
externals:
|
||||||
|
- spec: gcc@12.1.0 languages:='c,c++'
|
||||||
|
prefix: {tmp_path / 'gcc-12'}
|
||||||
|
extra_attributes:
|
||||||
|
compilers:
|
||||||
|
c: {tmp_path / 'gcc-12'}/bin/gcc
|
||||||
|
cxx: {tmp_path / 'gcc-12'}/bin/g++
|
||||||
|
|
||||||
|
- spec: gcc@14.1.0 languages:=fortran
|
||||||
|
prefix: {tmp_path / 'gcc-14'}
|
||||||
|
extra_attributes:
|
||||||
|
compilers:
|
||||||
|
fortran: {tmp_path / 'gcc-14'}/bin/gfortran
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
mutable_config.set("packages", packages_yaml["packages"])
|
||||||
|
|
||||||
|
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
|
||||||
|
spack.concretize.concretize_one("pkg-b %gcc@14")
|
||||||
|
|
||||||
|
s = spack.concretize.concretize_one("pkg-b %gcc")
|
||||||
|
assert s["c"].satisfies("gcc@12.1.0"), s.tree()
|
||||||
|
assert s["c"].external
|
||||||
|
assert s["c"].satisfies("languages=c,c++") and not s["c"].satisfies("languages=fortran")
|
||||||
|
|
||||||
|
|
||||||
|
def test_concrete_multi_valued_in_input_specs(default_mock_concretization):
|
||||||
|
"""Tests that we can use := to specify exactly multivalued variants in input specs."""
|
||||||
|
s = default_mock_concretization("gcc languages:=fortran")
|
||||||
|
assert not s.external and s["c"].external
|
||||||
|
assert s.satisfies("languages:=fortran")
|
||||||
|
assert not s.satisfies("languages=c") and not s.satisfies("languages=c++")
|
||||||
|
|
||||||
|
|
||||||
|
def test_concrete_multi_valued_variants_in_requirements(mutable_config, mock_packages, tmp_path):
|
||||||
|
"""Tests that concrete multivalued variants can be imposed by requirements."""
|
||||||
|
packages_yaml = syaml.load_config(
|
||||||
|
"""
|
||||||
|
packages:
|
||||||
|
pkg-a:
|
||||||
|
require:
|
||||||
|
- libs:=static
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
mutable_config.set("packages", packages_yaml["packages"])
|
||||||
|
|
||||||
|
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
|
||||||
|
spack.concretize.concretize_one("pkg-a libs=shared")
|
||||||
|
spack.concretize.concretize_one("pkg-a libs=shared,static")
|
||||||
|
|
||||||
|
s = spack.concretize.concretize_one("pkg-a")
|
||||||
|
assert s.satisfies("libs:=static")
|
||||||
|
assert not s.satisfies("libs=shared")
|
||||||
|
|
||||||
|
|
||||||
|
def test_concrete_multi_valued_variants_in_depends_on(default_mock_concretization):
|
||||||
|
"""Tests the use of := in depends_on directives"""
|
||||||
|
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
|
||||||
|
default_mock_concretization("gmt-concrete-mv-dependency ^mvdefaults foo:=c")
|
||||||
|
default_mock_concretization("gmt-concrete-mv-dependency ^mvdefaults foo:=a,c")
|
||||||
|
default_mock_concretization("gmt-concrete-mv-dependency ^mvdefaults foo:=b,c")
|
||||||
|
|
||||||
|
s = default_mock_concretization("gmt-concrete-mv-dependency")
|
||||||
|
assert s.satisfies("^mvdefaults foo:=a,b"), s.tree()
|
||||||
|
assert not s.satisfies("^mvdefaults foo=c")
|
||||||
|
|
||||||
|
|
||||||
|
def test_concrete_multi_valued_variants_when_args(default_mock_concretization):
|
||||||
|
"""Tests the use of := in conflicts and when= arguments"""
|
||||||
|
# Check conflicts("foo:=a,b", when="@0.9")
|
||||||
|
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
|
||||||
|
default_mock_concretization("mvdefaults@0.9 foo:=a,b")
|
||||||
|
|
||||||
|
for c in ("foo:=a", "foo:=a,b,c", "foo:=a,c", "foo:=b,c"):
|
||||||
|
s = default_mock_concretization(f"mvdefaults@0.9 {c}")
|
||||||
|
assert s.satisfies(c)
|
||||||
|
|
||||||
|
# Check depends_on("pkg-b", when="foo:=b,c")
|
||||||
|
s = default_mock_concretization("mvdefaults foo:=b,c")
|
||||||
|
assert s.satisfies("^pkg-b")
|
||||||
|
|
||||||
|
for c in ("foo:=a", "foo:=a,b,c", "foo:=a,b", "foo:=a,c"):
|
||||||
|
s = default_mock_concretization(f"mvdefaults {c}")
|
||||||
|
assert not s.satisfies("^pkg-b")
|
||||||
|
@ -22,7 +22,7 @@ paths:
|
|||||||
fi
|
fi
|
||||||
platforms: ["darwin", "linux"]
|
platforms: ["darwin", "linux"]
|
||||||
results:
|
results:
|
||||||
- spec: "gcc@9.4.0 languages=c,c++"
|
- spec: "gcc@9.4.0 languages:=c,c++"
|
||||||
extra_attributes:
|
extra_attributes:
|
||||||
compilers:
|
compilers:
|
||||||
c: ".*/bin/gcc"
|
c: ".*/bin/gcc"
|
||||||
@ -45,7 +45,7 @@ paths:
|
|||||||
fi
|
fi
|
||||||
platforms: ["darwin", "linux"]
|
platforms: ["darwin", "linux"]
|
||||||
results:
|
results:
|
||||||
- spec: "gcc@5.5.0 languages=c,c++,fortran"
|
- spec: "gcc@5.5.0 languages:=c,c++,fortran"
|
||||||
extra_attributes:
|
extra_attributes:
|
||||||
compilers:
|
compilers:
|
||||||
c: ".*/bin/gcc-5$"
|
c: ".*/bin/gcc-5$"
|
||||||
@ -115,7 +115,7 @@ paths:
|
|||||||
fi
|
fi
|
||||||
platforms: [darwin]
|
platforms: [darwin]
|
||||||
results:
|
results:
|
||||||
- spec: "gcc@14.1.0 languages=c"
|
- spec: "gcc@14.1.0 languages:=c"
|
||||||
extra_attributes:
|
extra_attributes:
|
||||||
compilers:
|
compilers:
|
||||||
c: ".*/bin/gcc-14$"
|
c: ".*/bin/gcc-14$"
|
||||||
|
@ -672,15 +672,13 @@ def determine_variants(cls, exes, version_str):
|
|||||||
translation = {"cxx": "c++"}
|
translation = {"cxx": "c++"}
|
||||||
for lang, compiler in compilers.items():
|
for lang, compiler in compilers.items():
|
||||||
languages.add(translation.get(lang, lang))
|
languages.add(translation.get(lang, lang))
|
||||||
variant_str = "languages={0}".format(",".join(languages))
|
variant_str = "languages:={0}".format(",".join(languages))
|
||||||
return variant_str, {"compilers": compilers}
|
return variant_str, {"compilers": compilers}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_detected_spec(cls, spec, extra_attributes):
|
def validate_detected_spec(cls, spec, extra_attributes):
|
||||||
# For GCC 'compilers' is a mandatory attribute
|
# For GCC 'compilers' is a mandatory attribute
|
||||||
msg = 'the extra attribute "compilers" must be set for ' 'the detected spec "{0}"'.format(
|
msg = f'the extra attribute "compilers" must be set for the detected spec "{spec}"'
|
||||||
spec
|
|
||||||
)
|
|
||||||
assert "compilers" in extra_attributes, msg
|
assert "compilers" in extra_attributes, msg
|
||||||
|
|
||||||
compilers = extra_attributes["compilers"]
|
compilers = extra_attributes["compilers"]
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
# Copyright Spack Project Developers. See COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
|
class GmtConcreteMvDependency(Package):
|
||||||
|
url = "http://www.example.com/"
|
||||||
|
|
||||||
|
version("2.0", md5="abcdef1234567890abcdef1234567890")
|
||||||
|
version("1.0", md5="abcdef1234567890abcdef1234567890")
|
||||||
|
|
||||||
|
depends_on("mvdefaults foo:=a,b")
|
@ -9,5 +9,9 @@ class Mvdefaults(Package):
|
|||||||
url = "http://www.example.com/mvdefaults-1.0.tar.gz"
|
url = "http://www.example.com/mvdefaults-1.0.tar.gz"
|
||||||
|
|
||||||
version("1.0", md5="abcdef1234567890abcdef1234567890")
|
version("1.0", md5="abcdef1234567890abcdef1234567890")
|
||||||
|
version("0.9", md5="abcdef1234567890abcdef1234567890")
|
||||||
|
|
||||||
variant("foo", values=("a", "b", "c"), default=("a", "b", "c"), multi=True, description="")
|
variant("foo", values=("a", "b", "c"), default=("a", "b", "c"), multi=True, description="")
|
||||||
|
conflicts("foo:=a,b", when="@0.9")
|
||||||
|
|
||||||
|
depends_on("pkg-b", when="foo:=b,c")
|
||||||
|
Loading…
Reference in New Issue
Block a user