From 5bae7428261fa06ec60d66d7437d329ac0962a1a Mon Sep 17 00:00:00 2001
From: Michael Kuhn <michael.kuhn@ovgu.de>
Date: Tue, 14 Mar 2023 09:22:20 +0100
Subject: concretizer: add mode to reuse dependencies only (#30990)

This adds a new mode for `concretizer:reuse` called `dependencies`,
which only reuses dependencies. Currently, `spack install foo` will
reuse older versions of `foo`, which might be surprising to users.
---
 lib/spack/spack/cmd/common/arguments.py      | 10 +++++++++-
 lib/spack/spack/schema/concretizer.py        |  4 +++-
 lib/spack/spack/solver/asp.py                | 13 ++++++++++---
 lib/spack/spack/test/cmd/common/arguments.py | 13 ++++++-------
 4 files changed, 28 insertions(+), 12 deletions(-)

(limited to 'lib')

diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py
index 8302a43754..ae40f58fe0 100644
--- a/lib/spack/spack/cmd/common/arguments.py
+++ b/lib/spack/spack/cmd/common/arguments.py
@@ -514,7 +514,15 @@ def add_concretizer_args(subparser):
         dest="concretizer:reuse",
         const=True,
         default=None,
-        help="reuse installed dependencies/buildcaches when possible",
+        help="reuse installed packages/buildcaches when possible",
+    )
+    subgroup.add_argument(
+        "--reuse-deps",
+        action=ConfigSetAction,
+        dest="concretizer:reuse",
+        const="dependencies",
+        default=None,
+        help="reuse installed dependencies only",
     )
 
 
diff --git a/lib/spack/spack/schema/concretizer.py b/lib/spack/spack/schema/concretizer.py
index cb021271c3..a62786f404 100644
--- a/lib/spack/spack/schema/concretizer.py
+++ b/lib/spack/spack/schema/concretizer.py
@@ -14,7 +14,9 @@ properties = {
         "type": "object",
         "additionalProperties": False,
         "properties": {
-            "reuse": {"type": "boolean"},
+            "reuse": {
+                "oneOf": [{"type": "boolean"}, {"type": "string", "enum": ["dependencies"]}]
+            },
             "enable_node_namespace": {"type": "boolean"},
             "targets": {
                 "type": "object",
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 670dcf6ef9..1de346b80a 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -2502,7 +2502,7 @@ class Solver(object):
                 spack.spec.Spec.ensure_valid_variants(s)
         return reusable
 
-    def _reusable_specs(self):
+    def _reusable_specs(self, specs):
         reusable_specs = []
         if self.reuse:
             # Specs from the local Database
@@ -2524,6 +2524,13 @@ class Solver(object):
                 # TODO: update mirror configuration so it can indicate that the
                 # TODO: source cache (or any mirror really) doesn't have binaries.
                 pass
+
+        # If we only want to reuse dependencies, remove the root specs
+        if self.reuse == "dependencies":
+            reusable_specs = [
+                spec for spec in reusable_specs if not any(root in spec for root in specs)
+            ]
+
         return reusable_specs
 
     def solve(self, specs, out=None, timers=False, stats=False, tests=False, setup_only=False):
@@ -2540,7 +2547,7 @@ class Solver(object):
         """
         # Check upfront that the variants are admissible
         reusable_specs = self._check_input_and_extract_concrete_specs(specs)
-        reusable_specs.extend(self._reusable_specs())
+        reusable_specs.extend(self._reusable_specs(specs))
         setup = SpackSolverSetup(tests=tests)
         output = OutputConfiguration(timers=timers, stats=stats, out=out, setup_only=setup_only)
         result, _, _ = self.driver.solve(setup, specs, reuse=reusable_specs, output=output)
@@ -2563,7 +2570,7 @@ class Solver(object):
             tests (bool): add test dependencies to the solve
         """
         reusable_specs = self._check_input_and_extract_concrete_specs(specs)
-        reusable_specs.extend(self._reusable_specs())
+        reusable_specs.extend(self._reusable_specs(specs))
         setup = SpackSolverSetup(tests=tests)
 
         # Tell clingo that we don't have to solve all the inputs at once
diff --git a/lib/spack/spack/test/cmd/common/arguments.py b/lib/spack/spack/test/cmd/common/arguments.py
index f3348259e6..e889fe55d8 100644
--- a/lib/spack/spack/test/cmd/common/arguments.py
+++ b/lib/spack/spack/test/cmd/common/arguments.py
@@ -122,19 +122,18 @@ def test_root_and_dep_match_returns_root(mock_packages, mutable_mock_env_path):
         assert env_spec2
 
 
-def test_concretizer_arguments(mutable_config, mock_packages):
+@pytest.mark.parametrize(
+    "arg,config", [("--reuse", True), ("--fresh", False), ("--reuse-deps", "dependencies")]
+)
+def test_concretizer_arguments(mutable_config, mock_packages, arg, config):
     """Ensure that ConfigSetAction is doing the right thing."""
     spec = spack.main.SpackCommand("spec")
 
     assert spack.config.get("concretizer:reuse", None) is None
 
-    spec("--reuse", "zlib")
-
-    assert spack.config.get("concretizer:reuse", None) is True
-
-    spec("--fresh", "zlib")
+    spec(arg, "zlib")
 
-    assert spack.config.get("concretizer:reuse", None) is False
+    assert spack.config.get("concretizer:reuse", None) == config
 
 
 def test_use_buildcache_type():
-- 
cgit v1.2.3-70-g09d2