Make strong preferences even stronger (#44373)

Before this PR, if Spack could see a possibility to reuse a spec that
doesn't match a strong preference, it would do so. After the PR, a
strong preference would take precedence.
This commit is contained in:
Massimiliano Culpo 2024-05-24 19:06:28 +02:00 committed by Harmen Stoppels
parent e7c86259bd
commit a3bc9dbfe8
2 changed files with 57 additions and 14 deletions

View file

@ -1345,8 +1345,10 @@ build(PackageNode) :- not attr("hash", PackageNode, _), attr("node", PackageNode
% topmost-priority criterion to reuse what is installed. % topmost-priority criterion to reuse what is installed.
% %
% The priority ranges are: % The priority ranges are:
% 200+ Shifted priorities for build nodes; correspond to priorities 0 - 99. % 1000+ Optimizations for concretization errors
% 100 - 199 Unshifted priorities. Currently only includes minimizing #builds. % 300 - 1000 Highest priority optimizations for valid solutions
% 200 - 299 Shifted priorities for build nodes; correspond to priorities 0 - 99.
% 100 - 199 Unshifted priorities. Currently only includes minimizing #builds and minimizing dupes.
% 0 - 99 Priorities for non-built nodes. % 0 - 99 Priorities for non-built nodes.
build_priority(PackageNode, 200) :- build(PackageNode), attr("node", PackageNode). build_priority(PackageNode, 200) :- build(PackageNode), attr("node", PackageNode).
build_priority(PackageNode, 0) :- not build(PackageNode), attr("node", PackageNode). build_priority(PackageNode, 0) :- not build(PackageNode), attr("node", PackageNode).
@ -1394,6 +1396,16 @@ build_priority(PackageNode, 0) :- not build(PackageNode), attr("node", Package
% 2. a `#minimize{ 0@2 : #true }.` statement that ensures the criterion % 2. a `#minimize{ 0@2 : #true }.` statement that ensures the criterion
% is displayed (clingo doesn't display sums over empty sets by default) % is displayed (clingo doesn't display sums over empty sets by default)
% 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(310, "requirement weight").
#minimize{ 0@310: #true }.
#minimize {
Weight@310,PackageNode,Group
: requirement_weight(PackageNode, Group, Weight)
}.
% Try hard to reuse installed packages (i.e., minimize the number built) % Try hard to reuse installed packages (i.e., minimize the number built)
opt_criterion(110, "number of packages to build (vs. reuse)"). opt_criterion(110, "number of packages to build (vs. reuse)").
#minimize { 0@110: #true }. #minimize { 0@110: #true }.
@ -1405,18 +1417,6 @@ opt_criterion(100, "number of nodes from the same package").
#minimize { ID@100,Package : attr("virtual_node", node(ID, Package)) }. #minimize { ID@100,Package : attr("virtual_node", node(ID, Package)) }.
#defined optimize_for_reuse/0. #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,PackageNode,Group
: requirement_weight(PackageNode, Group, Weight),
build_priority(PackageNode, Priority)
}.
% Minimize the number of deprecated versions being used % Minimize the number of deprecated versions being used
opt_criterion(73, "deprecated versions used"). opt_criterion(73, "deprecated versions used").
#minimize{ 0@273: #true }. #minimize{ 0@273: #true }.

View file

@ -1176,3 +1176,46 @@ def test_forward_multi_valued_variant_using_requires(
for constraint in not_expected: for constraint in not_expected:
assert not s.satisfies(constraint) assert not s.satisfies(constraint)
def test_strong_preferences_higher_priority_than_reuse(concretize_scope, mock_packages):
"""Tests that strong preferences have a higher priority than reusing specs."""
reused_spec = Spec("adios2~bzip2").concretized()
reuse_nodes = list(reused_spec.traverse())
root_specs = [Spec("ascent+adios2")]
# Check that without further configuration adios2 is reused
with spack.config.override("concretizer:reuse", True):
solver = spack.solver.asp.Solver()
setup = spack.solver.asp.SpackSolverSetup()
result, _, _ = solver.driver.solve(setup, root_specs, reuse=reuse_nodes)
ascent = result.specs[0]
assert ascent["adios2"].dag_hash() == reused_spec.dag_hash(), ascent
# If we stick a preference, adios2 is not reused
update_packages_config(
"""
packages:
adios2:
prefer:
- "+bzip2"
"""
)
with spack.config.override("concretizer:reuse", True):
solver = spack.solver.asp.Solver()
setup = spack.solver.asp.SpackSolverSetup()
result, _, _ = solver.driver.solve(setup, root_specs, reuse=reuse_nodes)
ascent = result.specs[0]
assert ascent["adios2"].dag_hash() != reused_spec.dag_hash()
assert ascent["adios2"].satisfies("+bzip2")
# A preference is still preference, so we can override from input
with spack.config.override("concretizer:reuse", True):
solver = spack.solver.asp.Solver()
setup = spack.solver.asp.SpackSolverSetup()
result, _, _ = solver.driver.solve(
setup, [Spec("ascent+adios2 ^adios2~bzip2")], reuse=reuse_nodes
)
ascent = result.specs[0]
assert ascent["adios2"].dag_hash() == reused_spec.dag_hash(), ascent