Add --create to spack env activate (#40896)

Add `--create` option to `env activate` to allow users to create and activate in one command.


---------

Co-authored-by: Wouter Deconinck <wdconinc@gmail.com>
Co-authored-by: Tamara Dahlgren <35777542+tldahlgren@users.noreply.github.com>
Co-authored-by: psakievich <psakievich@users.noreply.github.com>
This commit is contained in:
psakievich 2024-01-10 16:57:45 -07:00 committed by GitHub
parent ec758bfd5b
commit 12963529af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 208 additions and 112 deletions

View File

@ -142,6 +142,21 @@ user's prompt to begin with the environment name in brackets.
$ spack env activate -p myenv $ spack env activate -p myenv
[myenv] $ ... [myenv] $ ...
The ``activate`` command can also be used to create a new environment, if it is
not already defined, by adding the ``--create`` flag. Managed and anonymous
environments, anonymous environments are explained in the next section,
can both be created using the same flags that `spack env create` accepts.
If an environment already exists then spack will simply activate it and ignore the
create specific flags.
.. code-block:: console
$ spack env activate --create -p myenv
# ...
# [creates if myenv does not exist yet]
# ...
[myenv] $ ...
To deactivate an environment, use the command: To deactivate an environment, use the command:
.. code-block:: console .. code-block:: console

View File

@ -54,6 +54,104 @@
] ]
#
# env create
#
def env_create_setup_parser(subparser):
"""create a new environment"""
subparser.add_argument(
"env_name",
metavar="env",
help=(
"name of managed environment or directory of the anonymous env "
"(when using --dir/-d) to activate"
),
)
subparser.add_argument(
"-d", "--dir", action="store_true", help="create an environment in a specific directory"
)
subparser.add_argument(
"--keep-relative",
action="store_true",
help="copy relative develop paths verbatim into the new environment"
" when initializing from envfile",
)
view_opts = subparser.add_mutually_exclusive_group()
view_opts.add_argument(
"--without-view", action="store_true", help="do not maintain a view for this environment"
)
view_opts.add_argument(
"--with-view",
help="specify that this environment should maintain a view at the"
" specified path (by default the view is maintained in the"
" environment directory)",
)
subparser.add_argument(
"envfile",
nargs="?",
default=None,
help="either a lockfile (must end with '.json' or '.lock') or a manifest file",
)
def env_create(args):
if args.with_view:
# Expand relative paths provided on the command line to the current working directory
# This way we interpret `spack env create --with-view ./view --dir ./env` as
# a view in $PWD/view, not $PWD/env/view. This is different from specifying a relative
# path in the manifest, which is resolved relative to the manifest file's location.
with_view = os.path.abspath(args.with_view)
elif args.without_view:
with_view = False
else:
# Note that 'None' means unspecified, in which case the Environment
# object could choose to enable a view by default. False means that
# the environment should not include a view.
with_view = None
env = _env_create(
args.env_name,
init_file=args.envfile,
dir=args.dir,
with_view=with_view,
keep_relative=args.keep_relative,
)
# Generate views, only really useful for environments created from spack.lock files.
env.regenerate_views()
def _env_create(name_or_path, *, init_file=None, dir=False, with_view=None, keep_relative=False):
"""Create a new environment, with an optional yaml description.
Arguments:
name_or_path (str): name of the environment to create, or path to it
init_file (str or file): optional initialization file -- can be
a JSON lockfile (*.lock, *.json) or YAML manifest file
dir (bool): if True, create an environment in a directory instead
of a named environment
keep_relative (bool): if True, develop paths are copied verbatim into
the new environment file, otherwise they may be made absolute if the
new environment is in a different location
"""
if not dir:
env = ev.create(
name_or_path, init_file=init_file, with_view=with_view, keep_relative=keep_relative
)
tty.msg("Created environment '%s' in %s" % (name_or_path, env.path))
tty.msg("You can activate this environment with:")
tty.msg(" spack env activate %s" % (name_or_path))
return env
env = ev.create_in_dir(
name_or_path, init_file=init_file, with_view=with_view, keep_relative=keep_relative
)
tty.msg("Created environment in %s" % env.path)
tty.msg("You can activate this environment with:")
tty.msg(" spack env activate %s" % env.path)
return env
# #
# env activate # env activate
# #
@ -118,22 +216,46 @@ def env_activate_setup_parser(subparser):
help="decorate the command line prompt when activating", help="decorate the command line prompt when activating",
) )
env_options = subparser.add_mutually_exclusive_group() subparser.add_argument(
env_options.add_argument(
"--temp", "--temp",
action="store_true", action="store_true",
default=False, default=False,
help="create and activate an environment in a temporary directory", help="create and activate an environment in a temporary directory",
) )
env_options.add_argument( subparser.add_argument(
"-d", "--dir", default=None, help="activate the environment in this directory" "--create",
action="store_true",
default=False,
help="create and activate the environment if it doesn't exist",
) )
env_options.add_argument( subparser.add_argument(
metavar="env", "--envfile",
dest="activate_env",
nargs="?", nargs="?",
default=None, default=None,
help="name of environment to activate", help="either a lockfile (must end with '.json' or '.lock') or a manifest file",
)
subparser.add_argument(
"--keep-relative",
action="store_true",
help="copy relative develop paths verbatim into the new environment"
" when initializing from envfile",
)
subparser.add_argument(
"-d",
"--dir",
default=False,
action="store_true",
help="activate environment based on the directory supplied",
)
subparser.add_argument(
metavar="env",
dest="env_name",
nargs="?",
default=None,
help=(
"name of managed environment or directory of the anonymous env"
" (when using --dir/-d) to activate"
),
) )
@ -162,11 +284,17 @@ def env_activate(args):
if args.env or args.no_env or args.env_dir: if args.env or args.no_env or args.env_dir:
tty.die("Calling spack env activate with --env, --env-dir and --no-env is ambiguous") tty.die("Calling spack env activate with --env, --env-dir and --no-env is ambiguous")
env_name_or_dir = args.activate_env or args.dir # special parser error handling relative to the --temp flag
temp_conflicts = iter([args.keep_relative, args.dir, args.env_name, args.with_view])
if args.temp and any(temp_conflicts):
tty.die(
"spack env activate --temp cannot be combined with managed environments, --with-view,"
" --keep-relative, or --dir."
)
# When executing `spack env activate` without further arguments, activate # When executing `spack env activate` without further arguments, activate
# the default environment. It's created when it doesn't exist yet. # the default environment. It's created when it doesn't exist yet.
if not env_name_or_dir and not args.temp: if not args.env_name and not args.temp:
short_name = "default" short_name = "default"
if not ev.exists(short_name): if not ev.exists(short_name):
ev.create(short_name) ev.create(short_name)
@ -185,17 +313,25 @@ def env_activate(args):
_tty_info(f"Created and activated temporary environment in {env_path}") _tty_info(f"Created and activated temporary environment in {env_path}")
# Managed environment # Managed environment
elif ev.exists(env_name_or_dir) and not args.dir: elif ev.exists(args.env_name) and not args.dir:
env_path = ev.root(env_name_or_dir) env_path = ev.root(args.env_name)
short_name = env_name_or_dir short_name = args.env_name
# Environment directory # Environment directory
elif ev.is_env_dir(env_name_or_dir): elif ev.is_env_dir(args.env_name):
env_path = os.path.abspath(env_name_or_dir) env_path = os.path.abspath(args.env_name)
short_name = os.path.basename(env_path) short_name = os.path.basename(env_path)
# create if user requested, and then recall recursively
elif args.create:
tty.set_msg_enabled(False)
env_create(args)
tty.set_msg_enabled(True)
env_activate(args)
return
else: else:
tty.die("No such environment: '%s'" % env_name_or_dir) tty.die("No such environment: '%s'" % args.env_name)
env_prompt = "[%s]" % short_name env_prompt = "[%s]" % short_name
@ -290,97 +426,6 @@ def env_deactivate(args):
sys.stdout.write(cmds) sys.stdout.write(cmds)
#
# env create
#
def env_create_setup_parser(subparser):
"""create a new environment"""
subparser.add_argument("create_env", metavar="env", help="name of environment to create")
subparser.add_argument(
"-d", "--dir", action="store_true", help="create an environment in a specific directory"
)
subparser.add_argument(
"--keep-relative",
action="store_true",
help="copy relative develop paths verbatim into the new environment"
" when initializing from envfile",
)
view_opts = subparser.add_mutually_exclusive_group()
view_opts.add_argument(
"--without-view", action="store_true", help="do not maintain a view for this environment"
)
view_opts.add_argument(
"--with-view",
help="specify that this environment should maintain a view at the"
" specified path (by default the view is maintained in the"
" environment directory)",
)
subparser.add_argument(
"envfile",
nargs="?",
default=None,
help="either a lockfile (must end with '.json' or '.lock') or a manifest file",
)
def env_create(args):
if args.with_view:
# Expand relative paths provided on the command line to the current working directory
# This way we interpret `spack env create --with-view ./view --dir ./env` as
# a view in $PWD/view, not $PWD/env/view. This is different from specifying a relative
# path in the manifest, which is resolved relative to the manifest file's location.
with_view = os.path.abspath(args.with_view)
elif args.without_view:
with_view = False
else:
# Note that 'None' means unspecified, in which case the Environment
# object could choose to enable a view by default. False means that
# the environment should not include a view.
with_view = None
env = _env_create(
args.create_env,
init_file=args.envfile,
dir=args.dir,
with_view=with_view,
keep_relative=args.keep_relative,
)
# Generate views, only really useful for environments created from spack.lock files.
env.regenerate_views()
def _env_create(name_or_path, *, init_file=None, dir=False, with_view=None, keep_relative=False):
"""Create a new environment, with an optional yaml description.
Arguments:
name_or_path (str): name of the environment to create, or path to it
init_file (str or file): optional initialization file -- can be
a JSON lockfile (*.lock, *.json) or YAML manifest file
dir (bool): if True, create an environment in a directory instead
of a named environment
keep_relative (bool): if True, develop paths are copied verbatim into
the new environment file, otherwise they may be made absolute if the
new environment is in a different location
"""
if not dir:
env = ev.create(
name_or_path, init_file=init_file, with_view=with_view, keep_relative=keep_relative
)
tty.msg("Created environment '%s' in %s" % (name_or_path, env.path))
tty.msg("You can activate this environment with:")
tty.msg(" spack env activate %s" % (name_or_path))
return env
env = ev.create_in_dir(
name_or_path, init_file=init_file, with_view=with_view, keep_relative=keep_relative
)
tty.msg("Created environment in %s" % env.path)
tty.msg("You can activate this environment with:")
tty.msg(" spack env activate %s" % env.path)
return env
# #
# env remove # env remove
# #

View File

@ -2956,7 +2956,9 @@ def test_query_develop_specs(tmpdir):
) )
def test_activation_and_deactiviation_ambiguities(method, env, no_env, env_dir, capsys): def test_activation_and_deactiviation_ambiguities(method, env, no_env, env_dir, capsys):
"""spack [-e x | -E | -D x/] env [activate | deactivate] y are ambiguous""" """spack [-e x | -E | -D x/] env [activate | deactivate] y are ambiguous"""
args = Namespace(shell="sh", activate_env="a", env=env, no_env=no_env, env_dir=env_dir) args = Namespace(
shell="sh", env_name="a", env=env, no_env=no_env, env_dir=env_dir, keep_relative=False
)
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
method(args) method(args)
_, err = capsys.readouterr() _, err = capsys.readouterr()
@ -2997,6 +2999,34 @@ def test_activate_temp(monkeypatch, tmpdir):
assert ev.is_env_dir(str(tmpdir)) assert ev.is_env_dir(str(tmpdir))
@pytest.mark.parametrize(
"conflict_arg", [["--dir"], ["--keep-relative"], ["--with-view", "foo"], ["env"]]
)
def test_activate_parser_conflicts_with_temp(conflict_arg):
with pytest.raises(SpackCommandError):
env("activate", "--sh", "--temp", *conflict_arg)
def test_create_and_activate_managed(tmp_path):
with fs.working_dir(str(tmp_path)):
shell = env("activate", "--without-view", "--create", "--sh", "foo")
active_env_var = next(line for line in shell.splitlines() if ev.spack_env_var in line)
assert str(tmp_path) in active_env_var
active_ev = ev.active_environment()
assert "foo" == active_ev.name
env("deactivate")
def test_create_and_activate_unmanaged(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)
active_env_var = next(line for line in shell.splitlines() if ev.spack_env_var in line)
assert str(env_dir) in active_env_var
assert ev.is_env_dir(env_dir)
env("deactivate")
def test_activate_default(monkeypatch): def test_activate_default(monkeypatch):
"""Tests whether `spack env activate` creates / activates the default """Tests whether `spack env activate` creates / activates the default
environment""" environment"""

View File

@ -1030,7 +1030,7 @@ _spack_env() {
_spack_env_activate() { _spack_env_activate() {
if $list_options if $list_options
then then
SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh --with-view -v --without-view -V -p --prompt --temp -d --dir" SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh --with-view -v --without-view -V -p --prompt --temp --create --envfile --keep-relative -d --dir"
else else
_environments _environments
fi fi

View File

@ -1467,7 +1467,7 @@ complete -c spack -n '__fish_spack_using_command env' -s h -l help -f -a help
complete -c spack -n '__fish_spack_using_command env' -s h -l help -d 'show this help message and exit' complete -c spack -n '__fish_spack_using_command env' -s h -l help -d 'show this help message and exit'
# spack env activate # spack env activate
set -g __fish_spack_optspecs_spack_env_activate h/help sh csh fish bat pwsh v/with-view= V/without-view p/prompt temp d/dir= set -g __fish_spack_optspecs_spack_env_activate h/help sh csh fish bat pwsh v/with-view= V/without-view p/prompt temp create envfile= keep-relative d/dir
complete -c spack -n '__fish_spack_using_command_pos 0 env activate' -f -a '(__fish_spack_environments)' complete -c spack -n '__fish_spack_using_command_pos 0 env activate' -f -a '(__fish_spack_environments)'
complete -c spack -n '__fish_spack_using_command env activate' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command env activate' -s h -l help -f -a help
complete -c spack -n '__fish_spack_using_command env activate' -s h -l help -d 'show this help message and exit' complete -c spack -n '__fish_spack_using_command env activate' -s h -l help -d 'show this help message and exit'
@ -1489,8 +1489,14 @@ complete -c spack -n '__fish_spack_using_command env activate' -s p -l prompt -f
complete -c spack -n '__fish_spack_using_command env activate' -s p -l prompt -d 'decorate the command line prompt when activating' complete -c spack -n '__fish_spack_using_command env activate' -s p -l prompt -d 'decorate the command line prompt when activating'
complete -c spack -n '__fish_spack_using_command env activate' -l temp -f -a temp complete -c spack -n '__fish_spack_using_command env activate' -l temp -f -a temp
complete -c spack -n '__fish_spack_using_command env activate' -l temp -d 'create and activate an environment in a temporary directory' complete -c spack -n '__fish_spack_using_command env activate' -l temp -d 'create and activate an environment in a temporary directory'
complete -c spack -n '__fish_spack_using_command env activate' -s d -l dir -r -f -a dir complete -c spack -n '__fish_spack_using_command env activate' -l create -f -a create
complete -c spack -n '__fish_spack_using_command env activate' -s d -l dir -r -d 'activate the environment in this directory' complete -c spack -n '__fish_spack_using_command env activate' -l create -d 'create and activate the environment if it doesn\'t exist'
complete -c spack -n '__fish_spack_using_command env activate' -l envfile -r -f -a envfile
complete -c spack -n '__fish_spack_using_command env activate' -l envfile -r -d 'either a lockfile (must end with \'.json\' or \'.lock\') or a manifest file'
complete -c spack -n '__fish_spack_using_command env activate' -l keep-relative -f -a keep_relative
complete -c spack -n '__fish_spack_using_command env activate' -l keep-relative -d 'copy relative develop paths verbatim into the new environment when initializing from envfile'
complete -c spack -n '__fish_spack_using_command env activate' -s d -l dir -f -a dir
complete -c spack -n '__fish_spack_using_command env activate' -s d -l dir -d 'activate environment based on the directory supplied'
# spack env deactivate # spack env deactivate
set -g __fish_spack_optspecs_spack_env_deactivate h/help sh csh fish bat pwsh set -g __fish_spack_optspecs_spack_env_deactivate h/help sh csh fish bat pwsh