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] +