Compare commits

...

1 Commits

Author SHA1 Message Date
Gregory Becker cf7d625bdb `spack deconcretize` command 2023-07-09 10:40:43 -07:00
5 changed files with 215 additions and 24 deletions

View File

@ -0,0 +1,31 @@
# 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)

View File

@ -0,0 +1,157 @@
# 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, Optional
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
import spack.traverse as traverse
description = "remove specs from the concretized lockfile of an environment"
section = "environments"
level = "short"
error_message = """You can either:
a) use a more specific spec, or
b) specify the spec by its hash (e.g. `spack deconcretize /hash`), or
c) use `spack deconcretize --all` to deconcretize ALL matching specs.
"""
# 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(
"-f",
"--force",
action="store_true",
dest="force",
help="remove regardless of whether other packages or environments " "depend on this one",
)
arguments.add_common_arguments(subparser, ["recurse_dependents", "yes_to_all", "specs"])
subparser.add_argument(
"-a",
"--all",
action="store_true",
dest="all",
help="deconcretize ALL specs that match each supplied spec",
)
def find_matching_specs(
env: ev.Environment, specs: List[spack.spec.Spec], allow_multiple_matches: bool = False
):
"""Returns a list of concrete specs in the environment matching the abstract specs from CLI
Args:
env: active environment
specs: list of specs to be matched against concrete environment
allow_multiple_matches: if True mulitple matches are permitted
Return:
list: list of specs
"""
matching_specs = []
has_errors = False
for spec in specs:
if spec is any:
matching = [s for _, s in env.concretized_specs()]
else:
matching = [s for _, s in env.concretized_specs() if s.satisfies(spec)]
if not allow_multiple_matches and len(matching) > 1:
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()
has_errors = True
if len(matching) == 0 and spec is not any:
tty.die(f"{spec} does not match any concrete spec.")
matching_specs.extend(matching)
if has_errors:
tty.die(error_message)
return matching_specs
def concrete_dependents(
specs: List[spack.spec.Spec], env: ev.Environment
) -> List[spack.spec.Spec]:
# Note this traversal will not return any spec in the original list
return [
spec
for spec in traverse.traverse_nodes(
specs,
root=False,
order="breadth",
cover="nodes",
direction="parents",
key=lambda s: s.dag_hash(),
)
if spec.dag_hash() in env.concretized_order
]
def get_deconcretize_list(args, specs, env):
matching_specs = find_matching_specs(env, specs, args.all)
dependent_specs = concrete_dependents(matching_specs, env)
to_deconcretize = matching_specs + dependent_specs if args.dependents else matching_specs
dangling_dependents = dependent_specs and not args.dependents
has_error = not args.force and dangling_dependents
if has_error:
tty.info("Refusing to deconcretize the following specs")
spack.cmd.display_specs(matching_specs, **display_args)
print()
tty.info("The following dependents are still concrete in the environment:")
spack.cmd.display_specs(dependent_specs, **display_args)
tty.die(
"There are still dependents.",
"use `spack deconcretize --dependents` to deconcretize dependents too",
"use `spack deconcretize --force` to override",
)
return to_deconcretize
def deconcretize_specs(args, specs):
env = spack.cmd.require_active_env(cmd_name="deconcretize")
deconcretize_list = get_deconcretize_list(args, specs, env)
if not deconcretize_list:
tty.warn("There are no concrete specs to deconcretize.")
return
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)

View File

@ -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)

View File

@ -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(

View File

@ -1308,7 +1308,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":
@ -1323,11 +1323,29 @@ class Environment:
msg = "concretization strategy not implemented [{0}]"
raise SpackEnvironmentError(msg.format(self.unify))
def deconcretize(self, spec):
def deconcretize(self, spec, concrete=True):
# If concrete, find all instances of concrete spec
# Else, find single instance of user spec
# the distinction is only relevant if multiple roots concretize
# to the same spec
# 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()
indices = [
i
for i in range(len(self.concretized_order))
if self.concretized_order[i] == dag_hash
]
for index in indices:
del self.concretized_order[index]
del self.concretized_user_specs[index]
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: