Improved error messages from clingo (#26719)
This PR adds error message sentinels to the clingo solve, attached to each of the rules that could fail a solve. The unsat core is then restricted to these messages, which makes the minimization problem tractable. Errors that can only be generated by a bug in the logic program or generating code are prefaced with "Internal error" to make clear to users that something has gone wrong on the Spack side of things. * minimize unsat cores manually * only errors messages are choices/assumptions for performance * pre-check for unreachable nodes * update tests for new error message * make clingo concretization errors show up in cdash reports fully * clingo: make import of clingo.ast parsing routines robust to clingo version Older `clingo` has `parse_string`; newer `clingo` has `parse_files`. Make the code work wtih both. * make AST access functions backward-compatible with clingo 5.4.0 Clingo AST API has changed since 5.4.0; make some functions to help us handle both versions of the AST. Co-authored-by: Todd Gamblin <tgamblin@llnl.gov>
This commit is contained in:
parent
3187689862
commit
b3711c0d9d
@ -109,10 +109,7 @@ def solve(parser, args):
|
||||
return
|
||||
|
||||
# die if no solution was found
|
||||
# TODO: we need to be able to provide better error messages than this
|
||||
if not result.satisfiable:
|
||||
result.print_cores()
|
||||
tty.die("Unsatisfiable spec.")
|
||||
result.raise_if_unsat()
|
||||
|
||||
# dump the solutions as concretized specs
|
||||
if 'solutions' in dump:
|
||||
|
@ -728,11 +728,7 @@ def concretize_specs_together(*abstract_specs, **kwargs):
|
||||
def _concretize_specs_together_new(*abstract_specs, **kwargs):
|
||||
import spack.solver.asp
|
||||
result = spack.solver.asp.solve(abstract_specs)
|
||||
|
||||
if not result.satisfiable:
|
||||
result.print_cores()
|
||||
tty.die("Unsatisfiable spec.")
|
||||
|
||||
result.raise_if_unsat()
|
||||
return [s.copy() for s in result.specs]
|
||||
|
||||
|
||||
|
@ -117,11 +117,24 @@ class SpecError(SpackError):
|
||||
|
||||
|
||||
class UnsatisfiableSpecError(SpecError):
|
||||
"""Raised when a spec conflicts with package constraints.
|
||||
Provide the requirement that was violated when raising."""
|
||||
def __init__(self, provided, required, constraint_type):
|
||||
super(UnsatisfiableSpecError, self).__init__(
|
||||
"%s does not satisfy %s" % (provided, required))
|
||||
"""
|
||||
Raised when a spec conflicts with package constraints.
|
||||
|
||||
For original concretizer, provide the requirement that was violated when
|
||||
raising.
|
||||
"""
|
||||
def __init__(self, provided, required=None, constraint_type=None, conflicts=None):
|
||||
# required is only set by the original concretizer.
|
||||
# clingo concretizer handles error messages differently.
|
||||
if required is not None:
|
||||
assert not conflicts # can't mix formats
|
||||
super(UnsatisfiableSpecError, self).__init__(
|
||||
"%s does not satisfy %s" % (provided, required))
|
||||
else:
|
||||
indented = [' %s\n' % conflict for conflict in conflicts]
|
||||
conflict_msg = ''.join(indented)
|
||||
msg = '%s is unsatisfiable, conflicts are:\n%s' % (provided, conflict_msg)
|
||||
super(UnsatisfiableSpecError, self).__init__(msg)
|
||||
self.provided = provided
|
||||
self.required = required
|
||||
self.constraint_type = constraint_type
|
||||
|
@ -52,6 +52,25 @@
|
||||
else:
|
||||
from collections import Sequence
|
||||
|
||||
# these are from clingo.ast and bootstrapped later
|
||||
ASTType = None
|
||||
parse_files = None
|
||||
|
||||
|
||||
# backward compatibility functions for clingo ASTs
|
||||
def ast_getter(*names):
|
||||
def getter(node):
|
||||
for name in names:
|
||||
result = getattr(node, name, None)
|
||||
if result:
|
||||
return result
|
||||
raise KeyError("node has no such keys: %s" % names)
|
||||
return getter
|
||||
|
||||
|
||||
ast_type = ast_getter("ast_type", "type")
|
||||
ast_sym = ast_getter("symbol", "term")
|
||||
|
||||
|
||||
#: Enumeration like object to mark version provenance
|
||||
version_provenance = collections.namedtuple( # type: ignore
|
||||
@ -205,6 +224,9 @@ def __init__(self, specs, asp=None):
|
||||
self.warnings = None
|
||||
self.nmodels = 0
|
||||
|
||||
# Saved control object for reruns when necessary
|
||||
self.control = None
|
||||
|
||||
# specs ordered by optimization level
|
||||
self.answers = []
|
||||
self.cores = []
|
||||
@ -218,11 +240,85 @@ def __init__(self, specs, asp=None):
|
||||
# Concrete specs
|
||||
self._concrete_specs = None
|
||||
|
||||
def print_cores(self):
|
||||
for core in self.cores:
|
||||
tty.msg(
|
||||
"The following constraints are unsatisfiable:",
|
||||
*sorted(str(symbol) for symbol in core))
|
||||
def format_core(self, core):
|
||||
"""
|
||||
Format an unsatisfiable core for human readability
|
||||
|
||||
Returns a list of strings, where each string is the human readable
|
||||
representation of a single fact in the core, including a newline.
|
||||
|
||||
Modeled after traceback.format_stack.
|
||||
"""
|
||||
assert self.control
|
||||
|
||||
symbols = dict(
|
||||
(a.literal, a.symbol)
|
||||
for a in self.control.symbolic_atoms
|
||||
)
|
||||
|
||||
core_symbols = []
|
||||
for atom in core:
|
||||
sym = symbols[atom]
|
||||
if sym.name in ("rule", "error"):
|
||||
# these are special symbols we use to get messages in the core
|
||||
sym = sym.arguments[0].string
|
||||
core_symbols.append(sym)
|
||||
|
||||
return sorted(str(symbol) for symbol in core_symbols)
|
||||
|
||||
def minimize_core(self, core):
|
||||
"""
|
||||
Return a subset-minimal subset of the core.
|
||||
|
||||
Clingo cores may be thousands of lines when two facts are sufficient to
|
||||
ensure unsatisfiability. This algorithm reduces the core to only those
|
||||
essential facts.
|
||||
"""
|
||||
assert self.control
|
||||
|
||||
min_core = core[:]
|
||||
for fact in core:
|
||||
# Try solving without this fact
|
||||
min_core.remove(fact)
|
||||
ret = self.control.solve(assumptions=min_core)
|
||||
if not ret.unsatisfiable:
|
||||
min_core.append(fact)
|
||||
return min_core
|
||||
|
||||
def minimal_cores(self):
|
||||
"""
|
||||
Return a list of subset-minimal unsatisfiable cores.
|
||||
"""
|
||||
return [self.minimize_core(core) for core in self.cores]
|
||||
|
||||
def format_minimal_cores(self):
|
||||
"""List of facts for each core
|
||||
|
||||
Separate cores are separated by an empty line
|
||||
"""
|
||||
string_list = []
|
||||
for core in self.minimal_cores():
|
||||
if string_list:
|
||||
string_list.append('\n')
|
||||
string_list.extend(self.format_core(core))
|
||||
return string_list
|
||||
|
||||
def raise_if_unsat(self):
|
||||
"""
|
||||
Raise an appropriate error if the result is unsatisfiable.
|
||||
|
||||
The error is a UnsatisfiableSpecError, and includes the minimized cores
|
||||
resulting from the solve, formatted to be human readable.
|
||||
"""
|
||||
if self.satisfiable:
|
||||
return
|
||||
|
||||
constraints = self.abstract_specs
|
||||
if len(constraints) == 1:
|
||||
constraints = constraints[0]
|
||||
conflicts = self.format_minimal_cores()
|
||||
|
||||
raise spack.error.UnsatisfiableSpecError(constraints, conflicts=conflicts)
|
||||
|
||||
@property
|
||||
def specs(self):
|
||||
@ -276,6 +372,22 @@ def _normalize_packages_yaml(packages_yaml):
|
||||
return normalized_yaml
|
||||
|
||||
|
||||
def bootstrap_clingo():
|
||||
global clingo, ASTType, parse_files
|
||||
|
||||
if not clingo:
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
spack.bootstrap.ensure_clingo_importable_or_raise()
|
||||
import clingo
|
||||
|
||||
from clingo.ast import ASTType
|
||||
try:
|
||||
from clingo.ast import parse_files
|
||||
except ImportError:
|
||||
# older versions of clingo have this one namespace up
|
||||
from clingo import parse_files
|
||||
|
||||
|
||||
class PyclingoDriver(object):
|
||||
def __init__(self, cores=True, asp=None):
|
||||
"""Driver for the Python clingo interface.
|
||||
@ -286,11 +398,8 @@ def __init__(self, cores=True, asp=None):
|
||||
asp (file-like): optional stream to write a text-based ASP program
|
||||
for debugging or verification.
|
||||
"""
|
||||
global clingo
|
||||
if not clingo:
|
||||
with spack.bootstrap.ensure_bootstrap_configuration():
|
||||
spack.bootstrap.ensure_clingo_importable_or_raise()
|
||||
import clingo
|
||||
bootstrap_clingo()
|
||||
|
||||
self.out = asp or llnl.util.lang.Devnull()
|
||||
self.cores = cores
|
||||
|
||||
@ -311,15 +420,22 @@ def h2(self, name):
|
||||
def newline(self):
|
||||
self.out.write('\n')
|
||||
|
||||
def fact(self, head):
|
||||
"""ASP fact (a rule without a body)."""
|
||||
def fact(self, head, assumption=False):
|
||||
"""ASP fact (a rule without a body).
|
||||
|
||||
Arguments:
|
||||
head (AspFunction): ASP function to generate as fact
|
||||
assumption (bool): If True and using cores, use this fact as a
|
||||
choice point in ASP and include it in unsatisfiable cores
|
||||
"""
|
||||
symbol = head.symbol() if hasattr(head, 'symbol') else head
|
||||
|
||||
self.out.write("%s.\n" % str(symbol))
|
||||
|
||||
atom = self.backend.add_atom(symbol)
|
||||
self.backend.add_rule([atom], [], choice=self.cores)
|
||||
if self.cores:
|
||||
choice = self.cores and assumption
|
||||
self.backend.add_rule([atom], [], choice=choice)
|
||||
if choice:
|
||||
self.assumptions.append(atom)
|
||||
|
||||
def solve(
|
||||
@ -347,6 +463,22 @@ def solve(
|
||||
# read in the main ASP program and display logic -- these are
|
||||
# handwritten, not generated, so we load them as resources
|
||||
parent_dir = os.path.dirname(__file__)
|
||||
|
||||
# extract error messages from concretize.lp by inspecting its AST
|
||||
with self.backend:
|
||||
def visit(node):
|
||||
if ast_type(node) == ASTType.Rule:
|
||||
for term in node.body:
|
||||
if ast_type(term) == ASTType.Literal:
|
||||
if ast_type(term.atom) == ASTType.SymbolicAtom:
|
||||
if ast_sym(term.atom).name == "error":
|
||||
arg = ast_sym(ast_sym(term.atom).arguments[0])
|
||||
self.fact(fn.error(arg.string), assumption=True)
|
||||
|
||||
path = os.path.join(parent_dir, 'concretize.lp')
|
||||
parse_files([path], visit)
|
||||
|
||||
# Load the file itself
|
||||
self.control.load(os.path.join(parent_dir, 'concretize.lp'))
|
||||
self.control.load(os.path.join(parent_dir, "display.lp"))
|
||||
timer.phase("load")
|
||||
@ -367,6 +499,7 @@ def on_model(model):
|
||||
solve_kwargs = {"assumptions": self.assumptions,
|
||||
"on_model": on_model,
|
||||
"on_core": cores.append}
|
||||
|
||||
if clingo_cffi:
|
||||
solve_kwargs["on_unsat"] = cores.append
|
||||
solve_result = self.control.solve(**solve_kwargs)
|
||||
@ -409,18 +542,8 @@ def stringify(x):
|
||||
result.nmodels = len(models)
|
||||
|
||||
elif cores:
|
||||
symbols = dict(
|
||||
(a.literal, a.symbol)
|
||||
for a in self.control.symbolic_atoms
|
||||
)
|
||||
for core in cores:
|
||||
core_symbols = []
|
||||
for atom in core:
|
||||
sym = symbols[atom]
|
||||
if sym.name == "rule":
|
||||
sym = sym.arguments[0].string
|
||||
core_symbols.append(sym)
|
||||
result.cores.append(core_symbols)
|
||||
result.control = self.control
|
||||
result.cores.extend(cores)
|
||||
|
||||
if timers:
|
||||
timer.write_tty()
|
||||
@ -1372,6 +1495,14 @@ def setup(self, driver, specs, tests=False):
|
||||
virtuals=self.possible_virtuals,
|
||||
deptype=spack.dependency.all_deptypes
|
||||
)
|
||||
|
||||
# Fail if we already know an unreachable node is requested
|
||||
for spec in specs:
|
||||
missing_deps = [d for d in spec.traverse()
|
||||
if d.name not in possible and not d.virtual]
|
||||
if missing_deps:
|
||||
raise spack.spec.InvalidDependencyError(spec.name, missing_deps)
|
||||
|
||||
pkgs = set(possible)
|
||||
|
||||
# driver is used by all the functions below to add facts and
|
||||
|
@ -18,7 +18,8 @@ version_declared(Package, Version, Weight) :- version_declared(Package, Version,
|
||||
% We can't emit the same version **with the same weight** from two different sources
|
||||
:- version_declared(Package, Version, Weight, Origin1),
|
||||
version_declared(Package, Version, Weight, Origin2),
|
||||
Origin1 != Origin2.
|
||||
Origin1 != Origin2,
|
||||
error("Internal error: two versions with identical weights").
|
||||
|
||||
% versions are declared w/priority -- declared with priority implies declared
|
||||
version_declared(Package, Version) :- version_declared(Package, Version, _).
|
||||
@ -26,7 +27,7 @@ version_declared(Package, Version) :- version_declared(Package, Version, _).
|
||||
% If something is a package, it has only one version and that must be a
|
||||
% declared version.
|
||||
1 { version(Package, Version) : version_declared(Package, Version) } 1
|
||||
:- node(Package).
|
||||
:- node(Package), error("Each node must have exactly one version").
|
||||
|
||||
% A virtual package may have or not a version, but never has more than one
|
||||
:- virtual_node(Package), 2 { version(Package, _) }.
|
||||
@ -38,12 +39,13 @@ possible_version_weight(Package, Weight)
|
||||
:- version(Package, Version),
|
||||
version_declared(Package, Version, Weight).
|
||||
|
||||
1 { version_weight(Package, Weight) : possible_version_weight(Package, Weight) } 1 :- node(Package).
|
||||
1 { version_weight(Package, Weight) : possible_version_weight(Package, Weight) } 1 :- node(Package), error("Internal error: Package version must have a unique weight").
|
||||
|
||||
% version_satisfies implies that exactly one of the satisfying versions
|
||||
% is the package's version, and vice versa.
|
||||
1 { version(Package, Version) : version_satisfies(Package, Constraint, Version) } 1
|
||||
:- version_satisfies(Package, Constraint).
|
||||
:- version_satisfies(Package, Constraint),
|
||||
error("no version satisfies the given constraints").
|
||||
version_satisfies(Package, Constraint)
|
||||
:- version(Package, Version), version_satisfies(Package, Constraint, Version).
|
||||
|
||||
@ -122,14 +124,17 @@ node(Dependency) :- node(Package), depends_on(Package, Dependency).
|
||||
% dependencies) and get a two-node unconnected graph
|
||||
needed(Package) :- root(Package).
|
||||
needed(Dependency) :- needed(Package), depends_on(Package, Dependency).
|
||||
:- node(Package), not needed(Package).
|
||||
:- node(Package), not needed(Package),
|
||||
error("All dependencies must be reachable from root").
|
||||
|
||||
% Avoid cycles in the DAG
|
||||
% some combinations of conditional dependencies can result in cycles;
|
||||
% this ensures that we solve around them
|
||||
path(Parent, Child) :- depends_on(Parent, Child).
|
||||
path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
|
||||
:- path(A, B), path(B, A).
|
||||
:- path(A, B), path(B, A), error("Cyclic dependencies are not allowed").
|
||||
|
||||
#defined error/1.
|
||||
|
||||
#defined dependency_type/2.
|
||||
#defined dependency_condition/3.
|
||||
@ -141,7 +146,8 @@ path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
|
||||
not external(Package),
|
||||
conflict(Package, TriggerID, ConstraintID),
|
||||
condition_holds(TriggerID),
|
||||
condition_holds(ConstraintID).
|
||||
condition_holds(ConstraintID),
|
||||
error("A conflict was triggered").
|
||||
|
||||
#defined conflict/3.
|
||||
|
||||
@ -164,7 +170,7 @@ virtual_node(Virtual)
|
||||
% If there's a virtual node, we must select one and only one provider.
|
||||
% The provider must be selected among the possible providers.
|
||||
1 { provider(Package, Virtual) : possible_provider(Package, Virtual) } 1
|
||||
:- virtual_node(Virtual).
|
||||
:- virtual_node(Virtual), error("Virtual packages must be satisfied by a unique provider").
|
||||
|
||||
% virtual roots imply virtual nodes, and that one provider is a root
|
||||
virtual_node(Virtual) :- virtual_root(Virtual).
|
||||
@ -188,7 +194,8 @@ virtual_condition_holds(Provider, Virtual) :-
|
||||
|
||||
% A package cannot be the actual provider for a virtual if it does not
|
||||
% fulfill the conditions to provide that virtual
|
||||
:- provider(Package, Virtual), not virtual_condition_holds(Package, Virtual).
|
||||
:- provider(Package, Virtual), not virtual_condition_holds(Package, Virtual),
|
||||
error("Internal error: virtual when provides not respected").
|
||||
|
||||
#defined possible_provider/2.
|
||||
|
||||
@ -201,7 +208,7 @@ virtual_condition_holds(Provider, Virtual) :-
|
||||
% we select the weight, among the possible ones, that minimizes the overall objective function.
|
||||
1 { provider_weight(Dependency, Virtual, Weight, Reason) :
|
||||
possible_provider_weight(Dependency, Virtual, Weight, Reason) } 1
|
||||
:- provider(Dependency, Virtual).
|
||||
:- provider(Dependency, Virtual), error("Internal error: package provider weights must be unique").
|
||||
|
||||
% Get rid or the reason for enabling the possible weight (useful for debugging)
|
||||
provider_weight(Dependency, Virtual, Weight) :- provider_weight(Dependency, Virtual, Weight, _).
|
||||
@ -299,7 +306,7 @@ attr("node_compiler_version_satisfies", Package, Compiler, Version)
|
||||
% if a package is external its version must be one of the external versions
|
||||
1 { external_version(Package, Version, Weight):
|
||||
version_declared(Package, Version, Weight, "external") } 1
|
||||
:- external(Package).
|
||||
:- external(Package), error("External package version does not satisfy external spec").
|
||||
|
||||
version_weight(Package, Weight) :- external_version(Package, Version, Weight).
|
||||
version(Package, Version) :- external_version(Package, Version, Weight).
|
||||
@ -318,7 +325,8 @@ external(Package) :- external_spec_selected(Package, _).
|
||||
:- version(Package, Version),
|
||||
version_weight(Package, Weight),
|
||||
version_declared(Package, Version, Weight, "external"),
|
||||
not external(Package).
|
||||
not external(Package),
|
||||
error("Internal error: external weight used for internal spec").
|
||||
|
||||
% determine if an external spec has been selected
|
||||
external_spec_selected(Package, LocalIndex) :-
|
||||
@ -330,7 +338,8 @@ external_conditions_hold(Package, LocalIndex) :-
|
||||
|
||||
% it cannot happen that a spec is external, but none of the external specs
|
||||
% conditions hold.
|
||||
:- external(Package), not external_conditions_hold(Package, _).
|
||||
:- external(Package), not external_conditions_hold(Package, _),
|
||||
error("External package does not satisfy external spec").
|
||||
|
||||
#defined possible_external/3.
|
||||
#defined external_spec_index/3.
|
||||
@ -348,7 +357,8 @@ external_conditions_hold(Package, LocalIndex) :-
|
||||
} 1
|
||||
:- node(Package),
|
||||
variant(Package, Variant),
|
||||
variant_single_value(Package, Variant).
|
||||
variant_single_value(Package, Variant),
|
||||
error("Single valued variants must have a single value").
|
||||
|
||||
% at least one variant value for multi-valued variants.
|
||||
1 {
|
||||
@ -357,13 +367,15 @@ external_conditions_hold(Package, LocalIndex) :-
|
||||
}
|
||||
:- node(Package),
|
||||
variant(Package, Variant),
|
||||
not variant_single_value(Package, Variant).
|
||||
not variant_single_value(Package, Variant),
|
||||
error("Internal error: All variants must have a value").
|
||||
|
||||
% if a variant is set to anything, it is considered 'set'.
|
||||
variant_set(Package, Variant) :- variant_set(Package, Variant, _).
|
||||
|
||||
% A variant cannot have a value that is not also a possible value
|
||||
:- variant_value(Package, Variant, Value), not variant_possible_value(Package, Variant, Value).
|
||||
:- variant_value(Package, Variant, Value), not variant_possible_value(Package, Variant, Value),
|
||||
error("Variant set to invalid value").
|
||||
|
||||
% Some multi valued variants accept multiple values from disjoint sets.
|
||||
% Ensure that we respect that constraint and we don't pick values from more
|
||||
@ -372,7 +384,8 @@ variant_set(Package, Variant) :- variant_set(Package, Variant, _).
|
||||
variant_value(Package, Variant, Value2),
|
||||
variant_value_from_disjoint_sets(Package, Variant, Value1, Set1),
|
||||
variant_value_from_disjoint_sets(Package, Variant, Value2, Set2),
|
||||
Set1 != Set2.
|
||||
Set1 != Set2,
|
||||
error("Variant values selected from multiple disjoint sets").
|
||||
|
||||
% variant_set is an explicitly set variant value. If it's not 'set',
|
||||
% we revert to the default value. If it is set, we force the set value
|
||||
@ -437,7 +450,8 @@ variant_default_value(Package, Variant, Value) :- variant_default_value_from_cli
|
||||
% Treat 'none' in a special way - it cannot be combined with other
|
||||
% values even if the variant is multi-valued
|
||||
:- 2 {variant_value(Package, Variant, Value): variant_possible_value(Package, Variant, Value)},
|
||||
variant_value(Package, Variant, "none").
|
||||
variant_value(Package, Variant, "none"),
|
||||
error("Variant value 'none' cannot be combined with any other value").
|
||||
|
||||
% patches and dev_path are special variants -- they don't have to be
|
||||
% declared in the package, so we just allow them to spring into existence
|
||||
@ -467,7 +481,7 @@ variant_single_value(Package, "dev_path")
|
||||
%-----------------------------------------------------------------------------
|
||||
|
||||
% one platform per node
|
||||
:- M = #count { Platform : node_platform(Package, Platform) }, M !=1, node(Package).
|
||||
:- M = #count { Platform : node_platform(Package, Platform) }, M !=1, node(Package), error("A node must have exactly one platform").
|
||||
|
||||
% if no platform is set, fall back to the default
|
||||
node_platform(Package, Platform)
|
||||
@ -488,7 +502,7 @@ node_platform_set(Package) :- node_platform_set(Package, _).
|
||||
% OS semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
% one os per node
|
||||
1 { node_os(Package, OS) : os(OS) } 1 :- node(Package).
|
||||
1 { node_os(Package, OS) : os(OS) } 1 :- node(Package), error("Each node must have exactly one OS").
|
||||
|
||||
% node_os_set implies that the node must have that os
|
||||
node_os(Package, OS) :- node(Package), node_os_set(Package, OS).
|
||||
@ -515,11 +529,11 @@ node_os(Package, OS)
|
||||
% Target semantics
|
||||
%-----------------------------------------------------------------------------
|
||||
% one target per node -- optimization will pick the "best" one
|
||||
1 { node_target(Package, Target) : target(Target) } 1 :- node(Package).
|
||||
1 { node_target(Package, Target) : target(Target) } 1 :- node(Package), error("Each node must have exactly one target").
|
||||
|
||||
% node_target_satisfies semantics
|
||||
1 { node_target(Package, Target) : node_target_satisfies(Package, Constraint, Target) } 1
|
||||
:- node_target_satisfies(Package, Constraint).
|
||||
:- node_target_satisfies(Package, Constraint), error("Each node must have exactly one target").
|
||||
node_target_satisfies(Package, Constraint)
|
||||
:- node_target(Package, Target), node_target_satisfies(Package, Constraint, Target).
|
||||
#defined node_target_satisfies/3.
|
||||
@ -546,7 +560,8 @@ target_weight(Target, Package, Weight)
|
||||
:- node_target(Package, Target),
|
||||
not compiler_supports_target(Compiler, Version, Target),
|
||||
node_compiler(Package, Compiler),
|
||||
node_compiler_version(Package, Compiler, Version).
|
||||
node_compiler_version(Package, Compiler, Version),
|
||||
error("No satisfying compiler available is compatible with a satisfying target").
|
||||
|
||||
% if a target is set explicitly, respect it
|
||||
node_target(Package, Target)
|
||||
@ -583,7 +598,7 @@ compiler(Compiler) :- compiler_version(Compiler, _).
|
||||
% There must be only one compiler set per node. The compiler
|
||||
% is chosen among available versions.
|
||||
1 { node_compiler_version(Package, Compiler, Version)
|
||||
: compiler_version(Compiler, Version) } 1 :- node(Package).
|
||||
: compiler_version(Compiler, Version) } 1 :- node(Package), error("Each node must have exactly one compiler").
|
||||
|
||||
% Sometimes we just need to know the compiler and not the version
|
||||
node_compiler(Package, Compiler) :- node_compiler_version(Package, Compiler, _).
|
||||
@ -591,14 +606,15 @@ node_compiler(Package, Compiler) :- node_compiler_version(Package, Compiler, _).
|
||||
% We can't have a compiler be enforced and select the version from another compiler
|
||||
:- node_compiler(Package, Compiler1),
|
||||
node_compiler_version(Package, Compiler2, _),
|
||||
Compiler1 != Compiler2.
|
||||
Compiler1 != Compiler2,
|
||||
error("Internal error: mismatch between selected compiler and compiler version").
|
||||
|
||||
% define node_compiler_version_satisfies/3 from node_compiler_version_satisfies/4
|
||||
% version_satisfies implies that exactly one of the satisfying versions
|
||||
% is the package's version, and vice versa.
|
||||
1 { node_compiler_version(Package, Compiler, Version)
|
||||
: node_compiler_version_satisfies(Package, Compiler, Constraint, Version) } 1
|
||||
:- node_compiler_version_satisfies(Package, Compiler, Constraint).
|
||||
:- node_compiler_version_satisfies(Package, Compiler, Constraint), error("Internal error: node compiler version mismatch").
|
||||
node_compiler_version_satisfies(Package, Compiler, Constraint)
|
||||
:- node_compiler_version(Package, Compiler, Version),
|
||||
node_compiler_version_satisfies(Package, Compiler, Constraint, Version).
|
||||
@ -614,7 +630,8 @@ node_compiler_version(Package, Compiler, Version) :- node_compiler_version_set(P
|
||||
% are excluded from this check
|
||||
:- node_compiler_version(Package, Compiler, Version), node_os(Package, OS),
|
||||
not compiler_supports_os(Compiler, Version, OS),
|
||||
not allow_compiler(Compiler, Version).
|
||||
not allow_compiler(Compiler, Version),
|
||||
error("No satisfying compiler available is compatible with a satisfying os").
|
||||
|
||||
% If a package and one of its dependencies don't have the
|
||||
% same compiler there's a mismatch.
|
||||
|
@ -2608,10 +2608,7 @@ def _new_concretize(self, tests=False):
|
||||
return
|
||||
|
||||
result = spack.solver.asp.solve([self], tests=tests)
|
||||
if not result.satisfiable:
|
||||
result.print_cores()
|
||||
raise spack.error.UnsatisfiableSpecError(
|
||||
self, "unknown", "Unsatisfiable!")
|
||||
result.raise_if_unsat()
|
||||
|
||||
# take the best answer
|
||||
opt, i, answer = min(result.answers)
|
||||
|
@ -515,7 +515,7 @@ def test_cdash_report_concretization_error(tmpdir, mock_fetch, install_mockery,
|
||||
# new or the old concretizer
|
||||
expected_messages = (
|
||||
'Conflicts in concretized spec',
|
||||
'does not satisfy'
|
||||
'A conflict was triggered',
|
||||
)
|
||||
assert any(x in content for x in expected_messages)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user