spack/lib/spack/spack/solver/concretize.lp
Gregory Becker aa4f478ab8 propagation: improve performance
This updates the propagation logic used in `concretize.lp` to avoid rules with `path()`
in the body and instead base propagation around `depends_on()`.
2022-11-02 09:43:57 -07:00

1381 lines
57 KiB
Prolog

% Copyright 2013-2022 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
{ literal_solved(ID) } :- literal(ID).
literal_not_solved(ID) :- not 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 { 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) }.
% 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, 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).
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.
#defined literal/4.
#defined literal/5.
#defined literal/6.
%-----------------------------------------------------------------------------
% 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"),
version(Package, Version),
version_weight(Package, Weight),
not hash(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
{ version(Package, Version) : version_declared(Package, Version) }
:- node(Package).
error(2, "No version for '{0}' satisfies '@{1}' and '@{2}'", Package, Version1, Version2)
:- node(Package),
version(Package, Version1),
version(Package, Version2),
Version1 < Version2. % see[1]
error(2, "No versions available for package '{0}'", Package)
:- node(Package), not 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),
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).
possible_version_weight(Package, Weight)
:- 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),
version_weight(Package, Weight),
version_declared(Package, Version, Weight, "external"),
not external(Package).
% we can't use a weight from an installed spec if we are building it
% and vice-versa
:- version(Package, Version),
version_weight(Package, Weight),
version_declared(Package, Version, Weight, "installed"),
build(Package).
:- version(Package, Version),
version_weight(Package, Weight),
not version_declared(Package, Version, Weight, "installed"),
not build(Package).
1 { version_weight(Package, Weight) : version_declared(Package, Version, Weight) } 1
:- version(Package, Version),
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).
% 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, _).
% 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)},
C < 1.
node_version_satisfies(Package, Constraint)
:- 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, unless do_not_impose(ID)
% is derived. This allows imposed constraints to be canceled in special cases.
impose(ID) :- condition_holds(ID), not do_not_impose(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
:- node(Package), hash(Package, Hash),
variant_value(Package, Variant, Value),
not imposed_constraint(Hash, "variant_value", Package, Variant, Value).
% we cannot have additional flag values when we are working with concrete specs
:- node(Package), hash(Package, Hash),
node_flag(Package, FlagType, Flag),
not imposed_constraint(Hash, "node_flag", Package, FlagType, Flag).
#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) :- hash(Package, _), node(Package).
%-----------------------------------------------------------------------------
% Dependency semantics
%-----------------------------------------------------------------------------
% Dependencies of any type imply that one package "depends on" another
depends_on(Package, Dependency) :- 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.
dependency_holds(Package, Dependency, Type) :-
dependency_condition(ID, Package, Dependency),
dependency_type(ID, Type),
build(Package),
not external(Package),
condition_holds(ID).
% We cut off dependencies of externals (as we don't really know them).
% Don't impose constraints on dependencies that don't exist.
do_not_impose(ID) :-
not dependency_holds(Package, Dependency, _),
dependency_condition(ID, Package, Dependency).
% 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)
:- dependency_holds(Package, Dependency, Type),
not virtual(Dependency).
% every root must be a node
node(Package) :- root(Package).
% dependencies imply new nodes
node(Dependency) :- 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(Dependency) :- needed(Package), depends_on(Package, Dependency).
error(1, "'{0}' is not a valid dependency for any package in the DAG", Package)
:- 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(2, "Cyclic dependency detected between '{0}' and '{1}'\n 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(0, Msg) :- 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
#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
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)
:- 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).
error(2, "Cannot find valid provider for virtual {0}", Virtual)
:- 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),
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).
% 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).
% 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).
% The provider provides the virtual if some provider condition holds.
virtual_condition_holds(Provider, Virtual) :-
provider_condition(ID, Provider, Virtual),
condition_holds(ID),
virtual(Virtual).
% A package cannot be the actual provider for a virtual if it does not
% fulfill the conditions to provide that virtual
:- provider(Package, Virtual), not virtual_condition_holds(Package, Virtual),
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).
#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.
#defined external_version_declared/4.
#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
%-----------------------------------------------------------------------------
% 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(2, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(Package),
not external_version(Package, _, _).
error(2, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(Package),
external_version(Package, Version1, Weight1),
external_version(Package, Version2, Weight2),
(Version1, Weight1) < (Version2, Weight2). % see[1]
version_weight(Package, Weight) :- external_version(Package, Version, Weight).
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, _).
% a package is a real_node if it is not external
real_node(Package) :- node(Package), not external(Package).
% a package is external if we are using an external spec for it
external(Package) :- 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),
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) :-
external_conditions_hold(Package, LocalIndex),
node(Package),
not hash(Package, _).
external_conditions_hold(Package, LocalIndex) :-
possible_external(ID, Package, LocalIndex), condition_holds(ID).
% it cannot happen that a spec is external, but none of the external specs
% conditions hold.
error(2, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(Package),
not external_conditions_hold(Package, _).
#defined possible_external/3.
#defined external_spec_index/3.
#defined external_spec_condition/3.
#defined external_spec_condition/4.
#defined external_spec_condition/5.
#defined external_spec_condition/6.
%-----------------------------------------------------------------------------
% Config required semantics
%-----------------------------------------------------------------------------
activate_requirement_rules(Package) :- node(Package).
activate_requirement_rules(Package) :- virtual_node(Package).
requirement_group_satisfied(Package, X) :-
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } 1,
activate_requirement_rules(Package),
requirement_policy(Package, X, "one_of"),
requirement_group(Package, X).
requirement_weight(Package, W) :-
condition_holds(Y),
requirement_has_weight(Y, W),
requirement_group_member(Y, Package, X),
requirement_policy(Package, X, "one_of"),
requirement_group_satisfied(Package, X).
requirement_group_satisfied(Package, X) :-
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } ,
activate_requirement_rules(Package),
requirement_policy(Package, X, "any_of"),
requirement_group(Package, X).
requirement_weight(Package, W) :-
W = #min {
Z : requirement_has_weight(Y, Z), condition_holds(Y), requirement_group_member(Y, Package, X);
% 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, X, "any_of"),
requirement_group_satisfied(Package, X).
error(2, "Cannot satisfy requirement group for package '{0}'", Package) :-
activate_requirement_rules(Package),
requirement_group(Package, X),
not requirement_group_satisfied(Package, X).
#defined requirement_group/2.
#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).
variant_propagate(Package, Variant, Value, Source) :-
node(Package),
depends_on(Parent, Package),
variant_propagate(Parent, Variant, Value, Source),
not variant_set(Package, Variant).
variant_value(Package, Variant, Value) :-
node(Package),
variant(Package, Variant),
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),
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),
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, _),
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) :-
variant(Package, Variant),
not 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)
: variant_possible_value(Package, Variant, Value)
}
:- 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),
variant(Package, Variant),
variant_single_value(Package, Variant),
build(Package),
variant_value(Package, Variant, Value1),
variant_value(Package, Variant, Value2),
Value1 < Value2. % see[1]
error(2, "No valid value for variant '{1}' of package '{0}'", Package, Variant)
:- node(Package),
variant(Package, Variant),
build(Package),
C = #count{ Value : 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, _).
% 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),
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(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),
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
variant_value(Package, Variant, Value)
:- node(Package),
variant(Package, Variant),
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),
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),
% 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).
% 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).
% The variant is set in an external spec
external_with_variant_set(Package, Variant, Value)
:- variant_value(Package, Variant, Value),
condition_requirement(ID, "variant_value", Package, Variant, Value),
possible_external(ID, Package, _),
external(Package),
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 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, _).
variant_default_value(Package, Variant, Value) :- 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"),
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)
:- variant_set(Package, Variant, _), auto_variant(Variant).
variant_single_value(Package, "dev_path")
:- 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.
#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
node_platform(Package, Platform)
:- node(Package),
not 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).
% platform is set if set to anything
node_platform_set(Package) :- 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)},
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),
Platform1 < Platform2. % see[1]
#defined node_platform_set/2. % avoid warnings
%-----------------------------------------------------------------------------
% OS semantics
%-----------------------------------------------------------------------------
% convert weighted OS declarations to simple one
os(OS) :- os(OS, _).
% one os per node
{ node_os(Package, OS) : os(OS) } :- node(Package).
error(2, "Cannot find valid operating system for '{0}'", Package)
:- node(Package),
C = #count{ OS : 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),
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),
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),
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),
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).
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) },
node_os(Package, ReusedOS).
% If an OS is set explicitly respect the value
node_os(Package, OS) :- node_os_set(Package, OS), node(Package).
#defined node_os_set/2.
#defined os_compatible/2.
%-----------------------------------------------------------------------------
% Target semantics
%-----------------------------------------------------------------------------
% Each node has only one target chosen among the known targets
{ node_target(Package, Target) : target(Target) } :- node(Package).
error(2, "Cannot find valid target for '{0}'", Package)
:- node(Package),
C = #count{Target : 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),
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),
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).
% 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),
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),
target_compatible(Target, MyTarget).
% target_compatible(T1, T2) means code for T2 can run on T1
% This order is dependent -> dependency in the node DAG, which
% is contravariant with the target DAG.
target_compatible(Target, Target) :- target(Target).
target_compatible(Child, Parent) :- target_parent(Child, Parent).
target_compatible(Descendent, Ancestor)
:- target_parent(Target, Ancestor),
target_compatible(Descendent, Target),
target(Target).
#defined target_satisfies/2.
#defined target_parent/2.
% 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),
not compiler_supports_target(Compiler, Version, Target),
node_compiler(Package, Compiler),
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).
% each node has the weight of its assigned target
node_target_weight(Package, Weight)
:- node(Package),
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).
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(2, "'{0} target={1}' is not compatible with this machine", Package, Target)
:- node(Package),
node_target(Package, Target),
not target(Target).
#defined node_target_set/2.
#defined package_target_weight/3.
%-----------------------------------------------------------------------------
% Compiler semantics
%-----------------------------------------------------------------------------
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),
build(Package).
error(2, "No valid compiler version found for '{0}'", Package)
:- node(Package),
C = #count{ Version : 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),
(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, _).
% We can't have a compiler be enforced and select the version from another compiler
:- node_compiler(Package, Compiler1),
node_compiler_version(Package, Compiler2, _),
Compiler1 != Compiler2,
internal_error("Mismatch between selected compiler and compiler version").
% 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) },
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) },
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),
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).
% 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),
not compiler_supports_os(Compiler, Version, 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_version(Package, Compiler, Version),
node_compiler_version(Dependency, Compiler, Version).
compiler_mismatch(Package, Dependency)
:- depends_on(Package, 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),
node_compiler_preference(Package, Compiler, V, Weight).
compiler_weight(Package, Weight)
:- 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),
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
:- node_compiler_version(Package, Compiler, Version), not compiler_version(Compiler, Version).
#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, Compiler),
node_compiler(Dependency, Compiler),
not 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).
% Ensure propagation
:- node_flag_inherited(Package, FlagType, Flag),
can_inherit_flags(Package, Dependency, FlagType),
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),
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).
% compiler flags from compilers.yaml are put on nodes if compiler matches
node_flag(Package, FlagType, Flag)
:- compiler_version_flag(Compiler, Version, FlagType, Flag),
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, _),
compiler_version_flag(Compiler, Version, FlagType, Flag),
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)
:- 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).
#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").
% you can't choose an installed hash for a dev spec
:- hash(Package, Hash), variant_value(Package, "dev_path", _).
% You can't install a hash, if it is not installed
:- hash(Package, Hash), not installed_hash(Package, Hash).
% This should be redundant given the constraint above
:- hash(Package, Hash1), hash(Package, Hash2), Hash1 != Hash2.
% if a hash is selected, we impose all the constraints that implies
impose(Hash) :- 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).
% 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), node(Package), optimize_for_reuse().
build_priority(Package, 0) :- not build(Package), 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().
% 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.
:- 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. Here we optimize
% HEAVILY against the facts generated by those rules.
#minimize{ 0@1000: #true}.
#minimize{ 0@1001: #true}.
#minimize{ 0@1002: #true}.
#minimize{ 1000@1000+Priority,Msg: error(Priority, Msg) }.
#minimize{ 1000@1000+Priority,Msg,Arg1: error(Priority, Msg, Arg1) }.
#minimize{ 1000@1000+Priority,Msg,Arg1,Arg2: error(Priority, Msg, Arg1, Arg2) }.
#minimize{ 1000@1000+Priority,Msg,Arg1,Arg2,Arg3: error(Priority, Msg, Arg1, Arg2, Arg3) }.
#minimize{ 1000@1000+Priority,Msg,Arg1,Arg2,Arg3,Arg4: error(Priority, Msg, Arg1, Arg2, Arg3, Arg4) }.
#minimize{ 1000@1000+Priority,Msg,Arg1,Arg2,Arg3,Arg4,Arg5: error(Priority, Msg, Arg1, Arg2, Arg3, Arg4, Arg5) }.
%-----------------------------------------------------------------------------
% 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
: requirement_weight(Package, 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
: 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
: 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),
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),
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),
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 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 root(Provider),
build_priority(Provider, Priority)
}.
% Try to minimize the number of compiler mismatches in the DAG.
opt_criterion(40, "compiler mismatches").
#minimize{ 0@240: #true }.
#minimize{ 0@40: #true }.
#minimize{
1@40+Priority,Package,Dependency
: compiler_mismatch(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 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 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]
%-----------
% 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.