"spack uninstall": don't modify env (#33711)
"spack install foo" no longer adds package "foo" to the environment (i.e. to the list of root specs) by default: you must specify "--add". Likewise "spack uninstall foo" no longer removes package "foo" from the environment: you must specify --remove. Generally this means that install/uninstall commands will no longer modify the users list of root specs (which many users found problematic: they had to deactivate an environment if they wanted to uninstall a spec without changing their spack.yaml description). In more detail: if you have environments e1 and e2, and specs [P, Q, R] such that P depends on R, Q depends on R, [P, R] are in e1, and [Q, R] are in e2: * `spack uninstall --dependents --remove r` in e1: removes R from e1 (but does not uninstall it) and uninstalls (and removes) P * `spack uninstall -f --dependents r` in e1: will uninstall P, Q, and R (i.e. e2 will have dependent specs uninstalled as a side effect) * `spack uninstall -f --dependents --remove r` in e1: this uninstalls P, Q, and R, and removes [P, R] from e1 * `spack uninstall -f --remove r` in e1: uninstalls R (so it is "missing" in both environments) and removes R from e1 (note that e1 would still install R as a dependency of P, but it would no longer be listed as a root spec) * `spack uninstall --dependents r` in e1: will fail because e2 needs R Individual unit tests were created for each of these scenarios.
This commit is contained in:
parent
84a3d32aa3
commit
1a3415619e
@ -531,7 +531,6 @@ def ci_rebuild(args):
|
|||||||
slash_hash = "/{}".format(job_spec.dag_hash())
|
slash_hash = "/{}".format(job_spec.dag_hash())
|
||||||
deps_install_args = install_args
|
deps_install_args = install_args
|
||||||
root_install_args = install_args + [
|
root_install_args = install_args + [
|
||||||
"--no-add",
|
|
||||||
"--keep-stage",
|
"--keep-stage",
|
||||||
"--only=package",
|
"--only=package",
|
||||||
"--use-buildcache=package:never,dependencies:only",
|
"--use-buildcache=package:never,dependencies:only",
|
||||||
|
@ -193,14 +193,22 @@ def setup_parser(subparser):
|
|||||||
default=False,
|
default=False,
|
||||||
help="(with environment) only install already concretized specs",
|
help="(with environment) only install already concretized specs",
|
||||||
)
|
)
|
||||||
subparser.add_argument(
|
|
||||||
"--no-add",
|
updateenv_group = subparser.add_mutually_exclusive_group()
|
||||||
|
updateenv_group.add_argument(
|
||||||
|
"--add",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help="""(with environment) partially install an environment, limiting
|
help="""(with environment) add spec to the environment as a root.""",
|
||||||
to concrete specs in the environment matching the arguments.
|
|
||||||
Non-roots remain installed implicitly.""",
|
|
||||||
)
|
)
|
||||||
|
updateenv_group.add_argument(
|
||||||
|
"--no-add",
|
||||||
|
action="store_false",
|
||||||
|
dest="add",
|
||||||
|
help="""(with environment) do not add spec to the environment as a
|
||||||
|
root (the default behavior).""",
|
||||||
|
)
|
||||||
|
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
"-f",
|
"-f",
|
||||||
"--file",
|
"--file",
|
||||||
@ -289,11 +297,12 @@ def install_specs_inside_environment(specs, install_kwargs, cli_args):
|
|||||||
# the matches. Getting to this point means there were either
|
# the matches. Getting to this point means there were either
|
||||||
# no matches or exactly one match.
|
# no matches or exactly one match.
|
||||||
|
|
||||||
if not m_spec and cli_args.no_add:
|
if not m_spec and not cli_args.add:
|
||||||
msg = (
|
msg = (
|
||||||
"You asked to install {0} without adding it (--no-add), but no such spec "
|
"Cannot install '{0}' because it is not in the current environment."
|
||||||
"exists in environment"
|
" You can add it to the environment with 'spack add {0}', or as part"
|
||||||
).format(abstract.name)
|
" of the install command with 'spack install --add {0}'"
|
||||||
|
).format(str(abstract))
|
||||||
tty.die(msg)
|
tty.die(msg)
|
||||||
|
|
||||||
if not m_spec:
|
if not m_spec:
|
||||||
@ -303,14 +312,16 @@ def install_specs_inside_environment(specs, install_kwargs, cli_args):
|
|||||||
|
|
||||||
tty.debug("exactly one match for {0} in env -> {1}".format(m_spec.name, m_spec.dag_hash()))
|
tty.debug("exactly one match for {0} in env -> {1}".format(m_spec.name, m_spec.dag_hash()))
|
||||||
|
|
||||||
if m_spec in env.roots() or cli_args.no_add:
|
if m_spec in env.roots() or not cli_args.add:
|
||||||
# either the single match is a root spec (and --no-add is
|
# either the single match is a root spec (in which case
|
||||||
# the default for roots) or --no-add was stated explicitly
|
# the spec is not added to the env again), or the user did
|
||||||
|
# not specify --add (in which case it is assumed we are
|
||||||
|
# installing already-concretized specs in the env)
|
||||||
tty.debug("just install {0}".format(m_spec.name))
|
tty.debug("just install {0}".format(m_spec.name))
|
||||||
specs_to_install.append(m_spec)
|
specs_to_install.append(m_spec)
|
||||||
else:
|
else:
|
||||||
# the single match is not a root (i.e. it's a dependency),
|
# the single match is not a root (i.e. it's a dependency),
|
||||||
# and --no-add was not specified, so we'll add it as a
|
# and --add was specified, so we'll add it as a
|
||||||
# root before installing
|
# root before installing
|
||||||
tty.debug("add {0} then install it".format(m_spec.name))
|
tty.debug("add {0} then install it".format(m_spec.name))
|
||||||
specs_to_add.append((abstract, concrete))
|
specs_to_add.append((abstract, concrete))
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import itertools
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from llnl.util import tty
|
from llnl.util import tty
|
||||||
@ -61,6 +60,13 @@ def setup_parser(subparser):
|
|||||||
dest="force",
|
dest="force",
|
||||||
help="remove regardless of whether other packages or environments " "depend on this one",
|
help="remove regardless of whether other packages or environments " "depend on this one",
|
||||||
)
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--remove",
|
||||||
|
action="store_true",
|
||||||
|
dest="remove",
|
||||||
|
help="if in an environment, then the spec should also be removed from "
|
||||||
|
"the environment description",
|
||||||
|
)
|
||||||
arguments.add_common_arguments(
|
arguments.add_common_arguments(
|
||||||
subparser, ["recurse_dependents", "yes_to_all", "installed_specs"]
|
subparser, ["recurse_dependents", "yes_to_all", "installed_specs"]
|
||||||
)
|
)
|
||||||
@ -134,13 +140,21 @@ def installed_dependents(specs, env):
|
|||||||
env (spack.environment.Environment or None): the active environment, or None
|
env (spack.environment.Environment or None): the active environment, or None
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: two mappings: one from specs to their dependent environments in the
|
tuple: two mappings: one from specs to their dependent installs in the
|
||||||
active environment (or global scope if there is no environment), and one from
|
active environment, and one from specs to dependent installs outside of
|
||||||
specs to their dependents in *inactive* environments (empty if there is no
|
the active environment.
|
||||||
environment
|
|
||||||
|
Any of the input specs may appear in both mappings (if there are
|
||||||
|
dependents both inside and outside the current environment).
|
||||||
|
|
||||||
|
If a dependent spec is used both by the active environment and by
|
||||||
|
an inactive environment, it will only appear in the first mapping.
|
||||||
|
|
||||||
|
If there is not current active environment, the first mapping will be
|
||||||
|
empty.
|
||||||
"""
|
"""
|
||||||
active_dpts = {}
|
active_dpts = {}
|
||||||
inactive_dpts = {}
|
outside_dpts = {}
|
||||||
|
|
||||||
env_hashes = set(env.all_hashes()) if env else set()
|
env_hashes = set(env.all_hashes()) if env else set()
|
||||||
|
|
||||||
@ -153,12 +167,12 @@ def installed_dependents(specs, env):
|
|||||||
# dpts that are outside this environment
|
# dpts that are outside this environment
|
||||||
for dpt in installed:
|
for dpt in installed:
|
||||||
if dpt not in specs:
|
if dpt not in specs:
|
||||||
if not env or dpt.dag_hash() in env_hashes:
|
if dpt.dag_hash() in env_hashes:
|
||||||
active_dpts.setdefault(spec, set()).add(dpt)
|
active_dpts.setdefault(spec, set()).add(dpt)
|
||||||
else:
|
else:
|
||||||
inactive_dpts.setdefault(spec, set()).add(dpt)
|
outside_dpts.setdefault(spec, set()).add(dpt)
|
||||||
|
|
||||||
return active_dpts, inactive_dpts
|
return active_dpts, outside_dpts
|
||||||
|
|
||||||
|
|
||||||
def dependent_environments(specs):
|
def dependent_environments(specs):
|
||||||
@ -262,31 +276,65 @@ def is_ready(dag_hash):
|
|||||||
|
|
||||||
|
|
||||||
def get_uninstall_list(args, specs, env):
|
def get_uninstall_list(args, specs, env):
|
||||||
# Gets the list of installed specs that match the ones give via cli
|
"""Returns uninstall_list and remove_list: these may overlap (some things
|
||||||
|
may be both uninstalled and removed from the current environment).
|
||||||
|
|
||||||
|
It is assumed we are in an environment if --remove is specified (this
|
||||||
|
method raises an exception otherwise).
|
||||||
|
|
||||||
|
uninstall_list is topologically sorted: dependents come before
|
||||||
|
dependencies (so if a user uninstalls specs in the order provided,
|
||||||
|
the dependents will always be uninstalled first).
|
||||||
|
"""
|
||||||
|
if args.remove and not env:
|
||||||
|
raise ValueError("Can only use --remove when in an environment")
|
||||||
|
|
||||||
|
# Gets the list of installed specs that match the ones given via cli
|
||||||
# args.all takes care of the case where '-a' is given in the cli
|
# args.all takes care of the case where '-a' is given in the cli
|
||||||
uninstall_list = find_matching_specs(env, specs, args.all, args.force, args.origin)
|
base_uninstall_specs = set(find_matching_specs(env, specs, args.all, args.force))
|
||||||
|
|
||||||
# Takes care of '-R'
|
active_dpts, outside_dpts = installed_dependents(base_uninstall_specs, env)
|
||||||
active_dpts, inactive_dpts = installed_dependents(uninstall_list, env)
|
# It will be useful to track the unified set of specs with dependents, as
|
||||||
|
# well as to separately track specs in the current env with dependents
|
||||||
|
spec_to_dpts = {}
|
||||||
|
for spec, dpts in active_dpts.items():
|
||||||
|
spec_to_dpts[spec] = list(dpts)
|
||||||
|
for spec, dpts in outside_dpts.items():
|
||||||
|
if spec in spec_to_dpts:
|
||||||
|
spec_to_dpts[spec].extend(dpts)
|
||||||
|
else:
|
||||||
|
spec_to_dpts[spec] = list(dpts)
|
||||||
|
|
||||||
# if we are in the global scope, we complain if you try to remove a
|
all_uninstall_specs = set(base_uninstall_specs)
|
||||||
# spec that's in an environment. If we're in an environment, we'll
|
if args.dependents:
|
||||||
# just *remove* it from the environment, so we ignore this
|
for spec, lst in active_dpts.items():
|
||||||
# error when *in* an environment
|
all_uninstall_specs.update(lst)
|
||||||
spec_envs = dependent_environments(uninstall_list)
|
for spec, lst in outside_dpts.items():
|
||||||
spec_envs = inactive_dependent_environments(spec_envs)
|
all_uninstall_specs.update(lst)
|
||||||
|
|
||||||
# Process spec_dependents and update uninstall_list
|
# For each spec that we intend to uninstall, this tracks the set of
|
||||||
has_error = not args.force and (
|
# environments outside the current active environment which depend on the
|
||||||
(active_dpts and not args.dependents) # dependents in the current env
|
# spec. There may be environments not managed directly with Spack: such
|
||||||
or (not env and spec_envs) # there are environments that need specs
|
# environments would not be included here.
|
||||||
|
spec_to_other_envs = inactive_dependent_environments(
|
||||||
|
dependent_environments(all_uninstall_specs)
|
||||||
|
)
|
||||||
|
|
||||||
|
has_error = not args.force and (
|
||||||
|
# There are dependents in the current env and we didn't ask to remove
|
||||||
|
# dependents
|
||||||
|
(spec_to_dpts and not args.dependents)
|
||||||
|
# An environment different than the current env (if any) depends on
|
||||||
|
# one or more of the specs to be uninstalled. There may also be
|
||||||
|
# packages in those envs which depend on the base set of packages
|
||||||
|
# to uninstall, but this covers that scenario.
|
||||||
|
or (not args.remove and spec_to_other_envs)
|
||||||
)
|
)
|
||||||
|
|
||||||
# say why each problem spec is needed
|
|
||||||
if has_error:
|
if has_error:
|
||||||
specs = set(active_dpts)
|
# say why each problem spec is needed
|
||||||
if not env:
|
specs = set(spec_to_dpts)
|
||||||
specs.update(set(spec_envs)) # environments depend on this
|
specs.update(set(spec_to_other_envs)) # environments depend on this
|
||||||
|
|
||||||
for i, spec in enumerate(sorted(specs)):
|
for i, spec in enumerate(sorted(specs)):
|
||||||
# space out blocks of reasons
|
# space out blocks of reasons
|
||||||
@ -296,66 +344,86 @@ def get_uninstall_list(args, specs, env):
|
|||||||
spec_format = "{name}{@version}{%compiler}{/hash:7}"
|
spec_format = "{name}{@version}{%compiler}{/hash:7}"
|
||||||
tty.info("Will not uninstall %s" % spec.cformat(spec_format), format="*r")
|
tty.info("Will not uninstall %s" % spec.cformat(spec_format), format="*r")
|
||||||
|
|
||||||
dependents = active_dpts.get(spec)
|
dependents = spec_to_dpts.get(spec)
|
||||||
if dependents:
|
if dependents and not args.dependents:
|
||||||
print("The following packages depend on it:")
|
print("The following packages depend on it:")
|
||||||
spack.cmd.display_specs(dependents, **display_args)
|
spack.cmd.display_specs(dependents, **display_args)
|
||||||
|
|
||||||
if not env:
|
envs = spec_to_other_envs.get(spec)
|
||||||
envs = spec_envs.get(spec)
|
|
||||||
if envs:
|
if envs:
|
||||||
print("It is used by the following environments:")
|
if env:
|
||||||
|
env_context_qualifier = " other"
|
||||||
|
else:
|
||||||
|
env_context_qualifier = ""
|
||||||
|
print("It is used by the following{0} environments:".format(env_context_qualifier))
|
||||||
colify([e.name for e in envs], indent=4)
|
colify([e.name for e in envs], indent=4)
|
||||||
|
|
||||||
msgs = []
|
msgs = []
|
||||||
if active_dpts:
|
if spec_to_dpts and not args.dependents:
|
||||||
msgs.append("use `spack uninstall --dependents` to remove dependents too")
|
msgs.append("use `spack uninstall --dependents` to remove dependents too")
|
||||||
if spec_envs:
|
if spec_to_other_envs:
|
||||||
msgs.append("use `spack env remove` to remove from environments")
|
msgs.append("use `spack env remove` to remove from environments")
|
||||||
print()
|
print()
|
||||||
tty.die("There are still dependents.", *msgs)
|
tty.die("There are still dependents.", *msgs)
|
||||||
|
|
||||||
elif args.dependents:
|
# If we are in an environment, this will track specs in this environment
|
||||||
|
# which should only be removed from the environment rather than uninstalled
|
||||||
|
remove_only = set()
|
||||||
|
if args.remove and not args.force:
|
||||||
|
remove_only.update(spec_to_other_envs)
|
||||||
|
if remove_only:
|
||||||
|
tty.info(
|
||||||
|
"The following specs will be removed but not uninstalled because"
|
||||||
|
" they are also used by another environment: {speclist}".format(
|
||||||
|
speclist=", ".join(x.name for x in remove_only)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute the set of specs that should be removed from the current env.
|
||||||
|
# This may overlap (some specs may be uninstalled and also removed from
|
||||||
|
# the current environment).
|
||||||
|
if args.remove:
|
||||||
|
remove_specs = set(base_uninstall_specs)
|
||||||
|
if args.dependents:
|
||||||
|
# Any spec matched from the cli, or dependent of, should be removed
|
||||||
|
# from the environment
|
||||||
for spec, lst in active_dpts.items():
|
for spec, lst in active_dpts.items():
|
||||||
uninstall_list.extend(lst)
|
remove_specs.update(lst)
|
||||||
uninstall_list = list(set(uninstall_list))
|
else:
|
||||||
|
remove_specs = set()
|
||||||
|
|
||||||
# only force-remove (don't completely uninstall) specs that still
|
all_uninstall_specs -= remove_only
|
||||||
# have external dependent envs or pkgs
|
# Inefficient topological sort: uninstall dependents before dependencies
|
||||||
removes = set(inactive_dpts)
|
all_uninstall_specs = sorted(
|
||||||
if env:
|
all_uninstall_specs, key=lambda x: sum(1 for i in x.traverse()), reverse=True
|
||||||
removes.update(spec_envs)
|
)
|
||||||
|
|
||||||
# remove anything in removes from the uninstall list
|
return list(all_uninstall_specs), list(remove_specs)
|
||||||
uninstall_list = set(uninstall_list) - removes
|
|
||||||
|
|
||||||
return uninstall_list, removes
|
|
||||||
|
|
||||||
|
|
||||||
def uninstall_specs(args, specs):
|
def uninstall_specs(args, specs):
|
||||||
env = ev.active_environment()
|
env = ev.active_environment()
|
||||||
|
|
||||||
uninstall_list, remove_list = get_uninstall_list(args, specs, env)
|
uninstall_list, remove_list = get_uninstall_list(args, specs, env)
|
||||||
anything_to_do = set(uninstall_list).union(set(remove_list))
|
|
||||||
|
|
||||||
if not anything_to_do:
|
if not uninstall_list:
|
||||||
tty.warn("There are no package to uninstall.")
|
tty.warn("There are no package to uninstall.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not args.yes_to_all:
|
if not args.yes_to_all:
|
||||||
confirm_removal(anything_to_do)
|
confirm_removal(uninstall_list)
|
||||||
|
|
||||||
if env:
|
|
||||||
# Remove all the specs that are supposed to be uninstalled or just
|
|
||||||
# removed.
|
|
||||||
with env.write_transaction():
|
|
||||||
for spec in itertools.chain(remove_list, uninstall_list):
|
|
||||||
_remove_from_env(spec, env)
|
|
||||||
env.write()
|
|
||||||
|
|
||||||
# Uninstall everything on the list
|
# Uninstall everything on the list
|
||||||
do_uninstall(env, uninstall_list, args.force)
|
do_uninstall(env, uninstall_list, args.force)
|
||||||
|
|
||||||
|
if env:
|
||||||
|
with env.write_transaction():
|
||||||
|
for spec in remove_list:
|
||||||
|
_remove_from_env(spec, env)
|
||||||
|
env.write()
|
||||||
|
|
||||||
|
env.regenerate_views()
|
||||||
|
|
||||||
|
|
||||||
def confirm_removal(specs):
|
def confirm_removal(specs):
|
||||||
"""Display the list of specs to be removed and ask for confirmation.
|
"""Display the list of specs to be removed and ask for confirmation.
|
||||||
|
@ -1011,7 +1011,6 @@ def mystrip(s):
|
|||||||
|
|
||||||
assert "--keep-stage" in install_parts
|
assert "--keep-stage" in install_parts
|
||||||
assert "--no-check-signature" not in install_parts
|
assert "--no-check-signature" not in install_parts
|
||||||
assert "--no-add" in install_parts
|
|
||||||
assert "-f" in install_parts
|
assert "-f" in install_parts
|
||||||
flag_index = install_parts.index("-f")
|
flag_index = install_parts.index("-f")
|
||||||
assert "archive-files.json" in install_parts[flag_index + 1]
|
assert "archive-files.json" in install_parts[flag_index + 1]
|
||||||
@ -1261,7 +1260,7 @@ def test_push_mirror_contents(
|
|||||||
with open(json_path, "w") as ypfd:
|
with open(json_path, "w") as ypfd:
|
||||||
ypfd.write(spec_json)
|
ypfd.write(spec_json)
|
||||||
|
|
||||||
install_cmd("--keep-stage", json_path)
|
install_cmd("--add", "--keep-stage", json_path)
|
||||||
|
|
||||||
# env, spec, json_path, mirror_url, build_id, sign_binaries
|
# env, spec, json_path, mirror_url, build_id, sign_binaries
|
||||||
ci.push_mirror_contents(env, json_path, mirror_url, True)
|
ci.push_mirror_contents(env, json_path, mirror_url, True)
|
||||||
@ -1623,7 +1622,7 @@ def test_ci_rebuild_index(
|
|||||||
with open(json_path, "w") as ypfd:
|
with open(json_path, "w") as ypfd:
|
||||||
ypfd.write(spec_json)
|
ypfd.write(spec_json)
|
||||||
|
|
||||||
install_cmd("--keep-stage", "-f", json_path)
|
install_cmd("--add", "--keep-stage", "-f", json_path)
|
||||||
buildcache_cmd("create", "-u", "-a", "-f", "--mirror-url", mirror_url, "callpath")
|
buildcache_cmd("create", "-u", "-a", "-f", "--mirror-url", mirror_url, "callpath")
|
||||||
ci_cmd("rebuild-index")
|
ci_cmd("rebuild-index")
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ def test_env_install_single_spec(install_mockery, mock_fetch):
|
|||||||
|
|
||||||
e = ev.read("test")
|
e = ev.read("test")
|
||||||
with e:
|
with e:
|
||||||
install("cmake-client")
|
install("--add", "cmake-client")
|
||||||
|
|
||||||
e = ev.read("test")
|
e = ev.read("test")
|
||||||
assert e.user_specs[0].name == "cmake-client"
|
assert e.user_specs[0].name == "cmake-client"
|
||||||
@ -256,7 +256,7 @@ def test_env_modifications_error_on_activate(install_mockery, mock_fetch, monkey
|
|||||||
|
|
||||||
e = ev.read("test")
|
e = ev.read("test")
|
||||||
with e:
|
with e:
|
||||||
install("cmake-client")
|
install("--add", "cmake-client")
|
||||||
|
|
||||||
def setup_error(pkg, env):
|
def setup_error(pkg, env):
|
||||||
raise RuntimeError("cmake-client had issues!")
|
raise RuntimeError("cmake-client had issues!")
|
||||||
@ -277,7 +277,7 @@ def test_activate_adds_transitive_run_deps_to_path(install_mockery, mock_fetch,
|
|||||||
|
|
||||||
e = ev.read("test")
|
e = ev.read("test")
|
||||||
with e:
|
with e:
|
||||||
install("depends-on-run-env")
|
install("--add", "depends-on-run-env")
|
||||||
|
|
||||||
env_variables = {}
|
env_variables = {}
|
||||||
spack.environment.shell.activate(e).apply_modifications(env_variables)
|
spack.environment.shell.activate(e).apply_modifications(env_variables)
|
||||||
@ -290,7 +290,7 @@ def test_env_install_same_spec_twice(install_mockery, mock_fetch):
|
|||||||
e = ev.read("test")
|
e = ev.read("test")
|
||||||
with e:
|
with e:
|
||||||
# The first installation outputs the package prefix, updates the view
|
# The first installation outputs the package prefix, updates the view
|
||||||
out = install("cmake-client")
|
out = install("--add", "cmake-client")
|
||||||
assert "Updating view at" in out
|
assert "Updating view at" in out
|
||||||
|
|
||||||
# The second installation reports all packages already installed
|
# The second installation reports all packages already installed
|
||||||
@ -449,7 +449,7 @@ def test_env_status_broken_view(
|
|||||||
):
|
):
|
||||||
env_dir = str(tmpdir)
|
env_dir = str(tmpdir)
|
||||||
with ev.Environment(env_dir):
|
with ev.Environment(env_dir):
|
||||||
install("trivial-install-test-package")
|
install("--add", "trivial-install-test-package")
|
||||||
|
|
||||||
# switch to a new repo that doesn't include the installed package
|
# switch to a new repo that doesn't include the installed package
|
||||||
# test that Spack detects the missing package and warns the user
|
# test that Spack detects the missing package and warns the user
|
||||||
@ -468,7 +468,7 @@ def test_env_activate_broken_view(
|
|||||||
mutable_mock_env_path, mock_archive, mock_fetch, mock_custom_repository, install_mockery
|
mutable_mock_env_path, mock_archive, mock_fetch, mock_custom_repository, install_mockery
|
||||||
):
|
):
|
||||||
with ev.create("test"):
|
with ev.create("test"):
|
||||||
install("trivial-install-test-package")
|
install("--add", "trivial-install-test-package")
|
||||||
|
|
||||||
# switch to a new repo that doesn't include the installed package
|
# switch to a new repo that doesn't include the installed package
|
||||||
# test that Spack detects the missing package and fails gracefully
|
# test that Spack detects the missing package and fails gracefully
|
||||||
@ -1057,7 +1057,9 @@ def test_roots_display_with_variants():
|
|||||||
assert "boost +shared" in out
|
assert "boost +shared" in out
|
||||||
|
|
||||||
|
|
||||||
def test_uninstall_removes_from_env(mock_stage, mock_fetch, install_mockery):
|
def test_uninstall_keeps_in_env(mock_stage, mock_fetch, install_mockery):
|
||||||
|
# 'spack uninstall' without --remove should not change the environment
|
||||||
|
# spack.yaml file, just uninstall specs
|
||||||
env("create", "test")
|
env("create", "test")
|
||||||
with ev.read("test"):
|
with ev.read("test"):
|
||||||
add("mpileaks")
|
add("mpileaks")
|
||||||
@ -1065,12 +1067,32 @@ def test_uninstall_removes_from_env(mock_stage, mock_fetch, install_mockery):
|
|||||||
install("--fake")
|
install("--fake")
|
||||||
|
|
||||||
test = ev.read("test")
|
test = ev.read("test")
|
||||||
assert any(s.name == "mpileaks" for s in test.specs_by_hash.values())
|
# Save this spec to check later if it is still in the env
|
||||||
assert any(s.name == "libelf" for s in test.specs_by_hash.values())
|
(mpileaks_hash,) = list(x for x, y in test.specs_by_hash.items() if y.name == "mpileaks")
|
||||||
|
orig_user_specs = test.user_specs
|
||||||
|
orig_concretized_specs = test.concretized_order
|
||||||
|
|
||||||
with ev.read("test"):
|
with ev.read("test"):
|
||||||
uninstall("-ya")
|
uninstall("-ya")
|
||||||
|
|
||||||
|
test = ev.read("test")
|
||||||
|
assert test.concretized_order == orig_concretized_specs
|
||||||
|
assert test.user_specs.specs == orig_user_specs.specs
|
||||||
|
assert mpileaks_hash in test.specs_by_hash
|
||||||
|
assert not test.specs_by_hash[mpileaks_hash].package.installed
|
||||||
|
|
||||||
|
|
||||||
|
def test_uninstall_removes_from_env(mock_stage, mock_fetch, install_mockery):
|
||||||
|
# 'spack uninstall --remove' should update the environment
|
||||||
|
env("create", "test")
|
||||||
|
with ev.read("test"):
|
||||||
|
add("mpileaks")
|
||||||
|
add("libelf")
|
||||||
|
install("--fake")
|
||||||
|
|
||||||
|
with ev.read("test"):
|
||||||
|
uninstall("-y", "-a", "--remove")
|
||||||
|
|
||||||
test = ev.read("test")
|
test = ev.read("test")
|
||||||
assert not test.specs_by_hash
|
assert not test.specs_by_hash
|
||||||
assert not test.concretized_order
|
assert not test.concretized_order
|
||||||
@ -1256,7 +1278,7 @@ def test_env_updates_view_install_package(tmpdir, mock_stage, mock_fetch, instal
|
|||||||
view_dir = tmpdir.join("view")
|
view_dir = tmpdir.join("view")
|
||||||
env("create", "--with-view=%s" % view_dir, "test")
|
env("create", "--with-view=%s" % view_dir, "test")
|
||||||
with ev.read("test"):
|
with ev.read("test"):
|
||||||
install("--fake", "mpileaks")
|
install("--fake", "--add", "mpileaks")
|
||||||
|
|
||||||
assert os.path.exists(str(view_dir.join(".spack/mpileaks")))
|
assert os.path.exists(str(view_dir.join(".spack/mpileaks")))
|
||||||
|
|
||||||
@ -1276,7 +1298,7 @@ def test_env_updates_view_uninstall(tmpdir, mock_stage, mock_fetch, install_mock
|
|||||||
view_dir = tmpdir.join("view")
|
view_dir = tmpdir.join("view")
|
||||||
env("create", "--with-view=%s" % view_dir, "test")
|
env("create", "--with-view=%s" % view_dir, "test")
|
||||||
with ev.read("test"):
|
with ev.read("test"):
|
||||||
install("--fake", "mpileaks")
|
install("--fake", "--add", "mpileaks")
|
||||||
|
|
||||||
check_mpileaks_and_deps_in_view(view_dir)
|
check_mpileaks_and_deps_in_view(view_dir)
|
||||||
|
|
||||||
@ -1325,7 +1347,7 @@ def test_env_updates_view_force_remove(tmpdir, mock_stage, mock_fetch, install_m
|
|||||||
view_dir = tmpdir.join("view")
|
view_dir = tmpdir.join("view")
|
||||||
env("create", "--with-view=%s" % view_dir, "test")
|
env("create", "--with-view=%s" % view_dir, "test")
|
||||||
with ev.read("test"):
|
with ev.read("test"):
|
||||||
install("--fake", "mpileaks")
|
install("--add", "--fake", "mpileaks")
|
||||||
|
|
||||||
check_mpileaks_and_deps_in_view(view_dir)
|
check_mpileaks_and_deps_in_view(view_dir)
|
||||||
|
|
||||||
|
@ -356,7 +356,7 @@ def test_find_prefix_in_env(
|
|||||||
"""Test `find` formats requiring concrete specs work in environments."""
|
"""Test `find` formats requiring concrete specs work in environments."""
|
||||||
env("create", "test")
|
env("create", "test")
|
||||||
with ev.read("test"):
|
with ev.read("test"):
|
||||||
install("mpileaks")
|
install("--add", "mpileaks")
|
||||||
find("-p")
|
find("-p")
|
||||||
find("-l")
|
find("-l")
|
||||||
find("-L")
|
find("-L")
|
||||||
|
@ -771,7 +771,7 @@ def test_install_only_dependencies_in_env(
|
|||||||
dep = Spec("dependency-install").concretized()
|
dep = Spec("dependency-install").concretized()
|
||||||
root = Spec("dependent-install").concretized()
|
root = Spec("dependent-install").concretized()
|
||||||
|
|
||||||
install("-v", "--only", "dependencies", "dependent-install")
|
install("-v", "--only", "dependencies", "--add", "dependent-install")
|
||||||
|
|
||||||
assert os.path.exists(dep.prefix)
|
assert os.path.exists(dep.prefix)
|
||||||
assert not os.path.exists(root.prefix)
|
assert not os.path.exists(root.prefix)
|
||||||
@ -800,7 +800,7 @@ def test_install_only_dependencies_of_all_in_env(
|
|||||||
|
|
||||||
|
|
||||||
def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock_env_path):
|
def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock_env_path):
|
||||||
# To test behavior of --no-add option, we create the following environment:
|
# To test behavior of --add option, we create the following environment:
|
||||||
#
|
#
|
||||||
# mpileaks
|
# mpileaks
|
||||||
# ^callpath
|
# ^callpath
|
||||||
@ -849,18 +849,19 @@ def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock
|
|||||||
# Assert using --no-add with a spec not in the env fails
|
# Assert using --no-add with a spec not in the env fails
|
||||||
inst_out = install("--no-add", "boost", fail_on_error=False, output=str)
|
inst_out = install("--no-add", "boost", fail_on_error=False, output=str)
|
||||||
|
|
||||||
assert "no such spec exists in environment" in inst_out
|
assert "You can add it to the environment with 'spack add " in inst_out
|
||||||
|
|
||||||
# Ensure using --no-add with an ambiguous spec fails
|
# Without --add, ensure that install fails if the spec matches more
|
||||||
|
# than one root
|
||||||
with pytest.raises(ev.SpackEnvironmentError) as err:
|
with pytest.raises(ev.SpackEnvironmentError) as err:
|
||||||
inst_out = install("--no-add", "a", output=str)
|
inst_out = install("a", output=str)
|
||||||
|
|
||||||
assert "a matches multiple specs in the env" in str(err)
|
assert "a matches multiple specs in the env" in str(err)
|
||||||
|
|
||||||
# With "--no-add", install an unambiguous dependency spec (that already
|
# Install an unambiguous dependency spec (that already exists as a dep
|
||||||
# exists as a dep in the environment) using --no-add and make sure it
|
# in the environment) and make sure it gets installed (w/ deps),
|
||||||
# gets installed (w/ deps), but is not added to the environment.
|
# but is not added to the environment.
|
||||||
install("--no-add", "dyninst")
|
install("dyninst")
|
||||||
|
|
||||||
find_output = find("-l", output=str)
|
find_output = find("-l", output=str)
|
||||||
assert "dyninst" in find_output
|
assert "dyninst" in find_output
|
||||||
@ -872,31 +873,30 @@ def test_install_no_add_in_env(tmpdir, mock_fetch, install_mockery, mutable_mock
|
|||||||
assert all([s in env_specs for s in post_install_specs])
|
assert all([s in env_specs for s in post_install_specs])
|
||||||
|
|
||||||
# Make sure we can install a concrete dependency spec from a spec.json
|
# Make sure we can install a concrete dependency spec from a spec.json
|
||||||
# file on disk, using the ``--no-add` option, and the spec is installed
|
# file on disk, and the spec is installed but not added as a root
|
||||||
# but not added as a root
|
|
||||||
mpi_spec_json_path = tmpdir.join("{0}.json".format(mpi_spec.name))
|
mpi_spec_json_path = tmpdir.join("{0}.json".format(mpi_spec.name))
|
||||||
with open(mpi_spec_json_path.strpath, "w") as fd:
|
with open(mpi_spec_json_path.strpath, "w") as fd:
|
||||||
fd.write(mpi_spec.to_json(hash=ht.dag_hash))
|
fd.write(mpi_spec.to_json(hash=ht.dag_hash))
|
||||||
|
|
||||||
install("--no-add", "-f", mpi_spec_json_path.strpath)
|
install("-f", mpi_spec_json_path.strpath)
|
||||||
assert mpi_spec not in e.roots()
|
assert mpi_spec not in e.roots()
|
||||||
|
|
||||||
find_output = find("-l", output=str)
|
find_output = find("-l", output=str)
|
||||||
assert mpi_spec.name in find_output
|
assert mpi_spec.name in find_output
|
||||||
|
|
||||||
# Without "--no-add", install an unambiguous depependency spec (that
|
# Install an unambiguous depependency spec (that already exists as a
|
||||||
# already exists as a dep in the environment) without --no-add and make
|
# dep in the environment) with --add and make sure it is added as a
|
||||||
# sure it is added as a root of the environment as well as installed.
|
# root of the environment as well as installed.
|
||||||
assert b_spec not in e.roots()
|
assert b_spec not in e.roots()
|
||||||
|
|
||||||
install("b")
|
install("--add", "b")
|
||||||
|
|
||||||
assert b_spec in e.roots()
|
assert b_spec in e.roots()
|
||||||
assert b_spec not in e.uninstalled_specs()
|
assert b_spec not in e.uninstalled_specs()
|
||||||
|
|
||||||
# Without "--no-add", install a novel spec and make sure it is added
|
# Install a novel spec with --add and make sure it is added as a root
|
||||||
# as a root and installed.
|
# and installed.
|
||||||
install("bowtie")
|
install("--add", "bowtie")
|
||||||
|
|
||||||
assert any([s.name == "bowtie" for s in e.roots()])
|
assert any([s.name == "bowtie" for s in e.roots()])
|
||||||
assert not any([s.name == "bowtie" for s in e.uninstalled_specs()])
|
assert not any([s.name == "bowtie" for s in e.uninstalled_specs()])
|
||||||
|
@ -3,10 +3,13 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
|
||||||
|
import spack.environment
|
||||||
import spack.store
|
import spack.store
|
||||||
from spack.main import SpackCommand, SpackCommandError
|
from spack.main import SpackCommand, SpackCommandError
|
||||||
|
|
||||||
@ -166,3 +169,187 @@ def _warn(*args, **kwargs):
|
|||||||
monkeypatch.setattr(tty, "warn", _warn)
|
monkeypatch.setattr(tty, "warn", _warn)
|
||||||
# Now try to uninstall and check this doesn't trigger warnings
|
# Now try to uninstall and check this doesn't trigger warnings
|
||||||
uninstall("-y", "-a")
|
uninstall("-y", "-a")
|
||||||
|
|
||||||
|
|
||||||
|
# Note: I want to use https://docs.pytest.org/en/7.1.x/how-to/skipping.html#skip-all-test-functions-of-a-class-or-module
|
||||||
|
# the style formatter insists on separating these two lines.
|
||||||
|
pytest.mark.skipif(sys.platform == "win32", reason="Envs unsupported on Windows")
|
||||||
|
|
||||||
|
|
||||||
|
class TestUninstallFromEnv(object):
|
||||||
|
"""Tests an installation with two environments e1 and e2, which each have
|
||||||
|
shared package installations:
|
||||||
|
|
||||||
|
e1 has dt-diamond-left -> dt-diamond-bottom
|
||||||
|
|
||||||
|
e2 has dt-diamond-right -> dt-diamond-bottom
|
||||||
|
"""
|
||||||
|
|
||||||
|
env = SpackCommand("env")
|
||||||
|
add = SpackCommand("add")
|
||||||
|
concretize = SpackCommand("concretize")
|
||||||
|
find = SpackCommand("find")
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def environment_setup(
|
||||||
|
self, mutable_mock_env_path, config, mock_packages, mutable_database, install_mockery
|
||||||
|
):
|
||||||
|
TestUninstallFromEnv.env("create", "e1")
|
||||||
|
e1 = spack.environment.read("e1")
|
||||||
|
with e1:
|
||||||
|
TestUninstallFromEnv.add("dt-diamond-left")
|
||||||
|
TestUninstallFromEnv.add("dt-diamond-bottom")
|
||||||
|
TestUninstallFromEnv.concretize()
|
||||||
|
install("--fake")
|
||||||
|
|
||||||
|
TestUninstallFromEnv.env("create", "e2")
|
||||||
|
e2 = spack.environment.read("e2")
|
||||||
|
with e2:
|
||||||
|
TestUninstallFromEnv.add("dt-diamond-right")
|
||||||
|
TestUninstallFromEnv.add("dt-diamond-bottom")
|
||||||
|
TestUninstallFromEnv.concretize()
|
||||||
|
install("--fake")
|
||||||
|
|
||||||
|
def test_basic_env_sanity(self, environment_setup):
|
||||||
|
for env_name in ["e1", "e2"]:
|
||||||
|
e = spack.environment.read(env_name)
|
||||||
|
with e:
|
||||||
|
for _, concretized_spec in e.concretized_specs():
|
||||||
|
assert concretized_spec.package.installed
|
||||||
|
|
||||||
|
def test_uninstall_force_dependency_shared_between_envs(self, environment_setup):
|
||||||
|
"""If you "spack uninstall -f --dependents dt-diamond-bottom" from
|
||||||
|
e1, then all packages should be uninstalled (but not removed) from
|
||||||
|
both e1 and e2.
|
||||||
|
"""
|
||||||
|
e1 = spack.environment.read("e1")
|
||||||
|
with e1:
|
||||||
|
uninstall("-f", "-y", "--dependents", "dt-diamond-bottom")
|
||||||
|
|
||||||
|
# The specs should still be in the environment, since
|
||||||
|
# --remove was not specified
|
||||||
|
assert set(root.name for (root, _) in e1.concretized_specs()) == set(
|
||||||
|
["dt-diamond-left", "dt-diamond-bottom"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, concretized_spec in e1.concretized_specs():
|
||||||
|
assert not concretized_spec.package.installed
|
||||||
|
|
||||||
|
# Everything in e2 depended on dt-diamond-bottom, so should also
|
||||||
|
# have been uninstalled. The roots should be unchanged though.
|
||||||
|
e2 = spack.environment.read("e2")
|
||||||
|
with e2:
|
||||||
|
assert set(root.name for (root, _) in e2.concretized_specs()) == set(
|
||||||
|
["dt-diamond-right", "dt-diamond-bottom"]
|
||||||
|
)
|
||||||
|
for _, concretized_spec in e2.concretized_specs():
|
||||||
|
assert not concretized_spec.package.installed
|
||||||
|
|
||||||
|
def test_uninstall_remove_dependency_shared_between_envs(self, environment_setup):
|
||||||
|
"""If you "spack uninstall --dependents --remove dt-diamond-bottom" from
|
||||||
|
e1, then all packages are removed from e1 (it is now empty);
|
||||||
|
dt-diamond-left is also uninstalled (since only e1 needs it) but
|
||||||
|
dt-diamond-bottom is not uninstalled (since e2 needs it).
|
||||||
|
"""
|
||||||
|
e1 = spack.environment.read("e1")
|
||||||
|
with e1:
|
||||||
|
dtdiamondleft = next(
|
||||||
|
concrete
|
||||||
|
for (_, concrete) in e1.concretized_specs()
|
||||||
|
if concrete.name == "dt-diamond-left"
|
||||||
|
)
|
||||||
|
output = uninstall("-y", "--dependents", "--remove", "dt-diamond-bottom")
|
||||||
|
assert "The following specs will be removed but not uninstalled" in output
|
||||||
|
assert not list(e1.roots())
|
||||||
|
assert not dtdiamondleft.package.installed
|
||||||
|
|
||||||
|
# Since -f was not specified, all specs in e2 should still be installed
|
||||||
|
# (and e2 should be unchanged)
|
||||||
|
e2 = spack.environment.read("e2")
|
||||||
|
with e2:
|
||||||
|
assert set(root.name for (root, _) in e2.concretized_specs()) == set(
|
||||||
|
["dt-diamond-right", "dt-diamond-bottom"]
|
||||||
|
)
|
||||||
|
for _, concretized_spec in e2.concretized_specs():
|
||||||
|
assert concretized_spec.package.installed
|
||||||
|
|
||||||
|
def test_uninstall_dependency_shared_between_envs_fail(self, environment_setup):
|
||||||
|
"""If you "spack uninstall --dependents dt-diamond-bottom" from
|
||||||
|
e1 (without --remove or -f), then this should fail (this is needed by
|
||||||
|
e2).
|
||||||
|
"""
|
||||||
|
e1 = spack.environment.read("e1")
|
||||||
|
with e1:
|
||||||
|
output = uninstall("-y", "--dependents", "dt-diamond-bottom", fail_on_error=False)
|
||||||
|
assert "There are still dependents." in output
|
||||||
|
assert "use `spack env remove`" in output
|
||||||
|
|
||||||
|
# The environment should be unchanged and nothing should have been
|
||||||
|
# uninstalled
|
||||||
|
assert set(root.name for (root, _) in e1.concretized_specs()) == set(
|
||||||
|
["dt-diamond-left", "dt-diamond-bottom"]
|
||||||
|
)
|
||||||
|
for _, concretized_spec in e1.concretized_specs():
|
||||||
|
assert concretized_spec.package.installed
|
||||||
|
|
||||||
|
def test_uninstall_force_and_remove_dependency_shared_between_envs(self, environment_setup):
|
||||||
|
"""If you "spack uninstall -f --dependents --remove dt-diamond-bottom" from
|
||||||
|
e1, then all packages should be uninstalled and removed from e1.
|
||||||
|
All packages will also be uninstalled from e2, but the roots will
|
||||||
|
remain unchanged.
|
||||||
|
"""
|
||||||
|
e1 = spack.environment.read("e1")
|
||||||
|
with e1:
|
||||||
|
dtdiamondleft = next(
|
||||||
|
concrete
|
||||||
|
for (_, concrete) in e1.concretized_specs()
|
||||||
|
if concrete.name == "dt-diamond-left"
|
||||||
|
)
|
||||||
|
uninstall("-f", "-y", "--dependents", "--remove", "dt-diamond-bottom")
|
||||||
|
assert not list(e1.roots())
|
||||||
|
assert not dtdiamondleft.package.installed
|
||||||
|
|
||||||
|
e2 = spack.environment.read("e2")
|
||||||
|
with e2:
|
||||||
|
assert set(root.name for (root, _) in e2.concretized_specs()) == set(
|
||||||
|
["dt-diamond-right", "dt-diamond-bottom"]
|
||||||
|
)
|
||||||
|
for _, concretized_spec in e2.concretized_specs():
|
||||||
|
assert not concretized_spec.package.installed
|
||||||
|
|
||||||
|
def test_uninstall_keep_dependents_dependency_shared_between_envs(self, environment_setup):
|
||||||
|
"""If you "spack uninstall -f --remove dt-diamond-bottom" from
|
||||||
|
e1, then dt-diamond-bottom should be uninstalled, which leaves
|
||||||
|
"dangling" references in both environments, since
|
||||||
|
dt-diamond-left and dt-diamond-right both need it.
|
||||||
|
"""
|
||||||
|
e1 = spack.environment.read("e1")
|
||||||
|
with e1:
|
||||||
|
dtdiamondleft = next(
|
||||||
|
concrete
|
||||||
|
for (_, concrete) in e1.concretized_specs()
|
||||||
|
if concrete.name == "dt-diamond-left"
|
||||||
|
)
|
||||||
|
uninstall("-f", "-y", "--remove", "dt-diamond-bottom")
|
||||||
|
# dt-diamond-bottom was removed from the list of roots (note that
|
||||||
|
# it would still be installed since dt-diamond-left depends on it)
|
||||||
|
assert set(x.name for x in e1.roots()) == set(["dt-diamond-left"])
|
||||||
|
assert dtdiamondleft.package.installed
|
||||||
|
|
||||||
|
e2 = spack.environment.read("e2")
|
||||||
|
with e2:
|
||||||
|
assert set(root.name for (root, _) in e2.concretized_specs()) == set(
|
||||||
|
["dt-diamond-right", "dt-diamond-bottom"]
|
||||||
|
)
|
||||||
|
dtdiamondright = next(
|
||||||
|
concrete
|
||||||
|
for (_, concrete) in e2.concretized_specs()
|
||||||
|
if concrete.name == "dt-diamond-right"
|
||||||
|
)
|
||||||
|
assert dtdiamondright.package.installed
|
||||||
|
dtdiamondbottom = next(
|
||||||
|
concrete
|
||||||
|
for (_, concrete) in e2.concretized_specs()
|
||||||
|
if concrete.name == "dt-diamond-bottom"
|
||||||
|
)
|
||||||
|
assert not dtdiamondbottom.package.installed
|
||||||
|
@ -298,7 +298,7 @@ def test_modules_relative_to_view(
|
|||||||
):
|
):
|
||||||
with ev.Environment(str(tmpdir), with_view=True) as e:
|
with ev.Environment(str(tmpdir), with_view=True) as e:
|
||||||
module_configuration("with_view")
|
module_configuration("with_view")
|
||||||
install("cmake")
|
install("--add", "cmake")
|
||||||
|
|
||||||
spec = spack.spec.Spec("cmake").concretized()
|
spec = spack.spec.Spec("cmake").concretized()
|
||||||
|
|
||||||
|
@ -1203,7 +1203,7 @@ _spack_info() {
|
|||||||
_spack_install() {
|
_spack_install() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --fail-fast --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --use-buildcache --include-build-deps --no-check-signature --show-log-on-error --source -n --no-checksum --deprecated -v --verbose --fake --only-concrete --no-add -f --file --clean --dirty --test --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all -U --fresh --reuse"
|
SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --fail-fast --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --use-buildcache --include-build-deps --no-check-signature --show-log-on-error --source -n --no-checksum --deprecated -v --verbose --fake --only-concrete --add --no-add -f --file --clean --dirty --test --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all -U --fresh --reuse"
|
||||||
else
|
else
|
||||||
_all_packages
|
_all_packages
|
||||||
fi
|
fi
|
||||||
@ -1824,7 +1824,7 @@ _spack_undevelop() {
|
|||||||
_spack_uninstall() {
|
_spack_uninstall() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help -f --force -R --dependents -y --yes-to-all -a --all --origin"
|
SPACK_COMPREPLY="-h --help -f --force --remove -R --dependents -y --yes-to-all -a --all --origin"
|
||||||
else
|
else
|
||||||
_installed_packages
|
_installed_packages
|
||||||
fi
|
fi
|
||||||
|
Loading…
Reference in New Issue
Block a user