diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 7a47aace422..89d5fce628e 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -614,6 +614,23 @@ 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, conditions, condition_causes, literals, indent=""): + parents = [c for e, c in condition_causes if e == cause] + local = "required because %s " % conditions[cause] + + return [indent + local] + [ + c + for parent in parents + for c in self._get_cause_tree( + parent, conditions, condition_causes, literals, indent=indent + " " + ) + ] + + def get_cause_tree(self, cause): + conditions = dict(extract_args(self.model, "condition")) + condition_causes = list(extract_args(self.model, "condition_cause")) + return self._get_cause_tree(cause, conditions, condition_causes, []) + def handle_error(self, msg, *args): """Handle an error state derived by the solver.""" if msg == "multiple_values_error": @@ -622,10 +639,24 @@ 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 + cause_args = [] + else: + msg_args = args[:idx] + cause_args = args[idx + 1 :] + + msg = msg.format(*msg_args) + + for cause in set(cause_args): + for c in self.get_cause_tree(cause): + msg += f"\n{c}" + # 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))) @@ -1266,9 +1297,11 @@ def package_provider_rules(self, pkg): for when in whens: msg = "%s provides %s when %s" % (pkg.name, provided, when) condition_id = self.condition(when, provided, pkg.name, msg) - self.gen.fact(fn.imposed_constraint( - condition_id, "virtual_condition_holds", pkg.name, provided.name - )) + self.gen.fact( + fn.imposed_constraint( + condition_id, "virtual_condition_holds", pkg.name, provided.name + ) + ) self.gen.newline() def package_dependencies_rules(self, pkg): @@ -1298,9 +1331,11 @@ def package_dependencies_rules(self, pkg): for t in sorted(deptypes): # there is a declared dependency of type t - self.gen.fact(fn.imposed_constraint( - condition_id, "dependency_holds", pkg.name, dep.spec.name, t - )) + self.gen.fact( + fn.imposed_constraint( + condition_id, "dependency_holds", pkg.name, dep.spec.name, t + ) + ) self.gen.newline() @@ -1454,9 +1489,11 @@ def external_packages(self): for local_idx, spec in enumerate(external_specs): msg = "%s available as external when satisfying %s" % (spec.name, spec) condition_id = self.condition(spec, msg=msg) - self.gen.fact(fn.imposed_constraint( - condition_id, "external_conditions_hold", pkg_name, local_idx - )) + self.gen.fact( + fn.imposed_constraint( + condition_id, "external_conditions_hold", pkg_name, local_idx + ) + ) self.possible_versions[spec.name].add(spec.version) self.gen.newline() @@ -2310,16 +2347,20 @@ def literal_specs(self, specs): self.gen.fact(fn.condition_requirement(condition_id, "literal_solved", condition_id)) - self.gen.fact(fn.imposed_constraint( - condition_id, "virtual_root" if spec.virtual else "root", spec.name - )) + self.gen.fact( + fn.imposed_constraint( + condition_id, "virtual_root" if spec.virtual else "root", spec.name + ) + ) for clause in self.spec_clauses(spec): self.gen.fact(fn.imposed_constraint(condition_id, *clause.args)) if clause.args[0] == "variant_set": - self.gen.fact(fn.imposed_constraint( - condition_id, "variant_default_value_from_cli", *clause.args[1:] - )) + self.gen.fact( + fn.imposed_constraint( + condition_id, "variant_default_value_from_cli", *clause.args[1:] + ) + ) if self.concretize_everything: self.gen.fact(fn.concretize_everything()) diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp index d2f2cdb6fb1..0eda0ff22d0 100644 --- a/lib/spack/spack/solver/concretize.lp +++ b/lib/spack/spack/solver/concretize.lp @@ -86,6 +86,27 @@ version_satisfies(Package, Constraint, HashVersion) :- version_satisfies(Package { attr("version", Package, Version) : version_declared(Package, Version) } :- attr("node", Package). +% error we expect users to see +error(0, "Version '{0}' of {1} does not satisfy '@{2}'", Version, Package, Constraint, startcauses, VersionCause, ConstraintCause) + :- attr("node", Package), + attr("version", Package, Version), + imposed_constraint(VersionCause, "node_version_satisfies", Package, Version), + condition_holds(VersionCause), + attr("node_version_satisfies", Package, Constraint), + imposed_constraint(ConstraintCause, "node_version_satisfies", Package, Constraint), + condition_holds(ConstraintCause), + not version_satisfies(Package, Constraint, Version). + +% Error to ensure structure of the program is not violated +error(2, "No version from '{0}' satisfies '@{1}' and '@{2}'", Package, Version1, Version2) + :- attr("node", Package), + attr("version", Package, Version1), + attr("version", Package, Version2), + Version1 < Version2. % see[1] + +error(2, "No versions available for package '{0}'", Package) + :- attr("node", Package), not attr("version", Package, _). + % A virtual package may or may not have a version, but never has more than one error(100, "Cannot select a single version for virtual '{0}'", Virtual) :- attr("virtual_node", Virtual), diff --git a/lib/spack/spack/solver/display.lp b/lib/spack/spack/solver/display.lp index 0c3c3180ab0..ff7c8f987fc 100644 --- a/lib/spack/spack/solver/display.lp +++ b/lib/spack/spack/solver/display.lp @@ -23,6 +23,8 @@ #show error/4. #show error/5. #show error/6. +#show error/7. +#show error/8. % show cause -> effect data for errors #show condition_cause/2.