summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGreg Becker <becker33@llnl.gov>2023-11-10 14:55:35 -0800
committerGitHub <noreply@github.com>2023-11-10 14:55:35 -0800
commit13abfb7013ca282b06cf4069d96c3d7c093bc684 (patch)
treea5e485bc2775feb36438bde87b00c518b1811deb /lib
parentb41fc1ec79ee8ebcf439f3e3b9d18989274cc561 (diff)
downloadspack-13abfb7013ca282b06cf4069d96c3d7c093bc684.tar.gz
spack-13abfb7013ca282b06cf4069d96c3d7c093bc684.tar.bz2
spack-13abfb7013ca282b06cf4069d96c3d7c093bc684.tar.xz
spack-13abfb7013ca282b06cf4069d96c3d7c093bc684.zip
`spack deconcretize` command (#38803)
We have two ways to concretize now: * `spack concretize` concretizes only the root specs that are not concrete in the environment. * `spack concretize -f` eliminates all cached concretization data and reconcretizes the *entire* environment. This PR adds `spack deconcretize`, which eliminates cached concretization data for a spec. This allows users greater control over what is preserved from their `spack.lock` file and what is reused when not using `spack concretize -f`. If you want to update a spec installed in your environment, you can call `spack deconcretize` on it, and that spec and any relevant dependents will be removed from the lock file. `spack concretize` has two options: * `--root`: limits deconcretized specs to *specific* roots in the environment. You can use this to deconcretize exactly one root in a `unify: false` environment. i.e., if `foo` root is a dependent of `bar`, both roots, `spack deconcretize bar` will *not* deconcretize `foo`. * `--all`: deconcretize *all* specs that match the input spec. By default `spack deconcretize` will complain about multiple matches, like `spack uninstall`.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/cmd/common/confirmation.py30
-rw-r--r--lib/spack/spack/cmd/deconcretize.py103
-rw-r--r--lib/spack/spack/cmd/gc.py3
-rw-r--r--lib/spack/spack/cmd/uninstall.py20
-rw-r--r--lib/spack/spack/environment/environment.py33
-rw-r--r--lib/spack/spack/test/cmd/deconcretize.py78
6 files changed, 242 insertions, 25 deletions
diff --git a/lib/spack/spack/cmd/common/confirmation.py b/lib/spack/spack/cmd/common/confirmation.py
new file mode 100644
index 0000000000..8a5cd2592b
--- /dev/null
+++ b/lib/spack/spack/cmd/common/confirmation.py
@@ -0,0 +1,30 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import sys
+from typing import List
+
+import llnl.util.tty as tty
+
+import spack.cmd
+
+display_args = {"long": True, "show_flags": False, "variants": False, "indent": 4}
+
+
+def confirm_action(specs: List[spack.spec.Spec], participle: str, noun: str):
+ """Display the list of specs to be acted on and ask for confirmation.
+
+ Args:
+ specs: specs to be removed
+ participle: action expressed as a participle, e.g. "uninstalled"
+ noun: action expressed as a noun, e.g. "uninstallation"
+ """
+ tty.msg(f"The following {len(specs)} packages will be {participle}:\n")
+ spack.cmd.display_specs(specs, **display_args)
+ print("")
+ answer = tty.get_yes_or_no("Do you want to proceed?", default=False)
+ if not answer:
+ tty.msg(f"Aborting {noun}")
+ sys.exit(0)
diff --git a/lib/spack/spack/cmd/deconcretize.py b/lib/spack/spack/cmd/deconcretize.py
new file mode 100644
index 0000000000..dbcf72ea8b
--- /dev/null
+++ b/lib/spack/spack/cmd/deconcretize.py
@@ -0,0 +1,103 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import argparse
+import sys
+from typing import List
+
+import llnl.util.tty as tty
+
+import spack.cmd
+import spack.cmd.common.arguments as arguments
+import spack.cmd.common.confirmation as confirmation
+import spack.environment as ev
+import spack.spec
+
+description = "remove specs from the concretized lockfile of an environment"
+section = "environments"
+level = "long"
+
+# Arguments for display_specs when we find ambiguity
+display_args = {"long": True, "show_flags": False, "variants": False, "indent": 4}
+
+
+def setup_parser(subparser):
+ subparser.add_argument(
+ "--root", action="store_true", help="deconcretize only specific environment roots"
+ )
+ arguments.add_common_arguments(subparser, ["yes_to_all", "specs"])
+ subparser.add_argument(
+ "-a",
+ "--all",
+ action="store_true",
+ dest="all",
+ help="deconcretize ALL specs that match each supplied spec",
+ )
+
+
+def get_deconcretize_list(
+ args: argparse.Namespace, specs: List[spack.spec.Spec], env: ev.Environment
+) -> List[spack.spec.Spec]:
+ """
+ Get list of environment roots to deconcretize
+ """
+ env_specs = [s for _, s in env.concretized_specs()]
+ to_deconcretize = []
+ errors = []
+
+ for s in specs:
+ if args.root:
+ # find all roots matching given spec
+ to_deconc = [e for e in env_specs if e.satisfies(s)]
+ else:
+ # find all roots matching or depending on a matching spec
+ to_deconc = [e for e in env_specs if any(d.satisfies(s) for d in e.traverse())]
+
+ if len(to_deconc) < 1:
+ tty.warn(f"No matching specs to deconcretize for {s}")
+
+ elif len(to_deconc) > 1 and not args.all:
+ errors.append((s, to_deconc))
+
+ to_deconcretize.extend(to_deconc)
+
+ if errors:
+ for spec, matching in errors:
+ tty.error(f"{spec} matches multiple concrete specs:")
+ sys.stderr.write("\n")
+ spack.cmd.display_specs(matching, output=sys.stderr, **display_args)
+ sys.stderr.write("\n")
+ sys.stderr.flush()
+ tty.die("Use '--all' to deconcretize all matching specs, or be more specific")
+
+ return to_deconcretize
+
+
+def deconcretize_specs(args, specs):
+ env = spack.cmd.require_active_env(cmd_name="deconcretize")
+
+ if args.specs:
+ deconcretize_list = get_deconcretize_list(args, specs, env)
+ else:
+ deconcretize_list = [s for _, s in env.concretized_specs()]
+
+ if not args.yes_to_all:
+ confirmation.confirm_action(deconcretize_list, "deconcretized", "deconcretization")
+
+ with env.write_transaction():
+ for spec in deconcretize_list:
+ env.deconcretize(spec)
+ env.write()
+
+
+def deconcretize(parser, args):
+ if not args.specs and not args.all:
+ tty.die(
+ "deconcretize requires at least one spec argument.",
+ " Use `spack deconcretize --all` to deconcretize ALL specs.",
+ )
+
+ specs = spack.cmd.parse_specs(args.specs) if args.specs else [any]
+ deconcretize_specs(args, specs)
diff --git a/lib/spack/spack/cmd/gc.py b/lib/spack/spack/cmd/gc.py
index e4da6a103d..9918bf7479 100644
--- a/lib/spack/spack/cmd/gc.py
+++ b/lib/spack/spack/cmd/gc.py
@@ -6,6 +6,7 @@
import llnl.util.tty as tty
import spack.cmd.common.arguments
+import spack.cmd.common.confirmation
import spack.cmd.uninstall
import spack.environment as ev
import spack.store
@@ -41,6 +42,6 @@ def gc(parser, args):
return
if not args.yes_to_all:
- spack.cmd.uninstall.confirm_removal(specs)
+ spack.cmd.common.confirmation.confirm_action(specs, "uninstalled", "uninstallation")
spack.cmd.uninstall.do_uninstall(specs, force=False)
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index bc6a71cef1..3288404151 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -11,10 +11,9 @@ from llnl.util.tty.colify import colify
import spack.cmd
import spack.cmd.common.arguments as arguments
+import spack.cmd.common.confirmation as confirmation
import spack.environment as ev
-import spack.error
import spack.package_base
-import spack.repo
import spack.spec
import spack.store
import spack.traverse as traverse
@@ -278,7 +277,7 @@ def uninstall_specs(args, specs):
return
if not args.yes_to_all:
- confirm_removal(uninstall_list)
+ confirmation.confirm_action(uninstall_list, "uninstalled", "uninstallation")
# Uninstall everything on the list
do_uninstall(uninstall_list, args.force)
@@ -292,21 +291,6 @@ def uninstall_specs(args, specs):
env.regenerate_views()
-def confirm_removal(specs: List[spack.spec.Spec]):
- """Display the list of specs to be removed and ask for confirmation.
-
- Args:
- specs: specs to be removed
- """
- tty.msg("The following {} packages will be uninstalled:\n".format(len(specs)))
- spack.cmd.display_specs(specs, **display_args)
- print("")
- answer = tty.get_yes_or_no("Do you want to proceed?", default=False)
- if not answer:
- tty.msg("Aborting uninstallation")
- sys.exit(0)
-
-
def uninstall(parser, args):
if not args.specs and not args.all:
tty.die(
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
index cf6dffcb0d..5d6273506e 100644
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -1358,7 +1358,7 @@ class Environment:
# Remove concrete specs that no longer correlate to a user spec
for spec in set(self.concretized_user_specs) - set(self.user_specs):
- self.deconcretize(spec)
+ self.deconcretize(spec, concrete=False)
# Pick the right concretization strategy
if self.unify == "when_possible":
@@ -1373,15 +1373,36 @@ class Environment:
msg = "concretization strategy not implemented [{0}]"
raise SpackEnvironmentError(msg.format(self.unify))
- def deconcretize(self, spec):
+ def deconcretize(self, spec: spack.spec.Spec, concrete: bool = True):
+ """
+ Remove specified spec from environment concretization
+
+ Arguments:
+ spec: Spec to deconcretize. This must be a root of the environment
+ concrete: If True, find all instances of spec as concrete in the environemnt.
+ If False, find a single instance of the abstract spec as root of the environment.
+ """
# spec has to be a root of the environment
- index = self.concretized_user_specs.index(spec)
- dag_hash = self.concretized_order.pop(index)
- del self.concretized_user_specs[index]
+ if concrete:
+ dag_hash = spec.dag_hash()
+
+ pairs = zip(self.concretized_user_specs, self.concretized_order)
+ filtered = [(spec, h) for spec, h in pairs if h != dag_hash]
+ # Cannot use zip and unpack two values; it fails if filtered is empty
+ self.concretized_user_specs = [s for s, _ in filtered]
+ self.concretized_order = [h for _, h in filtered]
+ else:
+ index = self.concretized_user_specs.index(spec)
+ dag_hash = self.concretized_order.pop(index)
+
+ del self.concretized_user_specs[index]
# If this was the only user spec that concretized to this concrete spec, remove it
if dag_hash not in self.concretized_order:
- del self.specs_by_hash[dag_hash]
+ # if we deconcretized a dependency that doesn't correspond to a root, it
+ # won't be here.
+ if dag_hash in self.specs_by_hash:
+ del self.specs_by_hash[dag_hash]
def _get_specs_to_concretize(
self,
diff --git a/lib/spack/spack/test/cmd/deconcretize.py b/lib/spack/spack/test/cmd/deconcretize.py
new file mode 100644
index 0000000000..30e39604bf
--- /dev/null
+++ b/lib/spack/spack/test/cmd/deconcretize.py
@@ -0,0 +1,78 @@
+# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import pytest
+
+import spack.environment as ev
+from spack.main import SpackCommand, SpackCommandError
+
+deconcretize = SpackCommand("deconcretize")
+
+
+@pytest.fixture(scope="function")
+def test_env(mutable_mock_env_path, config, mock_packages):
+ ev.create("test")
+ with ev.read("test") as e:
+ e.add("a@2.0 foobar=bar ^b@1.0")
+ e.add("a@1.0 foobar=bar ^b@0.9")
+ e.concretize()
+ e.write()
+
+
+def test_deconcretize_dep(test_env):
+ with ev.read("test") as e:
+ deconcretize("-y", "b@1.0")
+ specs = [s for s, _ in e.concretized_specs()]
+
+ assert len(specs) == 1
+ assert specs[0].satisfies("a@1.0")
+
+
+def test_deconcretize_all_dep(test_env):
+ with ev.read("test") as e:
+ with pytest.raises(SpackCommandError):
+ deconcretize("-y", "b")
+ deconcretize("-y", "--all", "b")
+ specs = [s for s, _ in e.concretized_specs()]
+
+ assert len(specs) == 0
+
+
+def test_deconcretize_root(test_env):
+ with ev.read("test") as e:
+ output = deconcretize("-y", "--root", "b@1.0")
+ assert "No matching specs to deconcretize" in output
+ assert len(e.concretized_order) == 2
+
+ deconcretize("-y", "--root", "a@2.0")
+ specs = [s for s, _ in e.concretized_specs()]
+
+ assert len(specs) == 1
+ assert specs[0].satisfies("a@1.0")
+
+
+def test_deconcretize_all_root(test_env):
+ with ev.read("test") as e:
+ with pytest.raises(SpackCommandError):
+ deconcretize("-y", "--root", "a")
+
+ output = deconcretize("-y", "--root", "--all", "b")
+ assert "No matching specs to deconcretize" in output
+ assert len(e.concretized_order) == 2
+
+ deconcretize("-y", "--root", "--all", "a")
+ specs = [s for s, _ in e.concretized_specs()]
+
+ assert len(specs) == 0
+
+
+def test_deconcretize_all(test_env):
+ with ev.read("test") as e:
+ with pytest.raises(SpackCommandError):
+ deconcretize()
+ deconcretize("-y", "--all")
+ specs = [s for s, _ in e.concretized_specs()]
+
+ assert len(specs) == 0