diff --git a/lib/spack/spack/cmd/diff.py b/lib/spack/spack/cmd/diff.py index 703fa5c69c..013bb693db 100644 --- a/lib/spack/spack/cmd/diff.py +++ b/lib/spack/spack/cmd/diff.py @@ -46,6 +46,14 @@ def setup_parser(subparser): ) +def shift(asp_function): + """Transforms ``attr("foo", "bar")`` into ``foo("bar")``.""" + if not asp_function.args: + raise ValueError(f"Can't shift ASP function with no arguments: {str(asp_function)}") + first, *rest = asp_function.args + return asp.AspFunction(first, rest) + + def compare_specs(a, b, to_string=False, color=None): """ Generate a comparison, including diffs (for each side) and an intersection. @@ -71,22 +79,24 @@ def compare_specs(a, b, to_string=False, color=None): # get facts for specs, making sure to include build dependencies of concrete # specs and to descend into dependency hashes so we include all facts. a_facts = set( - t - for t in setup.spec_clauses( + shift(func) + for func in setup.spec_clauses( a, body=True, expand_hashes=True, concrete_build_deps=True, ) + if func.name == "attr" ) b_facts = set( - t - for t in setup.spec_clauses( + shift(func) + for func in setup.spec_clauses( b, body=True, expand_hashes=True, concrete_build_deps=True, ) + if func.name == "attr" ) # We want to present them to the user as simple key: values diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 1a22cf5c6f..8809121e1d 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -145,17 +145,14 @@ def getter(node): fixed_priority_offset = 100 -def build_criteria_names(costs, tuples): +def build_criteria_names(costs, arg_tuples): """Construct an ordered mapping from criteria names to costs.""" # pull optimization criteria names out of the solution priorities_names = [] num_fixed = 0 num_high_fixed = 0 - for pred, args in tuples: - if pred != "opt_criterion": - continue - + for args in arg_tuples: priority, name = args[:2] priority = int(priority) @@ -255,13 +252,33 @@ def _id(thing): class AspFunction(AspObject): def __init__(self, name, args=None): self.name = name - self.args = () if args is None else args + self.args = () if args is None else tuple(args) def _cmp_key(self): return (self.name, self.args) def __call__(self, *args): - return AspFunction(self.name, args) + """Return a new instance of this function with added arguments. + + Note that calls are additive, so you can do things like:: + + >>> attr = AspFunction("attr") + attr() + + >>> attr("version") + attr("version") + + >>> attr("version")("foo") + attr("version", "foo") + + >>> v = AspFunction("attr", "version") + attr("version") + + >>> v("foo", "bar") + attr("version", "foo", "bar") + + """ + return AspFunction(self.name, self.args + args) def symbol(self, positive=True): def argify(arg): @@ -537,6 +554,36 @@ def bootstrap_clingo(): from clingo import parse_files +def stringify(sym): + """Stringify symbols from clingo models. + + This will turn a ``clingo.Symbol`` into a string, or a sequence of ``clingo.Symbol`` + objects into a tuple of strings. + + """ + # TODO: simplify this when we no longer have to support older clingo versions. + if isinstance(sym, (list, tuple)): + return tuple(stringify(a) for a in sym) + + if clingo_cffi: + # Clingo w/ CFFI will throw an exception on failure + try: + return sym.string + except RuntimeError: + return str(sym) + else: + return sym.string or str(sym) + + +def extract_args(model, predicate_name): + """Extract the arguments to predicates with the provided name from a model. + + Pull out all the predicates with name ``predicate_name`` from the model, and return + their stringified arguments as tuples. + """ + return [stringify(sym.arguments) for sym in model if sym.name == predicate_name] + + class PyclingoDriver(object): def __init__(self, cores=True): """Driver for the Python clingo interface. @@ -592,6 +639,20 @@ def fact(self, head): if choice: self.assumptions.append(atom) + def handle_error(self, msg, *args): + """Handle an error state derived by the solver.""" + msg = msg.format(*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 + 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))) + + # TODO: this raises early -- we should handle multiple errors if there are any. + raise UnsatisfiableSpecError(msg) + def solve(self, setup, specs, reuse=None, output=None, control=None): """Set up the input and solve for dependencies of ``specs``. @@ -687,26 +748,27 @@ def on_model(model): # once done, construct the solve result result.satisfiable = solve_result.satisfiable - def stringify(x): - if clingo_cffi: - # Clingo w/ CFFI will throw an exception on failure - try: - return x.string - except RuntimeError: - return str(x) - else: - return x.string or str(x) - if result.satisfiable: - # build spec from the best model + # get the best model builder = SpecBuilder(specs, hash_lookup=setup.reusable_and_possible) min_cost, best_model = min(models) - tuples = [(sym.name, [stringify(a) for a in sym.arguments]) for sym in best_model] - answers = builder.build_specs(tuples) + + # first check for errors + error_args = extract_args(best_model, "error") + errors = sorted((int(priority), msg, args) for priority, msg, *args in error_args) + for _, msg, args in errors: + self.handle_error(msg, *args) + + # build specs from spec attributes in the model + spec_attrs = [(name, tuple(rest)) for name, *rest in extract_args(best_model, "attr")] + answers = builder.build_specs(spec_attrs) # add best spec to the results result.answers.append((list(min_cost), 0, answers)) - result.criteria = build_criteria_names(min_cost, tuples) + + # get optimization criteria + criteria_args = extract_args(best_model, "opt_criterion") + result.criteria = build_criteria_names(min_cost, criteria_args) # record the number of models the solver considered result.nmodels = len(models) @@ -714,6 +776,13 @@ def stringify(x): # record the possible dependencies in the solve result.possible_dependencies = setup.pkgs + # print any unknown functions in the model + for sym in best_model: + if sym.name not in ("attr", "error", "opt_criterion"): + tty.debug( + "UNKNOWN SYMBOL: %s(%s)" % (sym.name, ", ".join(stringify(sym.arguments))) + ) + elif cores: result.control = self.control result.cores.extend(cores) @@ -836,14 +905,14 @@ def spec_versions(self, spec): assert spec.name, msg if spec.concrete: - return [fn.version(spec.name, spec.version)] + return [fn.attr("version", spec.name, spec.version)] if spec.versions == spack.version.ver(":"): return [] # record all version constraints for later self.version_constraints.add((spec.name, spec.versions)) - return [fn.node_version_satisfies(spec.name, spec.versions)] + return [fn.attr("node_version_satisfies", spec.name, spec.versions)] def target_ranges(self, spec, single_target_fn): target = spec.architecture.target @@ -853,7 +922,7 @@ def target_ranges(self, spec, single_target_fn): return [single_target_fn(spec.name, target)] self.target_constraints.add(target) - return [fn.node_target_satisfies(spec.name, target)] + return [fn.attr("node_target_satisfies", spec.name, target)] def conflict_rules(self, pkg): default_msg = "{0} '{1}' conflicts with '{2}'" @@ -1097,7 +1166,7 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node= # requirements trigger the condition requirements = self.spec_clauses(named_cond, body=True, required_from=name) for pred in requirements: - self.gen.fact(fn.condition_requirement(condition_id, pred.name, *pred.args)) + self.gen.fact(fn.condition_requirement(condition_id, *pred.args)) if imposed_spec: self.impose(condition_id, imposed_spec, node=node, name=name) @@ -1108,9 +1177,9 @@ def impose(self, condition_id, imposed_spec, node=True, name=None, body=False): imposed_constraints = self.spec_clauses(imposed_spec, body=body, required_from=name) for pred in imposed_constraints: # imposed "node"-like conditions are no-ops - if not node and pred.name in ("node", "virtual_node"): + if not node and pred.args[0] in ("node", "virtual_node"): continue - self.gen.fact(fn.imposed_constraint(condition_id, pred.name, *pred.args)) + self.gen.fact(fn.imposed_constraint(condition_id, *pred.args)) def package_provider_rules(self, pkg): for provider_name in sorted(set(s.name for s in pkg.provided.keys())): @@ -1367,30 +1436,30 @@ def _spec_clauses( # TODO: do this with consistent suffixes. class Head(object): - node = fn.node - virtual_node = fn.virtual_node - node_platform = fn.node_platform_set - node_os = fn.node_os_set - node_target = fn.node_target_set - variant_value = fn.variant_set - node_compiler = fn.node_compiler_set - node_compiler_version = fn.node_compiler_version_set - node_flag = fn.node_flag_set - node_flag_propagate = fn.node_flag_propagate - variant_propagate = fn.variant_propagate + node = fn.attr("node") + virtual_node = fn.attr("virtual_node") + node_platform = fn.attr("node_platform_set") + node_os = fn.attr("node_os_set") + node_target = fn.attr("node_target_set") + variant_value = fn.attr("variant_set") + node_compiler = fn.attr("node_compiler_set") + node_compiler_version = fn.attr("node_compiler_version_set") + node_flag = fn.attr("node_flag_set") + node_flag_propagate = fn.attr("node_flag_propagate") + variant_propagate = fn.attr("variant_propagate") class Body(object): - node = fn.node - virtual_node = fn.virtual_node - node_platform = fn.node_platform - node_os = fn.node_os - node_target = fn.node_target - variant_value = fn.variant_value - node_compiler = fn.node_compiler - node_compiler_version = fn.node_compiler_version - node_flag = fn.node_flag - node_flag_propagate = fn.node_flag_propagate - variant_propagate = fn.variant_propagate + node = fn.attr("node") + virtual_node = fn.attr("virtual_node") + node_platform = fn.attr("node_platform") + node_os = fn.attr("node_os") + node_target = fn.attr("node_target") + variant_value = fn.attr("variant_value") + node_compiler = fn.attr("node_compiler") + node_compiler_version = fn.attr("node_compiler_version") + node_flag = fn.attr("node_flag") + node_flag_propagate = fn.attr("node_flag_propagate") + variant_propagate = fn.attr("variant_propagate") f = Body if body else Head @@ -1457,8 +1526,11 @@ class Body(object): elif spec.compiler.versions: clauses.append( - fn.node_compiler_version_satisfies( - spec.name, spec.compiler.name, spec.compiler.versions + fn.attr( + "node_compiler_version_satisfies", + spec.name, + spec.compiler.name, + spec.compiler.versions, ) ) self.compiler_version_constraints.add(spec.compiler) @@ -1474,8 +1546,8 @@ class Body(object): if spec.concrete: # older specs do not have package hashes, so we have to do this carefully if getattr(spec, "_package_hash", None): - clauses.append(fn.package_hash(spec.name, spec._package_hash)) - clauses.append(fn.hash(spec.name, spec.dag_hash())) + clauses.append(fn.attr("package_hash", spec.name, spec._package_hash)) + clauses.append(fn.attr("hash", spec.name, spec.dag_hash())) # add all clauses from dependencies if transitive: @@ -1489,18 +1561,18 @@ class Body(object): for dtype in dspec.deptypes: # skip build dependencies of already-installed specs if concrete_build_deps or dtype != "build": - clauses.append(fn.depends_on(spec.name, dep.name, dtype)) + clauses.append(fn.attr("depends_on", spec.name, dep.name, dtype)) # Ensure Spack will not coconcretize this with another provider # for the same virtual for virtual in dep.package.virtuals_provided: - clauses.append(fn.virtual_node(virtual.name)) + clauses.append(fn.attr("virtual_node", virtual.name)) clauses.append(fn.provider(dep.name, virtual.name)) # imposing hash constraints for all but pure build deps of # already-installed concrete specs. if concrete_build_deps or dspec.deptypes != ("build",): - clauses.append(fn.hash(dep.name, dep.dag_hash())) + clauses.append(fn.attr("hash", dep.name, dep.dag_hash())) # if the spec is abstract, descend into dependencies. # if it's concrete, then the hashes above take care of dependency @@ -2057,12 +2129,13 @@ def literal_specs(self, specs): self.gen.h2("Spec: %s" % str(spec)) self.gen.fact(fn.literal(idx)) - root_fn = fn.virtual_root(spec.name) if spec.virtual else fn.root(spec.name) - self.gen.fact(fn.literal(idx, root_fn.name, *root_fn.args)) + 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.name, *clause.args)) - if clause.name == "variant_set": - self.gen.fact(fn.literal(idx, "variant_default_value_from_cli", *clause.args)) + 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:]) + ) if self.concretize_everything: self.gen.fact(fn.concretize_everything()) @@ -2071,8 +2144,20 @@ def literal_specs(self, specs): class SpecBuilder(object): """Class with actions to rebuild a spec from ASP results.""" - #: Attributes that don't need actions - ignored_attributes = ["opt_criterion"] + #: Regex for attributes that don't need actions b/c they aren't used to construct specs. + ignored_attributes = re.compile( + "|".join( + [ + r"^.*_propagate$", + r"^.*_satisfies$", + r"^.*_set$", + r"^package_hash$", + r"^root$", + r"^virtual_node$", + r"^virtual_root$", + ] + ) + ) def __init__(self, specs, hash_lookup=None): self._specs = {} @@ -2109,17 +2194,6 @@ def node_os(self, pkg, os): def node_target(self, pkg, target): self._arch(pkg).target = target - def error(self, priority, msg, *args): - msg = msg.format(*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 - 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))) - raise UnsatisfiableSpecError(msg) - def variant_value(self, pkg, name, value): # FIXME: is there a way not to special case 'dev_path' everywhere? if name == "dev_path": @@ -2244,10 +2318,7 @@ def deprecated(self, pkg, version): @staticmethod def sort_fn(function_tuple): name = function_tuple[0] - if name == "error": - priority = function_tuple[1][0] - return (-5, priority) - elif name == "hash": + if name == "hash": return (-4, 0) elif name == "node": return (-3, 0) @@ -2262,18 +2333,18 @@ def build_specs(self, function_tuples): # Functions don't seem to be in particular order in output. Sort # them here so that directives that build objects (like node and # node_compiler) are called in the right order. - self.function_tuples = function_tuples - self.function_tuples.sort(key=self.sort_fn) + self.function_tuples = sorted(set(function_tuples), key=self.sort_fn) self._specs = {} - for name, args in function_tuples: - if name in SpecBuilder.ignored_attributes: + for name, args in self.function_tuples: + if SpecBuilder.ignored_attributes.match(name): continue action = getattr(self, name, None) + # print out unknown actions so we can display them for debugging if not action: - msg = "%s(%s)" % (name, ", ".join(str(a) for a in args)) + msg = 'UNKNOWN SYMBOL: attr("%s", %s)' % (name, ", ".join(str(a) for a in args)) tty.debug(msg) continue diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp index ab40d28bd9..e2ff08889d 100644 --- a/lib/spack/spack/solver/concretize.lp +++ b/lib/spack/spack/solver/concretize.lp @@ -28,28 +28,11 @@ opt_criterion(300, "number of input specs not concretized"). #minimize { 1@300,ID : literal_not_solved(ID) }. % Map constraint on the literal ID to the correct PSID -attr(Name, A1) :- literal(LiteralID, Name, A1), literal_solved(LiteralID). -attr(Name, A1, A2) :- literal(LiteralID, Name, A1, A2), literal_solved(LiteralID). -attr(Name, A1, A2, A3) :- literal(LiteralID, Name, A1, A2, A3), literal_solved(LiteralID). +attr(Name, A1) :- literal(LiteralID, Name, A1), literal_solved(LiteralID). +attr(Name, A1, A2) :- literal(LiteralID, Name, A1, A2), literal_solved(LiteralID). +attr(Name, A1, A2, A3) :- literal(LiteralID, Name, A1, A2, A3), literal_solved(LiteralID). attr(Name, A1, A2, A3, A4) :- literal(LiteralID, Name, A1, A2, A3, A4), literal_solved(LiteralID). -% For these two atoms we only need implications in one direction -root(Package) :- attr("root", Package). -virtual_root(Package) :- attr("virtual_root", Package). - -node_platform_set(Package, Platform) :- attr("node_platform_set", Package, Platform). -node_os_set(Package, OS) :- attr("node_os_set", Package, OS). -node_target_set(Package, Target) :- attr("node_target_set", Package, Target). -node_flag_set(Package, Flag, Value) :- attr("node_flag_set", Package, Flag, Value). - -node_compiler_version_set(Package, Compiler, Version) - :- attr("node_compiler_version_set", Package, Compiler, Version). -node_compiler_set(Package, Compiler) - :- attr("node_compiler_set", Package, Compiler). - -variant_default_value_from_cli(Package, Variant, Value) - :- attr("variant_default_value_from_cli", Package, Variant, Value). - #defined concretize_everything/0. #defined literal/1. #defined literal/3. @@ -73,9 +56,9 @@ version_declared(Package, Version, Weight) :- version_declared(Package, Version, % We cannot use a version declared for an installed package if we end up building it :- version_declared(Package, Version, Weight, "installed"), - version(Package, Version), + attr("version", Package, Version), version_weight(Package, Weight), - not hash(Package, _), + not attr("hash", Package, _), internal_error("Reuse version weight used for built package"). % versions are declared w/priority -- declared with priority implies declared @@ -92,34 +75,36 @@ version_satisfies(Package, Constraint, HashVersion) :- version_satisfies(Package % is not precisely one version chosen. Error facts are heavily optimized % against to ensure they cannot be inferred when a non-error solution is % possible -{ version(Package, Version) : version_declared(Package, Version) } - :- node(Package). +{ attr("version", Package, Version) : version_declared(Package, Version) } + :- attr("node", Package). error(2, "No version for '{0}' satisfies '@{1}' and '@{2}'", Package, Version1, Version2) - :- node(Package), - version(Package, Version1), - version(Package, 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) - :- node(Package), not version(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(2, "No version for '{0}' satisfies '@{1}' and '@{2}'", Virtual, Version1, Version2) - :- virtual_node(Virtual), - version(Virtual, Version1), - version(Virtual, Version2), + :- attr("virtual_node", Virtual), + attr("version", Virtual, Version1), + attr("version", Virtual, Version2), Version1 < Version2. % see[1] % If we select a deprecated version, mark the package as deprecated -deprecated(Package, Version) :- version(Package, Version), deprecated_version(Package, Version). +attr("deprecated", Package, Version) :- + attr("version", Package, Version), + deprecated_version(Package, Version). possible_version_weight(Package, Weight) - :- version(Package, Version), + :- attr("version", Package, Version), version_declared(Package, Version, Weight). % we can't use the weight for an external version if we don't use the % corresponding external spec. -:- version(Package, Version), +:- attr("version", Package, Version), version_weight(Package, Weight), version_declared(Package, Version, Weight, "external"), not external(Package), @@ -127,44 +112,45 @@ possible_version_weight(Package, Weight) % we can't use a weight from an installed spec if we are building it % and vice-versa -:- version(Package, Version), +:- attr("version", Package, Version), version_weight(Package, Weight), version_declared(Package, Version, Weight, "installed"), build(Package), internal_error("Reuse version weight used for build package"). -:- version(Package, Version), +:- attr("version", Package, Version), version_weight(Package, Weight), not version_declared(Package, Version, Weight, "installed"), not build(Package), internal_error("Build version weight used for reused package"). 1 { version_weight(Package, Weight) : version_declared(Package, Version, Weight) } 1 - :- version(Package, Version), - node(Package). + :- attr("version", Package, Version), + attr("node", Package). % node_version_satisfies implies that exactly one of the satisfying versions % is the package's version, and vice versa. % While this choice rule appears redundant with the initial choice rule for % versions, virtual nodes with version constraints require this rule to be % able to choose versions -{ version(Package, Version) : version_satisfies(Package, Constraint, Version) } - :- node_version_satisfies(Package, Constraint). +{ attr("version", Package, Version) : version_satisfies(Package, Constraint, Version) } + :- attr("node_version_satisfies", Package, Constraint). % If there is at least a version that satisfy the constraint, impose a lower % bound on the choice rule to avoid false positives with the error below -1 { version(Package, Version) : version_satisfies(Package, Constraint, Version) } - :- node_version_satisfies(Package, Constraint), version_satisfies(Package, Constraint, _). +1 { attr("version", Package, Version) : version_satisfies(Package, Constraint, Version) } + :- attr("node_version_satisfies", Package, Constraint), + version_satisfies(Package, Constraint, _). % More specific error message if the version cannot satisfy some constraint % Otherwise covered by `no_version_error` and `versions_conflict_error`. error(1, "No valid version for '{0}' satisfies '@{1}'", Package, Constraint) - :- node_version_satisfies(Package, Constraint), - C = #count{ Version : version(Package, Version), version_satisfies(Package, Constraint, Version)}, + :- attr("node_version_satisfies", Package, Constraint), + C = #count{ Version : attr("version", Package, Version), version_satisfies(Package, Constraint, Version)}, C < 1. -node_version_satisfies(Package, Constraint) - :- version(Package, Version), version_satisfies(Package, Constraint, Version). +attr("node_version_satisfies", Package, Constraint) + :- attr("version", Package, Version), version_satisfies(Package, Constraint, Version). #defined version_satisfies/3. #defined deprecated_version/2. @@ -199,14 +185,14 @@ attr(Name, A1, A2, A3) :- impose(ID), imposed_constraint(ID, Name, A1, A2, A attr(Name, A1, A2, A3, A4) :- impose(ID), imposed_constraint(ID, Name, A1, A2, A3, A4). % we cannot have additional variant values when we are working with concrete specs -:- node(Package), hash(Package, Hash), - variant_value(Package, Variant, Value), +:- attr("node", Package), attr("hash", Package, Hash), + attr("variant_value", Package, Variant, Value), not imposed_constraint(Hash, "variant_value", Package, Variant, Value), internal_error("imposed hash without imposing all variant values"). % we cannot have additional flag values when we are working with concrete specs -:- node(Package), hash(Package, Hash), - node_flag(Package, FlagType, Flag), +:- attr("node", Package), attr("hash", Package, Hash), + attr("node_flag", Package, FlagType, Flag), not imposed_constraint(Hash, "node_flag", Package, FlagType, Flag), internal_error("imposed hash without imposing all flag values"). @@ -224,13 +210,13 @@ attr(Name, A1, A2, A3, A4) :- impose(ID), imposed_constraint(ID, Name, A1, A2, A % Concrete specs %----------------------------------------------------------------------------- % if a package is assigned a hash, it's concrete. -concrete(Package) :- hash(Package, _), node(Package). +concrete(Package) :- attr("hash", Package, _), attr("node", Package). %----------------------------------------------------------------------------- % Dependency semantics %----------------------------------------------------------------------------- % Dependencies of any type imply that one package "depends on" another -depends_on(Package, Dependency) :- depends_on(Package, Dependency, _). +depends_on(Package, Dependency) :- attr("depends_on", Package, Dependency, _). % a dependency holds if its condition holds and if it is not external or % concrete. We chop off dependencies for externals, and dependencies of @@ -252,23 +238,23 @@ do_not_impose(ID) :- % declared dependencies are real if they're not virtual AND % the package is not an external. % They're only triggered if the associated dependnecy condition holds. -depends_on(Package, Dependency, Type) +attr("depends_on", Package, Dependency, Type) :- dependency_holds(Package, Dependency, Type), not virtual(Dependency). % every root must be a node -node(Package) :- root(Package). +attr("node", Package) :- attr("root", Package). % dependencies imply new nodes -node(Dependency) :- node(Package), depends_on(Package, Dependency). +attr("node", Dependency) :- attr("node", Package), depends_on(Package, Dependency). % all nodes in the graph must be reachable from some root % this ensures a user can't say `zlib ^libiconv` (neither of which have any % dependencies) and get a two-node unconnected graph -needed(Package) :- root(Package). +needed(Package) :- attr("root", Package). needed(Dependency) :- needed(Package), depends_on(Package, Dependency). error(1, "'{0}' is not a valid dependency for any package in the DAG", Package) - :- node(Package), + :- attr("node", Package), not needed(Package). % Avoid cycles in the DAG @@ -286,12 +272,12 @@ error(2, "Cyclic dependency detected between '{0}' and '{1}'\n Consider chang %----------------------------------------------------------------------------- % Conflicts %----------------------------------------------------------------------------- -error(0, Msg) :- node(Package), +error(0, Msg) :- attr("node", Package), conflict(Package, TriggerID, ConstraintID, Msg), condition_holds(TriggerID), condition_holds(ConstraintID), not external(Package), % ignore conflicts for externals - not hash(Package, _). % ignore conflicts for installed packages + not attr("hash", Package, _). % ignore conflicts for installed packages #defined conflict/4. @@ -301,43 +287,43 @@ error(0, Msg) :- node(Package), % 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 -depends_on(Package, Provider, Type) +attr("depends_on", Package, Provider, Type) :- dependency_holds(Package, Virtual, Type), provider(Provider, Virtual), not external(Package). % dependencies on virtuals also imply that the virtual is a virtual node -virtual_node(Virtual) +attr("virtual_node", Virtual) :- dependency_holds(Package, Virtual, Type), virtual(Virtual), not external(Package). % If there's a virtual node, we must select one and only one provider. % The provider must be selected among the possible providers. { provider(Package, Virtual) : possible_provider(Package, Virtual) } - :- virtual_node(Virtual). + :- attr("virtual_node", Virtual). error(2, "Cannot find valid provider for virtual {0}", Virtual) - :- virtual_node(Virtual), + :- attr("virtual_node", Virtual), P = #count{ Package : provider(Package, Virtual)}, P < 1. error(2, "Spec cannot include multiple providers for virtual '{0}'\n Requested '{1}' and '{2}'", Virtual, P1, P2) - :- virtual_node(Virtual), + :- attr("virtual_node", Virtual), provider(P1, Virtual), provider(P2, Virtual), P1 < P2. % virtual roots imply virtual nodes, and that one provider is a root -virtual_node(Virtual) :- virtual_root(Virtual). +attr("virtual_node", Virtual) :- attr("virtual_root", Virtual). % If we asked for a virtual root and we have a provider for that, % then the provider is the root package. -root(Package) :- virtual_root(Virtual), provider(Package, Virtual). +attr("root", Package) :- attr("virtual_root", Virtual), provider(Package, Virtual). % If we asked for a root package and that root provides a virtual, % the root is a provider for that virtual. This rule is mostly relevant % for environments that are concretized together (e.g. where we % asks to install "mpich" and "hdf5+mpi" and we want "mpich" to % be the mpi provider) -provider(Package, Virtual) :- node(Package), virtual_condition_holds(Package, Virtual). +provider(Package, Virtual) :- attr("node", Package), virtual_condition_holds(Package, Virtual). % The provider provides the virtual if some provider condition holds. virtual_condition_holds(Provider, Virtual) :- @@ -387,73 +373,15 @@ possible_provider_weight(Dependency, Virtual, Weight, "default") % Any provider can use 100 as a weight, which is very high and discourage its use possible_provider_weight(Dependency, Virtual, 100, "fallback") :- provider(Dependency, Virtual). +% do not warn if generated program contains none of these. #defined possible_provider/2. #defined provider_condition/3. #defined required_provider_condition/3. #defined required_provider_condition/4. #defined required_provider_condition/5. #defined required_provider_condition/6. - -%----------------------------------------------------------------------------- -% Spec Attributes -%----------------------------------------------------------------------------- -% Equivalencies of the form: -% -% name(Arg1, Arg2, ...) :- attr("name", Arg1, Arg2, ...). -% attr("name", Arg1, Arg2, ...) :- name(Arg1, Arg2, ...). -% -% These allow us to easily define conditional dependency and conflict rules -% without enumerating all spec attributes every time. -node(Package) :- attr("node", Package). -virtual_node(Virtual) :- attr("virtual_node", Virtual). -hash(Package, Hash) :- attr("hash", Package, Hash). -version(Package, Version) :- attr("version", Package, Version). -node_version_satisfies(Package, Constraint) :- attr("node_version_satisfies", Package, Constraint). -node_platform(Package, Platform) :- attr("node_platform", Package, Platform). -node_os(Package, OS) :- attr("node_os", Package, OS). -node_target(Package, Target) :- attr("node_target", Package, Target). -node_target_satisfies(Package, Target) :- attr("node_target_satisfies", Package, Target). -variant_value(Package, Variant, Value) :- attr("variant_value", Package, Variant, Value). -variant_set(Package, Variant, Value) :- attr("variant_set", Package, Variant, Value). -variant_propagate(Package, Variant, Value, Source) :- attr("variant_propagate", Package, Variant, Value, Source). -node_flag(Package, FlagType, Flag) :- attr("node_flag", Package, FlagType, Flag). -node_compiler(Package, Compiler) :- attr("node_compiler", Package, Compiler). -depends_on(Package, Dependency, Type) :- attr("depends_on", Package, Dependency, Type). -node_compiler_version(Package, Compiler, Version) - :- attr("node_compiler_version", Package, Compiler, Version). -node_compiler_version_satisfies(Package, Compiler, Version) - :- attr("node_compiler_version_satisfies", Package, Compiler, Version). -node_flag_propagate(Package, FlagType) - :- attr("node_flag_propagate", Package, FlagType). - -attr("node", Package) :- node(Package). -attr("virtual_node", Virtual) :- virtual_node(Virtual). -attr("hash", Package, Hash) :- hash(Package, Hash). -attr("version", Package, Version) :- version(Package, Version). -attr("node_version_satisfies", Package, Constraint) :- node_version_satisfies(Package, Constraint). -attr("node_platform", Package, Platform) :- node_platform(Package, Platform). -attr("node_os", Package, OS) :- node_os(Package, OS). -attr("node_target", Package, Target) :- node_target(Package, Target). -attr("node_target_satisfies", Package, Target) :- node_target_satisfies(Package, Target). -attr("variant_value", Package, Variant, Value) :- variant_value(Package, Variant, Value). -attr("variant_set", Package, Variant, Value) :- variant_set(Package, Variant, Value). -attr("variant_propagate", Package, Variant, Value, Source) :- variant_propagate(Package, Variant, Value, Source). -attr("node_flag", Package, FlagType, Flag) :- node_flag(Package, FlagType, Flag). -attr("node_compiler", Package, Compiler) :- node_compiler(Package, Compiler). -attr("depends_on", Package, Dependency, Type) :- depends_on(Package, Dependency, Type). -attr("node_compiler_version", Package, Compiler, Version) - :- node_compiler_version(Package, Compiler, Version). -attr("node_compiler_version_satisfies", Package, Compiler, Version) - :- node_compiler_version_satisfies(Package, Compiler, Version). -attr("node_flag_propagate", Package, FlagType) - :- node_flag_propagate(Package, FlagType). - -% do not warn if generated program contains none of these. -#defined depends_on/3. #defined declared_dependency/3. #defined virtual/1. -#defined virtual_node/1. -#defined virtual_root/1. #defined virtual_condition_holds/2. #defined external/1. #defined external_spec/2. @@ -461,9 +389,6 @@ attr("node_flag_propagate", Package, FlagType) #defined buildable_false/1. #defined pkg_provider_preference/4. #defined default_provider_preference/3. -#defined node_version_satisfies/2. -#defined node_compiler_version_satisfies/3. -#defined root/1. %----------------------------------------------------------------------------- % External semantics @@ -483,32 +408,32 @@ error(2, "Attempted to use external for '{0}' which does not satisfy any configu (Version1, Weight1) < (Version2, Weight2). % see[1] version_weight(Package, Weight) :- external_version(Package, Version, Weight). -version(Package, Version) :- external_version(Package, Version, Weight). +attr("version", Package, Version) :- external_version(Package, Version, Weight). % if a package is not buildable, only externals or hashed specs are allowed external(Package) :- buildable_false(Package), - node(Package), - not hash(Package, _). + attr("node", Package), + not attr("hash", Package, _). % a package is a real_node if it is not external -real_node(Package) :- node(Package), not external(Package). +real_node(Package) :- attr("node", Package), not external(Package). % a package is external if we are using an external spec for it -external(Package) :- external_spec_selected(Package, _). +external(Package) :- attr("external_spec_selected", Package, _). % we can't use the weight for an external version if we don't use the % corresponding external spec. -:- version(Package, Version), +:- attr("version", Package, Version), version_weight(Package, Weight), version_declared(Package, Version, Weight, "external"), not external(Package), internal_error("External weight used for internal spec"). % determine if an external spec has been selected -external_spec_selected(Package, LocalIndex) :- +attr("external_spec_selected", Package, LocalIndex) :- external_conditions_hold(Package, LocalIndex), - node(Package), - not hash(Package, _). + attr("node", Package), + not attr("hash", Package, _). external_conditions_hold(Package, LocalIndex) :- possible_external(ID, Package, LocalIndex), condition_holds(ID). @@ -530,8 +455,8 @@ error(2, "Attempted to use external for '{0}' which does not satisfy any configu % Config required semantics %----------------------------------------------------------------------------- -activate_requirement_rules(Package) :- node(Package). -activate_requirement_rules(Package) :- virtual_node(Package). +activate_requirement_rules(Package) :- attr("node", Package). +activate_requirement_rules(Package) :- attr("virtual_node", Package). requirement_group_satisfied(Package, X) :- 1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } 1, @@ -581,77 +506,77 @@ error(2, "Cannot satisfy the requirements in packages.yaml for the '{0}' package variant(Package, Variant) :- variant_condition(ID, Package, Variant), condition_holds(ID). -variant_propagate(Package, Variant, Value, Source) :- - node(Package), +attr("variant_propagate", Package, Variant, Value, Source) :- + attr("node", Package), depends_on(Parent, Package), - variant_propagate(Parent, Variant, Value, Source), - not variant_set(Package, Variant). + attr("variant_propagate", Parent, Variant, Value, Source), + not attr("variant_set", Package, Variant). -variant_value(Package, Variant, Value) :- - node(Package), +attr("variant_value", Package, Variant, Value) :- + attr("node", Package), variant(Package, Variant), - variant_propagate(Package, Variant, Value, _), + attr("variant_propagate", Package, Variant, Value, _), variant_possible_value(Package, Variant, Value). error(2, "{0} and {1} cannot both propagate variant '{2}' to package {3} with values '{4}' and '{5}'", Source1, Source2, Variant, Package, Value1, Value2) :- - variant_propagate(Package, Variant, Value1, Source1), - variant_propagate(Package, Variant, Value2, Source2), + attr("variant_propagate", Package, Variant, Value1, Source1), + attr("variant_propagate", Package, Variant, Value2, Source2), variant(Package, Variant), Value1 != Value2. % a variant cannot be set if it is not a variant on the package error(2, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package) - :- variant_set(Package, Variant), + :- attr("variant_set", Package, Variant), not variant(Package, Variant), build(Package). % a variant cannot take on a value if it is not a variant of the package error(2, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package) - :- variant_value(Package, Variant, _), + :- attr("variant_value", Package, Variant, _), not variant(Package, Variant), build(Package). % if a variant is sticky and not set its value is the default value -variant_value(Package, Variant, Value) :- +attr("variant_value", Package, Variant, Value) :- variant(Package, Variant), - not variant_set(Package, Variant), + not attr("variant_set", Package, Variant), variant_sticky(Package, Variant), variant_default_value(Package, Variant, Value), build(Package). % at most one variant value for single-valued variants. { - variant_value(Package, Variant, Value) + attr("variant_value", Package, Variant, Value) : variant_possible_value(Package, Variant, Value) } - :- node(Package), + :- attr("node", Package), variant(Package, Variant), build(Package). error(2, "'{0}' required multiple values for single-valued variant '{1}'\n Requested 'Spec({1}={2})' and 'Spec({1}={3})'", Package, Variant, Value1, Value2) - :- node(Package), + :- attr("node", Package), variant(Package, Variant), variant_single_value(Package, Variant), build(Package), - variant_value(Package, Variant, Value1), - variant_value(Package, Variant, Value2), + attr("variant_value", Package, Variant, Value1), + attr("variant_value", Package, Variant, Value2), Value1 < Value2. % see[1] error(2, "No valid value for variant '{1}' of package '{0}'", Package, Variant) - :- node(Package), + :- attr("node", Package), variant(Package, Variant), build(Package), - C = #count{ Value : variant_value(Package, Variant, Value) }, + C = #count{ Value : attr("variant_value", Package, Variant, Value) }, C < 1. % if a variant is set to anything, it is considered 'set'. -variant_set(Package, Variant) :- variant_set(Package, Variant, _). +attr("variant_set", Package, Variant) :- attr("variant_set", Package, Variant, _). % A variant cannot have a value that is not also a possible value % This only applies to packages we need to build -- concrete packages may % have been built w/different variants from older/different package versions. error(1, "'Spec({1}={2})' is not a valid value for '{0}' variant '{1}'", Package, Variant, Value) - :- variant_value(Package, Variant, Value), + :- attr("variant_value", Package, Variant, Value), not variant_possible_value(Package, Variant, Value), build(Package). @@ -659,8 +584,8 @@ error(1, "'Spec({1}={2})' is not a valid value for '{0}' variant '{1}'", Package % Ensure that we respect that constraint and we don't pick values from more % than one set at once error(2, "{0} variant '{1}' cannot have values '{2}' and '{3}' as they come from disjoing value sets", Package, Variant, Value1, Value2) - :- variant_value(Package, Variant, Value1), - variant_value(Package, Variant, Value2), + :- attr("variant_value", Package, Variant, Value1), + attr("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, % see[1] @@ -668,41 +593,41 @@ error(2, "{0} variant '{1}' cannot have values '{2}' and '{3}' as they come from % 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 -variant_value(Package, Variant, Value) - :- node(Package), +attr("variant_value", Package, Variant, Value) + :- attr("node", Package), variant(Package, Variant), - variant_set(Package, Variant, Value). + attr("variant_set", Package, Variant, Value). % The rules below allow us to prefer default values for variants % whenever possible. If a variant is set in a spec, or if it is % specified in an external, we score it as if it was a default value. variant_not_default(Package, Variant, Value) - :- variant_value(Package, Variant, Value), + :- attr("variant_value", Package, Variant, Value), not variant_default_value(Package, Variant, Value), % variants set explicitly on the CLI don't count as non-default - not variant_set(Package, Variant, Value), + not attr("variant_set", Package, Variant, Value), % variant values forced by propagation don't count as non-default - not variant_propagate(Package, Variant, Value, _), + not attr("variant_propagate", Package, Variant, Value, _), % variants set on externals that we could use don't count as non-default % this makes spack prefer to use an external over rebuilding with the % default configuration not external_with_variant_set(Package, Variant, Value), - node(Package). + attr("node", Package). % A default variant value that is not used variant_default_not_used(Package, Variant, Value) :- variant_default_value(Package, Variant, Value), - not variant_value(Package, Variant, Value), - node(Package). + not attr("variant_value", Package, Variant, Value), + attr("node", Package). % The variant is set in an external spec external_with_variant_set(Package, Variant, Value) - :- variant_value(Package, Variant, Value), + :- attr("variant_value", Package, Variant, Value), condition_requirement(ID, "variant_value", Package, Variant, Value), possible_external(ID, Package, _), external(Package), - node(Package). + attr("node", Package). % The default value for a variant in a package is what is prescribed: % @@ -714,19 +639,20 @@ external_with_variant_set(Package, Variant, Value) variant_default_value(Package, Variant, Value) :- variant_default_value_from_package_py(Package, Variant, Value), not variant_default_value_from_packages_yaml(Package, Variant, _), - not variant_default_value_from_cli(Package, Variant, _). + not attr("variant_default_value_from_cli", Package, Variant, _). variant_default_value(Package, Variant, Value) :- variant_default_value_from_packages_yaml(Package, Variant, Value), - not variant_default_value_from_cli(Package, Variant, _). + not attr("variant_default_value_from_cli", Package, Variant, _). -variant_default_value(Package, Variant, Value) :- variant_default_value_from_cli(Package, Variant, Value). +variant_default_value(Package, Variant, Value) :- + attr("variant_default_value_from_cli", Package, Variant, Value). % Treat 'none' in a special way - it cannot be combined with other % values even if the variant is multi-valued error(2, "{0} variant '{1}' cannot have values '{2}' and 'none'", Package, Variant, Value) - :- variant_value(Package, Variant, Value), - variant_value(Package, Variant, "none"), + :- attr("variant_value", Package, Variant, Value), + attr("variant_value", Package, Variant, "none"), Value != "none", build(Package). @@ -736,16 +662,15 @@ error(2, "{0} variant '{1}' cannot have values '{2}' and 'none'", Package, Varia auto_variant("dev_path"). auto_variant("patches"). variant(Package, Variant) - :- variant_set(Package, Variant, _), auto_variant(Variant). + :- attr("variant_set", Package, Variant, _), auto_variant(Variant). variant_single_value(Package, "dev_path") - :- variant_set(Package, "dev_path", _). + :- attr("variant_set", Package, "dev_path", _). % suppress warnings about this atom being unset. It's only set if some % spec or some package sets it, and without this, clingo will give % warnings like 'info: atom does not occur in any rule head'. #defined variant/2. #defined variant_sticky/2. -#defined variant_propagate/4. #defined variant_set/3. #defined variant_condition/3. #defined variant_single_value/2. @@ -761,32 +686,30 @@ variant_single_value(Package, "dev_path") %----------------------------------------------------------------------------- % if no platform is set, fall back to the default -node_platform(Package, Platform) - :- node(Package), - not node_platform_set(Package), +attr("node_platform", Package, Platform) + :- attr("node", Package), + not attr("node_platform_set", Package), node_platform_default(Platform). % setting platform on a node is a hard constraint -node_platform(Package, Platform) - :- node(Package), node_platform_set(Package, Platform). +attr("node_platform", Package, Platform) + :- attr("node", Package), attr("node_platform_set", Package, Platform). % platform is set if set to anything -node_platform_set(Package) :- node_platform_set(Package, _). +attr("node_platform_set", Package) :- attr("node_platform_set", Package, _). % each node must have a single platform error(2, "No valid platform found for {0}", Package) - :- node(Package), - C = #count{ Platform : node_platform(Package, Platform)}, + :- attr("node", Package), + C = #count{ Platform : attr("node_platform", Package, Platform)}, C < 1. error(2, "Cannot concretize {0} with multiple platforms\n Requested 'platform={1}' and 'platform={2}'", Package, Platform1, Platform2) - :- node(Package), - node_platform(Package, Platform1), - node_platform(Package, Platform2), + :- attr("node", Package), + attr("node_platform", Package, Platform1), + attr("node_platform", Package, Platform2), Platform1 < Platform2. % see[1] -#defined node_platform_set/2. % avoid warnings - %----------------------------------------------------------------------------- % OS semantics %----------------------------------------------------------------------------- @@ -794,42 +717,42 @@ error(2, "Cannot concretize {0} with multiple platforms\n Requested 'platform os(OS) :- os(OS, _). % one os per node -{ node_os(Package, OS) : os(OS) } :- node(Package). +{ attr("node_os", Package, OS) : os(OS) } :- attr("node", Package). error(2, "Cannot find valid operating system for '{0}'", Package) - :- node(Package), - C = #count{ OS : node_os(Package, OS)}, + :- attr("node", Package), + C = #count{ OS : attr("node_os", Package, OS)}, C < 1. error(2, "Cannot concretize {0} with multiple operating systems\n Requested 'os={1}' and 'os={2}'", Package, OS1, OS2) - :- node(Package), - node_os(Package, OS1), - node_os(Package, OS2), + :- attr("node", Package), + attr("node_os", Package, OS1), + attr("node_os", Package, OS2), OS1 < OS2. %see [1] % can't have a non-buildable OS on a node we need to build error(2, "Cannot concretize '{0} os={1}'. Operating system '{1}' is not buildable", Package, OS) :- build(Package), - node_os(Package, OS), + attr("node_os", Package, OS), not buildable_os(OS). % can't have dependencies on incompatible OS's error(2, "{0} and dependency {1} have incompatible operating systems 'os={2}' and 'os={3}'", Package, Dependency, PackageOS, DependencyOS) :- depends_on(Package, Dependency), - node_os(Package, PackageOS), - node_os(Dependency, DependencyOS), + attr("node_os", Package, PackageOS), + attr("node_os", Dependency, DependencyOS), not os_compatible(PackageOS, DependencyOS), build(Package). % give OS choice weights according to os declarations node_os_weight(Package, Weight) - :- node(Package), - node_os(Package, OS), + :- attr("node", Package), + attr("node_os", Package, OS), os(OS, Weight). % match semantics for OS's node_os_match(Package, Dependency) :- - depends_on(Package, Dependency), node_os(Package, OS), node_os(Dependency, OS). + depends_on(Package, Dependency), attr("node_os", Package, OS), attr("node_os", Dependency, OS). node_os_mismatch(Package, Dependency) :- depends_on(Package, Dependency), not node_os_match(Package, Dependency). @@ -843,13 +766,12 @@ os_compatible(OS1, OS3) :- os_compatible(OS1, OS2), os_compatible(OS2, OS3). % for which we can build software. We need a cardinality constraint % since we might have more than one "buildable_os(OS)" fact. :- not 1 { os_compatible(CurrentOS, ReusedOS) : buildable_os(CurrentOS) }, - node_os(Package, ReusedOS), + attr("node_os", Package, ReusedOS), internal_error("Reused OS incompatible with build OS"). % If an OS is set explicitly respect the value -node_os(Package, OS) :- node_os_set(Package, OS), node(Package). +attr("node_os", Package, OS) :- attr("node_os_set", Package, OS), attr("node", Package). -#defined node_os_set/2. #defined os_compatible/2. %----------------------------------------------------------------------------- @@ -857,41 +779,41 @@ node_os(Package, OS) :- node_os_set(Package, OS), node(Package). %----------------------------------------------------------------------------- % Each node has only one target chosen among the known targets -{ node_target(Package, Target) : target(Target) } :- node(Package). +{ attr("node_target", Package, Target) : target(Target) } :- attr("node", Package). error(2, "Cannot find valid target for '{0}'", Package) - :- node(Package), - C = #count{Target : node_target(Package, Target)}, + :- attr("node", Package), + C = #count{Target : attr("node_target", Package, Target)}, C < 1. error(2, "Cannot concretize '{0}' with multiple targets\n Requested 'target={1}' and 'target={2}'", Package, Target1, Target2) - :- node(Package), - node_target(Package, Target1), - node_target(Package, Target2), + :- attr("node", Package), + attr("node_target", Package, Target1), + attr("node_target", Package, Target2), Target1 < Target2. % see[1] % If a node must satisfy a target constraint, enforce it error(1, "'{0} target={1}' cannot satisfy constraint 'target={2}'", Package, Target, Constraint) - :- node_target(Package, Target), - node_target_satisfies(Package, Constraint), + :- attr("node_target", Package, Target), + attr("node_target_satisfies", Package, Constraint), not target_satisfies(Constraint, Target). % If a node has a target and the target satisfies a constraint, then the target % associated with the node satisfies the same constraint -node_target_satisfies(Package, Constraint) - :- node_target(Package, Target), target_satisfies(Constraint, Target). +attr("node_target_satisfies", Package, Constraint) + :- attr("node_target", Package, Target), target_satisfies(Constraint, Target). % If a node has a target, all of its dependencies must be compatible with that target error(2, "Cannot find compatible targets for {0} and {1}", Package, Dependency) :- depends_on(Package, Dependency), - node_target(Package, Target), + attr("node_target", Package, Target), not node_target_compatible(Dependency, Target). % Intermediate step for performance reasons % When the integrity constraint above was formulated including this logic % we suffered a substantial performance penalty node_target_compatible(Package, Target) - :- node_target(Package, MyTarget), + :- attr("node_target", Package, MyTarget), target_compatible(Target, MyTarget). % target_compatible(T1, T2) means code for T2 can run on T1 @@ -909,27 +831,27 @@ target_compatible(Descendent, Ancestor) % can't use targets on node if the compiler for the node doesn't support them error(2, "{0} compiler '{2}@{3}' incompatible with 'target={1}'", Package, Target, Compiler, Version) - :- node_target(Package, Target), + :- attr("node_target", Package, Target), not compiler_supports_target(Compiler, Version, Target), - node_compiler(Package, Compiler), - node_compiler_version(Package, Compiler, Version), + attr("node_compiler", Package, Compiler), + attr("node_compiler_version", Package, Compiler, Version), build(Package). % if a target is set explicitly, respect it -node_target(Package, Target) - :- node(Package), node_target_set(Package, Target). +attr("node_target", Package, Target) + :- attr("node", Package), attr("node_target_set", Package, Target). % each node has the weight of its assigned target node_target_weight(Package, Weight) - :- node(Package), - node_target(Package, Target), + :- attr("node", Package), + attr("node_target", Package, Target), target_weight(Package, Target, Weight). % compatibility rules for targets among nodes node_target_match(Parent, Dependency) :- depends_on(Parent, Dependency), - node_target(Parent, Target), - node_target(Dependency, Target). + attr("node_target", Parent, Target), + attr("node_target", Dependency, Target). node_target_mismatch(Parent, Dependency) :- depends_on(Parent, Dependency), @@ -937,11 +859,10 @@ node_target_mismatch(Parent, Dependency) % disallow reusing concrete specs that don't have a compatible target error(2, "'{0} target={1}' is not compatible with this machine", Package, Target) - :- node(Package), - node_target(Package, Target), + :- attr("node", Package), + attr("node_target", Package, Target), not target(Target). -#defined node_target_set/2. #defined package_target_weight/3. %----------------------------------------------------------------------------- @@ -951,67 +872,68 @@ compiler(Compiler) :- compiler_version(Compiler, _). % There must be only one compiler set per built node. The compiler % is chosen among available versions. -{ node_compiler_version(Package, Compiler, Version) : compiler_version(Compiler, Version) } :- - node(Package), +{ attr("node_compiler_version", Package, Compiler, Version) : compiler_version(Compiler, Version) } :- + attr("node", Package), build(Package). error(2, "No valid compiler version found for '{0}'", Package) - :- node(Package), - C = #count{ Version : node_compiler_version(Package, _, Version)}, + :- attr("node", Package), + C = #count{ Version : attr("node_compiler_version", Package, _, Version)}, C < 1. error(2, "'{0}' compiler constraints '%{1}@{2}' and '%{3}@{4}' are incompatible", Package, Compiler1, Version1, Compiler2, Version2) - :- node(Package), - node_compiler_version(Package, Compiler1, Version1), - node_compiler_version(Package, Compiler2, Version2), + :- attr("node", Package), + attr("node_compiler_version", Package, Compiler1, Version1), + attr("node_compiler_version", Package, Compiler2, Version2), (Compiler1, Version1) < (Compiler2, Version2). % see[1] % Sometimes we just need to know the compiler and not the version -node_compiler(Package, Compiler) :- node_compiler_version(Package, Compiler, _). +attr("node_compiler", Package, Compiler) :- attr("node_compiler_version", Package, Compiler, _). % We can't have a compiler be enforced and select the version from another compiler error(2, "Cannot concretize {0} with two compilers {1}@{2} and {3}@{4}", Package, C1, V1, C2, V2) - :- node_compiler_version(Package, C1, V1), - node_compiler_version(Package, C2, V2), + :- attr("node_compiler_version", Package, C1, V1), + attr("node_compiler_version", Package, C2, V2), (C1, V1) != (C2, V2). error(2, "Cannot concretize {0} with two compilers {1} and {2}@{3}", Package, Compiler1, Compiler2, Version) - :- node_compiler(Package, Compiler1), - node_compiler_version(Package, Compiler2, Version), + :- attr("node_compiler", Package, Compiler1), + attr("node_compiler_version", Package, Compiler2, Version), Compiler1 != Compiler2. % If the compiler of a node cannot be satisfied, raise error(1, "No valid compiler for {0} satisfies '%{1}'", Package, Compiler) - :- node(Package), - node_compiler_version_satisfies(Package, Compiler, ":"), - C = #count{ Version : node_compiler_version(Package, Compiler, Version), compiler_version_satisfies(Compiler, ":", Version) }, + :- attr("node", Package), + attr("node_compiler_version_satisfies", Package, Compiler, ":"), + C = #count{ Version : attr("node_compiler_version", Package, Compiler, Version), compiler_version_satisfies(Compiler, ":", Version) }, C < 1. % If the compiler of a node must satisfy a constraint, then its version % must be chosen among the ones that satisfy said constraint error(2, "No valid version for '{0}' compiler '{1}' satisfies '@{2}'", Package, Compiler, Constraint) - :- node(Package), - node_compiler_version_satisfies(Package, Compiler, Constraint), - C = #count{ Version : node_compiler_version(Package, Compiler, Version), compiler_version_satisfies(Compiler, Constraint, Version) }, + :- attr("node", Package), + attr("node_compiler_version_satisfies", Package, Compiler, Constraint), + C = #count{ Version : attr("node_compiler_version", Package, Compiler, Version), compiler_version_satisfies(Compiler, Constraint, Version) }, C < 1. % If the node is associated with a compiler and the compiler satisfy a constraint, then % the compiler associated with the node satisfy the same constraint -node_compiler_version_satisfies(Package, Compiler, Constraint) - :- node_compiler_version(Package, Compiler, Version), +attr("node_compiler_version_satisfies", Package, Compiler, Constraint) + :- attr("node_compiler_version", Package, Compiler, Version), compiler_version_satisfies(Compiler, Constraint, Version). #defined compiler_version_satisfies/3. % If the compiler version was set from the command line, % respect it verbatim -node_compiler_version(Package, Compiler, Version) :- node_compiler_version_set(Package, Compiler, Version). +attr("node_compiler_version", Package, Compiler, Version) :- + attr("node_compiler_version_set", Package, Compiler, Version). % Cannot select a compiler if it is not supported on the OS % Compilers that are explicitly marked as allowed % are excluded from this check error(2, "{0} compiler '%{1}@{2}' incompatible with 'os={3}'", Package, Compiler, Version, OS) - :- node_compiler_version(Package, Compiler, Version), - node_os(Package, OS), + :- attr("node_compiler_version", Package, Compiler, Version), + attr("node_os", Package, OS), not compiler_supports_os(Compiler, Version, OS), not allow_compiler(Compiler, Version), build(Package). @@ -1020,40 +942,38 @@ error(2, "{0} compiler '%{1}@{2}' incompatible with 'os={3}'", Package, Compiler % same compiler there's a mismatch. compiler_match(Package, Dependency) :- depends_on(Package, Dependency), - node_compiler_version(Package, Compiler, Version), - node_compiler_version(Dependency, Compiler, Version). + attr("node_compiler_version", Package, Compiler, Version), + attr("node_compiler_version", Dependency, Compiler, Version). compiler_mismatch(Package, Dependency) :- depends_on(Package, Dependency), - not node_compiler_set(Dependency, _), + not attr("node_compiler_set", Dependency, _), not compiler_match(Package, Dependency). compiler_mismatch_required(Package, Dependency) :- depends_on(Package, Dependency), - node_compiler_set(Dependency, _), + attr("node_compiler_set", Dependency, _), not compiler_match(Package, Dependency). -#defined node_compiler_set/2. -#defined node_compiler_version_set/3. #defined compiler_supports_os/3. #defined allow_compiler/2. % compilers weighted by preference according to packages.yaml compiler_weight(Package, Weight) - :- node_compiler_version(Package, Compiler, V), + :- attr("node_compiler_version", Package, Compiler, V), node_compiler_preference(Package, Compiler, V, Weight). compiler_weight(Package, Weight) - :- node_compiler_version(Package, Compiler, V), + :- attr("node_compiler_version", Package, Compiler, V), not node_compiler_preference(Package, Compiler, V, _), default_compiler_preference(Compiler, V, Weight). compiler_weight(Package, 100) - :- node_compiler_version(Package, Compiler, Version), + :- attr("node_compiler_version", Package, Compiler, Version), not node_compiler_preference(Package, Compiler, Version, _), not default_compiler_preference(Compiler, Version, _). % For the time being, be strict and reuse only if the compiler match one we have on the system error(2, "Compiler {1}@{2} requested for {0} cannot be found. Set install_missing_compilers:true if intended.", Package, Compiler, Version) - :- node_compiler_version(Package, Compiler, Version), not compiler_version(Compiler, Version). + :- attr("node_compiler_version", Package, Compiler, Version), not compiler_version(Compiler, Version). #defined node_compiler_preference/4. #defined default_compiler_preference/3. @@ -1065,85 +985,82 @@ error(2, "Compiler {1}@{2} requested for {0} cannot be found. Set install_missin % propagate flags when compiler match can_inherit_flags(Package, Dependency, FlagType) :- depends_on(Package, Dependency), - node_compiler(Package, Compiler), - node_compiler(Dependency, Compiler), - not node_flag_set(Dependency, FlagType, _), + attr("node_compiler", Package, Compiler), + attr("node_compiler", Dependency, Compiler), + not attr("node_flag_set", Dependency, FlagType, _), compiler(Compiler), flag_type(FlagType). node_flag_inherited(Dependency, FlagType, Flag) - :- node_flag_set(Package, FlagType, Flag), can_inherit_flags(Package, Dependency, FlagType), - node_flag_propagate(Package, FlagType). + :- attr("node_flag_set", Package, FlagType, Flag), can_inherit_flags(Package, Dependency, FlagType), + attr("node_flag_propagate", Package, FlagType). % Ensure propagation :- node_flag_inherited(Package, FlagType, Flag), can_inherit_flags(Package, Dependency, FlagType), - node_flag_propagate(Package, FlagType). + attr("node_flag_propagate", Package, FlagType). error(2, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Source1, Source2, Package, FlagType) :- depends_on(Source1, Package), depends_on(Source2, Package), - node_flag_propagate(Source1, FlagType), - node_flag_propagate(Source2, FlagType), + attr("node_flag_propagate", Source1, FlagType), + attr("node_flag_propagate", Source2, FlagType), can_inherit_flags(Source1, Package, FlagType), can_inherit_flags(Source2, Package, FlagType), Source1 != Source2. % remember where flags came from -node_flag_source(Package, FlagType, Package) :- node_flag_set(Package, FlagType, _). -node_flag_source(Dependency, FlagType, Q) - :- node_flag_source(Package, FlagType, Q), node_flag_inherited(Dependency, FlagType, _), - node_flag_propagate(Package, FlagType). +attr("node_flag_source", Package, FlagType, Package) :- attr("node_flag_set", Package, FlagType, _). +attr("node_flag_source", Dependency, FlagType, Q) + :- attr("node_flag_source", Package, FlagType, Q), node_flag_inherited(Dependency, FlagType, _), + attr("node_flag_propagate", Package, FlagType). % compiler flags from compilers.yaml are put on nodes if compiler matches -node_flag(Package, FlagType, Flag) +attr("node_flag", Package, FlagType, Flag) :- compiler_version_flag(Compiler, Version, FlagType, Flag), - node_compiler_version(Package, Compiler, Version), + attr("node_compiler_version", Package, Compiler, Version), flag_type(FlagType), compiler(Compiler), compiler_version(Compiler, Version). -node_flag_compiler_default(Package) - :- not node_flag_set(Package, FlagType, _), +attr("node_flag_compiler_default", Package) + :- not attr("node_flag_set", Package, FlagType, _), compiler_version_flag(Compiler, Version, FlagType, Flag), - node_compiler_version(Package, Compiler, Version), + attr("node_compiler_version", Package, Compiler, Version), flag_type(FlagType), compiler(Compiler), compiler_version(Compiler, Version). % if a flag is set to something or inherited, it's included -node_flag(Package, FlagType, Flag) :- node_flag_set(Package, FlagType, Flag). -node_flag(Package, FlagType, Flag) +attr("node_flag", Package, FlagType, Flag) :- attr("node_flag_set", Package, FlagType, Flag). +attr("node_flag", Package, FlagType, Flag) :- node_flag_inherited(Package, FlagType, Flag). % if no node flags are set for a type, there are no flags. -no_flags(Package, FlagType) - :- not node_flag(Package, FlagType, _), node(Package), flag_type(FlagType). +attr("no_flags", Package, FlagType) + :- not attr("node_flag", Package, FlagType, _), attr("node", Package), flag_type(FlagType). #defined compiler_version_flag/4. -#defined node_flag/3. -#defined node_flag_set/3. -#defined node_flag_propagate/2. %----------------------------------------------------------------------------- % Installed packages %----------------------------------------------------------------------------- % the solver is free to choose at most one installed hash for each package -{ hash(Package, Hash) : installed_hash(Package, Hash) } 1 - :- node(Package), internal_error("Package must resolve to at most one hash"). +{ attr("hash", Package, Hash) : installed_hash(Package, Hash) } 1 + :- attr("node", Package), internal_error("Package must resolve to at most one hash"). % you can't choose an installed hash for a dev spec -:- hash(Package, Hash), variant_value(Package, "dev_path", _). +:- attr("hash", Package, Hash), attr("variant_value", Package, "dev_path", _). % You can't install a hash, if it is not installed -:- hash(Package, Hash), not installed_hash(Package, Hash). +:- attr("hash", Package, Hash), not installed_hash(Package, Hash). % This should be redundant given the constraint above -:- hash(Package, Hash1), hash(Package, Hash2), Hash1 != Hash2. +:- attr("hash", Package, Hash1), attr("hash", Package, Hash2), Hash1 != Hash2. % if a hash is selected, we impose all the constraints that implies -impose(Hash) :- hash(Package, Hash). +impose(Hash) :- attr("hash", Package, Hash). % if we haven't selected a hash for a package, we'll be building it -build(Package) :- not hash(Package, _), node(Package). +build(Package) :- not attr("hash", Package, _), attr("node", Package). % Minimizing builds is tricky. We want a minimizing criterion @@ -1160,11 +1077,11 @@ build(Package) :- not hash(Package, _), node(Package). % 200+ Shifted priorities for build nodes; correspond to priorities 0 - 99. % 100 - 199 Unshifted priorities. Currently only includes minimizing #builds. % 0 - 99 Priorities for non-built nodes. -build_priority(Package, 200) :- build(Package), node(Package), optimize_for_reuse(). -build_priority(Package, 0) :- not build(Package), node(Package), optimize_for_reuse(). +build_priority(Package, 200) :- build(Package), attr("node", Package), optimize_for_reuse(). +build_priority(Package, 0) :- not build(Package), attr("node", Package), optimize_for_reuse(). % don't adjust build priorities if reuse is not enabled -build_priority(Package, 0) :- node(Package), not optimize_for_reuse(). +build_priority(Package, 0) :- attr("node", Package), not optimize_for_reuse(). % don't assign versions from installed packages unless reuse is enabled % NOTE: that "installed" means the declared version was only included because @@ -1177,7 +1094,7 @@ build_priority(Package, 0) :- node(Package), not optimize_for_reuse(). % currently *won't* force versions for `bar`'s build dependencies -- `--fresh` % will instead build the latest bar. When we actually include transitive % build deps in the solve, consider using them as a preference to resolve this. -:- version(Package, Version), +:- attr("version", Package, Version), version_weight(Package, Weight), version_declared(Package, Version, Weight, "installed"), not optimize_for_reuse(). @@ -1233,20 +1150,20 @@ opt_criterion(73, "deprecated versions used"). #minimize{ 0@73: #true }. #minimize{ 1@73+Priority,Package - : deprecated(Package, _), + : attr("deprecated", Package, _), build_priority(Package, Priority) }. % Minimize the: % 1. Version weight % 2. Number of variants with a non default value, if not set -% for the root(Package) +% for the root package. opt_criterion(70, "version weight"). #minimize{ 0@270: #true }. #minimize{ 0@70: #true }. #minimize { Weight@70+Priority - : root(Package),version_weight(Package, Weight), + : attr("root", Package), version_weight(Package, Weight), build_priority(Package, Priority) }. @@ -1256,7 +1173,7 @@ opt_criterion(65, "number of non-default variants (roots)"). #minimize { 1@65+Priority,Package,Variant,Value : variant_not_default(Package, Variant, Value), - root(Package), + attr("root", Package), build_priority(Package, Priority) }. @@ -1266,7 +1183,7 @@ opt_criterion(60, "preferred providers for roots"). #minimize{ Weight@60+Priority,Provider,Virtual : provider_weight(Provider, Virtual, Weight), - root(Provider), + attr("root", Provider), build_priority(Provider, Priority) }. @@ -1276,7 +1193,7 @@ opt_criterion(55, "default values of variants not being used (roots)"). #minimize{ 1@55+Priority,Package,Variant,Value : variant_default_not_used(Package, Variant, Value), - root(Package), + attr("root", Package), build_priority(Package, Priority) }. @@ -1287,7 +1204,7 @@ opt_criterion(50, "number of non-default variants (non-roots)"). #minimize { 1@50+Priority,Package,Variant,Value : variant_not_default(Package, Variant, Value), - not root(Package), + not attr("root", Package), build_priority(Package, Priority) }. @@ -1298,7 +1215,7 @@ opt_criterion(45, "preferred providers (non-roots)"). #minimize{ 0@45: #true }. #minimize{ Weight@45+Priority,Provider,Virtual - : provider_weight(Provider, Virtual, Weight), not root(Provider), + : provider_weight(Provider, Virtual, Weight), not attr("root", Provider), build_priority(Provider, Priority) }. @@ -1357,7 +1274,7 @@ opt_criterion(20, "default values of variants not being used (non-roots)"). #minimize{ 1@20+Priority,Package,Variant,Value : variant_default_not_used(Package, Variant, Value), - not root(Package), + not attr("root", Package), build_priority(Package, Priority) }. @@ -1394,14 +1311,14 @@ opt_criterion(5, "non-preferred targets"). %----------------- % Domain heuristic %----------------- -#heuristic version(Package, Version) : version_declared(Package, Version, 0), node(Package). [10, true] -#heuristic version_weight(Package, 0) : version_declared(Package, Version, 0), node(Package). [10, true] -#heuristic node_target(Package, Target) : package_target_weight(Target, Package, 0), node(Package). [10, true] -#heuristic node_target_weight(Package, 0) : node(Package). [10, true] -#heuristic variant_value(Package, Variant, Value) : variant_default_value(Package, Variant, Value), node(Package). [10, true] -#heuristic provider(Package, Virtual) : possible_provider_weight(Package, Virtual, 0, _), virtual_node(Virtual). [10, true] -#heuristic node(Package) : possible_provider_weight(Package, Virtual, 0, _), virtual_node(Virtual). [10, true] -#heuristic node_os(Package, OS) : buildable_os(OS). [10, true] +#heuristic attr("version", Package, Version) : version_declared(Package, Version, 0), attr("node", Package). [10, true] +#heuristic version_weight(Package, 0) : version_declared(Package, Version, 0), attr("node", Package). [10, true] +#heuristic attr("node_target", Package, Target) : package_target_weight(Target, Package, 0), attr("node", Package). [10, true] +#heuristic node_target_weight(Package, 0) : attr("node", Package). [10, true] +#heuristic attr("variant_value", Package, Variant, Value) : variant_default_value(Package, Variant, Value), attr("node", Package). [10, true] +#heuristic provider(Package, Virtual) : possible_provider_weight(Package, Virtual, 0, _), attr("virtual_node", Virtual). [10, true] +#heuristic attr("node", Package) : possible_provider_weight(Package, Virtual, 0, _), attr("virtual_node", Virtual). [10, true] +#heuristic attr("node_os", Package, OS) : buildable_os(OS). [10, true] %----------- % Notes diff --git a/lib/spack/spack/solver/display.lp b/lib/spack/spack/solver/display.lp index 4ba8e9e2cb..320f063e31 100644 --- a/lib/spack/spack/solver/display.lp +++ b/lib/spack/spack/solver/display.lp @@ -9,33 +9,14 @@ % This section determines what parts of the model are printed at the end %============================================================================== -% Spec-related functions. -% Used to build the result of the solve. -#show node/1. -#show hash/2. -#show depends_on/3. -#show version/2. -#show variant_value/3. -#show node_platform/2. -#show node_os/2. -#show node_target/2. -#show node_compiler/2. -#show node_compiler_version/3. -#show node_flag/3. -#show node_flag_compiler_default/1. -#show node_flag_source/3. -#show no_flags/2. -#show external_spec_selected/2. -#show version_equivalent/3. - -#show build/1. +% Spec attributes +#show attr/2. +#show attr/3. +#show attr/4. % names of optimization criteria #show opt_criterion/2. -% deprecated packages -#show deprecated/2. - % error types #show error/2. #show error/3.