Avoid verifying variants in default package requirements (#35037)
Default package requirements might contain variants that are not defined in each package, so we shouldn't verify them when emitting facts for the ASP solver. Account for group when enforcing requirements packages:all : don't emit facts for requirement conditions that can't apply to current spec
This commit is contained in:
parent
ce693ff304
commit
50691ccdd9
3 changed files with 58 additions and 23 deletions
|
@ -1012,11 +1012,13 @@ def package_compiler_defaults(self, pkg):
|
||||||
def package_requirement_rules(self, pkg):
|
def package_requirement_rules(self, pkg):
|
||||||
pkg_name = pkg.name
|
pkg_name = pkg.name
|
||||||
config = spack.config.get("packages")
|
config = spack.config.get("packages")
|
||||||
requirements = config.get(pkg_name, {}).get("require", []) or config.get("all", {}).get(
|
requirements, raise_on_failure = config.get(pkg_name, {}).get("require", []), True
|
||||||
"require", []
|
if not requirements:
|
||||||
)
|
requirements, raise_on_failure = config.get("all", {}).get("require", []), False
|
||||||
rules = self._rules_from_requirements(pkg_name, requirements)
|
rules = self._rules_from_requirements(pkg_name, requirements)
|
||||||
self.emit_facts_from_requirement_rules(rules, virtual=False)
|
self.emit_facts_from_requirement_rules(
|
||||||
|
rules, virtual=False, raise_on_failure=raise_on_failure
|
||||||
|
)
|
||||||
|
|
||||||
def _rules_from_requirements(self, pkg_name, requirements):
|
def _rules_from_requirements(self, pkg_name, requirements):
|
||||||
"""Manipulate requirements from packages.yaml, and return a list of tuples
|
"""Manipulate requirements from packages.yaml, and return a list of tuples
|
||||||
|
@ -1161,11 +1163,13 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=
|
||||||
named_cond.name = named_cond.name or name
|
named_cond.name = named_cond.name or name
|
||||||
assert named_cond.name, "must provide name for anonymous condtions!"
|
assert named_cond.name, "must provide name for anonymous condtions!"
|
||||||
|
|
||||||
|
# Check if we can emit the requirements before updating the condition ID counter.
|
||||||
|
# In this way, if a condition can't be emitted but the exception is handled in the caller,
|
||||||
|
# we won't emit partial facts.
|
||||||
|
requirements = self.spec_clauses(named_cond, body=True, required_from=name)
|
||||||
|
|
||||||
condition_id = next(self._condition_id_counter)
|
condition_id = next(self._condition_id_counter)
|
||||||
self.gen.fact(fn.condition(condition_id, msg))
|
self.gen.fact(fn.condition(condition_id, msg))
|
||||||
|
|
||||||
# requirements trigger the condition
|
|
||||||
requirements = self.spec_clauses(named_cond, body=True, required_from=name)
|
|
||||||
for pred in requirements:
|
for pred in requirements:
|
||||||
self.gen.fact(fn.condition_requirement(condition_id, *pred.args))
|
self.gen.fact(fn.condition_requirement(condition_id, *pred.args))
|
||||||
|
|
||||||
|
@ -1261,23 +1265,39 @@ def provider_requirements(self):
|
||||||
rules = self._rules_from_requirements(virtual_str, requirements)
|
rules = self._rules_from_requirements(virtual_str, requirements)
|
||||||
self.emit_facts_from_requirement_rules(rules, virtual=True)
|
self.emit_facts_from_requirement_rules(rules, virtual=True)
|
||||||
|
|
||||||
def emit_facts_from_requirement_rules(self, rules, virtual=False):
|
def emit_facts_from_requirement_rules(self, rules, *, virtual=False, raise_on_failure=True):
|
||||||
"""Generate facts to enforce requirements from packages.yaml."""
|
"""Generate facts to enforce requirements from packages.yaml.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rules: rules for which we want facts to be emitted
|
||||||
|
virtual: if True the requirements are on a virtual spec
|
||||||
|
raise_on_failure: if True raise an exception when a requirement condition is invalid
|
||||||
|
for the current spec. If False, just skip that condition
|
||||||
|
"""
|
||||||
for requirement_grp_id, (pkg_name, policy, requirement_grp) in enumerate(rules):
|
for requirement_grp_id, (pkg_name, policy, requirement_grp) in enumerate(rules):
|
||||||
self.gen.fact(fn.requirement_group(pkg_name, requirement_grp_id))
|
self.gen.fact(fn.requirement_group(pkg_name, requirement_grp_id))
|
||||||
self.gen.fact(fn.requirement_policy(pkg_name, requirement_grp_id, policy))
|
self.gen.fact(fn.requirement_policy(pkg_name, requirement_grp_id, policy))
|
||||||
for requirement_weight, spec_str in enumerate(requirement_grp):
|
requirement_weight = 0
|
||||||
|
for spec_str in requirement_grp:
|
||||||
spec = spack.spec.Spec(spec_str)
|
spec = spack.spec.Spec(spec_str)
|
||||||
if not spec.name:
|
if not spec.name:
|
||||||
spec.name = pkg_name
|
spec.name = pkg_name
|
||||||
when_spec = spec
|
when_spec = spec
|
||||||
if virtual:
|
if virtual:
|
||||||
when_spec = spack.spec.Spec(pkg_name)
|
when_spec = spack.spec.Spec(pkg_name)
|
||||||
|
|
||||||
|
try:
|
||||||
member_id = self.condition(
|
member_id = self.condition(
|
||||||
required_spec=when_spec, imposed_spec=spec, name=pkg_name, node=virtual
|
required_spec=when_spec, imposed_spec=spec, name=pkg_name, node=virtual
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
if raise_on_failure:
|
||||||
|
raise RuntimeError("cannot emit requirements for the solver") from e
|
||||||
|
continue
|
||||||
|
|
||||||
self.gen.fact(fn.requirement_group_member(member_id, pkg_name, requirement_grp_id))
|
self.gen.fact(fn.requirement_group_member(member_id, pkg_name, requirement_grp_id))
|
||||||
self.gen.fact(fn.requirement_has_weight(member_id, requirement_weight))
|
self.gen.fact(fn.requirement_has_weight(member_id, requirement_weight))
|
||||||
|
requirement_weight += 1
|
||||||
|
|
||||||
def external_packages(self):
|
def external_packages(self):
|
||||||
"""Facts on external packages, as read from packages.yaml"""
|
"""Facts on external packages, as read from packages.yaml"""
|
||||||
|
|
|
@ -464,12 +464,12 @@ requirement_group_satisfied(Package, X) :-
|
||||||
requirement_policy(Package, X, "one_of"),
|
requirement_policy(Package, X, "one_of"),
|
||||||
requirement_group(Package, X).
|
requirement_group(Package, X).
|
||||||
|
|
||||||
requirement_weight(Package, W) :-
|
requirement_weight(Package, Group, W) :-
|
||||||
condition_holds(Y),
|
condition_holds(Y),
|
||||||
requirement_has_weight(Y, W),
|
requirement_has_weight(Y, W),
|
||||||
requirement_group_member(Y, Package, X),
|
requirement_group_member(Y, Package, Group),
|
||||||
requirement_policy(Package, X, "one_of"),
|
requirement_policy(Package, Group, "one_of"),
|
||||||
requirement_group_satisfied(Package, X).
|
requirement_group_satisfied(Package, Group).
|
||||||
|
|
||||||
requirement_group_satisfied(Package, X) :-
|
requirement_group_satisfied(Package, X) :-
|
||||||
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } ,
|
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } ,
|
||||||
|
@ -477,16 +477,16 @@ requirement_group_satisfied(Package, X) :-
|
||||||
requirement_policy(Package, X, "any_of"),
|
requirement_policy(Package, X, "any_of"),
|
||||||
requirement_group(Package, X).
|
requirement_group(Package, X).
|
||||||
|
|
||||||
requirement_weight(Package, W) :-
|
requirement_weight(Package, Group, W) :-
|
||||||
W = #min {
|
W = #min {
|
||||||
Z : requirement_has_weight(Y, Z), condition_holds(Y), requirement_group_member(Y, Package, X);
|
Z : requirement_has_weight(Y, Z), condition_holds(Y), requirement_group_member(Y, Package, Group);
|
||||||
% We need this to avoid an annoying warning during the solve
|
% We need this to avoid an annoying warning during the solve
|
||||||
% concretize.lp:1151:5-11: info: tuple ignored:
|
% concretize.lp:1151:5-11: info: tuple ignored:
|
||||||
% #sup@73
|
% #sup@73
|
||||||
10000
|
10000
|
||||||
},
|
},
|
||||||
requirement_policy(Package, X, "any_of"),
|
requirement_policy(Package, Group, "any_of"),
|
||||||
requirement_group_satisfied(Package, X).
|
requirement_group_satisfied(Package, Group).
|
||||||
|
|
||||||
error(2, "Cannot satisfy the requirements in packages.yaml for the '{0}' package. You may want to delete them to proceed with concretization. To check where the requirements are defined run 'spack config blame packages'", Package) :-
|
error(2, "Cannot satisfy the requirements in packages.yaml for the '{0}' package. You may want to delete them to proceed with concretization. To check where the requirements are defined run 'spack config blame packages'", Package) :-
|
||||||
activate_requirement_rules(Package),
|
activate_requirement_rules(Package),
|
||||||
|
@ -1139,8 +1139,8 @@ opt_criterion(75, "requirement weight").
|
||||||
#minimize{ 0@275: #true }.
|
#minimize{ 0@275: #true }.
|
||||||
#minimize{ 0@75: #true }.
|
#minimize{ 0@75: #true }.
|
||||||
#minimize {
|
#minimize {
|
||||||
Weight@75+Priority
|
Weight@75+Priority,Package,Group
|
||||||
: requirement_weight(Package, Weight),
|
: requirement_weight(Package, Group, Weight),
|
||||||
build_priority(Package, Priority)
|
build_priority(Package, Priority)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
|
|
@ -413,3 +413,18 @@ def test_incompatible_virtual_requirements_raise(concretize_scope, mock_packages
|
||||||
spec = Spec("callpath ^zmpi")
|
spec = Spec("callpath ^zmpi")
|
||||||
with pytest.raises(UnsatisfiableSpecError):
|
with pytest.raises(UnsatisfiableSpecError):
|
||||||
spec.concretize()
|
spec.concretize()
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_existing_variants_under_all(concretize_scope, mock_packages):
|
||||||
|
if spack.config.get("config:concretizer") == "original":
|
||||||
|
pytest.skip("Original concretizer does not support configuration" " requirements")
|
||||||
|
conf_str = """\
|
||||||
|
packages:
|
||||||
|
all:
|
||||||
|
require:
|
||||||
|
- any_of: ["~foo", "@:"]
|
||||||
|
"""
|
||||||
|
update_packages_config(conf_str)
|
||||||
|
|
||||||
|
spec = Spec("callpath ^zmpi").concretized()
|
||||||
|
assert "~foo" not in spec
|
||||||
|
|
Loading…
Reference in a new issue