spack/lib/spack/spack/solver/concretize.lp

687 lines
27 KiB
Plaintext

%=============================================================================
% Generate
%=============================================================================
%-----------------------------------------------------------------------------
% Version semantics
%-----------------------------------------------------------------------------
% versions are declared w/priority -- declared with priority implies declared
version_declared(Package, Version) :- version_declared(Package, Version, _).
% If something is a package, it has only one version and that must be a
% declared version.
1 { version(Package, Version) : version_declared(Package, Version) } 1
:- node(Package).
version_weight(Package, Weight)
:- version(Package, Version), version_declared(Package, Version, Weight),
not preferred_version_declared(Package, Version, _).
version_weight(Package, Weight)
:- version(Package, Version), preferred_version_declared(Package, Version, Weight).
% version_satisfies implies that exactly one of the satisfying versions
% is the package's version, and vice versa.
1 { version(Package, Version) : version_satisfies(Package, Constraint, Version) } 1
:- version_satisfies(Package, Constraint).
version_satisfies(Package, Constraint)
:- version(Package, Version), version_satisfies(Package, Constraint, Version).
#defined preferred_version_declared/3.
#defined version_satisfies/3.
%-----------------------------------------------------------------------------
% Dependency semantics
%-----------------------------------------------------------------------------
% Dependencies of any type imply that one package "depends on" another
depends_on(Package, Dependency) :- depends_on(Package, Dependency, _).
% declared dependencies are real if they're not virtual AND
% the package is not an external
depends_on(Package, Dependency, Type)
:- dependency_conditions(Package, Dependency, Type),
not virtual(Dependency),
not external(Package).
% if you declare a dependency on a virtual AND the package is not an external,
% you depend on one of its providers
1 {
depends_on(Package, Provider, Type)
: provides_virtual(Provider, Virtual)
} 1
:- dependency_conditions(Package, Virtual, Type),
virtual(Virtual),
not external(Package).
% if any individual condition below is true, trigger the dependency.
dependency_conditions(P, D, T) :-
dependency_conditions_hold(P, D, I),
dependency_type(I, T).
% collect all the dependency conditions into a single conditional rule
dependency_conditions_hold(Package, Dependency, ID) :-
version(Package, Version)
: required_dependency_condition(ID, "version", Package, Version);
version_satisfies(Package, Constraint)
: required_dependency_condition(ID, "version_satisfies", Package, Constraint);
node_platform(Package, Platform)
: required_dependency_condition(ID, "node_platform", Package, Platform);
node_os(Package, OS)
: required_dependency_condition(ID, "node_os", Package, OS);
node_target(Package, Target)
: required_dependency_condition(ID, "node_target", Package, Target);
variant_value(Package, Variant, Value)
: required_dependency_condition(ID, "variant_value", Package, Variant, Value);
node_compiler(Package, Compiler)
: required_dependency_condition(ID, "node_compiler", Package, Compiler);
node_compiler_version(Package, Compiler, Version)
: required_dependency_condition(ID, "node_compiler_version", Package, Compiler, Version);
node_flag(Package, FlagType, Flag)
: required_dependency_condition(ID, "node_flag", Package, FlagType, Flag);
dependency_condition(Package, Dependency, ID);
node(Package).
% Implications from matching a dependency condition
node(Dependency) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency).
version(Dependency, Version) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "version", Dependency, Version).
version_satisfies(Dependency, Constraint) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "version_satisfies", Dependency, Constraint).
node_platform(Dependency, Platform) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_platform", Dependency, Platform).
node_os(Dependency, OS) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_os", Dependency, OS).
node_target(Dependency, Target) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_target", Dependency, Target).
variant_set(Dependency, Variant, Value) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "variant_set", Dependency, Variant, Value).
node_compiler(Dependency, Compiler) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_compiler", Dependency, Compiler).
node_compiler_version(Dependency, Compiler, Version) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_compiler_version", Dependency, Compiler, Version).
node_compiler_version_satisfies(Dependency, Compiler, Version) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_compiler_version_satisfies", Dependency, Compiler, Version).
node_flag(Dependency, FlagType, Flag) :-
dependency_conditions_hold(Package, Dependency, ID),
depends_on(Package, Dependency),
imposed_dependency_condition(ID, "node_flag", Dependency, FlagType, Flag).
% if a virtual was required by some root spec, one provider is in the DAG
1 { node(Package) : provides_virtual(Package, Virtual) } 1
:- virtual_node(Virtual).
% a node that provides a virtual is a provider
provider(Package, Virtual)
:- node(Package), provides_virtual(Package, Virtual).
% for any virtual, there can be at most one provider in the DAG
0 { provider(Package, Virtual) :
node(Package), provides_virtual(Package, Virtual) } 1 :- virtual(Virtual).
% give dependents the virtuals they want
provider_weight(Dependency, 0)
:- virtual(Virtual), depends_on(Package, Dependency),
provider(Dependency, Virtual),
external(Dependency).
provider_weight(Dependency, Weight)
:- virtual(Virtual), depends_on(Package, Dependency),
provider(Dependency, Virtual),
pkg_provider_preference(Package, Virtual, Dependency, Weight),
not external(Dependency).
provider_weight(Dependency, Weight)
:- virtual(Virtual), depends_on(Package, Dependency),
provider(Dependency, Virtual),
not pkg_provider_preference(Package, Virtual, Dependency, _),
not external(Dependency),
default_provider_preference(Virtual, Dependency, Weight).
% if there's no preference for something, it costs 100 to discourage its
% use with minimization
provider_weight(Dependency, 100)
:- virtual(Virtual),
provider(Dependency, Virtual),
depends_on(Package, Dependency),
not external(Dependency),
not pkg_provider_preference(Package, Virtual, Dependency, _),
not default_provider_preference(Virtual, Dependency, _).
% Do the same for virtual roots
provider_weight(Package, Weight)
:- virtual_root(Virtual),
provider(Package, Virtual),
default_provider_preference(Virtual, Package, Weight).
provider_weight(Package, 100)
:- virtual_root(Virtual),
provider(Package, Virtual),
not default_provider_preference(Virtual, Package, _).
% all nodes must be reachable from some root
node(Package) :- root(Package).
1 { root(Package) : provides_virtual(Package, Virtual) } 1
:- virtual_root(Virtual).
needed(Package) :- root(Package).
needed(Dependency) :- needed(Package), depends_on(Package, Dependency).
:- node(Package), not needed(Package).
% real dependencies imply new nodes.
node(Dependency) :- node(Package), depends_on(Package, Dependency).
% Avoid cycles in the DAG
path(Parent, Child) :- depends_on(Parent, Child).
path(Parent, Descendant) :- path(Parent, A), depends_on(A, Descendant).
:- path(A, B), path(B, A).
% 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 provides_virtual/2.
#defined external/1.
#defined external_spec/2.
#defined external_version_declared/4.
#defined external_only/1.
#defined pkg_provider_preference/4.
#defined default_provider_preference/3.
#defined version_satisfies/2.
#defined node_compiler_version_satisfies/3.
#defined root/1.
%-----------------------------------------------------------------------------
% External semantics
%-----------------------------------------------------------------------------
% if an external version is declared, it is also declared globally
version_declared(Package, Version, Weight) :- external_version_declared(Package, Version, Weight, _).
% if a package is external its version must be one of the external versions
1 { version(Package, Version): external_version_declared(Package, Version, _, _) } 1 :- external(Package).
% if a package is not buildable (external_only), only externals are allowed
external(Package) :- external_only(Package), node(Package).
% a package is a real_node if it is not external
real_node(Package) :- node(Package), not external(Package).
% if an external version is selected, the package is external and
% we are using the corresponding spec
external(Package) :-
version(Package, Version), version_weight(Package, Weight),
external_version_declared(Package, Version, Weight, ID).
external_spec(Package, ID) :-
version(Package, Version), version_weight(Package, Weight),
external_version_declared(Package, Version, Weight, ID).
%-----------------------------------------------------------------------------
% Variant semantics
%-----------------------------------------------------------------------------
% one variant value for single-valued variants.
1 {
variant_value(Package, Variant, Value)
: variant_possible_value(Package, Variant, Value)
} 1
:- node(Package),
variant(Package, Variant),
variant_single_value(Package, Variant).
% at least one variant value for multi-valued variants.
1 {
variant_value(Package, Variant, Value)
: variant_possible_value(Package, Variant, Value)
}
:- node(Package),
variant(Package, Variant),
not variant_single_value(Package, Variant).
% if a variant is set to anything, it is considered 'set'.
variant_set(Package, Variant) :- variant_set(Package, Variant, _).
% A variant cannot have a value that is not also a possible value
:- variant_value(Package, Variant, Value), not variant_possible_value(Package, Variant, Value).
% variant_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).
% prefer default values.
variant_not_default(Package, Variant, Value, 1)
:- variant_value(Package, Variant, Value),
not variant_default_value(Package, Variant, Value),
not variant_set(Package, Variant, Value),
node(Package).
variant_not_default(Package, Variant, Value, 0)
:- variant_value(Package, Variant, Value),
variant_default_value(Package, Variant, Value),
node(Package).
variant_not_default(Package, Variant, Value, 0)
:- variant_value(Package, Variant, Value),
variant_set(Package, Variant, Value),
node(Package).
% The default value for a variant in a package is what is written
% in the package.py file, unless some preference is set in packages.yaml
variant_default_value(Package, Variant, Value)
:- variant_default_value_from_package_py(Package, Variant, Value),
not variant_default_value_from_packages_yaml(Package, Variant, _).
variant_default_value(Package, Variant, Value)
:- variant_default_value_from_packages_yaml(Package, Variant, Value).
% Treat 'none' in a special way - it cannot be combined with other
% values even if the variant is multi-valued
:- 2 {variant_value(Package, Variant, Value): variant_possible_value(Package, Variant, Value)},
variant_value(Package, Variant, "none").
% 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, "dev_path")
:- 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_set/3.
#defined variant_single_value/2.
#defined variant_default_value/3.
#defined variant_possible_value/3.
#defined variant_default_value_from_packages_yaml/3.
#defined variant_default_value_from_package_py/3.
%-----------------------------------------------------------------------------
% Platform semantics
%-----------------------------------------------------------------------------
% one platform per node
1 { node_platform(Package, Platform) : node_platform(Packagee, Platform) } 1
:- node(Package).
% 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, _).
#defined node_platform_set/2. % avoid warnings
%-----------------------------------------------------------------------------
% OS semantics
%-----------------------------------------------------------------------------
% one os per node
1 { node_os(Package, OS) : os(OS) } 1 :- node(Package).
% node_os_set implies that the node must have that os
node_os(Package, OS) :- node(Package), node_os_set(Package, OS).
node_os_set(Package) :- node_os_set(Package, _).
% inherit OS along dependencies
node_os_inherit(Package, OS) :- node_os_set(Package, OS).
node_os_inherit(Dependency, OS)
:- node_os_inherit(Package, OS), depends_on(Package, Dependency),
not node_os_set(Dependency).
node_os_inherit(Package) :- node_os_inherit(Package, _).
% fall back to default if not set or inherited
node_os(Package, OS)
:- node(Package),
not node_os_set(Package), not node_os_inherit(Package),
node_os_default(OS).
#defined node_os_set/2.
%-----------------------------------------------------------------------------
% Target semantics
%-----------------------------------------------------------------------------
% one target per node -- optimization will pick the "best" one
1 { node_target(Package, Target) : target(Target) } 1 :- node(Package).
% node_target_satisfies semantics
1 { node_target(Package, Target) : node_target_satisfies(Package, Constraint, Target) } 1
:- node_target_satisfies(Package, Constraint).
node_target_satisfies(Package, Constraint)
:- node_target(Package, Target), node_target_satisfies(Package, Constraint, Target).
#defined node_target_satisfies/3.
% The target weight is either the default target weight
% or a more specific per-package weight if set
target_weight(Target, Package, Weight)
:- default_target_weight(Target, Weight),
node(Package),
not derive_target_from_parent(_, Package),
not package_target_weight(Target, Package, _).
% TODO: Need to account for the case of more than one parent
% TODO: each of which sets different targets
target_weight(Target, Dependency, Weight)
:- depends_on(Package, Dependency),
derive_target_from_parent(Package, Dependency),
target_weight(Target, Package, Weight).
target_weight(Target, Package, Weight)
:- package_target_weight(Target, Package, Weight).
% can't use targets on node if the compiler for the node doesn't support them
:- node_target(Package, Target),
not compiler_supports_target(Compiler, Version, Target),
node_compiler(Package, Compiler),
node_compiler_version(Package, Compiler, Version).
% 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(Target, Package, Weight).
% compatibility rules for targets among nodes
node_target_match_pref(Dependency, Target)
:- depends_on(Package, Dependency),
node_target_match_pref(Package, Target),
not node_target_set(Dependency, _).
node_target_match_pref(Dependency, Target)
:- depends_on(Package, Dependency),
node_target_set(Package, Target),
not node_target_match_pref(Package, Target),
not node_target_set(Dependency, _).
node_target_match_pref(Dependency, Target)
:- depends_on(Package, Dependency),
root(Package), node_target(Package, Target),
not node_target_match_pref(Package, _).
node_target_match(Package, 1)
:- node_target(Package, Target), node_target_match_pref(Package, Target).
derive_target_from_parent(Parent, Package)
:- depends_on(Parent, Package), not package_target_weight(_, Package, _).
#defined node_target_set/2.
#defined package_target_weight/3.
%-----------------------------------------------------------------------------
% Compiler semantics
%-----------------------------------------------------------------------------
% one compiler per node
1 { node_compiler(Package, Compiler) : compiler(Compiler) } 1 :- node(Package).
1 { node_compiler_version(Package, Compiler, Version)
: compiler_version(Compiler, Version) } 1 :- node(Package).
1 { compiler_weight(Package, Weight) : compiler_weight(Package, Weight) } 1
:- node(Package).
% define node_compiler_version_satisfies/3 from node_compiler_version_satisfies/4
% version_satisfies implies that exactly one of the satisfying versions
% is the package's version, and vice versa.
1 { node_compiler_version(Package, Compiler, Version)
: node_compiler_version_satisfies(Package, Compiler, Constraint, Version) } 1
:- node_compiler_version_satisfies(Package, Compiler, Constraint).
node_compiler_version_satisfies(Package, Compiler, Constraint)
:- node_compiler_version(Package, Compiler, Version),
node_compiler_version_satisfies(Package, Compiler, Constraint, Version).
#defined node_compiler_version_satisfies/4.
% If the compiler version was set from the command line,
% respect it verbatim
node_compiler_version(Package, Compiler, Version) :- node_compiler_version_hard(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
:- node_compiler_version(Package, Compiler, Version), node_os(Package, OS),
not compiler_supports_os(Compiler, Version, OS),
not allow_compiler(Compiler, Version).
% If the compiler is what was prescribed from command line etc.
% or is the same as a root node, there is a version match
% Compiler prescribed in the root spec
node_compiler_version_match_pref(Package, Compiler, V)
:- node_compiler_hard(Package, Compiler),
node_compiler_version(Package, Compiler, V),
not external(Package).
% Compiler inherited from a parent node
node_compiler_version_match_pref(Dependency, Compiler, V)
:- depends_on(Package, Dependency),
node_compiler_version_match_pref(Package, Compiler, V),
node_compiler_version(Dependency, Compiler, V),
not node_compiler_hard(Dependency, Compiler).
% Compiler inherited from the root package
node_compiler_version_match_pref(Dependency, Compiler, V)
:- depends_on(Package, Dependency),
node_compiler_version(Package, Compiler, V), root(Package),
node_compiler_version(Dependency, Compiler, V),
not node_compiler_hard(Dependency, Compiler).
compiler_version_match(Package, 1)
:- node_compiler_version(Package, Compiler, V),
node_compiler_version_match_pref(Package, Compiler, V).
#defined node_compiler_hard/2.
#defined node_compiler_version_hard/3.
#defined compiler_supports_os/3.
#defined allow_compiler/2.
% compilers weighted by preference according to packages.yaml
compiler_weight(Package, Weight)
:- node_compiler(Package, Compiler),
node_compiler_version(Package, Compiler, V),
node_compiler_preference(Package, Compiler, V, Weight).
compiler_weight(Package, Weight)
:- node_compiler(Package, Compiler),
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(Package, Compiler),
node_compiler_version(Package, Compiler, Version),
not node_compiler_preference(Package, Compiler, Version, _),
not default_compiler_preference(Compiler, Version, _).
#defined node_compiler_preference/4.
#defined default_compiler_preference/3.
%-----------------------------------------------------------------------------
% Compiler flags
%-----------------------------------------------------------------------------
% propagate flags when compilers match
inherit_flags(Package, Dependency)
:- depends_on(Package, Dependency),
node_compiler(Package, Compiler),
node_compiler(Dependency, Compiler),
compiler(Compiler), flag_type(FlagType).
node_flag_inherited(Dependency, FlagType, Flag)
:- node_flag_set(Package, FlagType, Flag), inherit_flags(Package, Dependency).
node_flag_inherited(Dependency, FlagType, Flag)
:- node_flag_inherited(Package, FlagType, Flag),
inherit_flags(Package, Dependency).
% node with flags set to anythingg is "set"
node_flag_set(Package) :- node_flag_set(Package, _, _).
% remember where flags came from
node_flag_source(Package, Package) :- node_flag_set(Package).
node_flag_source(Dependency, Q)
:- node_flag_source(Package, Q), inherit_flags(Package, Dependency).
% compiler flags from compilers.yaml are put on nodes if compiler matches
node_flag(Package, FlagType, Flag)
:- not node_flag_set(Package),
compiler_version_flag(Compiler, Version, FlagType, Flag),
node_compiler(Package, Compiler),
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),
compiler_version_flag(Compiler, Version, FlagType, Flag),
node_compiler(Package, Compiler),
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.
%-----------------------------------------------------------------------------
% How to optimize the spec (high to low priority)
%-----------------------------------------------------------------------------
% weight root preferences higher
%
% TODO: how best to deal with this issue? It's not clear how best to
% weight all the constraints. Without this root preference, `spack solve
% hdf5` will pick mpich instead of openmpi, even if openmpi is the
% preferred provider, because openmpi has a version constraint on hwloc.
% It ends up choosing between settling for an old version of hwloc, or
% picking the second-best provider. This workaround weights root
% preferences higher so that hdf5's prefs are more important, but it's
% not clear this is a general solution. It would be nice to weight by
% distance to root, but that seems to slow down the solve a lot.
%
% One option is to make preferences hard constraints. Or maybe we need
% to look more closely at where a constraint came from and factor that
% into our weights. e.g., a non-default variant resulting from a version
% constraint counts like a version constraint. Needs more thought later.
%
root(Package, 2) :- root(Package), node(Package).
root(Dependency, 1) :- not root(Dependency), node(Dependency).
% The highest priority is to minimize the:
% 1. Version weight
% 2. Number of variants with a non default value, if not set
% for the root(Package)
#minimize { Weight@15 : root(Package),version_weight(Package, Weight)}.
#minimize {
Weight@14,Package,Variant,Value
: variant_not_default(Package, Variant, Value, Weight), root(Package)
}.
% If the value is a multivalued variant there could be multiple
% values set as default. Since a default value has a weight of 0 we
% need to maximize their number below to ensure they're all set
#maximize {
1@13,Package,Variant,Value
: variant_not_default(Package, Variant, Value, Weight),
not variant_single_value(Package, Variant),
root(Package)
}.
#minimize{
Weight@13,Provider
: provider_weight(Provider, Weight), root(Provider)
}.
% Try to use default variants or variants that have been set
#minimize {
Weight@11,Package,Variant,Value
: variant_not_default(Package, Variant, Value, Weight), not root(Package)
}.
% Minimize the weights of the providers, i.e. use as much as
% possible the most preferred providers
#minimize{
Weight@9,Provider
: provider_weight(Provider, Weight), not root(Provider)
}.
% If the value is a multivalued variant there could be multiple
% values set as default. Since a default value has a weight of 0 we
% need to maximize their number below to ensure they're all set
#maximize {
1@8,Package,Variant,Value
: variant_not_default(Package, Variant, Value, Weight),
not variant_single_value(Package, Variant),
not root(Package)
}.
% Try to maximize the number of compiler matches in the DAG,
% while minimizing the number of nodes. This is done because
% a maximization on the number of matches for compilers is highly
% correlated to a preference to have as many nodes as possible
#minimize{ 1@7,Package : node(Package) }.
#maximize{ Weight@7,Package : compiler_version_match(Package, Weight) }.
% Choose more recent versions for nodes
#minimize{
Weight@6,Package : version_weight(Package, Weight)
}.
% Try to use preferred compilers
#minimize{ Weight@5,Package : compiler_weight(Package, Weight) }.
% Maximize the number of matches for targets in the DAG, try
% to select the preferred target.
#maximize{ Weight@4,Package : node_target_match(Package, Weight) }.
#minimize{ Weight@3,Package : node_target_weight(Package, Weight) }.