solver: first prototype of chained error messages for one message type

This commit is contained in:
Gregory Becker 2022-12-12 18:36:04 -08:00
parent 59acfe4f0b
commit e9012c7781
3 changed files with 80 additions and 16 deletions

View File

@ -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(
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(
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(
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(
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(
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())

View File

@ -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),

View File

@ -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.