1396 lines
58 KiB
Prolog
1396 lines
58 KiB
Prolog
% Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
|
|
% Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
%
|
|
% SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
|
|
%=============================================================================
|
|
% This logic program implements Spack's concretizer
|
|
%=============================================================================
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Map literal input specs to facts that drive the solve
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% Give clingo the choice to solve an input spec or not
|
|
{ attr("literal_solved", ID) } :- literal(ID).
|
|
literal_not_solved(ID) :- not attr("literal_solved", ID), literal(ID).
|
|
|
|
% If concretize_everything() is a fact, then we cannot have unsolved specs
|
|
:- literal_not_solved(ID), concretize_everything.
|
|
|
|
% Make a problem with "zero literals solved" unsat. This is to trigger
|
|
% looking for solutions to the ASP problem with "errors", which results
|
|
% in better reporting for users. See #30669 for details.
|
|
1 { attr("literal_solved", ID) : literal(ID) }.
|
|
|
|
opt_criterion(300, "number of input specs not concretized").
|
|
#minimize{ 0@300: #true }.
|
|
#minimize { 1@300,ID : literal_not_solved(ID) }.
|
|
|
|
#defined concretize_everything/0.
|
|
#defined literal/1.
|
|
|
|
% Attributes for node packages which must have a single value
|
|
attr_single_value("version").
|
|
attr_single_value("node_platform").
|
|
attr_single_value("node_os").
|
|
attr_single_value("node_target").
|
|
|
|
% Error when no attribute is selected
|
|
error(100, no_value_error, Attribute, Package)
|
|
:- attr("node", Package),
|
|
attr_single_value(Attribute),
|
|
not attr(Attribute, Package, _).
|
|
|
|
% Error when multiple attr need to be selected
|
|
error(100, multiple_values_error, Attribute, Package)
|
|
:- attr("node", Package),
|
|
attr_single_value(Attribute),
|
|
2 { attr(Attribute, Package, Version) }.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Version semantics
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% Versions are declared with a weight and an origin, which indicates where the
|
|
% version was declared (e.g. "package_py" or "external").
|
|
version_declared(Package, Version, Weight) :- version_declared(Package, Version, Weight, _).
|
|
|
|
% 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,
|
|
internal_error("Two versions with identical weights").
|
|
|
|
% We cannot use a version declared for an installed package if we end up building it
|
|
:- version_declared(Package, Version, Weight, "installed"),
|
|
attr("version", Package, Version),
|
|
version_weight(Package, Weight),
|
|
not attr("hash", Package, _),
|
|
internal_error("Reuse version weight used for built package").
|
|
|
|
% versions are declared w/priority -- declared with priority implies declared
|
|
version_declared(Package, Version) :- version_declared(Package, Version, _).
|
|
|
|
% a spec with a git hash version is equivalent to one with the same matched version
|
|
version_satisfies(Package, Constraint, HashVersion) :- version_satisfies(Package, Constraint, EquivalentVersion),
|
|
version_equivalent(Package, HashVersion, EquivalentVersion).
|
|
#defined version_equivalent/3.
|
|
|
|
% If something is a package, it has only one version and that must be a
|
|
% declared version.
|
|
% We allow clingo to choose any version(s), and infer an error if there
|
|
% 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
|
|
{ attr("version", Package, Version) : version_declared(Package, Version) }
|
|
:- attr("node", Package).
|
|
|
|
% error we expect users to see
|
|
error(0, "Version '{0}' of {1} does not satisfy '@{2}'", Version, Package, Constraint, startcauses, VersionCause, ConstraintCause)
|
|
:- attr("node", Package),
|
|
attr("version", Package, Version),
|
|
imposed_constraint(VersionCause, "node_version_satisfies", Package, Version),
|
|
condition_holds(VersionCause),
|
|
attr("node_version_satisfies", Package, Constraint),
|
|
imposed_constraint(ConstraintCause, "node_version_satisfies", Package, Constraint),
|
|
condition_holds(ConstraintCause),
|
|
not version_satisfies(Package, Constraint, Version).
|
|
|
|
% Error to ensure structure of the program is not violated
|
|
error(2, "No version from '{0}' satisfies '@{1}' and '@{2}'", Package, Version1, Version2)
|
|
:- attr("node", Package),
|
|
attr("version", Package, Version1),
|
|
attr("version", Package, Version2),
|
|
Version1 < Version2. % see[1]
|
|
|
|
error(2, "No versions available for package '{0}'", Package)
|
|
:- attr("node", Package), not attr("version", Package, _).
|
|
|
|
% A virtual package may or may not have a version, but never has more than one
|
|
% Error to catch how it happens
|
|
error(0, "Version '{0}' of {1} does not satisfy '@{2}'", Version, Virtual, Constraint, startcauses, VersionCause, ConstraintCause)
|
|
:- attr("virtual_node", Virtual),
|
|
attr("version", Virtual, Version),
|
|
imposed_constraint(VersionCause, "node_version_satisfies", Virtual, Version),
|
|
condition_holds(VersionCause),
|
|
attr("node_version_satisfies", Virtual, Constraint),
|
|
imposed_constraint(ConstraintCause, "node_version_satisfies", Virtual, Constraint),
|
|
condition_holds(ConstraintCause),
|
|
not version_satisfies(Virtual, Constraint, Version).
|
|
|
|
% fallback error for structure in case there's another way for it to happen
|
|
error(100, "Cannot select a single version for virtual '{0}'", Virtual)
|
|
:- attr("virtual_node", Virtual),
|
|
2 { attr("version", Virtual, Version) }.
|
|
|
|
% If we select a deprecated version, mark the package as deprecated
|
|
attr("deprecated", Package, Version) :-
|
|
attr("version", Package, Version),
|
|
deprecated_version(Package, Version).
|
|
|
|
possible_version_weight(Package, Weight)
|
|
:- 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.
|
|
:- attr("version", Package, Version),
|
|
version_weight(Package, Weight),
|
|
version_declared(Package, Version, Weight, "external"),
|
|
not external(Package),
|
|
internal_error("External weight used for built package").
|
|
|
|
% we can't use a weight from an installed spec if we are building it
|
|
% and vice-versa
|
|
:- 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").
|
|
|
|
:- 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
|
|
:- 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
|
|
{ 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 { 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(0, "No valid version for '{0}' satisfies '@{1}'", Package, Constraint, startcauses, ConstraintCause)
|
|
:- attr("node_version_satisfies", Package, Constraint),
|
|
imposed_constraint(ConstraintCause, "node_version_satisfies", Package, Constraint),
|
|
condition_holds(ConstraintCause),
|
|
C = #count{ Version : attr("version", Package, Version), version_satisfies(Package, Constraint, Version)},
|
|
C < 1.
|
|
|
|
% Error for structure of program
|
|
error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint)
|
|
:- attr("node_version_satisfies", Package, Constraint),
|
|
attr("version", Package, Version),
|
|
not 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.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Spec conditions and imposed constraints
|
|
%
|
|
% Given Spack directives like these:
|
|
% depends_on("foo@1.0+bar", when="@2.0+variant")
|
|
% provides("mpi@2:", when="@1.9:")
|
|
%
|
|
% The conditions are `@2.0+variant` and `@1.9:`, and the imposed constraints
|
|
% are `@1.0+bar` on `foo` and `@2:` on `mpi`.
|
|
%-----------------------------------------------------------------------------
|
|
% conditions are specified with `condition_requirement` and hold when
|
|
% corresponding spec attributes hold.
|
|
condition_holds(ID) :-
|
|
condition(ID, _);
|
|
attr(Name, A1) : condition_requirement(ID, Name, A1);
|
|
attr(Name, A1, A2) : condition_requirement(ID, Name, A1, A2);
|
|
attr(Name, A1, A2, A3) : condition_requirement(ID, Name, A1, A2, A3);
|
|
attr(Name, A1, A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4).
|
|
|
|
% condition_holds(ID) implies all imposed_constraints.
|
|
impose(ID) :- condition_holds(ID).
|
|
|
|
% conditions that hold impose constraints on other specs
|
|
attr(Name, A1) :- impose(ID), imposed_constraint(ID, Name, A1).
|
|
attr(Name, A1, A2) :- impose(ID), imposed_constraint(ID, Name, A1, A2).
|
|
attr(Name, A1, A2, A3) :- impose(ID), imposed_constraint(ID, Name, A1, A2, A3).
|
|
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
|
|
:- 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
|
|
:- 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").
|
|
|
|
% associated conditions by cause -> effect
|
|
condition_cause(Effect, Cause) :-
|
|
condition_holds(Effect), condition_holds(Cause),
|
|
attr(Name, A1),
|
|
condition_requirement(Effect, Name, A1),
|
|
imposed_constraint(Cause, Name, A1).
|
|
condition_cause(Effect, Cause) :-
|
|
condition_holds(Effect), condition_holds(Cause),
|
|
attr(Name, A1, A2),
|
|
condition_requirement(Effect, Name, A1, A2),
|
|
imposed_constraint(Cause, Name, A1, A2).
|
|
condition_cause(Effect, Cause) :-
|
|
condition_holds(Effect), condition_holds(Cause),
|
|
attr(Name, A1, A2, A3),
|
|
condition_requirement(Effect, Name, A1, A2, A3),
|
|
imposed_constraint(Cause, Name, A1, A2, A3).
|
|
condition_cause(Effect, Cause) :-
|
|
condition_holds(Effect), condition_holds(Cause),
|
|
attr(Name, A1, A2, A3, A4),
|
|
condition_requirement(Effect, Name, A1, A2, A3, A4),
|
|
imposed_constraint(Cause, Name, A1, A2, A3, A4).
|
|
|
|
#defined condition/2.
|
|
#defined condition_requirement/3.
|
|
#defined condition_requirement/4.
|
|
#defined condition_requirement/5.
|
|
#defined condition_requirement/6.
|
|
#defined imposed_constraint/3.
|
|
#defined imposed_constraint/4.
|
|
#defined imposed_constraint/5.
|
|
#defined imposed_constraint/6.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Concrete specs
|
|
%-----------------------------------------------------------------------------
|
|
% if a package is assigned a hash, it's concrete.
|
|
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) :- 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
|
|
% concrete specs don't need to be resolved -- they arise from the concrete
|
|
% specs themselves. This attr is used in constraints from dependency conditions
|
|
attr("spack_installed", Package) :- build(Package), not external(Package).
|
|
|
|
% 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.
|
|
attr("depends_on", Package, Dependency, Type)
|
|
:- attr("dependency_holds", Package, Dependency, Type),
|
|
not virtual(Dependency).
|
|
|
|
% every root must be a node
|
|
attr("node", Package) :- attr("root", Package).
|
|
|
|
% dependencies imply new nodes
|
|
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) :- attr("root", Package).
|
|
needed(Dependency) :- needed(Package), depends_on(Package, Dependency).
|
|
error(10, "'{0}' is not a valid dependency for any package in the DAG", Package)
|
|
:- attr("node", Package),
|
|
not needed(Package).
|
|
|
|
% 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).
|
|
error(100, "Cyclic dependency detected between '{0}' and '{1}' (consider changing variants to avoid the cycle)", A, B)
|
|
:- path(A, B),
|
|
path(B, A).
|
|
|
|
#defined dependency_type/2.
|
|
#defined dependency_condition/3.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Conflicts
|
|
%-----------------------------------------------------------------------------
|
|
error(1, Msg) :- attr("node", Package),
|
|
conflict(Package, TriggerID, ConstraintID, Msg),
|
|
condition_holds(TriggerID),
|
|
condition_holds(ConstraintID),
|
|
not external(Package), % ignore conflicts for externals
|
|
not attr("hash", Package, _). % ignore conflicts for installed packages
|
|
|
|
#defined conflict/4.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Virtual dependencies
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% 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
|
|
attr("depends_on", Package, Provider, Type)
|
|
:- attr("dependency_holds", Package, Virtual, Type),
|
|
provider(Provider, Virtual),
|
|
not external(Package).
|
|
|
|
% dependencies on virtuals also imply that the virtual is a virtual node
|
|
attr("virtual_node", Virtual)
|
|
:- attr("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) }
|
|
:- attr("virtual_node", Virtual).
|
|
|
|
error(100, "Cannot find valid provider for virtual {0}", Virtual)
|
|
:- attr("virtual_node", Virtual),
|
|
not provider(_, Virtual).
|
|
|
|
error(100, "Cannot select a single provider for virtual '{0}'", Virtual)
|
|
:- attr("virtual_node", Virtual),
|
|
2 { provider(P, Virtual) }.
|
|
|
|
% virtual roots imply virtual nodes, and that one provider is a root
|
|
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.
|
|
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) :- attr("node", Package), attr("virtual_condition_holds", Package, 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 attr("virtual_condition_holds", Package, Virtual),
|
|
internal_error("Virtual when provides not respected").
|
|
|
|
#defined possible_provider/2.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Virtual dependency weights
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% A provider may have different possible weights depending on whether it's an external
|
|
% or not, or on preferences expressed in packages.yaml etc. This rule ensures that
|
|
% 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), 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, _).
|
|
|
|
% A provider that is an external can use a weight of 0
|
|
possible_provider_weight(Dependency, Virtual, 0, "external")
|
|
:- provider(Dependency, Virtual),
|
|
external(Dependency).
|
|
|
|
% A provider mentioned in packages.yaml can use a weight
|
|
% according to its priority in the list of providers
|
|
possible_provider_weight(Dependency, Virtual, Weight, "packages_yaml")
|
|
:- provider(Dependency, Virtual),
|
|
depends_on(Package, Dependency),
|
|
pkg_provider_preference(Package, Virtual, Dependency, Weight).
|
|
|
|
% A provider mentioned in the default configuration can use a weight
|
|
% according to its priority in the list of providers
|
|
possible_provider_weight(Dependency, Virtual, Weight, "default")
|
|
:- provider(Dependency, Virtual),
|
|
default_provider_preference(Virtual, Dependency, Weight).
|
|
|
|
% 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 declared_dependency/3.
|
|
#defined virtual/1.
|
|
#defined external/1.
|
|
#defined external_spec/2.
|
|
#defined external_version_declared/4.
|
|
#defined buildable_false/1.
|
|
#defined pkg_provider_preference/4.
|
|
#defined default_provider_preference/3.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% External semantics
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% if a package is external its version must be one of the external versions
|
|
{ external_version(Package, Version, Weight):
|
|
version_declared(Package, Version, Weight, "external") }
|
|
:- external(Package).
|
|
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
|
|
:- external(Package),
|
|
not external_version(Package, _, _).
|
|
|
|
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
|
|
:- external(Package),
|
|
2 { external_version(Package, Version, Weight) }.
|
|
|
|
version_weight(Package, Weight) :- 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),
|
|
attr("node", Package),
|
|
not attr("hash", Package, _).
|
|
|
|
% a package is a real_node if it is not external
|
|
real_node(Package) :- attr("node", Package), not external(Package).
|
|
|
|
% a package is external if we are using an external spec for it
|
|
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.
|
|
:- 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
|
|
attr("external_spec_selected", Package, LocalIndex) :-
|
|
attr("external_conditions_hold", Package, LocalIndex),
|
|
attr("node", Package),
|
|
not attr("hash", Package, _).
|
|
|
|
% it cannot happen that a spec is external, but none of the external specs
|
|
% conditions hold.
|
|
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
|
|
:- external(Package),
|
|
not attr("external_conditions_hold", Package, _).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Config required semantics
|
|
%-----------------------------------------------------------------------------
|
|
|
|
package_in_dag(Package) :- attr("node", Package).
|
|
package_in_dag(Package) :- attr("virtual_node", Package).
|
|
|
|
activate_requirement(Package, X) :-
|
|
package_in_dag(Package),
|
|
requirement_group(Package, X),
|
|
not requirement_conditional(Package, X, _).
|
|
|
|
activate_requirement(Package, X) :-
|
|
package_in_dag(Package),
|
|
requirement_group(Package, X),
|
|
condition_holds(Y),
|
|
requirement_conditional(Package, X, Y).
|
|
|
|
requirement_group_satisfied(Package, X) :-
|
|
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } 1,
|
|
requirement_policy(Package, X, "one_of"),
|
|
activate_requirement(Package, X),
|
|
requirement_group(Package, X).
|
|
|
|
requirement_weight(Package, Group, W) :-
|
|
condition_holds(Y),
|
|
requirement_has_weight(Y, W),
|
|
requirement_group_member(Y, Package, Group),
|
|
requirement_policy(Package, Group, "one_of"),
|
|
requirement_group_satisfied(Package, Group).
|
|
|
|
requirement_group_satisfied(Package, X) :-
|
|
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } ,
|
|
requirement_policy(Package, X, "any_of"),
|
|
activate_requirement(Package, X),
|
|
requirement_group(Package, X).
|
|
|
|
% TODO: the following two choice rules allow the solver to add compiler
|
|
% flags if their only source is from a requirement. This is overly-specific
|
|
% and should use a more-generic approach like in https://github.com/spack/spack/pull/37180
|
|
{ attr("node_flag", A1, A2, A3) } :-
|
|
requirement_group_member(Y, Package, X),
|
|
activate_requirement(Package, X),
|
|
imposed_constraint(Y,"node_flag_set", A1, A2, A3).
|
|
|
|
{ attr("node_flag_source", A1, A2, A3) } :-
|
|
requirement_group_member(Y, Package, X),
|
|
activate_requirement(Package, X),
|
|
imposed_constraint(Y,"node_flag_source", A1, A2, A3).
|
|
|
|
requirement_weight(Package, Group, W) :-
|
|
W = #min {
|
|
Z : requirement_has_weight(Y, Z), condition_holds(Y), requirement_group_member(Y, Package, Group);
|
|
% We need this to avoid an annoying warning during the solve
|
|
% concretize.lp:1151:5-11: info: tuple ignored:
|
|
% #sup@73
|
|
10000
|
|
},
|
|
requirement_policy(Package, Group, "any_of"),
|
|
requirement_group_satisfied(Package, Group).
|
|
|
|
error(100, "cannot satisfy a requirement for package '{0}'.", Package) :-
|
|
activate_requirement(Package, X),
|
|
requirement_group(Package, X),
|
|
not requirement_message(Package, X, _),
|
|
not requirement_group_satisfied(Package, X).
|
|
|
|
|
|
error(10, Message) :-
|
|
activate_requirement(Package, X),
|
|
requirement_group(Package, X),
|
|
requirement_message(Package, X, Message),
|
|
not requirement_group_satisfied(Package, X).
|
|
|
|
|
|
#defined requirement_group/2.
|
|
#defined requirement_conditional/3.
|
|
#defined requirement_message/3.
|
|
#defined requirement_group_member/3.
|
|
#defined requirement_has_weight/2.
|
|
#defined requirement_policy/3.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Variant semantics
|
|
%-----------------------------------------------------------------------------
|
|
% a variant is a variant of a package if it is a variant under some condition
|
|
% and that condition holds
|
|
variant(Package, Variant) :- variant_condition(ID, Package, Variant),
|
|
condition_holds(ID).
|
|
|
|
attr("variant_propagate", Package, Variant, Value, Source) :-
|
|
attr("node", Package),
|
|
depends_on(Parent, Package),
|
|
attr("variant_propagate", Parent, Variant, Value, Source),
|
|
not attr("variant_set", Package, Variant).
|
|
|
|
attr("variant_value", Package, Variant, Value) :-
|
|
attr("node", Package),
|
|
variant(Package, Variant),
|
|
attr("variant_propagate", Package, Variant, Value, _),
|
|
variant_possible_value(Package, Variant, Value).
|
|
|
|
error(100, "{0} and {1} cannot both propagate variant '{2}' to package {3} with values '{4}' and '{5}'", Source1, Source2, Variant, Package, Value1, Value2) :-
|
|
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(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package)
|
|
:- 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(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package)
|
|
:- 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
|
|
attr("variant_value", Package, Variant, Value) :-
|
|
variant(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.
|
|
{
|
|
attr("variant_value", Package, Variant, Value)
|
|
: variant_possible_value(Package, Variant, Value)
|
|
}
|
|
:- attr("node", Package),
|
|
variant(Package, Variant),
|
|
build(Package).
|
|
|
|
error(0, "'{0}' required multiple values for single-valued variant '{1}'\n Requested 'Spec({1}={2})' and 'Spec({1}={3})'", Package, Variant, Value1, Value2, startcauses, Cause1, Cause2)
|
|
:- attr("node", Package),
|
|
variant(Package, Variant),
|
|
variant_single_value(Package, Variant),
|
|
build(Package),
|
|
attr("variant_value", Package, Variant, Value1),
|
|
imposed_constraint(Cause1, "variant_set", Package, Variant, Value1),
|
|
condition_holds(Cause1),
|
|
attr("variant_value", Package, Variant, Value2),
|
|
imposed_constraint(Cause2, "variant_set", Package, Variant, Value2),
|
|
condition_holds(Cause2),
|
|
Value1 < Value2. % see[1]
|
|
|
|
error(100, "'{0}' required multiple values for single-valued variant '{1}'", Package, Variant)
|
|
:- attr("node", Package),
|
|
variant(Package, Variant),
|
|
variant_single_value(Package, Variant),
|
|
build(Package),
|
|
2 { attr("variant_value", Package, Variant, Value) }.
|
|
|
|
error(100, "No valid value for variant '{1}' of package '{0}'", Package, Variant)
|
|
:- attr("node", Package),
|
|
variant(Package, Variant),
|
|
build(Package),
|
|
not attr("variant_value", Package, Variant, _).
|
|
|
|
% if a variant is set to anything, it is considered 'set'.
|
|
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(10, "'Spec({1}={2})' is not a valid value for '{0}' variant '{1}'", Package, Variant, Value)
|
|
:- attr("variant_value", Package, Variant, Value),
|
|
not variant_possible_value(Package, Variant, Value),
|
|
build(Package).
|
|
|
|
% Some multi valued variants accept multiple values from disjoint sets.
|
|
% Ensure that we respect that constraint and we don't pick values from more
|
|
% than one set at once
|
|
error(100, "{0} variant '{1}' cannot have values '{2}' and '{3}' as they come from disjoint value sets", Package, Variant, Value1, 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]
|
|
build(Package).
|
|
|
|
% 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
|
|
attr("variant_value", Package, Variant, Value)
|
|
:- attr("node", Package),
|
|
variant(Package, Variant),
|
|
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)
|
|
:- 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 attr("variant_set", Package, Variant, Value),
|
|
% variant values forced by propagation don't count as non-default
|
|
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),
|
|
attr("node", Package).
|
|
|
|
|
|
% A default variant value that is not used
|
|
variant_default_not_used(Package, Variant, Value)
|
|
:- variant_default_value(Package, Variant, Value),
|
|
variant(Package, Variant),
|
|
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)
|
|
:- attr("variant_value", Package, Variant, Value),
|
|
condition_requirement(ID, "variant_value", Package, Variant, Value),
|
|
imposed_constraint(ID, "external_conditions_hold", Package, _),
|
|
external(Package),
|
|
attr("node", Package).
|
|
|
|
% The default value for a variant in a package is what is prescribed:
|
|
%
|
|
% 1. On the command line
|
|
% 2. In packages.yaml (if there's no command line settings)
|
|
% 3. In the package.py file (if there are no settings in
|
|
% packages.yaml and the command line)
|
|
%
|
|
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 attr("variant_default_value_from_cli", Package, Variant, _).
|
|
|
|
variant_default_value(Package, Variant, Value)
|
|
:- variant_default_value_from_packages_yaml(Package, Variant, Value),
|
|
not attr("variant_default_value_from_cli", Package, Variant, _).
|
|
|
|
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(100, "{0} variant '{1}' cannot have values '{2}' and 'none'", Package, Variant, Value)
|
|
:- attr("variant_value", Package, Variant, Value),
|
|
attr("variant_value", Package, Variant, "none"),
|
|
Value != "none",
|
|
build(Package).
|
|
|
|
% 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
|
|
% when assigned a value.
|
|
auto_variant("dev_path").
|
|
auto_variant("patches").
|
|
variant(Package, Variant)
|
|
:- attr("variant_set", Package, Variant, _), auto_variant(Variant).
|
|
variant_single_value(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_set/3.
|
|
#defined variant_condition/3.
|
|
#defined variant_single_value/2.
|
|
#defined variant_default_value/3.
|
|
#defined variant_possible_value/3.
|
|
#defined variant_default_value_from_cli/3.
|
|
#defined variant_default_value_from_packages_yaml/3.
|
|
#defined variant_default_value_from_package_py/3.
|
|
#defined variant_value_from_disjoint_sets/4.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Platform semantics
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% if no platform is set, fall back to the default
|
|
:- attr("node_platform", _, Platform), not allowed_platform(Platform).
|
|
|
|
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
|
|
attr("node_platform", Package, Platform)
|
|
:- attr("node", Package), attr("node_platform_set", Package, Platform).
|
|
|
|
% platform is set if set to anything
|
|
attr("node_platform_set", Package) :- attr("node_platform_set", Package, _).
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% OS semantics
|
|
%-----------------------------------------------------------------------------
|
|
% convert weighted OS declarations to simple one
|
|
os(OS) :- os(OS, _).
|
|
|
|
% one os per node
|
|
{ attr("node_os", Package, OS) : os(OS) } :- attr("node", Package).
|
|
|
|
% can't have a non-buildable OS on a node we need to build
|
|
error(100, "Cannot select '{0} os={1}' (operating system '{1}' is not buildable)", Package, OS)
|
|
:- build(Package),
|
|
attr("node_os", Package, OS),
|
|
not buildable_os(OS).
|
|
|
|
% can't have dependencies on incompatible OS's
|
|
error(100, "{0} and dependency {1} have incompatible operating systems 'os={2}' and 'os={3}'", Package, Dependency, PackageOS, DependencyOS)
|
|
:- depends_on(Package, Dependency),
|
|
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)
|
|
:- 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), 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).
|
|
|
|
% every OS is compatible with itself. We can use `os_compatible` to declare
|
|
os_compatible(OS, OS) :- os(OS).
|
|
|
|
% Transitive compatibility among operating systems
|
|
os_compatible(OS1, OS3) :- os_compatible(OS1, OS2), os_compatible(OS2, OS3).
|
|
|
|
% We can select only operating systems compatible with the ones
|
|
% 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) },
|
|
attr("node_os", Package, ReusedOS),
|
|
internal_error("Reused OS incompatible with build OS").
|
|
|
|
% If an OS is set explicitly respect the value
|
|
attr("node_os", Package, OS) :- attr("node_os_set", Package, OS), attr("node", Package).
|
|
|
|
#defined os_compatible/2.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Target semantics
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% Each node has only one target chosen among the known targets
|
|
{ attr("node_target", Package, Target) : target(Target) } :- attr("node", Package).
|
|
|
|
% If a node must satisfy a target constraint, enforce it
|
|
error(10, "'{0} target={1}' cannot satisfy constraint 'target={2}'", Package, Target, 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
|
|
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(100, "Cannot find compatible targets for {0} and {1}", Package, Dependency)
|
|
:- depends_on(Package, Dependency),
|
|
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)
|
|
:- attr("node_target", Package, MyTarget),
|
|
target_compatible(Target, MyTarget).
|
|
|
|
#defined target_satisfies/2.
|
|
|
|
% can't use targets on node if the compiler for the node doesn't support them
|
|
error(100, "{0} compiler '{2}@{3}' incompatible with 'target={1}'", Package, Target, Compiler, Version)
|
|
:- attr("node_target", Package, Target),
|
|
node_compiler(Package, CompilerID),
|
|
not compiler_supports_target(CompilerID, Target),
|
|
compiler_name(CompilerID, Compiler),
|
|
compiler_version(CompilerID, Version),
|
|
build(Package).
|
|
|
|
% if a target is set explicitly, respect it
|
|
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)
|
|
:- 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),
|
|
attr("node_target", Parent, Target),
|
|
attr("node_target", Dependency, Target).
|
|
|
|
node_target_mismatch(Parent, Dependency)
|
|
:- depends_on(Parent, Dependency),
|
|
not node_target_match(Parent, Dependency).
|
|
|
|
% disallow reusing concrete specs that don't have a compatible target
|
|
error(100, "'{0} target={1}' is not compatible with this machine", Package, Target)
|
|
:- attr("node", Package),
|
|
attr("node_target", Package, Target),
|
|
not target(Target).
|
|
|
|
#defined package_target_weight/3.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Compiler semantics
|
|
%-----------------------------------------------------------------------------
|
|
% There must be only one compiler set per built node.
|
|
{ node_compiler(Package, CompilerID) : compiler_id(CompilerID) } :-
|
|
attr("node", Package),
|
|
build(Package).
|
|
|
|
% Infer the compiler that matches a reused node
|
|
node_compiler(Package, CompilerID)
|
|
:- attr("node_compiler_version", Package, CompilerName, CompilerVersion),
|
|
attr("node", Package),
|
|
compiler_name(CompilerID, CompilerName),
|
|
compiler_version(CompilerID, CompilerVersion),
|
|
concrete(Package).
|
|
|
|
% Expand the internal attribute into "attr("node_compiler_version")
|
|
attr("node_compiler_version", Package, CompilerName, CompilerVersion)
|
|
:- node_compiler(Package, CompilerID),
|
|
compiler_name(CompilerID, CompilerName),
|
|
compiler_version(CompilerID, CompilerVersion),
|
|
build(Package).
|
|
|
|
attr("node_compiler", Package, CompilerName)
|
|
:- attr("node_compiler_version", Package, CompilerName, CompilerVersion).
|
|
|
|
error(100, "No valid compiler version found for '{0}'", Package)
|
|
:- attr("node", Package),
|
|
not node_compiler(Package, _).
|
|
|
|
% We can't have a compiler be enforced and select the version from another compiler
|
|
error(100, "Cannot select a single compiler for package {0}", Package)
|
|
:- attr("node", Package),
|
|
2 { attr("node_compiler_version", Package, C, V) }.
|
|
|
|
error(100, "Cannot concretize {0} with two compilers {1} and {2}@{3}", Package, Compiler1, 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(10, "No valid compiler for {0} satisfies '%{1}'", Package, Compiler)
|
|
:- attr("node", Package),
|
|
attr("node_compiler_version_satisfies", Package, Compiler, ":"),
|
|
not compiler_version_satisfies(Compiler, ":", _).
|
|
|
|
% If the compiler of a node must satisfy a constraint, then its version
|
|
% must be chosen among the ones that satisfy said constraint
|
|
error(100, "No valid version for '{0}' compiler '{1}' satisfies '@{2}'", Package, Compiler, Constraint)
|
|
:- attr("node", Package),
|
|
attr("node_compiler_version_satisfies", Package, Compiler, Constraint),
|
|
not compiler_version_satisfies(Compiler, Constraint, _).
|
|
|
|
error(100, "No valid version for '{0}' compiler '{1}' satisfies '@{2}'", Package, Compiler, Constraint)
|
|
:- attr("node", Package),
|
|
attr("node_compiler_version_satisfies", Package, Compiler, Constraint),
|
|
not compiler_version_satisfies(Compiler, Constraint, ID),
|
|
node_compiler(Package, ID).
|
|
|
|
% 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
|
|
attr("node_compiler_version_satisfies", Package, Compiler, Constraint)
|
|
:- node_compiler(Package, CompilerID),
|
|
compiler_name(CompilerID, Compiler),
|
|
compiler_version_satisfies(Compiler, Constraint, CompilerID).
|
|
|
|
#defined compiler_version_satisfies/3.
|
|
|
|
% If the compiler version was set from the command line,
|
|
% respect it verbatim
|
|
:- attr("node_compiler_version_set", Package, Compiler, Version),
|
|
not attr("node_compiler_version", Package, Compiler, Version).
|
|
|
|
:- attr("node_compiler_set", Package, Compiler),
|
|
not attr("node_compiler_version", Package, Compiler, _).
|
|
|
|
% 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(100, "{0} compiler '%{1}@{2}' incompatible with 'os={3}'", Package, Compiler, Version, OS)
|
|
:- attr("node_os", Package, OS),
|
|
node_compiler(Package, CompilerID),
|
|
compiler_name(CompilerID, Compiler),
|
|
compiler_version(CompilerID, Version),
|
|
not compiler_os(CompilerID, OS),
|
|
not allow_compiler(Compiler, Version),
|
|
build(Package).
|
|
|
|
% If a package and one of its dependencies don't have the
|
|
% same compiler there's a mismatch.
|
|
compiler_match(Package, Dependency)
|
|
:- depends_on(Package, Dependency),
|
|
node_compiler(Package, CompilerID),
|
|
node_compiler(Dependency, CompilerID).
|
|
|
|
compiler_mismatch(Package, Dependency)
|
|
:- depends_on(Package, Dependency),
|
|
not attr("node_compiler_set", Dependency, _),
|
|
not compiler_match(Package, Dependency).
|
|
|
|
compiler_mismatch_required(Package, Dependency)
|
|
:- depends_on(Package, Dependency),
|
|
attr("node_compiler_set", Dependency, _),
|
|
not compiler_match(Package, Dependency).
|
|
|
|
#defined compiler_os/3.
|
|
#defined allow_compiler/2.
|
|
|
|
% compilers weighted by preference according to packages.yaml
|
|
compiler_weight(Package, Weight)
|
|
:- node_compiler(Package, CompilerID),
|
|
compiler_name(CompilerID, Compiler),
|
|
compiler_version(CompilerID, V),
|
|
node_compiler_preference(Package, Compiler, V, Weight).
|
|
compiler_weight(Package, Weight)
|
|
:- node_compiler(Package, CompilerID),
|
|
compiler_name(CompilerID, Compiler),
|
|
compiler_version(CompilerID, V),
|
|
not node_compiler_preference(Package, Compiler, V, _),
|
|
default_compiler_preference(CompilerID, Weight).
|
|
compiler_weight(Package, 100)
|
|
:- node_compiler(Package, CompilerID),
|
|
compiler_name(CompilerID, Compiler),
|
|
compiler_version(CompilerID, V),
|
|
not node_compiler_preference(Package, Compiler, V, _),
|
|
not default_compiler_preference(CompilerID, _).
|
|
|
|
% For the time being, be strict and reuse only if the compiler match one we have on the system
|
|
error(100, "Compiler {1}@{2} requested for {0} cannot be found. Set install_missing_compilers:true if intended.", Package, Compiler, Version)
|
|
:- attr("node_compiler_version", Package, Compiler, Version),
|
|
not node_compiler(Package, _).
|
|
|
|
#defined node_compiler_preference/4.
|
|
#defined default_compiler_preference/3.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Compiler flags
|
|
%-----------------------------------------------------------------------------
|
|
|
|
% propagate flags when compiler match
|
|
can_inherit_flags(Package, Dependency, FlagType)
|
|
:- depends_on(Package, Dependency),
|
|
node_compiler(Package, CompilerID),
|
|
node_compiler(Dependency, CompilerID),
|
|
not attr("node_flag_set", Dependency, FlagType, _),
|
|
compiler_id(CompilerID),
|
|
flag_type(FlagType).
|
|
|
|
node_flag_inherited(Dependency, FlagType, Flag)
|
|
:- 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),
|
|
attr("node_flag_propagate", Package, FlagType).
|
|
|
|
error(100, "{0} and {1} cannot both propagate compiler flags '{2}' to {3}", Source1, Source2, Package, FlagType) :-
|
|
depends_on(Source1, Package),
|
|
depends_on(Source2, Package),
|
|
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
|
|
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
|
|
attr("node_flag", Package, FlagType, Flag)
|
|
:- compiler_flag(CompilerID, FlagType, Flag),
|
|
node_compiler(Package, CompilerID),
|
|
flag_type(FlagType),
|
|
compiler_id(CompilerID),
|
|
compiler_name(CompilerID, CompilerName),
|
|
compiler_version(CompilerID, Version).
|
|
|
|
attr("node_flag_compiler_default", Package)
|
|
:- not attr("node_flag_set", Package, FlagType, _),
|
|
compiler_flag(CompilerID, FlagType, Flag),
|
|
node_compiler(Package, CompilerID),
|
|
flag_type(FlagType),
|
|
compiler_id(CompilerID),
|
|
compiler_name(CompilerID, CompilerName),
|
|
compiler_version(CompilerID, Version).
|
|
|
|
% if a flag is set to something or inherited, it's included
|
|
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.
|
|
attr("no_flags", Package, FlagType)
|
|
:- not attr("node_flag", Package, FlagType, _), attr("node", Package), flag_type(FlagType).
|
|
|
|
#defined compiler_flag/3.
|
|
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% Installed packages
|
|
%-----------------------------------------------------------------------------
|
|
% the solver is free to choose at most one installed hash for each package
|
|
{ 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
|
|
:- attr("hash", Package, Hash), attr("variant_value", Package, "dev_path", _).
|
|
|
|
% You can't install a hash, if it is not installed
|
|
:- attr("hash", Package, Hash), not installed_hash(Package, Hash).
|
|
% This should be redundant given the constraint above
|
|
:- attr("node", Package), 2 { attr("hash", Package, Hash) }.
|
|
|
|
% if a hash is selected, we impose all the constraints that implies
|
|
impose(Hash) :- attr("hash", Package, Hash).
|
|
|
|
% if we haven't selected a hash for a package, we'll be building it
|
|
build(Package) :- not attr("hash", Package, _), attr("node", Package).
|
|
|
|
% Minimizing builds is tricky. We want a minimizing criterion
|
|
|
|
% because we want to reuse what is avaialble, but
|
|
% we also want things that are built to stick to *default preferences* from
|
|
% the package and from the user. We therefore treat built specs differently and apply
|
|
% a different set of optimization criteria to them. Spack's *first* priority is to
|
|
% reuse what it *can*, but if it builds something, the built specs will respect
|
|
% defaults and preferences. This is implemented by bumping the priority of optimization
|
|
% criteria for built specs -- so that they take precedence over the otherwise
|
|
% topmost-priority criterion to reuse what is installed.
|
|
%
|
|
% The priority ranges are:
|
|
% 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), 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) :- 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
|
|
% that package happens to be installed, NOT because it was asked for on the
|
|
% command line. If the user specifies a hash, the origin will be "spec".
|
|
%
|
|
% TODO: There's a slight inconsistency with this: if the user concretizes
|
|
% and installs `foo ^bar`, for some build dependency `bar`, and then later
|
|
% does a `spack install --fresh foo ^bar/abcde` (i.e.,the hash of `bar`, it
|
|
% 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.
|
|
:- attr("version", Package, Version),
|
|
version_weight(Package, Weight),
|
|
version_declared(Package, Version, Weight, "installed"),
|
|
not optimize_for_reuse().
|
|
|
|
#defined installed_hash/2.
|
|
|
|
%-----------------------------------------------------------------
|
|
% Optimization to avoid errors
|
|
%-----------------------------------------------------------------
|
|
% Some errors are handled as rules instead of constraints because
|
|
% it allows us to explain why something failed.
|
|
#minimize{ 0@1000: #true}.
|
|
|
|
#minimize{ Weight@1000,Msg: error(Weight, Msg) }.
|
|
#minimize{ Weight@1000,Msg,Arg1: error(Weight, Msg, Arg1) }.
|
|
#minimize{ Weight@1000,Msg,Arg1,Arg2: error(Weight, Msg, Arg1, Arg2) }.
|
|
#minimize{ Weight@1000,Msg,Arg1,Arg2,Arg3: error(Weight, Msg, Arg1, Arg2, Arg3) }.
|
|
#minimize{ Weight@1000,Msg,Arg1,Arg2,Arg3,Arg4: error(Weight, Msg, Arg1, Arg2, Arg3, Arg4) }.
|
|
|
|
%-----------------------------------------------------------------------------
|
|
% How to optimize the spec (high to low priority)
|
|
%-----------------------------------------------------------------------------
|
|
% Each criterion below has:
|
|
% 1. an opt_criterion(ID, Name) fact that describes the criterion, and
|
|
% 2. a `#minimize{ 0@2 : #true }.` statement that ensures the criterion
|
|
% is displayed (clingo doesn't display sums over empty sets by default)
|
|
|
|
% Try hard to reuse installed packages (i.e., minimize the number built)
|
|
opt_criterion(100, "number of packages to build (vs. reuse)").
|
|
#minimize { 0@100: #true }.
|
|
#minimize { 1@100,Package : build(Package), optimize_for_reuse() }.
|
|
#defined optimize_for_reuse/0.
|
|
|
|
% A condition group specifies one or more specs that must be satisfied.
|
|
% Specs declared first are preferred, so we assign increasing weights and
|
|
% minimize the weights.
|
|
opt_criterion(75, "requirement weight").
|
|
#minimize{ 0@275: #true }.
|
|
#minimize{ 0@75: #true }.
|
|
#minimize {
|
|
Weight@75+Priority,Package,Group
|
|
: requirement_weight(Package, Group, Weight),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Minimize the number of deprecated versions being used
|
|
opt_criterion(73, "deprecated versions used").
|
|
#minimize{ 0@273: #true }.
|
|
#minimize{ 0@73: #true }.
|
|
#minimize{
|
|
1@73+Priority,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.
|
|
opt_criterion(70, "version weight").
|
|
#minimize{ 0@270: #true }.
|
|
#minimize{ 0@70: #true }.
|
|
#minimize {
|
|
Weight@70+Priority
|
|
: attr("root", Package), version_weight(Package, Weight),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
opt_criterion(65, "number of non-default variants (roots)").
|
|
#minimize{ 0@265: #true }.
|
|
#minimize{ 0@65: #true }.
|
|
#minimize {
|
|
1@65+Priority,Package,Variant,Value
|
|
: variant_not_default(Package, Variant, Value),
|
|
attr("root", Package),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
opt_criterion(60, "preferred providers for roots").
|
|
#minimize{ 0@260: #true }.
|
|
#minimize{ 0@60: #true }.
|
|
#minimize{
|
|
Weight@60+Priority,Provider,Virtual
|
|
: provider_weight(Provider, Virtual, Weight),
|
|
attr("root", Provider),
|
|
build_priority(Provider, Priority)
|
|
}.
|
|
|
|
opt_criterion(55, "default values of variants not being used (roots)").
|
|
#minimize{ 0@255: #true }.
|
|
#minimize{ 0@55: #true }.
|
|
#minimize{
|
|
1@55+Priority,Package,Variant,Value
|
|
: variant_default_not_used(Package, Variant, Value),
|
|
attr("root", Package),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Try to use default variants or variants that have been set
|
|
opt_criterion(50, "number of non-default variants (non-roots)").
|
|
#minimize{ 0@250: #true }.
|
|
#minimize{ 0@50: #true }.
|
|
#minimize {
|
|
1@50+Priority,Package,Variant,Value
|
|
: variant_not_default(Package, Variant, Value),
|
|
not attr("root", Package),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Minimize the weights of the providers, i.e. use as much as
|
|
% possible the most preferred providers
|
|
opt_criterion(45, "preferred providers (non-roots)").
|
|
#minimize{ 0@245: #true }.
|
|
#minimize{ 0@45: #true }.
|
|
#minimize{
|
|
Weight@45+Priority,Provider,Virtual
|
|
: provider_weight(Provider, Virtual, Weight), not attr("root", Provider),
|
|
build_priority(Provider, Priority)
|
|
}.
|
|
|
|
% Try to minimize the number of compiler mismatches in the DAG.
|
|
opt_criterion(40, "compiler mismatches that are not from CLI").
|
|
#minimize{ 0@240: #true }.
|
|
#minimize{ 0@40: #true }.
|
|
#minimize{
|
|
1@40+Priority,Package,Dependency
|
|
: compiler_mismatch(Package, Dependency),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
opt_criterion(39, "compiler mismatches that are not from CLI").
|
|
#minimize{ 0@239: #true }.
|
|
#minimize{ 0@39: #true }.
|
|
#minimize{
|
|
1@39+Priority,Package,Dependency
|
|
: compiler_mismatch_required(Package, Dependency),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Try to minimize the number of compiler mismatches in the DAG.
|
|
opt_criterion(35, "OS mismatches").
|
|
#minimize{ 0@235: #true }.
|
|
#minimize{ 0@35: #true }.
|
|
#minimize{
|
|
1@35+Priority,Package,Dependency
|
|
: node_os_mismatch(Package, Dependency),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
opt_criterion(30, "non-preferred OS's").
|
|
#minimize{ 0@230: #true }.
|
|
#minimize{ 0@30: #true }.
|
|
#minimize{
|
|
Weight@30+Priority,Package
|
|
: node_os_weight(Package, Weight),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Choose more recent versions for nodes
|
|
opt_criterion(25, "version badness").
|
|
#minimize{ 0@225: #true }.
|
|
#minimize{ 0@25: #true }.
|
|
#minimize{
|
|
Weight@25+Priority,Package
|
|
: version_weight(Package, Weight),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Try to use all the default values of variants
|
|
opt_criterion(20, "default values of variants not being used (non-roots)").
|
|
#minimize{ 0@220: #true }.
|
|
#minimize{ 0@20: #true }.
|
|
#minimize{
|
|
1@20+Priority,Package,Variant,Value
|
|
: variant_default_not_used(Package, Variant, Value),
|
|
not attr("root", Package),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Try to use preferred compilers
|
|
opt_criterion(15, "non-preferred compilers").
|
|
#minimize{ 0@215: #true }.
|
|
#minimize{ 0@15: #true }.
|
|
#minimize{
|
|
Weight@15+Priority,Package
|
|
: compiler_weight(Package, Weight),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
% Minimize the number of mismatches for targets in the DAG, try
|
|
% to select the preferred target.
|
|
opt_criterion(10, "target mismatches").
|
|
#minimize{ 0@210: #true }.
|
|
#minimize{ 0@10: #true }.
|
|
#minimize{
|
|
1@10+Priority,Package,Dependency
|
|
: node_target_mismatch(Package, Dependency),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
opt_criterion(5, "non-preferred targets").
|
|
#minimize{ 0@205: #true }.
|
|
#minimize{ 0@5: #true }.
|
|
#minimize{
|
|
Weight@5+Priority,Package
|
|
: node_target_weight(Package, Weight),
|
|
build_priority(Package, Priority)
|
|
}.
|
|
|
|
%-----------------
|
|
% Domain heuristic
|
|
%-----------------
|
|
#heuristic literal_solved(ID) : literal(ID). [1, sign]
|
|
#heuristic literal_solved(ID) : literal(ID). [50, init]
|
|
#heuristic attr("hash", Package, Hash) : attr("root", Package). [45, init]
|
|
|
|
#heuristic attr("version", Package, Version) : version_declared(Package, Version, 0), attr("root", Package). [40, true]
|
|
#heuristic version_weight(Package, 0) : version_declared(Package, Version, 0), attr("root", Package). [40, true]
|
|
#heuristic attr("variant_value", Package, Variant, Value) : variant_default_value(Package, Variant, Value), attr("root", Package). [40, true]
|
|
#heuristic attr("node_target", Package, Target) : package_target_weight(Target, Package, 0), attr("root", Package). [40, true]
|
|
#heuristic node_target_weight(Package, 0) : attr("root", Package). [40, true]
|
|
#heuristic node_compiler(Package, CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("root", Package). [40, true]
|
|
|
|
#heuristic provider(Package, Virtual) : possible_provider_weight(Package, Virtual, 0, _), attr("virtual_node", Virtual). [30, true]
|
|
#heuristic provider_weight(Package, Virtual, 0, R) : possible_provider_weight(Package, Virtual, 0, R), attr("virtual_node", Virtual). [30, true]
|
|
#heuristic attr("node", Package) : possible_provider_weight(Package, Virtual, 0, _), attr("virtual_node", Virtual). [30, true]
|
|
|
|
#heuristic attr("version", Package, Version) : version_declared(Package, Version, 0), attr("node", Package). [20, true]
|
|
#heuristic version_weight(Package, 0) : version_declared(Package, Version, 0), attr("node", Package). [20, true]
|
|
|
|
#heuristic attr("node_target", Package, Target) : package_target_weight(Target, Package, 0), attr("node", Package). [20, true]
|
|
#heuristic node_target_weight(Package, 0) : attr("node", Package). [20, true]
|
|
#heuristic node_compiler(Package, CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("node", Package). [15, true]
|
|
|
|
#heuristic attr("variant_value", Package, Variant, Value) : variant_default_value(Package, Variant, Value), attr("node", Package). [10, true]
|
|
#heuristic attr("node_os", Package, OS) : buildable_os(OS). [10, true]
|
|
|
|
%-----------
|
|
% Notes
|
|
%-----------
|
|
|
|
% [1] Clingo ensures a total ordering among all atoms. We rely on that total ordering
|
|
% to reduce symmetry in the solution by checking `<` instead of `!=` in symmetric
|
|
% cases. These choices are made without loss of generality.
|