diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 0e298bfb4e92f31bed85f8e463b02f80f6b549d9..9bd44a8d7d96ea7f14149c30448beb968f7907be 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -8,12 +8,11 @@
import enum
import itertools
import os
-import pathlib
import pprint
import re
import types
import warnings
-from typing import Callable, Dict, List, NamedTuple, Optional, Sequence, Set, Tuple, Union
+from typing import Dict, List, NamedTuple, Optional, Sequence, Tuple, Union
import archspec.cpu
@@ -338,13 +337,6 @@ def __getattr__(self, name):
fn = AspFunctionBuilder()
-TransformFunction = Callable[[spack.spec.Spec, List[AspFunction]], List[AspFunction]]
-
-
-def remove_node(spec: spack.spec.Spec, facts: List[AspFunction]) -> List[AspFunction]:
- """Transformation that removes all "node" and "virtual_node" from the input list of facts."""
- return list(filter(lambda x: x.args[0] not in ("node", "virtual_node"), facts))
-
def _create_counter(specs, tests):
strategy = spack.config.CONFIG.get("concretizer:duplicates:strategy", "none")
@@ -692,7 +684,7 @@ def extract_args(model, predicate_name):
class ErrorHandler:
def __init__(self, model):
self.model = model
- self.full_model = None
+ self.error_args = extract_args(model, "error")
def multiple_values_error(self, attribute, pkg):
return f'Cannot select a single "{attribute}" for package "{pkg}"'
@@ -700,48 +692,6 @@ def multiple_values_error(self, attribute, pkg):
def no_value_error(self, attribute, pkg):
return f'Cannot select a single "{attribute}" for package "{pkg}"'
- def _get_cause_tree(
- self,
- cause: Tuple[str, str],
- conditions: Dict[str, str],
- condition_causes: List[Tuple[Tuple[str, str], Tuple[str, str]]],
- seen: Set,
- indent: str = " ",
- ) -> List[str]:
- """
- Implementation of recursion for self.get_cause_tree. Much of this operates on tuples
- (condition_id, set_id) in which the latter idea means that the condition represented by
- the former held in the condition set represented by the latter.
- """
- seen.add(cause)
- parents = [c for e, c in condition_causes if e == cause and c not in seen]
- local = "required because %s " % conditions[cause[0]]
-
- return [indent + local] + [
- c
- for parent in parents
- for c in self._get_cause_tree(
- parent, conditions, condition_causes, seen, indent=indent + " "
- )
- ]
-
- def get_cause_tree(self, cause: Tuple[str, str]) -> List[str]:
- """
- Get the cause tree associated with the given cause.
-
- Arguments:
- cause: The root cause of the tree (final condition)
-
- Returns:
- A list of strings describing the causes, formatted to display tree structure.
- """
- conditions: Dict[str, str] = dict(extract_args(self.full_model, "condition_reason"))
- condition_causes: List[Tuple[Tuple[str, str], Tuple[str, str]]] = list(
- ((Effect, EID), (Cause, CID))
- for Effect, EID, Cause, CID in extract_args(self.full_model, "condition_cause")
- )
- return self._get_cause_tree(cause, conditions, condition_causes, set())
-
def handle_error(self, msg, *args):
"""Handle an error state derived by the solver."""
if msg == "multiple_values_error":
@@ -750,31 +700,14 @@ def handle_error(self, msg, *args):
if msg == "no_value_error":
return self.no_value_error(*args)
- try:
- idx = args.index("startcauses")
- except ValueError:
- msg_args = args
- causes = []
- else:
- msg_args = args[:idx]
- cause_args = args[idx + 1 :]
- cause_args_conditions = cause_args[::2]
- cause_args_ids = cause_args[1::2]
- causes = list(zip(cause_args_conditions, cause_args_ids))
-
- msg = msg.format(*msg_args)
-
# For variant formatting, we sometimes have to construct specs
# to format values properly. Find/replace all occurances of
# Spec(...) with the string representation of the spec mentioned
+ msg = msg.format(*args)
specs_to_construct = re.findall(r"Spec\(([^)]*)\)", msg)
for spec_str in specs_to_construct:
msg = msg.replace("Spec(%s)" % spec_str, str(spack.spec.Spec(spec_str)))
- for cause in set(causes):
- for c in self.get_cause_tree(cause):
- msg += f"\n{c}"
-
return msg
def message(self, errors) -> str:
@@ -786,31 +719,11 @@ def message(self, errors) -> str:
return "\n".join([header] + messages)
def raise_if_errors(self):
- initial_error_args = extract_args(self.model, "error")
- if not initial_error_args:
+ if not self.error_args:
return
- error_causation = clingo.Control()
-
- parent_dir = pathlib.Path(__file__).parent
- errors_lp = parent_dir / "error_messages.lp"
-
- def on_model(model):
- self.full_model = model.symbols(shown=True, terms=True)
-
- with error_causation.backend() as backend:
- for atom in self.model:
- atom_id = backend.add_atom(atom)
- backend.add_rule([atom_id], [], choice=False)
-
- error_causation.load(str(errors_lp))
- error_causation.ground([("base", []), ("error_messages", [])])
- _ = error_causation.solve(on_model=on_model)
-
- # No choices so there will be only one model
- error_args = extract_args(self.full_model, "error")
errors = sorted(
- [(int(priority), msg, args) for priority, msg, *args in error_args], reverse=True
+ [(int(priority), msg, args) for priority, msg, *args in self.error_args], reverse=True
)
try:
msg = self.message(errors)
@@ -1199,7 +1112,7 @@ def conflict_rules(self, pkg):
default_msg = "{0}: '{1}' conflicts with '{2}'"
no_constraint_msg = "{0}: conflicts with '{1}'"
for trigger, constraints in pkg.conflicts.items():
- trigger_msg = f"conflict is triggered when {str(trigger)}"
+ trigger_msg = "conflict trigger %s" % str(trigger)
trigger_spec = spack.spec.Spec(trigger)
trigger_id = self.condition(
trigger_spec, name=trigger_spec.name or pkg.name, msg=trigger_msg
@@ -1211,11 +1124,7 @@ def conflict_rules(self, pkg):
conflict_msg = no_constraint_msg.format(pkg.name, trigger)
else:
conflict_msg = default_msg.format(pkg.name, trigger, constraint)
-
- spec_for_msg = (
- spack.spec.Spec(pkg.name) if constraint == spack.spec.Spec() else constraint
- )
- constraint_msg = f"conflict applies to spec {str(spec_for_msg)}"
+ constraint_msg = "conflict constraint %s" % str(constraint)
constraint_id = self.condition(constraint, name=pkg.name, msg=constraint_msg)
self.gen.fact(
fn.pkg_fact(pkg.name, fn.conflict(trigger_id, constraint_id, conflict_msg))
@@ -1371,7 +1280,7 @@ def trigger_rules(self):
self.gen.h2("Trigger conditions")
for name in self._trigger_cache:
cache = self._trigger_cache[name]
- for (spec_str, _), (trigger_id, requirements) in cache.items():
+ for spec_str, (trigger_id, requirements) in cache.items():
self.gen.fact(fn.pkg_fact(name, fn.trigger_id(trigger_id)))
self.gen.fact(fn.pkg_fact(name, fn.trigger_msg(spec_str)))
for predicate in requirements:
@@ -1384,7 +1293,7 @@ def effect_rules(self):
self.gen.h2("Imposed requirements")
for name in self._effect_cache:
cache = self._effect_cache[name]
- for (spec_str, _), (effect_id, requirements) in cache.items():
+ for spec_str, (effect_id, requirements) in cache.items():
self.gen.fact(fn.pkg_fact(name, fn.effect_id(effect_id)))
self.gen.fact(fn.pkg_fact(name, fn.effect_msg(spec_str)))
for predicate in requirements:
@@ -1483,26 +1392,18 @@ def variant_rules(self, pkg):
self.gen.newline()
- def condition(
- self,
- required_spec: spack.spec.Spec,
- imposed_spec: Optional[spack.spec.Spec] = None,
- name: Optional[str] = None,
- msg: Optional[str] = None,
- transform_required: Optional[TransformFunction] = None,
- transform_imposed: Optional[TransformFunction] = remove_node,
- ):
+ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=False):
"""Generate facts for a dependency or virtual provider condition.
Arguments:
- required_spec: the constraints that triggers this condition
- imposed_spec: the constraints that are imposed when this condition is triggered
- name: name for `required_spec` (required if required_spec is anonymous, ignored if not)
- msg: description of the condition
- transform_required: transformation applied to facts from the required spec. Defaults
- to leave facts as they are.
- transform_imposed: transformation applied to facts from the imposed spec. Defaults
- to removing "node" and "virtual_node" facts.
+ required_spec (spack.spec.Spec): the spec that triggers this condition
+ imposed_spec (spack.spec.Spec or None): the spec with constraints that
+ are imposed when this condition is triggered
+ name (str or None): name for `required_spec` (required if
+ required_spec is anonymous, ignored if not)
+ msg (str or None): description of the condition
+ node (bool): if False does not emit "node" or "virtual_node" requirements
+ from the imposed spec
Returns:
int: id of the condition created by this function
"""
@@ -1520,14 +1421,10 @@ def condition(
cache = self._trigger_cache[named_cond.name]
- named_cond_key = (str(named_cond), transform_required)
+ named_cond_key = str(named_cond)
if named_cond_key not in cache:
trigger_id = next(self._id_counter)
requirements = self.spec_clauses(named_cond, body=True, required_from=name)
-
- if transform_required:
- requirements = transform_required(named_cond, requirements)
-
cache[named_cond_key] = (trigger_id, requirements)
trigger_id, requirements = cache[named_cond_key]
self.gen.fact(fn.pkg_fact(named_cond.name, fn.condition_trigger(condition_id, trigger_id)))
@@ -1536,14 +1433,14 @@ def condition(
return condition_id
cache = self._effect_cache[named_cond.name]
- imposed_spec_key = (str(imposed_spec), transform_imposed)
+ imposed_spec_key = str(imposed_spec)
if imposed_spec_key not in cache:
effect_id = next(self._id_counter)
requirements = self.spec_clauses(imposed_spec, body=False, required_from=name)
-
- if transform_imposed:
- requirements = transform_imposed(imposed_spec, requirements)
-
+ if not node:
+ requirements = list(
+ filter(lambda x: x.args[0] not in ("node", "virtual_node"), requirements)
+ )
cache[imposed_spec_key] = (effect_id, requirements)
effect_id, requirements = cache[imposed_spec_key]
self.gen.fact(fn.pkg_fact(named_cond.name, fn.condition_effect(condition_id, effect_id)))
@@ -1603,33 +1500,22 @@ def package_dependencies_rules(self, pkg):
if not depflag:
continue
- msg = f"{pkg.name} depends on {dep.spec}"
+ msg = "%s depends on %s" % (pkg.name, dep.spec.name)
if cond != spack.spec.Spec():
- msg += f" when {cond}"
+ msg += " when %s" % cond
else:
pass
- def track_dependencies(input_spec, requirements):
- return requirements + [fn.attr("track_dependencies", input_spec.name)]
-
- def dependency_holds(input_spec, requirements):
- return remove_node(input_spec, requirements) + [
- fn.attr(
- "dependency_holds", pkg.name, input_spec.name, dt.flag_to_string(t)
- )
- for t in dt.ALL_FLAGS
- if t & depflag
- ]
-
- self.condition(
- cond,
- dep.spec,
- name=pkg.name,
- msg=msg,
- transform_required=track_dependencies,
- transform_imposed=dependency_holds,
+ condition_id = self.condition(cond, dep.spec, pkg.name, msg)
+ self.gen.fact(
+ fn.pkg_fact(pkg.name, fn.dependency_condition(condition_id, dep.spec.name))
)
+ for t in dt.ALL_FLAGS:
+ if t & depflag:
+ # there is a declared dependency of type t
+ self.gen.fact(fn.dependency_type(condition_id, dt.flag_to_string(t)))
+
self.gen.newline()
def virtual_preferences(self, pkg_name, func):
@@ -1724,17 +1610,8 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
when_spec = spack.spec.Spec(pkg_name)
try:
- # With virtual we want to emit "node" and "virtual_node" in imposed specs
- transform: Optional[TransformFunction] = remove_node
- if virtual:
- transform = None
-
member_id = self.condition(
- required_spec=when_spec,
- imposed_spec=spec,
- name=pkg_name,
- transform_imposed=transform,
- msg=f"{spec_str} is a requirement for package {pkg_name}",
+ required_spec=when_spec, imposed_spec=spec, name=pkg_name, node=virtual
)
except Exception as e:
# Do not raise if the rule comes from the 'all' subsection, since usability
@@ -1797,16 +1674,8 @@ def external_packages(self):
# Declare external conditions with a local index into packages.yaml
for local_idx, spec in enumerate(external_specs):
msg = "%s available as external when satisfying %s" % (spec.name, spec)
-
- def external_imposition(input_spec, _):
- return [fn.attr("external_conditions_hold", input_spec.name, local_idx)]
-
- self.condition(
- spec,
- spack.spec.Spec(spec.name),
- msg=msg,
- transform_imposed=external_imposition,
- )
+ condition_id = self.condition(spec, msg=msg)
+ self.gen.fact(fn.pkg_fact(pkg_name, fn.possible_external(condition_id, local_idx)))
self.possible_versions[spec.name].add(spec.version)
self.gen.newline()
@@ -2036,7 +1905,6 @@ class Body:
if not body:
for virtual in virtuals:
clauses.append(fn.attr("provider_set", spec.name, virtual))
- clauses.append(fn.attr("virtual_node", virtual))
else:
for virtual in virtuals:
clauses.append(fn.attr("virtual_on_incoming_edges", spec.name, virtual))
@@ -2671,43 +2539,20 @@ def setup(
self.define_target_constraints()
def literal_specs(self, specs):
- for spec in specs:
+ for idx, spec in enumerate(specs):
self.gen.h2("Spec: %s" % str(spec))
- condition_id = next(self._id_counter)
- trigger_id = next(self._id_counter)
+ self.gen.fact(fn.literal(idx))
- # Special condition triggered by "literal_solved"
- self.gen.fact(fn.literal(trigger_id))
- self.gen.fact(fn.pkg_fact(spec.name, fn.condition_trigger(condition_id, trigger_id)))
- self.gen.fact(fn.condition_reason(condition_id, f"{spec} requested explicitly"))
-
- imposed_spec_key = str(spec), None
- cache = self._effect_cache[spec.name]
- if imposed_spec_key in cache:
- effect_id, requirements = cache[imposed_spec_key]
- else:
- effect_id = next(self._id_counter)
- requirements = self.spec_clauses(spec)
- root_name = spec.name
- for clause in requirements:
- clause_name = clause.args[0]
- if clause_name == "variant_set":
- requirements.append(
- fn.attr("variant_default_value_from_cli", *clause.args[1:])
+ self.gen.fact(fn.literal(idx, "virtual_root" if spec.virtual else "root", spec.name))
+ for clause in self.spec_clauses(spec):
+ self.gen.fact(fn.literal(idx, *clause.args))
+ if clause.args[0] == "variant_set":
+ self.gen.fact(
+ fn.literal(idx, "variant_default_value_from_cli", *clause.args[1:])
)
- elif clause_name in ("node", "virtual_node", "hash"):
- # These facts are needed to compute the "condition_set" of the root
- pkg_name = clause.args[1]
- self.gen.fact(fn.mentioned_in_literal(trigger_id, root_name, pkg_name))
-
- requirements.append(fn.attr("virtual_root" if spec.virtual else "root", spec.name))
- cache[imposed_spec_key] = (effect_id, requirements)
- self.gen.fact(fn.pkg_fact(spec.name, fn.condition_effect(condition_id, effect_id)))
if self.concretize_everything:
- self.gen.fact(fn.solve_literal(trigger_id))
-
- self.effect_rules()
+ self.gen.fact(fn.solve_literal(idx))
def validate_and_define_versions_from_requirements(
self, *, allow_deprecated: bool, require_checksum: bool
diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp
index d5f24ddc3b33f0e58b34f23e1d392d68073395ac..46569f61b27b8861b240534188329b4414004e68 100644
--- a/lib/spack/spack/solver/concretize.lp
+++ b/lib/spack/spack/solver/concretize.lp
@@ -10,8 +10,9 @@
% ID of the nodes in the "root" link-run sub-DAG
#const min_dupe_id = 0.
-#const direct_link_run = 0.
-#const direct_build = 1.
+#const link_run = 0.
+#const direct_link_run =1.
+#const direct_build = 2.
% Allow clingo to create nodes
{ attr("node", node(0..X-1, Package)) } :- max_dupes(Package, X), not virtual(Package).
@@ -29,21 +30,23 @@
:- attr("variant_value", PackageNode, _, _), not attr("node", PackageNode).
:- attr("node_flag_compiler_default", PackageNode), not attr("node", PackageNode).
:- attr("node_flag", PackageNode, _, _), not attr("node", PackageNode).
+:- attr("node_flag_source", PackageNode, _, _), not attr("node", PackageNode).
:- attr("no_flags", PackageNode, _), not attr("node", PackageNode).
:- attr("external_spec_selected", PackageNode, _), not attr("node", PackageNode).
:- attr("depends_on", ParentNode, _, _), not attr("node", ParentNode).
:- attr("depends_on", _, ChildNode, _), not attr("node", ChildNode).
:- attr("node_flag_source", ParentNode, _, _), not attr("node", ParentNode).
:- attr("node_flag_source", _, _, ChildNode), not attr("node", ChildNode).
-:- attr("virtual_node", VirtualNode), not provider(_, VirtualNode), internal_error("virtual node with no provider").
-:- provider(_, VirtualNode), not attr("virtual_node", VirtualNode), internal_error("provider with no virtual node").
-:- provider(PackageNode, _), not attr("node", PackageNode), internal_error("provider with no real node").
-:- attr("root", node(ID, PackageNode)), ID > min_dupe_id, internal_error("root with a non-minimal duplicate ID").
+:- attr("virtual_node", VirtualNode), not provider(_, VirtualNode).
+:- provider(_, VirtualNode), not attr("virtual_node", VirtualNode).
+:- provider(PackageNode, _), not attr("node", PackageNode).
+
+:- attr("root", node(ID, PackageNode)), ID > min_dupe_id.
% Nodes in the "root" unification set cannot depend on non-root nodes if the dependency is "link" or "run"
-:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "link"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("link dependency out of the root unification set").
-:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "run"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("run dependency out of the root unification set").
+:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "link"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)).
+:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "run"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)).
% Rules on "unification sets", i.e. on sets of nodes allowing a single configuration of any given package
unify(SetID, PackageName) :- unification_set(SetID, node(_, PackageName)).
@@ -83,24 +86,22 @@ unification_set(SetID, VirtualNode)
%----
% In the "root" unification set only ID = 0 are allowed
-:- unification_set("root", node(ID, _)), ID != 0, internal_error("root unification set has node with non-zero unification set ID").
+:- unification_set("root", node(ID, _)), ID != 0.
% In the "root" unification set we allow only packages from the link-run possible subDAG
-:- unification_set("root", node(_, Package)), not possible_in_link_run(Package), not virtual(Package), internal_error("package outside possible link/run graph in root unification set").
+:- unification_set("root", node(_, Package)), not possible_in_link_run(Package), not virtual(Package).
% Each node must belong to at least one unification set
-:- attr("node", PackageNode), not unification_set(_, PackageNode), internal_error("node belongs to no unification set").
+:- attr("node", PackageNode), not unification_set(_, PackageNode).
% Cannot have a node with an ID, if lower ID of the same package are not used
:- attr("node", node(ID1, Package)),
not attr("node", node(ID2, Package)),
- max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1,
- internal_error("node skipped id number").
+ max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1.
:- attr("virtual_node", node(ID1, Package)),
not attr("virtual_node", node(ID2, Package)),
- max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1,
- internal_error("virtual node skipped id number").
+ max_dupes(Package, X), ID1=0..X-1, ID2=0..X-1, ID2 < ID1.
%-----------------------------------------------------------------------------
% Map literal input specs to facts that drive the solve
@@ -114,28 +115,29 @@ multiple_nodes_attribute("depends_on").
multiple_nodes_attribute("virtual_on_edge").
multiple_nodes_attribute("provider_set").
-trigger_condition_holds(TriggerID, node(min_dupe_id, Package)) :-
- solve_literal(TriggerID),
- pkg_fact(Package, condition_trigger(_, TriggerID)),
- literal(TriggerID).
+% Map constraint on the literal ID to facts on the node
+attr(Name, node(min_dupe_id, A1)) :- literal(LiteralID, Name, A1), solve_literal(LiteralID).
+attr(Name, node(min_dupe_id, A1), A2) :- literal(LiteralID, Name, A1, A2), solve_literal(LiteralID), not multiple_nodes_attribute(Name).
+attr(Name, node(min_dupe_id, A1), A2, A3) :- literal(LiteralID, Name, A1, A2, A3), solve_literal(LiteralID), not multiple_nodes_attribute(Name).
+attr(Name, node(min_dupe_id, A1), A2, A3, A4) :- literal(LiteralID, Name, A1, A2, A3, A4), solve_literal(LiteralID).
-trigger_node(TriggerID, Node, Node) :-
- trigger_condition_holds(TriggerID, Node),
- literal(TriggerID).
+% Special cases where nodes occur in arguments other than A1
+attr("node_flag_source", node(min_dupe_id, A1), A2, node(min_dupe_id, A3)) :- literal(LiteralID, "node_flag_source", A1, A2, A3), solve_literal(LiteralID).
+attr("depends_on", node(min_dupe_id, A1), node(min_dupe_id, A2), A3) :- literal(LiteralID, "depends_on", A1, A2, A3), solve_literal(LiteralID).
-% Since we trigger the existence of literal nodes from a condition, we need to construct
-% the condition_set/2 manually below
-mentioned_in_literal(Root, Mentioned) :- mentioned_in_literal(TriggerID, Root, Mentioned), solve_literal(TriggerID).
-condition_set(node(min_dupe_id, Root), node(min_dupe_id, Mentioned)) :- mentioned_in_literal(Root, Mentioned).
+attr("virtual_node", node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", _, Virtual), solve_literal(LiteralID).
+attr("provider_set", node(min_dupe_id, Provider), node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", Provider, Virtual), solve_literal(LiteralID).
+provider(node(min_dupe_id, Provider), node(min_dupe_id, Virtual)) :- literal(LiteralID, "provider_set", Provider, Virtual), solve_literal(LiteralID).
% Discriminate between "roots" that have been explicitly requested, and roots that are deduced from "virtual roots"
-explicitly_requested_root(node(min_dupe_id, Package)) :-
- solve_literal(TriggerID),
- trigger_and_effect(Package, TriggerID, EffectID),
- imposed_constraint(EffectID, "root", Package).
+explicitly_requested_root(node(min_dupe_id, A1)) :- literal(LiteralID, "root", A1), solve_literal(LiteralID).
#defined concretize_everything/0.
#defined literal/1.
+#defined literal/3.
+#defined literal/4.
+#defined literal/5.
+#defined literal/6.
% Attributes for node packages which must have a single value
attr_single_value("version").
@@ -233,8 +235,7 @@ possible_version_weight(node(ID, Package), Weight)
1 { version_weight(node(ID, Package), Weight) : pkg_fact(Package, version_declared(Version, Weight)) } 1
:- attr("version", node(ID, Package), Version),
- attr("node", node(ID, Package)),
- internal_error("version weights must exist and be unique").
+ attr("node", node(ID, Package)).
% node_version_satisfies implies that exactly one of the satisfying versions
% is the package's version, and vice versa.
@@ -248,8 +249,7 @@ possible_version_weight(node(ID, Package), Weight)
% bound on the choice rule to avoid false positives with the error below
1 { attr("version", node(ID, Package), Version) : pkg_fact(Package, version_satisfies(Constraint, Version)) }
:- attr("node_version_satisfies", node(ID, Package), Constraint),
- pkg_fact(Package, version_satisfies(Constraint, _)),
- internal_error("must choose a single version to satisfy version constraints").
+ pkg_fact(Package, version_satisfies(Constraint, _)).
% More specific error message if the version cannot satisfy some constraint
% Otherwise covered by `no_version_error` and `versions_conflict_error`.
@@ -362,7 +362,7 @@ imposed_nodes(ConditionID, PackageNode, node(X, A1))
% 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), 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)).
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, A4) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1, A2, A3, A4), imposed_nodes(ID, PackageNode, node(X, A1)).
@@ -373,16 +373,6 @@ attr("node_flag_source", node(X, A1), A2, node(Y, A3))
imposed_constraint(ID, "node_flag_source", A1, A2, A3),
condition_set(node(Y, A3), node(X, A1)).
-% Provider set is relevant only for literals, since it's the only place where `^[virtuals=foo] bar`
-% might appear in the HEAD of a rule
-attr("provider_set", node(min_dupe_id, Provider), node(min_dupe_id, Virtual))
- :- solve_literal(TriggerID),
- trigger_and_effect(_, TriggerID, EffectID),
- impose(EffectID, _),
- imposed_constraint(EffectID, "provider_set", Provider, Virtual).
-
-provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, VirtualNode).
-
% Here we can't use the condition set because it's a recursive definition, that doesn't define the
% node index, and leads to unsatisfiability. Hence we say that one and only one node index must
% satisfy the dependency.
@@ -442,11 +432,24 @@ depends_on(PackageNode, DependencyNode) :- attr("depends_on", PackageNode, Depen
% concrete. We chop off dependencies for externals, and dependencies of
% concrete specs don't need to be resolved -- they arise from the concrete
% specs themselves.
-attr("track_dependencies", Node) :- build(Node), not external(Node).
+dependency_holds(node(NodeID, Package), Dependency, Type) :-
+ pkg_fact(Package, dependency_condition(ID, Dependency)),
+ dependency_type(ID, Type),
+ build(node(NodeID, Package)),
+ not external(node(NodeID, Package)),
+ condition_holds(ID, node(NodeID, Package)).
+
+% We cut off dependencies of externals (as we don't really know them).
+% Don't impose constraints on dependencies that don't exist.
+do_not_impose(EffectID, node(NodeID, Package)) :-
+ not dependency_holds(node(NodeID, Package), Dependency, _),
+ attr("node", node(NodeID, Package)),
+ pkg_fact(Package, dependency_condition(ID, Dependency)),
+ pkg_fact(Package, condition_effect(ID, EffectID)).
% If a dependency holds on a package node, there must be one and only one dependency node satisfying it
1 { attr("depends_on", PackageNode, node(0..Y-1, Dependency), Type) : max_dupes(Dependency, Y) } 1
- :- attr("dependency_holds", PackageNode, Dependency, Type),
+ :- dependency_holds(PackageNode, Dependency, Type),
not virtual(Dependency).
% all nodes in the graph must be reachable from some root
@@ -496,7 +499,7 @@ error(100, "Package '{0}' needs to provide both '{1}' and '{2}' together, but pr
% if a package depends on a virtual, it's not external and we have a
% provider for that virtual then it depends on the provider
node_depends_on_virtual(PackageNode, Virtual, Type)
- :- attr("dependency_holds", PackageNode, Virtual, Type),
+ :- dependency_holds(PackageNode, Virtual, Type),
virtual(Virtual),
not external(PackageNode).
@@ -506,7 +509,7 @@ node_depends_on_virtual(PackageNode, Virtual) :- node_depends_on_virtual(Package
:- node_depends_on_virtual(PackageNode, Virtual, Type).
attr("virtual_on_edge", PackageNode, ProviderNode, Virtual)
- :- attr("dependency_holds", PackageNode, Virtual, Type),
+ :- dependency_holds(PackageNode, Virtual, Type),
attr("depends_on", PackageNode, ProviderNode, Type),
provider(ProviderNode, node(_, Virtual)),
not external(PackageNode).
@@ -615,11 +618,11 @@ possible_provider_weight(node(ProviderID, Provider), VirtualNode, 100, "fallback
pkg_fact(Package, version_declared(Version, Weight, "external")) }
:- external(node(ID, Package)).
-error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec version", Package)
+error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(node(ID, Package)),
not external_version(node(ID, Package), _, _).
-error(100, "Attempted to use external for '{0}' which does not satisfy a unique configured external spec version", Package)
+error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(node(ID, Package)),
2 { external_version(node(ID, Package), Version, Weight) }.
@@ -648,15 +651,18 @@ external(PackageNode) :- attr("external_spec_selected", PackageNode, _).
% determine if an external spec has been selected
attr("external_spec_selected", node(ID, Package), LocalIndex) :-
- attr("external_conditions_hold", node(ID, Package), LocalIndex),
+ external_conditions_hold(node(ID, Package), LocalIndex),
attr("node", node(ID, Package)),
not attr("hash", node(ID, Package), _).
+external_conditions_hold(node(PackageID, Package), LocalIndex) :-
+ pkg_fact(Package, possible_external(ID, LocalIndex)), condition_holds(ID, node(PackageID, Package)).
+
% it cannot happen that a spec is external, but none of the external specs
% conditions hold.
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(node(ID, Package)),
- not attr("external_conditions_hold", node(ID, Package), _).
+ not external_conditions_hold(node(ID, Package), _).
%-----------------------------------------------------------------------------
% Config required semantics
@@ -888,9 +894,8 @@ variant_default_not_used(node(ID, Package), Variant, Value)
% The variant is set in an external spec
external_with_variant_set(node(NodeID, Package), Variant, Value)
:- attr("variant_value", node(NodeID, Package), Variant, Value),
- condition_requirement(TriggerID, "variant_value", Package, Variant, Value),
- trigger_and_effect(Package, TriggerID, EffectID),
- imposed_constraint(EffectID, "external_conditions_hold", Package, _),
+ condition_requirement(ID, "variant_value", Package, Variant, Value),
+ pkg_fact(Package, possible_external(ID, _)),
external(node(NodeID, Package)),
attr("node", node(NodeID, Package)).
diff --git a/lib/spack/spack/solver/display.lp b/lib/spack/spack/solver/display.lp
index 58d04d42ea30022715adfa57160893248b821f1b..fffffb2c0430bd90511310fbdd48dae563b94468 100644
--- a/lib/spack/spack/solver/display.lp
+++ b/lib/spack/spack/solver/display.lp
@@ -24,29 +24,4 @@
#show error/5.
#show error/6.
-% for error causation
-#show condition_reason/2.
-
-% For error messages to use later
-#show pkg_fact/2.
-#show condition_holds/2.
-#show imposed_constraint/3.
-#show imposed_constraint/4.
-#show imposed_constraint/5.
-#show imposed_constraint/6.
-#show condition_requirement/3.
-#show condition_requirement/4.
-#show condition_requirement/5.
-#show condition_requirement/6.
-#show node_has_variant/2.
-#show build/1.
-#show external/1.
-#show external_version/3.
-#show trigger_and_effect/3.
-#show unification_set/2.
-#show provider/2.
-#show condition_nodes/3.
-#show trigger_node/3.
-#show imposed_nodes/3.
-
% debug
diff --git a/lib/spack/spack/solver/error_messages.lp b/lib/spack/spack/solver/error_messages.lp
deleted file mode 100644
index 7eb383860d8c7523c77c4f554db2391410c53f04..0000000000000000000000000000000000000000
--- a/lib/spack/spack/solver/error_messages.lp
+++ /dev/null
@@ -1,239 +0,0 @@
-% Copyright 2013-2023 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)
-
-%=============================================================================
-% This logic program adds detailed error messages to Spack's concretizer
-%=============================================================================
-
-#program error_messages.
-
-% Create a causal tree between trigger conditions by locating the effect conditions
-% that are triggers for another condition. Condition2 is caused by Condition1
-condition_cause(Condition2, ID2, Condition1, ID1) :-
- condition_holds(Condition2, node(ID2, Package2)),
- pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
- condition_requirement(Trigger, Name, Package),
- condition_nodes(Trigger, TriggerNode, node(ID, Package)),
- trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
- attr(Name, node(ID, Package)),
- condition_holds(Condition1, node(ID1, Package1)),
- pkg_fact(Package1, condition_effect(Condition1, Effect)),
- imposed_constraint(Effect, Name, Package),
- imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
-
-condition_cause(Condition2, ID2, Condition1, ID1) :-
- condition_holds(Condition2, node(ID2, Package2)),
- pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
- condition_requirement(Trigger, Name, Package, A1),
- condition_nodes(Trigger, TriggerNode, node(ID, Package)),
- trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
- attr(Name, node(ID, Package), A1),
- condition_holds(Condition1, node(ID1, Package1)),
- pkg_fact(Package1, condition_effect(Condition1, Effect)),
- imposed_constraint(Effect, Name, Package, A1),
- imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
-
-condition_cause(Condition2, ID2, Condition1, ID1) :-
- condition_holds(Condition2, node(ID2, Package2)),
- pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
- condition_requirement(Trigger, Name, Package, A1, A2),
- condition_nodes(Trigger, TriggerNode, node(ID, Package)),
- trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
- attr(Name, node(ID, Package), A1, A2),
- condition_holds(Condition1, node(ID1, Package1)),
- pkg_fact(Package1, condition_effect(Condition1, Effect)),
- imposed_constraint(Effect, Name, Package, A1, A2),
- imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
-
-condition_cause(Condition2, ID2, Condition1, ID1) :-
- condition_holds(Condition2, node(ID2, Package2)),
- pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
- condition_requirement(Trigger, Name, Package, A1, A2, A3),
- condition_nodes(Trigger, TriggerNode, node(ID, Package)),
- trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
- attr(Name, node(ID, Package), A1, A2, A3),
- condition_holds(Condition1, node(ID1, Package1)),
- pkg_fact(Package1, condition_effect(Condition1, Effect)),
- imposed_constraint(Effect, Name, Package, A1, A2, A3),
- imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)).
-
-% special condition cause for dependency conditions
-% we can't simply impose the existence of the node for dependency conditions
-% because we need to allow for the choice of which dupe ID the node gets
-condition_cause(Condition2, ID2, Condition1, ID1) :-
- condition_holds(Condition2, node(ID2, Package2)),
- pkg_fact(Package2, condition_trigger(Condition2, Trigger)),
- condition_requirement(Trigger, "node", Package),
- condition_nodes(Trigger, TriggerNode, node(ID, Package)),
- trigger_node(Trigger, TriggerNode, node(ID2, Package2)),
- attr("node", node(ID, Package)),
- condition_holds(Condition1, node(ID1, Package1)),
- pkg_fact(Package1, condition_effect(Condition1, Effect)),
- imposed_constraint(Effect, "dependency_holds", Parent, Package, Type),
- imposed_nodes(Effect, node(ID1, Package1), node(ID, Package)),
- attr("depends_on", node(X, Parent), node(ID, Package), Type).
-
-% The literal startcauses is used to separate the variables that are part of the error from the
-% ones describing the causal tree of the error. After startcauses, each successive pair must be
-% a condition and a condition_set id for which it holds.
-
-% More specific error message if the version cannot satisfy some constraint
-% Otherwise covered by `no_version_error` and `versions_conflict_error`.
-error(1, "Cannot satisfy '{0}@{1}'", Package, Constraint, startcauses, ConstraintCause, CauseID)
- :- attr("node_version_satisfies", node(ID, Package), Constraint),
- pkg_fact(TriggerPkg, condition_effect(ConstraintCause, EffectID)),
- imposed_constraint(EffectID, "node_version_satisfies", Package, Constraint),
- condition_holds(ConstraintCause, node(CauseID, TriggerPkg)),
- attr("version", node(ID, Package), Version),
- not pkg_fact(Package, version_satisfies(Constraint, Version)).
-
-error(0, "Cannot satisfy '{0}@{1}' and '{0}@{2}", Package, Constraint1, Constraint2, startcauses, Cause1, C1ID, Cause2, C2ID)
- :- attr("node_version_satisfies", node(ID, Package), Constraint1),
- pkg_fact(TriggerPkg1, condition_effect(Cause1, EffectID1)),
- imposed_constraint(EffectID1, "node_version_satisfies", Package, Constraint1),
- condition_holds(Cause1, node(C1ID, TriggerPkg1)),
- % two constraints
- attr("node_version_satisfies", node(ID, Package), Constraint2),
- pkg_fact(TriggerPkg2, condition_effect(Cause2, EffectID2)),
- imposed_constraint(EffectID2, "node_version_satisfies", Package, Constraint2),
- condition_holds(Cause2, node(C2ID, TriggerPkg2)),
- % version chosen
- attr("version", node(ID, Package), Version),
- % version satisfies one but not the other
- pkg_fact(Package, version_satisfies(Constraint1, Version)),
- not pkg_fact(Package, version_satisfies(Constraint2, Version)).
-
-% causation tracking error for no or multiple virtual providers
-error(0, "Cannot find a valid provider for virtual {0}", Virtual, startcauses, Cause, CID)
- :- attr("virtual_node", node(X, Virtual)),
- not provider(_, node(X, Virtual)),
- imposed_constraint(EID, "dependency_holds", Parent, Virtual, Type),
- pkg_fact(TriggerPkg, condition_effect(Cause, EID)),
- condition_holds(Cause, node(CID, TriggerPkg)).
-
-
-% At most one variant value for single-valued variants
-error(0, "'{0}' required multiple values for single-valued variant '{1}'\n Requested 'Spec({1}={2})' and 'Spec({1}={3})'", Package, Variant, Value1, Value2, startcauses, Cause1, X, Cause2, X)
- :- attr("node", node(X, Package)),
- node_has_variant(node(X, Package), Variant),
- pkg_fact(Package, variant_single_value(Variant)),
- build(node(X, Package)),
- attr("variant_value", node(X, Package), Variant, Value1),
- imposed_constraint(EID1, "variant_set", Package, Variant, Value1),
- pkg_fact(TriggerPkg1, condition_effect(Cause1, EID1)),
- condition_holds(Cause1, node(X, TriggerPkg1)),
- attr("variant_value", node(X, Package), Variant, Value2),
- imposed_constraint(EID2, "variant_set", Package, Variant, Value2),
- pkg_fact(TriggerPkg2, condition_effect(Cause2, EID2)),
- condition_holds(Cause2, node(X, TriggerPkg2)),
- Value1 < Value2. % see[1] in concretize.lp
-
-% Externals have to specify external conditions
-error(0, "Attempted to use external for {0} which does not satisfy any configured external spec version", Package, startcauses, ExternalCause, CID)
- :- external(node(ID, Package)),
- attr("external_spec_selected", node(ID, Package), Index),
- imposed_constraint(EID, "external_conditions_hold", Package, Index),
- pkg_fact(TriggerPkg, condition_effect(ExternalCause, EID)),
- condition_holds(ExternalCause, node(CID, TriggerPkg)),
- not external_version(node(ID, Package), _, _).
-
-error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}') is an external constraint for {0} which was not satisfied", Package, Name, A1)
- :- external(node(ID, Package)),
- not attr("external_conditions_hold", node(ID, Package), _),
- imposed_constraint(EID, "external_conditions_hold", Package, _),
- trigger_and_effect(Package, TID, EID),
- condition_requirement(TID, Name, A1),
- not attr(Name, node(_, A1)).
-
-error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}', '{3}') is an external constraint for {0} which was not satisfied", Package, Name, A1, A2)
- :- external(node(ID, Package)),
- not attr("external_conditions_hold", node(ID, Package), _),
- imposed_constraint(EID, "external_conditions_hold", Package, _),
- trigger_and_effect(Package, TID, EID),
- condition_requirement(TID, Name, A1, A2),
- not attr(Name, node(_, A1), A2).
-
-error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}', '{3}', '{4}') is an external constraint for {0} which was not satisfied", Package, Name, A1, A2, A3)
- :- external(node(ID, Package)),
- not attr("external_conditions_hold", node(ID, Package), _),
- imposed_constraint(EID, "external_conditions_hold", Package, _),
- trigger_and_effect(Package, TID, EID),
- condition_requirement(TID, Name, A1, A2, A3),
- not attr(Name, node(_, A1), A2, A3).
-
-error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n 'Spec({0} {1}={2})' is an external constraint for {0} which was not satisfied\n 'Spec({0} {1}={3})' required", Package, Variant, Value, OtherValue, startcauses, OtherValueCause, CID)
- :- external(node(ID, Package)),
- not attr("external_conditions_hold", node(ID, Package), _),
- imposed_constraint(EID, "external_conditions_hold", Package, _),
- trigger_and_effect(Package, TID, EID),
- condition_requirement(TID, "variant_value", Package, Variant, Value),
- not attr("variant_value", node(ID, Package), Variant, Value),
- attr("variant_value", node(ID, Package), Variant, OtherValue),
- imposed_constraint(EID2, "variant_set", Package, Variant, OtherValue),
- pkg_fact(TriggerPkg, condition_effect(OtherValueCause, EID2)),
- condition_holds(OtherValueCause, node(CID, TriggerPkg)).
-
-error(0, "Attempted to build package {0} which is not buildable and does not have a satisfying external\n attr('{1}', '{2}', '{3}', '{4}', '{5}') is an external constraint for {0} which was not satisfied", Package, Name, A1, A2, A3, A4)
- :- external(node(ID, Package)),
- not attr("external_conditions_hold", node(ID, Package), _),
- imposed_constraint(EID, "external_conditions_hold", Package, _),
- trigger_and_effect(Package, TID, EID),
- condition_requirement(TID, Name, A1, A2, A3, A4),
- not attr(Name, node(_, A1), A2, A3, A4).
-
-% error message with causes for conflicts
-error(0, Msg, startcauses, TriggerID, ID1, ConstraintID, ID2)
- :- attr("node", node(ID, Package)),
- pkg_fact(Package, conflict(TriggerID, ConstraintID, Msg)),
- % node(ID1, TriggerPackage) is node(ID2, Package) in most, but not all, cases
- condition_holds(TriggerID, node(ID1, TriggerPackage)),
- condition_holds(ConstraintID, node(ID2, Package)),
- unification_set(X, node(ID2, Package)),
- unification_set(X, node(ID1, TriggerPackage)),
- not external(node(ID, Package)), % ignore conflicts for externals
- not attr("hash", node(ID, Package), _). % ignore conflicts for installed packages
-
-% variables to show
-#show error/2.
-#show error/3.
-#show error/4.
-#show error/5.
-#show error/6.
-#show error/7.
-#show error/8.
-#show error/9.
-#show error/10.
-#show error/11.
-
-#show condition_cause/4.
-#show condition_reason/2.
-
-% Define all variables used to avoid warnings at runtime when the model doesn't happen to have one
-#defined error/2.
-#defined error/3.
-#defined error/4.
-#defined error/5.
-#defined error/6.
-#defined attr/2.
-#defined attr/3.
-#defined attr/4.
-#defined attr/5.
-#defined pkg_fact/2.
-#defined imposed_constraint/3.
-#defined imposed_constraint/4.
-#defined imposed_constraint/5.
-#defined imposed_constraint/6.
-#defined condition_requirement/3.
-#defined condition_requirement/4.
-#defined condition_requirement/5.
-#defined condition_requirement/6.
-#defined condition_holds/2.
-#defined unification_set/2.
-#defined external/1.
-#defined trigger_and_effect/3.
-#defined build/1.
-#defined node_has_variant/2.
-#defined provider/2.
-#defined external_version/3.
diff --git a/lib/spack/spack/solver/heuristic.lp b/lib/spack/spack/solver/heuristic.lp
index cc87207047d4386ea444b7908d6929c76c380f30..242a6e7c5ee81e6b8ff5bf06f9df3a4a371e62b5 100644
--- a/lib/spack/spack/solver/heuristic.lp
+++ b/lib/spack/spack/solver/heuristic.lp
@@ -11,6 +11,10 @@
%-----------------
% Domain heuristic
%-----------------
+#heuristic attr("hash", node(0, Package), Hash) : literal(_, "root", Package). [45, init]
+#heuristic attr("root", node(0, Package)) : literal(_, "root", Package). [45, true]
+#heuristic attr("node", node(0, Package)) : literal(_, "root", Package). [45, true]
+#heuristic attr("node", node(0, Package)) : literal(_, "node", Package). [45, true]
% Root node
#heuristic attr("version", node(0, Package), Version) : pkg_fact(Package, version_declared(Version, 0)), attr("root", node(0, Package)). [35, true]
@@ -22,3 +26,4 @@
% Providers
#heuristic attr("node", node(0, Package)) : default_provider_preference(Virtual, Package, 0), possible_in_link_run(Package). [30, true]
+