concretization for conditional dependencies

Signed-off-by: Gregory Becker <becker33@llnl.gov>
This commit is contained in:
Gregory Becker 2025-04-23 15:25:38 -07:00
parent e24bd2ef3c
commit 89e0cf886d
No known key found for this signature in database
GPG Key ID: 2362541F6D14ED84
2 changed files with 83 additions and 18 deletions

View File

@ -1416,12 +1416,17 @@ class ConstraintOrigin(enum.Enum):
result. result.
""" """
CONDITIONAL_SPEC = 0
DEPENDS_ON = 1 DEPENDS_ON = 1
REQUIRE = 2 REQUIRE = 2
@staticmethod @staticmethod
def _SUFFIXES() -> Dict["ConstraintOrigin", str]: def _SUFFIXES() -> Dict["ConstraintOrigin", str]:
return {ConstraintOrigin.DEPENDS_ON: "_dep", ConstraintOrigin.REQUIRE: "_req"} return {
ConstraintOrigin.CONDITIONAL_SPEC: "_cond",
ConstraintOrigin.DEPENDS_ON: "_dep",
ConstraintOrigin.REQUIRE: "_req",
}
@staticmethod @staticmethod
def append_type_suffix(pkg_id: str, kind: "ConstraintOrigin") -> str: def append_type_suffix(pkg_id: str, kind: "ConstraintOrigin") -> str:
@ -2613,6 +2618,10 @@ def _spec_clauses(
if transitive: if transitive:
# TODO: Eventually distinguish 2 deps on the same pkg (build and link) # TODO: Eventually distinguish 2 deps on the same pkg (build and link)
for dspec in spec.edges_to_dependencies(): for dspec in spec.edges_to_dependencies():
# Ignore conditional dependencies, they are handled by caller
if dspec.when != spack.spec.Spec():
continue
dep = dspec.spec dep = dspec.spec
if spec.concrete: if spec.concrete:
@ -2674,22 +2683,13 @@ def _spec_clauses(
# if it's concrete, then the hashes above take care of dependency # if it's concrete, then the hashes above take care of dependency
# constraints, but expand the hashes if asked for. # constraints, but expand the hashes if asked for.
if not spec.concrete or expand_hashes: if not spec.concrete or expand_hashes:
if dspec.when != spack.spec.Spec(): dependency_clauses = self._spec_clauses(
# Are the non-attr things issued here a problem? dep,
dependency_clauses, _ = self.condition_clauses( body=body,
required_spec=dspec.when, expand_hashes=expand_hashes,
imposed_spec=dep, concrete_build_deps=concrete_build_deps,
required_name=dspec.when.name or spec.name, context=context,
msg=f"{spec.name} depends conditionally on {dep.name}", )
)
else:
dependency_clauses = self._spec_clauses(
dep,
body=body,
expand_hashes=expand_hashes,
concrete_build_deps=concrete_build_deps,
context=context,
)
if dspec.depflag == dt.BUILD: if dspec.depflag == dt.BUILD:
clauses.append(fn.attr("depends_on", spec.name, dep.name, "build")) clauses.append(fn.attr("depends_on", spec.name, dep.name, "build"))
if body is False: if body is False:
@ -3248,6 +3248,9 @@ def setup(
self.gen.h1("Spec Constraints") self.gen.h1("Spec Constraints")
self.literal_specs(specs) self.literal_specs(specs)
self.trigger_rules()
self.effect_rules()
self.gen.h1("Variant Values defined in specs") self.gen.h1("Variant Values defined in specs")
self.define_variant_values() self.define_variant_values()
@ -3381,6 +3384,41 @@ def literal_specs(self, specs):
cache[imposed_spec_key] = (effect_id, requirements) cache[imposed_spec_key] = (effect_id, requirements)
self.gen.fact(fn.pkg_fact(spec.name, fn.condition_effect(condition_id, effect_id))) self.gen.fact(fn.pkg_fact(spec.name, fn.condition_effect(condition_id, effect_id)))
# Create subcondition with any conditional dependencies
# self.spec_clauses does not do anything with conditional
# dependencies
for dspec in spec.traverse_edges():
# Ignore unconditional deps
if dspec.when == spack.spec.Spec():
continue
# Cannot use "virtual_node" attr as key for condition
# because reused specs do not track virtual nodes.
# Instead, track whether the parent uses the virtual
def virtual_handler(input_spec, requirements):
ret = remove_facts("virtual_node")(input_spec, requirements)
for edge in input_spec.traverse_edges(root=False, cover="edges"):
if spack.repo.PATH.is_virtual(edge.spec.name):
ret.append(fn.attr("uses_virtual", edge.parent.name, edge.spec.name))
return ret
context = ConditionContext()
context.source = ConstraintOrigin.append_type_suffix(
dspec.parent.name, ConstraintOrigin.CONDITIONAL_SPEC
)
# Default is to remove node-like attrs, override here
context.transform_required = virtual_handler
context.transform_imposed = lambda x, y: y
subcondition_id = self.condition(
dspec.when,
dspec.spec,
required_name=dspec.parent.name,
context=context,
msg=f"Conditional dependency in literal ^[when={dspec.when}]{dspec.spec}",
)
self.gen.fact(fn.subcondition(subcondition_id, condition_id))
if self.concretize_everything: if self.concretize_everything:
self.gen.fact(fn.solve_literal(trigger_id)) self.gen.fact(fn.solve_literal(trigger_id))
@ -3833,6 +3871,7 @@ class SpecBuilder:
r"^package_hash$", r"^package_hash$",
r"^root$", r"^root$",
r"^track_dependencies$", r"^track_dependencies$",
r"^uses_virtual$",
r"^variant_default_value_from_cli$", r"^variant_default_value_from_cli$",
r"^virtual_node$", r"^virtual_node$",
r"^virtual_on_incoming_edges$", r"^virtual_on_incoming_edges$",

View File

@ -429,8 +429,23 @@ trigger_and_effect(Package, TriggerID, EffectID)
% condition_holds(ID, node(ID, Package)) implies all imposed_constraints, unless do_not_impose(ID, node(ID, Package)) % condition_holds(ID, node(ID, Package)) implies all imposed_constraints, unless do_not_impose(ID, node(ID, Package))
% is derived. This allows imposed constraints to be canceled in special cases. % is derived. This allows imposed constraints to be canceled in special cases.
% Effects of direct conditions hold if the trigger holds
impose(EffectID, node(X, Package)) impose(EffectID, node(X, Package))
:- trigger_and_effect(Package, TriggerID, EffectID), :- pkg_fact(Package, condition_effect(ConditionID, EffectID)),
not subcondition(ConditionID, _),
trigger_and_effect(Package, TriggerID, EffectID),
trigger_node(TriggerID, _, node(X, Package)),
trigger_condition_holds(TriggerID, node(X, Package)),
not do_not_impose(EffectID, node(X, Package)).
% Effects of subconditions hold if the trigger holds and the
% primary condition holds
impose(EffectID, node(X, Package))
:- pkg_fact(Package, condition_effect(SubconditionId, EffectID)),
subcondition(SubconditionID, ConditionID),
condition_holds(ConditionID, node(X, Package)),
trigger_and_effect(Package, TriggerID, EffectID),
trigger_node(TriggerID, _, node(X, Package)), trigger_node(TriggerID, _, node(X, Package)),
trigger_condition_holds(TriggerID, node(X, Package)), trigger_condition_holds(TriggerID, node(X, Package)),
not do_not_impose(EffectID, node(X, Package)). not do_not_impose(EffectID, node(X, Package)).
@ -645,6 +660,16 @@ virtual_condition_holds(node(Y, A2), Virtual)
attr("virtual_on_edge", node(X, A1), node(Y, A2), Virtual), attr("virtual_on_edge", node(X, A1), node(Y, A2), Virtual),
not build(node(X, A1)). not build(node(X, A1)).
% Simplified virtual information for conditionl requirements in
% conditional dependencies
% Most specs track virtuals on edges
attr("uses_virtual", PackageNode, Virtual) :-
attr("virtual_on_edge", PackageNode, _, Virtual).
% Reused specs don't track a real edge to build-only deps
attr("uses_virtual", PackageNode, Virtual) :-
attr("virtual_on_build_edge", PackageNode, _, Virtual).
% we cannot have additional variant values when we are working with concrete specs % we cannot have additional variant values when we are working with concrete specs
:- attr("node", node(ID, Package)), :- attr("node", node(ID, Package)),
attr("hash", node(ID, Package), Hash), attr("hash", node(ID, Package), Hash),
@ -660,6 +685,7 @@ virtual_condition_holds(node(Y, A2), Virtual)
internal_error("imposed hash without imposing all flag values"). internal_error("imposed hash without imposing all flag values").
#defined condition/2. #defined condition/2.
#defined subcondition/2.
#defined condition_requirement/3. #defined condition_requirement/3.
#defined condition_requirement/4. #defined condition_requirement/4.
#defined condition_requirement/5. #defined condition_requirement/5.