Add ability to rename environments (#43296)

This commit is contained in:
Kyle Knoepfel
2024-03-28 16:15:04 -05:00
committed by GitHub
parent 9f2451ddff
commit 5f9228746e
4 changed files with 239 additions and 5 deletions

View File

@@ -9,6 +9,7 @@
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Optional
import llnl.string as string
@@ -44,6 +45,7 @@
"deactivate",
"create",
["remove", "rm"],
["rename", "mv"],
["list", "ls"],
["status", "st"],
"loads",
@@ -472,11 +474,82 @@ def env_remove(args):
tty.msg(f"Successfully removed environment '{bad_env_name}'")
#
# env rename
#
def env_rename_setup_parser(subparser):
"""rename an existing environment"""
subparser.add_argument(
"mv_from", metavar="from", help="name (or path) of existing environment"
)
subparser.add_argument(
"mv_to", metavar="to", help="new name (or path) for existing environment"
)
subparser.add_argument(
"-d",
"--dir",
action="store_true",
help="the specified arguments correspond to directory paths",
)
subparser.add_argument(
"-f", "--force", action="store_true", help="allow overwriting of an existing environment"
)
def env_rename(args):
"""Rename an environment.
This renames a managed environment or moves an anonymous environment.
"""
# Directory option has been specified
if args.dir:
if not ev.is_env_dir(args.mv_from):
tty.die("The specified path does not correspond to a valid spack environment")
from_path = Path(args.mv_from)
if not args.force:
if ev.is_env_dir(args.mv_to):
tty.die(
"The new path corresponds to an existing environment;"
" specify the --force flag to overwrite it."
)
if Path(args.mv_to).exists():
tty.die("The new path already exists; specify the --force flag to overwrite it.")
to_path = Path(args.mv_to)
# Name option being used
elif ev.exists(args.mv_from):
from_path = ev.environment.environment_dir_from_name(args.mv_from)
if not args.force and ev.exists(args.mv_to):
tty.die(
"The new name corresponds to an existing environment;"
" specify the --force flag to overwrite it."
)
to_path = ev.environment.root(args.mv_to)
# Neither
else:
tty.die("The specified name does not correspond to a managed spack environment")
# Guard against renaming from or to an active environment
active_env = ev.active_environment()
if active_env:
from_env = ev.Environment(from_path)
if from_env.path == active_env.path:
tty.die("Cannot rename active environment")
if to_path == active_env.path:
tty.die(f"{args.mv_to} is an active environment")
shutil.rmtree(to_path, ignore_errors=True)
fs.rename(from_path, to_path)
tty.msg(f"Successfully renamed environment {args.mv_from} to {args.mv_to}")
#
# env list
#
def env_list_setup_parser(subparser):
"""list available environments"""
"""list managed environments"""
def env_list(args):

View File

@@ -188,6 +188,127 @@ def test_env_remove(capfd):
assert "bar" not in out
def test_env_rename_managed(capfd):
# Need real environment
with pytest.raises(spack.main.SpackCommandError):
env("rename", "foo", "bar")
assert (
"The specified name does not correspond to a managed spack environment"
in capfd.readouterr()[0]
)
env("create", "foo")
out = env("list")
assert "foo" in out
out = env("rename", "foo", "bar")
assert "Successfully renamed environment foo to bar" in out
out = env("list")
assert "foo" not in out
assert "bar" in out
bar = ev.read("bar")
with bar:
# Cannot rename active environment
with pytest.raises(spack.main.SpackCommandError):
env("rename", "bar", "baz")
assert "Cannot rename active environment" in capfd.readouterr()[0]
env("create", "qux")
# Cannot rename to an active environment (even with force flag)
with pytest.raises(spack.main.SpackCommandError):
env("rename", "-f", "qux", "bar")
assert "bar is an active environment" in capfd.readouterr()[0]
# Can rename inactive environment when another's active
out = env("rename", "qux", "quux")
assert "Successfully renamed environment qux to quux" in out
out = env("list")
assert "bar" in out
assert "baz" not in out
env("create", "baz")
# Cannot rename to existing environment without --force
with pytest.raises(spack.main.SpackCommandError):
env("rename", "bar", "baz")
errmsg = (
"The new name corresponds to an existing environment;"
" specify the --force flag to overwrite it."
)
assert errmsg in capfd.readouterr()[0]
env("rename", "-f", "bar", "baz")
out = env("list")
assert "bar" not in out
assert "baz" in out
def test_env_rename_anonymous(capfd, tmpdir):
# Need real environment
with pytest.raises(spack.main.SpackCommandError):
env("rename", "-d", "./non-existing", "./also-non-existing")
assert (
"The specified path does not correspond to a valid spack environment"
in capfd.readouterr()[0]
)
anon_foo = str(tmpdir / "foo")
env("create", "-d", anon_foo)
anon_bar = str(tmpdir / "bar")
out = env("rename", "-d", anon_foo, anon_bar)
assert f"Successfully renamed environment {anon_foo} to {anon_bar}" in out
assert not ev.is_env_dir(anon_foo)
assert ev.is_env_dir(anon_bar)
# Cannot rename active environment
anon_baz = str(tmpdir / "baz")
env("activate", "--sh", "-d", anon_bar)
with pytest.raises(spack.main.SpackCommandError):
env("rename", "-d", anon_bar, anon_baz)
assert "Cannot rename active environment" in capfd.readouterr()[0]
env("deactivate", "--sh")
assert ev.is_env_dir(anon_bar)
assert not ev.is_env_dir(anon_baz)
# Cannot rename to existing environment without --force
env("create", "-d", anon_baz)
with pytest.raises(spack.main.SpackCommandError):
env("rename", "-d", anon_bar, anon_baz)
errmsg = (
"The new path corresponds to an existing environment;"
" specify the --force flag to overwrite it."
)
assert errmsg in capfd.readouterr()[0]
assert ev.is_env_dir(anon_bar)
assert ev.is_env_dir(anon_baz)
env("rename", "-f", "-d", anon_bar, anon_baz)
assert not ev.is_env_dir(anon_bar)
assert ev.is_env_dir(anon_baz)
# Cannot rename to existing (non-environment) path without --force
qux = tmpdir / "qux"
qux.mkdir()
anon_qux = str(qux)
assert not ev.is_env_dir(anon_qux)
with pytest.raises(spack.main.SpackCommandError):
env("rename", "-d", anon_baz, anon_qux)
errmsg = "The new path already exists; specify the --force flag to overwrite it."
assert errmsg in capfd.readouterr()[0]
env("rename", "-f", "-d", anon_baz, anon_qux)
assert not ev.is_env_dir(anon_baz)
assert ev.is_env_dir(anon_qux)
def test_concretize():
e = ev.create("test")
e.add("mpileaks")
@@ -3133,7 +3254,7 @@ def test_create_and_activate_managed(tmp_path):
env("deactivate")
def test_create_and_activate_unmanaged(tmp_path):
def test_create_and_activate_anonymous(tmp_path):
with fs.working_dir(str(tmp_path)):
env_dir = os.path.join(str(tmp_path), "foo")
shell = env("activate", "--without-view", "--create", "--sh", "-d", env_dir)