summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarmen Stoppels <me@harmenstoppels.nl>2023-10-17 15:40:48 +0200
committerGitHub <noreply@github.com>2023-10-17 15:40:48 +0200
commitbd165ebc4dbd0001d4e125e1caeb9fe09064c632 (patch)
tree15f114d530689e960e0a44c4164ce89ec66854c4
parent348e5cb522eb6041f8556ed5823fb63aa23305c7 (diff)
downloadspack-bd165ebc4dbd0001d4e125e1caeb9fe09064c632.tar.gz
spack-bd165ebc4dbd0001d4e125e1caeb9fe09064c632.tar.bz2
spack-bd165ebc4dbd0001d4e125e1caeb9fe09064c632.tar.xz
spack-bd165ebc4dbd0001d4e125e1caeb9fe09064c632.zip
Support `spack env activate --with-view <name> <env>` (#40549)
Currently `spack env activate --with-view` exists, but is a no-op. So, it is not too much of a breaking change to make this redundant flag accept a value `spack env activate --with-view <name>` which activates a particular view by name. The view name is stored in `SPACK_ENV_VIEW`. This also fixes an issue where deactivating a view that was activated with `--without-view` possibly removes entries from PATH, since now we keep track of whether the default view was "enabled" or not.
-rw-r--r--lib/spack/spack/cmd/env.py33
-rw-r--r--lib/spack/spack/environment/__init__.py2
-rw-r--r--lib/spack/spack/environment/environment.py64
-rw-r--r--lib/spack/spack/environment/shell.py55
-rw-r--r--lib/spack/spack/test/cmd/env.py29
-rwxr-xr-xshare/spack/spack-completion.bash2
-rwxr-xr-xshare/spack/spack-completion.fish10
7 files changed, 120 insertions, 75 deletions
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
index 6c22e70a5d..cf5671aafa 100644
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -8,6 +8,7 @@ import os
import shutil
import sys
import tempfile
+from typing import Optional
import llnl.string as string
import llnl.util.filesystem as fs
@@ -96,22 +97,16 @@ def env_activate_setup_parser(subparser):
view_options = subparser.add_mutually_exclusive_group()
view_options.add_argument(
- "-v",
"--with-view",
- action="store_const",
- dest="with_view",
- const=True,
- default=True,
- help="update PATH, etc., with associated view",
+ "-v",
+ metavar="name",
+ help="set runtime environment variables for specific view",
)
view_options.add_argument(
- "-V",
"--without-view",
- action="store_const",
- dest="with_view",
- const=False,
- default=True,
- help="do not update PATH, etc., with associated view",
+ "-V",
+ action="store_true",
+ help="do not set runtime environment variables for any view",
)
subparser.add_argument(
@@ -197,10 +192,20 @@ def env_activate(args):
# Activate new environment
active_env = ev.Environment(env_path)
+
+ # Check if runtime environment variables are requested, and if so, for what view.
+ view: Optional[str] = None
+ if args.with_view:
+ view = args.with_view
+ if not active_env.has_view(view):
+ tty.die(f"The environment does not have a view named '{view}'")
+ elif not args.without_view and active_env.has_view(ev.default_view_name):
+ view = ev.default_view_name
+
cmds += spack.environment.shell.activate_header(
- env=active_env, shell=args.shell, prompt=env_prompt if args.prompt else None
+ env=active_env, shell=args.shell, prompt=env_prompt if args.prompt else None, view=view
)
- env_mods.extend(spack.environment.shell.activate(env=active_env, add_view=args.with_view))
+ env_mods.extend(spack.environment.shell.activate(env=active_env, view=view))
cmds += env_mods.shell_modifications(args.shell)
sys.stdout.write(cmds)
diff --git a/lib/spack/spack/environment/__init__.py b/lib/spack/spack/environment/__init__.py
index 227b48670c..ac598e8421 100644
--- a/lib/spack/spack/environment/__init__.py
+++ b/lib/spack/spack/environment/__init__.py
@@ -365,6 +365,7 @@ from .environment import (
read,
root,
spack_env_var,
+ spack_env_view_var,
update_yaml,
)
@@ -397,5 +398,6 @@ __all__ = [
"read",
"root",
"spack_env_var",
+ "spack_env_view_var",
"update_yaml",
]
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
index 496a8b332a..ee48955ac5 100644
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -64,6 +64,8 @@ from spack.variant import UnknownVariantError
#: environment variable used to indicate the active environment
spack_env_var = "SPACK_ENV"
+#: environment variable used to indicate the active environment view
+spack_env_view_var = "SPACK_ENV_VIEW"
#: currently activated environment
_active_environment: Optional["Environment"] = None
@@ -1595,16 +1597,14 @@ class Environment:
@property
def default_view(self):
- if not self.views:
- raise SpackEnvironmentError("{0} does not have a view enabled".format(self.name))
-
- if default_view_name not in self.views:
- raise SpackEnvironmentError(
- "{0} does not have a default view enabled".format(self.name)
- )
+ if not self.has_view(default_view_name):
+ raise SpackEnvironmentError(f"{self.name} does not have a default view enabled")
return self.views[default_view_name]
+ def has_view(self, view_name: str) -> bool:
+ return view_name in self.views
+
def update_default_view(self, path_or_bool: Union[str, bool]) -> None:
"""Updates the path of the default view.
@@ -1690,14 +1690,14 @@ class Environment:
"Loading the environment view will require reconcretization." % self.name
)
- def _env_modifications_for_default_view(self, reverse=False):
+ def _env_modifications_for_view(self, view: ViewDescriptor, reverse: bool = False):
all_mods = spack.util.environment.EnvironmentModifications()
visited = set()
errors = []
for root_spec in self.concrete_roots():
- if root_spec in self.default_view and root_spec.installed and root_spec.package:
+ if root_spec in view and root_spec.installed and root_spec.package:
for spec in root_spec.traverse(deptype="run", root=True):
if spec.name in visited:
# It is expected that only one instance of the package
@@ -1714,7 +1714,7 @@ class Environment:
visited.add(spec.name)
try:
- mods = uenv.environment_modifications_for_spec(spec, self.default_view)
+ mods = uenv.environment_modifications_for_spec(spec, view)
except Exception as e:
msg = "couldn't get environment settings for %s" % spec.format(
"{name}@{version} /{hash:7}"
@@ -1726,22 +1726,22 @@ class Environment:
return all_mods, errors
- def add_default_view_to_env(self, env_mod):
- """
- Collect the environment modifications to activate an environment using the
- default view. Removes duplicate paths.
+ def add_view_to_env(
+ self, env_mod: spack.util.environment.EnvironmentModifications, view: str
+ ) -> spack.util.environment.EnvironmentModifications:
+ """Collect the environment modifications to activate an environment using the provided
+ view. Removes duplicate paths.
Args:
- env_mod (spack.util.environment.EnvironmentModifications): the environment
- modifications object that is modified.
- """
- if default_view_name not in self.views:
- # No default view to add to shell
+ env_mod: the environment modifications object that is modified.
+ view: the name of the view to activate."""
+ descriptor = self.views.get(view)
+ if not descriptor:
return env_mod
- env_mod.extend(uenv.unconditional_environment_modifications(self.default_view))
+ env_mod.extend(uenv.unconditional_environment_modifications(descriptor))
- mods, errors = self._env_modifications_for_default_view()
+ mods, errors = self._env_modifications_for_view(descriptor)
env_mod.extend(mods)
if errors:
for err in errors:
@@ -1753,22 +1753,22 @@ class Environment:
return env_mod
- def rm_default_view_from_env(self, env_mod):
- """
- Collect the environment modifications to deactivate an environment using the
- default view. Reverses the action of ``add_default_view_to_env``.
+ def rm_view_from_env(
+ self, env_mod: spack.util.environment.EnvironmentModifications, view: str
+ ) -> spack.util.environment.EnvironmentModifications:
+ """Collect the environment modifications to deactivate an environment using the provided
+ view. Reverses the action of ``add_view_to_env``.
Args:
- env_mod (spack.util.environment.EnvironmentModifications): the environment
- modifications object that is modified.
- """
- if default_view_name not in self.views:
- # No default view to add to shell
+ env_mod: the environment modifications object that is modified.
+ view: the name of the view to deactivate."""
+ descriptor = self.views.get(view)
+ if not descriptor:
return env_mod
- env_mod.extend(uenv.unconditional_environment_modifications(self.default_view).reversed())
+ env_mod.extend(uenv.unconditional_environment_modifications(descriptor).reversed())
- mods, _ = self._env_modifications_for_default_view(reverse=True)
+ mods, _ = self._env_modifications_for_view(descriptor, reverse=True)
env_mod.extend(mods)
return env_mod
diff --git a/lib/spack/spack/environment/shell.py b/lib/spack/spack/environment/shell.py
index 380e49fa0f..a4f9634a8d 100644
--- a/lib/spack/spack/environment/shell.py
+++ b/lib/spack/spack/environment/shell.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
+from typing import Optional
import llnl.util.tty as tty
from llnl.util.tty.color import colorize
@@ -13,12 +14,14 @@ import spack.store
from spack.util.environment import EnvironmentModifications
-def activate_header(env, shell, prompt=None):
+def activate_header(env, shell, prompt=None, view: Optional[str] = None):
# Construct the commands to run
cmds = ""
if shell == "csh":
# TODO: figure out how to make color work for csh
cmds += "setenv SPACK_ENV %s;\n" % env.path
+ if view:
+ cmds += "setenv SPACK_ENV_VIEW %s;\n" % view
cmds += 'alias despacktivate "spack env deactivate";\n'
if prompt:
cmds += "if (! $?SPACK_OLD_PROMPT ) "
@@ -29,6 +32,8 @@ def activate_header(env, shell, prompt=None):
prompt = colorize("@G{%s} " % prompt, color=True)
cmds += "set -gx SPACK_ENV %s;\n" % env.path
+ if view:
+ cmds += "set -gx SPACK_ENV_VIEW %s;\n" % view
cmds += "function despacktivate;\n"
cmds += " spack env deactivate;\n"
cmds += "end;\n"
@@ -40,15 +45,21 @@ def activate_header(env, shell, prompt=None):
elif shell == "bat":
# TODO: Color
cmds += 'set "SPACK_ENV=%s"\n' % env.path
+ if view:
+ cmds += 'set "SPACK_ENV_VIEW=%s"\n' % view
# TODO: despacktivate
# TODO: prompt
elif shell == "pwsh":
cmds += "$Env:SPACK_ENV='%s'\n" % env.path
+ if view:
+ cmds += "$Env:SPACK_ENV_VIEW='%s'\n" % view
else:
if "color" in os.getenv("TERM", "") and prompt:
prompt = colorize("@G{%s}" % prompt, color=True, enclose=True)
cmds += "export SPACK_ENV=%s;\n" % env.path
+ if view:
+ cmds += "export SPACK_ENV_VIEW=%s;\n" % view
cmds += "alias despacktivate='spack env deactivate';\n"
if prompt:
cmds += "if [ -z ${SPACK_OLD_PS1+x} ]; then\n"
@@ -66,12 +77,14 @@ def deactivate_header(shell):
cmds = ""
if shell == "csh":
cmds += "unsetenv SPACK_ENV;\n"
+ cmds += "unsetenv SPACK_ENV_VIEW;\n"
cmds += "if ( $?SPACK_OLD_PROMPT ) "
cmds += ' eval \'set prompt="$SPACK_OLD_PROMPT" &&'
cmds += " unsetenv SPACK_OLD_PROMPT';\n"
cmds += "unalias despacktivate;\n"
elif shell == "fish":
cmds += "set -e SPACK_ENV;\n"
+ cmds += "set -e SPACK_ENV_VIEW;\n"
cmds += "functions -e despacktivate;\n"
#
# NOTE: Not changing fish_prompt (above) => no need to restore it here.
@@ -79,14 +92,19 @@ def deactivate_header(shell):
elif shell == "bat":
# TODO: Color
cmds += 'set "SPACK_ENV="\n'
+ cmds += 'set "SPACK_ENV_VIEW="\n'
# TODO: despacktivate
# TODO: prompt
elif shell == "pwsh":
cmds += "Set-Item -Path Env:SPACK_ENV\n"
+ cmds += "Set-Item -Path Env:SPACK_ENV_VIEW\n"
else:
cmds += "if [ ! -z ${SPACK_ENV+x} ]; then\n"
cmds += "unset SPACK_ENV; export SPACK_ENV;\n"
cmds += "fi;\n"
+ cmds += "if [ ! -z ${SPACK_ENV_VIEW+x} ]; then\n"
+ cmds += "unset SPACK_ENV_VIEW; export SPACK_ENV_VIEW;\n"
+ cmds += "fi;\n"
cmds += "alias despacktivate > /dev/null 2>&1 && unalias despacktivate;\n"
cmds += "if [ ! -z ${SPACK_OLD_PS1+x} ]; then\n"
cmds += " if [ \"$SPACK_OLD_PS1\" = '$$$$' ]; then\n"
@@ -100,24 +118,23 @@ def deactivate_header(shell):
return cmds
-def activate(env, use_env_repo=False, add_view=True):
- """
- Activate an environment and append environment modifications
+def activate(
+ env: ev.Environment, use_env_repo=False, view: Optional[str] = "default"
+) -> EnvironmentModifications:
+ """Activate an environment and append environment modifications
To activate an environment, we add its configuration scope to the
existing Spack configuration, and we set active to the current
environment.
Arguments:
- env (spack.environment.Environment): the environment to activate
- use_env_repo (bool): use the packages exactly as they appear in the
- environment's repository
- add_view (bool): generate commands to add view to path variables
+ env: the environment to activate
+ use_env_repo: use the packages exactly as they appear in the environment's repository
+ view: generate commands to add runtime environment variables for named view
Returns:
spack.util.environment.EnvironmentModifications: Environment variables
- modifications to activate environment.
- """
+ modifications to activate environment."""
ev.activate(env, use_env_repo=use_env_repo)
env_mods = EnvironmentModifications()
@@ -129,9 +146,9 @@ def activate(env, use_env_repo=False, add_view=True):
# become PATH variables.
#
try:
- if add_view and ev.default_view_name in env.views:
+ if view and env.has_view(view):
with spack.store.STORE.db.read_transaction():
- env.add_default_view_to_env(env_mods)
+ env.add_view_to_env(env_mods, view)
except (spack.repo.UnknownPackageError, spack.repo.UnknownNamespaceError) as e:
tty.error(e)
tty.die(
@@ -145,17 +162,15 @@ def activate(env, use_env_repo=False, add_view=True):
return env_mods
-def deactivate():
- """
- Deactivate an environment and collect corresponding environment modifications.
+def deactivate() -> EnvironmentModifications:
+ """Deactivate an environment and collect corresponding environment modifications.
Note: unloads the environment in its current state, not in the state it was
loaded in, meaning that specs that were removed from the spack environment
after activation are not unloaded.
Returns:
- spack.util.environment.EnvironmentModifications: Environment variables
- modifications to activate environment.
+ Environment variables modifications to activate environment.
"""
env_mods = EnvironmentModifications()
active = ev.active_environment()
@@ -163,10 +178,12 @@ def deactivate():
if active is None:
return env_mods
- if ev.default_view_name in active.views:
+ active_view = os.getenv(ev.spack_env_view_var)
+
+ if active_view and active.has_view(active_view):
try:
with spack.store.STORE.db.read_transaction():
- active.rm_default_view_from_env(env_mods)
+ active.rm_view_from_env(env_mods, active_view)
except (spack.repo.UnknownPackageError, spack.repo.UnknownNamespaceError) as e:
tty.warn(e)
tty.warn(
diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py
index cef5ccbcd5..4845d12206 100644
--- a/lib/spack/spack/test/cmd/env.py
+++ b/lib/spack/spack/test/cmd/env.py
@@ -663,7 +663,7 @@ packages:
e.write()
env_mod = spack.util.environment.EnvironmentModifications()
- e.add_default_view_to_env(env_mod)
+ e.add_view_to_env(env_mod, "default")
env_variables = {}
env_mod.apply_modifications(env_variables)
assert str(fake_bin) in env_variables["PATH"]
@@ -2356,7 +2356,7 @@ def test_env_activate_sh_prints_shell_output(tmpdir, mock_stage, mock_fetch, ins
This is a cursory check; ``share/spack/qa/setup-env-test.sh`` checks
for correctness.
"""
- env("create", "test", add_view=True)
+ env("create", "test")
out = env("activate", "--sh", "test")
assert "export SPACK_ENV=" in out
@@ -2371,7 +2371,7 @@ def test_env_activate_sh_prints_shell_output(tmpdir, mock_stage, mock_fetch, ins
def test_env_activate_csh_prints_shell_output(tmpdir, mock_stage, mock_fetch, install_mockery):
"""Check the shell commands output by ``spack env activate --csh``."""
- env("create", "test", add_view=True)
+ env("create", "test")
out = env("activate", "--csh", "test")
assert "setenv SPACK_ENV" in out
@@ -2388,7 +2388,7 @@ def test_env_activate_csh_prints_shell_output(tmpdir, mock_stage, mock_fetch, in
def test_env_activate_default_view_root_unconditional(mutable_mock_env_path):
"""Check that the root of the default view in the environment is added
to the shell unconditionally."""
- env("create", "test", add_view=True)
+ env("create", "test")
with ev.read("test") as e:
viewdir = e.default_view.root
@@ -2403,6 +2403,27 @@ def test_env_activate_default_view_root_unconditional(mutable_mock_env_path):
)
+def test_env_activate_custom_view(tmp_path: pathlib.Path, mock_packages):
+ """Check that an environment can be activated with a non-default view."""
+ env_template = tmp_path / "spack.yaml"
+ default_dir = tmp_path / "defaultdir"
+ nondefaultdir = tmp_path / "nondefaultdir"
+ with open(env_template, "w") as f:
+ f.write(
+ f"""\
+spack:
+ specs: [a]
+ view:
+ default:
+ root: {default_dir}
+ nondefault:
+ root: {nondefaultdir}"""
+ )
+ env("create", "test", str(env_template))
+ shell = env("activate", "--sh", "--with-view", "nondefault", "test")
+ assert os.path.join(nondefaultdir, "bin") in shell
+
+
def test_concretize_user_specs_together():
e = ev.create("coconcretization")
e.unify = True
diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash
index b9521b8f0c..0280524536 100755
--- a/share/spack/spack-completion.bash
+++ b/share/spack/spack-completion.bash
@@ -1016,7 +1016,7 @@ _spack_env() {
_spack_env_activate() {
if $list_options
then
- SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh -v --with-view -V --without-view -p --prompt --temp -d --dir"
+ SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh --with-view -v --without-view -V -p --prompt --temp -d --dir"
else
_environments
fi
diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish
index f4ac310ada..e37b3448d5 100755
--- a/share/spack/spack-completion.fish
+++ b/share/spack/spack-completion.fish
@@ -1427,7 +1427,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'
# 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 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 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'
@@ -1441,10 +1441,10 @@ complete -c spack -n '__fish_spack_using_command env activate' -l bat -f -a shel
complete -c spack -n '__fish_spack_using_command env activate' -l bat -d 'print bat commands to activate the environment'
complete -c spack -n '__fish_spack_using_command env activate' -l pwsh -f -a shell
complete -c spack -n '__fish_spack_using_command env activate' -l pwsh -d 'print powershell commands to activate environment'
-complete -c spack -n '__fish_spack_using_command env activate' -s v -l with-view -f -a with_view
-complete -c spack -n '__fish_spack_using_command env activate' -s v -l with-view -d 'update PATH, etc., with associated view'
-complete -c spack -n '__fish_spack_using_command env activate' -s V -l without-view -f -a with_view
-complete -c spack -n '__fish_spack_using_command env activate' -s V -l without-view -d 'do not update PATH, etc., with associated view'
+complete -c spack -n '__fish_spack_using_command env activate' -l with-view -s v -r -f -a with_view
+complete -c spack -n '__fish_spack_using_command env activate' -l with-view -s v -r -d 'set runtime environment variables for specific view'
+complete -c spack -n '__fish_spack_using_command env activate' -l without-view -s V -f -a without_view
+complete -c spack -n '__fish_spack_using_command env activate' -l without-view -s V -d 'do not set runtime environment variables for any view'
complete -c spack -n '__fish_spack_using_command env activate' -s p -l prompt -f -a prompt
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