Move shell aware env into spack.environment.shell (#25608)

Cherry-picked from #25564 so this is standalone.

With this PR we can activate an environment in Spack itself, without computing changes to environment variables only necessary for "shell aware" env activation.

1. Activating an environment:
    
    ```python
    spack.environment.activate(Environment(xyz)) -> None
    ```
    this basically just sets `_active_environment` and modifies some config scopes.

2. Activating an environment **and** getting environment variable modifications for the shell:

    ```python
    spack.environment.shell.activate(Environment(xyz)) -> EnvironmentModifications
    ```

This should make it easier/faster to do unit tests and scripting with spack, without the cli interface.
This commit is contained in:
Harmen Stoppels 2021-10-05 20:25:43 +02:00 committed by GitHub
parent 713bbdbe7c
commit d998ea1bd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 345 additions and 217 deletions

View file

@ -539,7 +539,7 @@ def _add_externals_if_missing():
def ensure_bootstrap_configuration():
bootstrap_store_path = store_path()
user_configuration = _read_and_sanitize_configuration()
with spack.environment.deactivate_environment():
with spack.environment.no_active_environment():
with spack.architecture.use_platform(spack.architecture.real_platform()):
with spack.repo.use_repositories(spack.paths.packages_path):
with spack.store.use_store(bootstrap_store_path):

View file

@ -69,7 +69,7 @@ def _specs(self, **kwargs):
# If an environment is provided, we'll restrict the search to
# only its installed packages.
env = ev._active_environment
env = ev.active_environment()
if env:
kwargs['hashes'] = set(env.all_hashes())

View file

@ -19,6 +19,7 @@
import spack.cmd.uninstall
import spack.config
import spack.environment as ev
import spack.environment.shell
import spack.schema.env
import spack.util.string as string
@ -81,6 +82,7 @@ def env_activate_setup_parser(subparser):
def env_activate(args):
env = args.activate_env
if not args.shell:
spack.cmd.common.shell_init_instructions(
"spack env activate",
@ -110,10 +112,19 @@ def env_activate(args):
tty.debug("Environment %s is already active" % args.activate_env)
return
cmds = ev.activate(
ev.Environment(spack_env), add_view=args.with_view, shell=args.shell,
# Activate new environment
active_env = ev.Environment(spack_env)
cmds = spack.environment.shell.activate_header(
env=active_env,
shell=args.shell,
prompt=env_prompt if args.prompt else None
)
env_mods = spack.environment.shell.activate(
env=active_env,
add_view=args.with_view
)
cmds += env_mods.shell_modifications(args.shell)
sys.stdout.write(cmds)
@ -150,7 +161,9 @@ def env_deactivate(args):
if 'SPACK_ENV' not in os.environ:
tty.die('No environment is currently active.')
cmds = ev.deactivate(shell=args.shell)
cmds = spack.environment.shell.deactivate_header(args.shell)
env_mods = spack.environment.shell.deactivate()
cmds += env_mods.shell_modifications(args.shell)
sys.stdout.write(cmds)

View file

@ -0,0 +1,57 @@
# Copyright 2013-2021 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)
from .environment import (
Environment,
SpackEnvironmentError,
activate,
active,
active_environment,
all_environment_names,
all_environments,
config_dict,
create,
deactivate,
default_manifest_yaml,
default_view_name,
display_specs,
exists,
is_env_dir,
is_latest_format,
lockfile_name,
manifest_file,
manifest_name,
no_active_environment,
read,
root,
spack_env_var,
update_yaml,
)
__all__ = [
'Environment',
'SpackEnvironmentError',
'activate',
'active',
'active_environment',
'all_environment_names',
'all_environments',
'config_dict',
'create',
'deactivate',
'default_manifest_yaml',
'default_view_name',
'display_specs',
'exists',
'is_env_dir',
'is_latest_format',
'lockfile_name',
'manifest_file',
'manifest_name',
'no_active_environment',
'read',
'root',
'spack_env_var',
'update_yaml',
]

View file

@ -16,13 +16,13 @@
import llnl.util.filesystem as fs
import llnl.util.tty as tty
from llnl.util.tty.color import colorize
import spack.concretize
import spack.config
import spack.error
import spack.hash_types as ht
import spack.hooks
import spack.paths
import spack.repo
import spack.schema.env
import spack.spec
@ -108,9 +108,7 @@ def validate_env_name(name):
return name
def activate(
env, use_env_repo=False, add_view=True, shell='sh', prompt=None
):
def activate(env, use_env_repo=False):
"""Activate an environment.
To activate an environment, we add its configuration scope to the
@ -121,96 +119,26 @@ def activate(
env (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
shell (str): One of `sh`, `csh`, `fish`.
prompt (str): string to add to the users prompt, or None
Returns:
str: Shell commands to activate environment.
TODO: environment to use the activated spack environment.
"""
global _active_environment
_active_environment = env
prepare_config_scope(_active_environment)
# Fail early to avoid ending in an invalid state
if not isinstance(env, Environment):
raise TypeError("`env` should be of type {0}".format(Environment.__name__))
prepare_config_scope(env)
if use_env_repo:
spack.repo.path.put_first(_active_environment.repo)
spack.repo.path.put_first(env.repo)
tty.debug("Using environment '%s'" % _active_environment.name)
tty.debug("Using environment '%s'" % env.name)
# 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
cmds += 'alias despacktivate "spack env deactivate";\n'
if prompt:
cmds += 'if (! $?SPACK_OLD_PROMPT ) '
cmds += 'setenv SPACK_OLD_PROMPT "${prompt}";\n'
cmds += 'set prompt="%s ${prompt}";\n' % prompt
elif shell == 'fish':
if os.getenv('TERM') and 'color' in os.getenv('TERM') and prompt:
prompt = colorize('@G{%s} ' % prompt, color=True)
cmds += 'set -gx SPACK_ENV %s;\n' % env.path
cmds += 'function despacktivate;\n'
cmds += ' spack env deactivate;\n'
cmds += 'end;\n'
#
# NOTE: We're not changing the fish_prompt function (which is fish's
# solution to the PS1 variable) here. This is a bit fiddly, and easy to
# screw up => spend time reasearching a solution. Feedback welcome.
#
else:
if os.getenv('TERM') and 'color' in os.getenv('TERM') and prompt:
prompt = colorize('@G{%s} ' % prompt, color=True)
cmds += 'export SPACK_ENV=%s;\n' % env.path
cmds += "alias despacktivate='spack env deactivate';\n"
if prompt:
cmds += 'if [ -z ${SPACK_OLD_PS1+x} ]; then\n'
cmds += ' if [ -z ${PS1+x} ]; then\n'
cmds += " PS1='$$$$';\n"
cmds += ' fi;\n'
cmds += ' export SPACK_OLD_PS1="${PS1}";\n'
cmds += 'fi;\n'
cmds += 'export PS1="%s ${PS1}";\n' % prompt
#
# NOTE in the fish-shell: Path variables are a special kind of variable
# used to support colon-delimited path lists including PATH, CDPATH,
# MANPATH, PYTHONPATH, etc. All variables that end in PATH (case-sensitive)
# become PATH variables.
#
try:
if add_view and default_view_name in env.views:
with spack.store.db.read_transaction():
cmds += env.add_default_view_to_shell(shell)
except (spack.repo.UnknownPackageError,
spack.repo.UnknownNamespaceError) as e:
tty.error(e)
tty.die(
'Environment view is broken due to a missing package or repo.\n',
' To activate without views enabled, activate with:\n',
' spack env activate -V {0}\n'.format(env.name),
' To remove it and resolve the issue, '
'force concretize with the command:\n',
' spack -e {0} concretize --force'.format(env.name))
return cmds
# Do this last, because setting up the config must succeed first.
_active_environment = env
def deactivate(shell='sh'):
"""Undo any configuration or repo settings modified by ``activate()``.
Arguments:
shell (str): One of `sh`, `csh`, `fish`. Shell style to use.
Returns:
str: shell commands for `shell` to undo environment variables
"""
def deactivate():
"""Undo any configuration or repo settings modified by ``activate()``."""
global _active_environment
if not _active_environment:
@ -222,47 +150,9 @@ def deactivate(shell='sh'):
if _active_environment._repo:
spack.repo.path.remove(_active_environment._repo)
cmds = ''
if shell == 'csh':
cmds += 'unsetenv SPACK_ENV;\n'
cmds += 'if ( $?SPACK_OLD_PROMPT ) '
cmds += '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 += 'functions -e despacktivate;\n'
#
# NOTE: Not changing fish_prompt (above) => no need to restore it here.
#
else:
cmds += 'if [ ! -z ${SPACK_ENV+x} ]; then\n'
cmds += 'unset SPACK_ENV; export SPACK_ENV;\n'
cmds += 'fi;\n'
cmds += 'unalias despacktivate;\n'
cmds += 'if [ ! -z ${SPACK_OLD_PS1+x} ]; then\n'
cmds += ' if [ "$SPACK_OLD_PS1" = \'$$$$\' ]; then\n'
cmds += ' unset PS1; export PS1;\n'
cmds += ' else\n'
cmds += ' export PS1="$SPACK_OLD_PS1";\n'
cmds += ' fi;\n'
cmds += ' unset SPACK_OLD_PS1; export SPACK_OLD_PS1;\n'
cmds += 'fi;\n'
try:
if default_view_name in _active_environment.views:
with spack.store.db.read_transaction():
cmds += _active_environment.rm_default_view_from_shell(shell)
except (spack.repo.UnknownPackageError,
spack.repo.UnknownNamespaceError) as e:
tty.warn(e)
tty.warn('Could not fully deactivate view due to missing package '
'or repo, shell environment may be corrupt.')
tty.debug("Deactivated environment '%s'" % _active_environment.name)
_active_environment = None
return cmds
_active_environment = None
def active_environment():
@ -1345,12 +1235,18 @@ def _env_modifications_for_default_view(self, reverse=False):
return all_mods, errors
def add_default_view_to_shell(self, shell):
env_mod = spack.util.environment.EnvironmentModifications()
def add_default_view_to_env(self, env_mod):
"""
Collect the environment modifications to activate an environment using the
default 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
return env_mod.shell_modifications(shell)
return env_mod
env_mod.extend(uenv.unconditional_environment_modifications(
self.default_view))
@ -1365,14 +1261,20 @@ def add_default_view_to_shell(self, shell):
for env_var in env_mod.group_by_name():
env_mod.prune_duplicate_paths(env_var)
return env_mod.shell_modifications(shell)
return env_mod
def rm_default_view_from_shell(self, shell):
env_mod = spack.util.environment.EnvironmentModifications()
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``.
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
return env_mod.shell_modifications(shell)
return env_mod
env_mod.extend(uenv.unconditional_environment_modifications(
self.default_view).reversed())
@ -1380,7 +1282,7 @@ def rm_default_view_from_shell(self, shell):
mods, _ = self._env_modifications_for_default_view(reverse=True)
env_mod.extend(mods)
return env_mod.shell_modifications(shell)
return env_mod
def _add_concrete_spec(self, spec, concrete, new=True):
"""Called when a new concretized spec is added to the environment.
@ -2165,14 +2067,17 @@ def is_latest_format(manifest):
@contextlib.contextmanager
def deactivate_environment():
"""Deactivate an active environment for the duration of the context."""
global _active_environment
current, _active_environment = _active_environment, None
def no_active_environment():
"""Deactivate the active environment for the duration of the context. Has no
effect when there is no active environment."""
env = active_environment()
try:
deactivate()
yield
finally:
_active_environment = current
# TODO: we don't handle `use_env_repo` here.
if env:
activate(env)
class SpackEnvironmentError(spack.error.SpackError):

View file

@ -0,0 +1,161 @@
# Copyright 2013-2021 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 os
import llnl.util.tty as tty
from llnl.util.tty.color import colorize
import spack.environment as ev
import spack.repo
import spack.store
from spack.util.environment import EnvironmentModifications
def activate_header(env, shell, prompt=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
cmds += 'alias despacktivate "spack env deactivate";\n'
if prompt:
cmds += 'if (! $?SPACK_OLD_PROMPT ) '
cmds += 'setenv SPACK_OLD_PROMPT "${prompt}";\n'
cmds += 'set prompt="%s ${prompt}";\n' % prompt
elif shell == 'fish':
if 'color' in os.getenv('TERM', '') and prompt:
prompt = colorize('@G{%s} ' % prompt, color=True)
cmds += 'set -gx SPACK_ENV %s;\n' % env.path
cmds += 'function despacktivate;\n'
cmds += ' spack env deactivate;\n'
cmds += 'end;\n'
#
# NOTE: We're not changing the fish_prompt function (which is fish's
# solution to the PS1 variable) here. This is a bit fiddly, and easy to
# screw up => spend time reasearching a solution. Feedback welcome.
#
else:
if 'color' in os.getenv('TERM', '') and prompt:
prompt = colorize('@G{%s} ' % prompt, color=True)
cmds += 'export SPACK_ENV=%s;\n' % env.path
cmds += "alias despacktivate='spack env deactivate';\n"
if prompt:
cmds += 'if [ -z ${SPACK_OLD_PS1+x} ]; then\n'
cmds += ' if [ -z ${PS1+x} ]; then\n'
cmds += " PS1='$$$$';\n"
cmds += ' fi;\n'
cmds += ' export SPACK_OLD_PS1="${PS1}";\n'
cmds += 'fi;\n'
cmds += 'export PS1="%s ${PS1}";\n' % prompt
return cmds
def deactivate_header(shell):
cmds = ''
if shell == 'csh':
cmds += 'unsetenv SPACK_ENV;\n'
cmds += 'if ( $?SPACK_OLD_PROMPT ) '
cmds += '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 += 'functions -e despacktivate;\n'
#
# NOTE: Not changing fish_prompt (above) => no need to restore it here.
#
else:
cmds += 'if [ ! -z ${SPACK_ENV+x} ]; then\n'
cmds += 'unset SPACK_ENV; export SPACK_ENV;\n'
cmds += 'fi;\n'
cmds += 'unalias despacktivate;\n'
cmds += 'if [ ! -z ${SPACK_OLD_PS1+x} ]; then\n'
cmds += ' if [ "$SPACK_OLD_PS1" = \'$$$$\' ]; then\n'
cmds += ' unset PS1; export PS1;\n'
cmds += ' else\n'
cmds += ' export PS1="$SPACK_OLD_PS1";\n'
cmds += ' fi;\n'
cmds += ' unset SPACK_OLD_PS1; export SPACK_OLD_PS1;\n'
cmds += 'fi;\n'
return cmds
def activate(env, use_env_repo=False, add_view=True):
"""
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
Returns:
spack.util.environment.EnvironmentModifications: Environment variables
modifications to activate environment.
"""
ev.activate(env, use_env_repo=use_env_repo)
env_mods = EnvironmentModifications()
#
# NOTE in the fish-shell: Path variables are a special kind of variable
# used to support colon-delimited path lists including PATH, CDPATH,
# MANPATH, PYTHONPATH, etc. All variables that end in PATH (case-sensitive)
# become PATH variables.
#
try:
if add_view and ev.default_view_name in env.views:
with spack.store.db.read_transaction():
env.add_default_view_to_env(env_mods)
except (spack.repo.UnknownPackageError,
spack.repo.UnknownNamespaceError) as e:
tty.error(e)
tty.die(
'Environment view is broken due to a missing package or repo.\n',
' To activate without views enabled, activate with:\n',
' spack env activate -V {0}\n'.format(env.name),
' To remove it and resolve the issue, '
'force concretize with the command:\n',
' spack -e {0} concretize --force'.format(env.name))
return env_mods
def deactivate():
"""
Deactivate an environment and collect corresponding environment modifications
Returns:
spack.util.environment.EnvironmentModifications: Environment variables
modifications to activate environment.
"""
env_mods = EnvironmentModifications()
active = ev.active_environment()
if active is None:
return env_mods
if ev.default_view_name in active.views:
try:
with spack.store.db.read_transaction():
active.rm_default_view_from_env(env_mods)
except (spack.repo.UnknownPackageError,
spack.repo.UnknownNamespaceError) as e:
tty.warn(e)
tty.warn('Could not fully deactivate view due to missing package '
'or repo, shell environment may be corrupt.')
ev.deactivate()
return env_mods

View file

@ -745,7 +745,7 @@ def main(argv=None):
if not args.no_env:
env = spack.cmd.find_environment(args)
if env:
ev.activate(env, args.use_env_repo, add_view=False)
ev.activate(env, args.use_env_repo)
if args.print_shell_vars:
print_setup_info(*args.print_shell_vars.split(','))

View file

@ -22,6 +22,10 @@
import spack.architecture
import spack.config
import spack.environment
import spack.main
import spack.repo
import spack.store
_serialize = sys.version_info >= (3, 8) and sys.platform == 'darwin'
@ -63,26 +67,23 @@ class PackageInstallContext(object):
needs to be transmitted to a child process.
"""
def __init__(self, pkg):
import spack.environment as ev # break import cycle
if _serialize:
self.serialized_pkg = serialize(pkg)
self.serialized_env = serialize(ev._active_environment)
self.serialized_env = serialize(spack.environment.active_environment())
else:
self.pkg = pkg
self.env = ev._active_environment
self.env = spack.environment.active_environment()
self.spack_working_dir = spack.main.spack_working_dir
self.test_state = TestState()
def restore(self):
import spack.environment as ev # break import cycle
self.test_state.restore()
spack.main.spack_working_dir = self.spack_working_dir
if _serialize:
ev._active_environment = pickle.load(self.serialized_env)
return pickle.load(self.serialized_pkg)
else:
ev._active_environment = self.env
return self.pkg
env = pickle.load(self.serialized_env) if _serialize else self.env
pkg = pickle.load(self.serialized_pkg) if _serialize else self.pkg
if env:
spack.environment.activate(env)
return pkg
class TestState(object):

View file

@ -54,13 +54,6 @@ def _set_project_dir(path):
os.environ.pop('CI_PROJECT_DIR')
@pytest.fixture()
def env_deactivate():
yield
ev._active_environment = None
os.environ.pop('SPACK_ENV', None)
def set_env_var(key, val):
os.environ[key] = val
@ -124,7 +117,7 @@ def test_specs_staging(config):
assert (spec_a_label in stages[3])
def test_ci_generate_with_env(tmpdir, mutable_mock_env_path, env_deactivate,
def test_ci_generate_with_env(tmpdir, mutable_mock_env_path,
install_mockery, mock_packages, project_dir_env):
"""Make sure we can get a .gitlab-ci.yml from an environment file
which has the gitlab-ci, cdash, and mirrors sections."""
@ -220,7 +213,7 @@ def _validate_needs_graph(yaml_contents, needs_graph, artifacts):
def test_ci_generate_bootstrap_gcc(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, project_dir_env):
"""Test that we can bootstrap a compiler and use it as the
compiler for a spec in the environment"""
@ -283,7 +276,6 @@ def test_ci_generate_bootstrap_gcc(tmpdir, mutable_mock_env_path,
def test_ci_generate_bootstrap_artifacts_buildcache(tmpdir,
mutable_mock_env_path,
env_deactivate,
install_mockery,
mock_packages,
project_dir_env):
@ -350,7 +342,7 @@ def test_ci_generate_bootstrap_artifacts_buildcache(tmpdir,
def test_ci_generate_with_env_missing_section(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, project_dir_env):
"""Make sure we get a reasonable message if we omit gitlab-ci section"""
project_dir_env(tmpdir.strpath)
@ -375,7 +367,7 @@ def test_ci_generate_with_env_missing_section(tmpdir, mutable_mock_env_path,
def test_ci_generate_with_cdash_token(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, project_dir_env):
"""Make sure we it doesn't break if we configure cdash"""
project_dir_env(tmpdir.strpath)
@ -430,7 +422,7 @@ def test_ci_generate_with_cdash_token(tmpdir, mutable_mock_env_path,
def test_ci_generate_with_custom_scripts(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env):
"""Test use of user-provided scripts"""
@ -521,7 +513,7 @@ def test_ci_generate_with_custom_scripts(tmpdir, mutable_mock_env_path,
def test_ci_generate_pkg_with_deps(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, project_dir_env):
"""Test pipeline generation for a package w/ dependencies"""
project_dir_env(tmpdir.strpath)
@ -575,7 +567,7 @@ def test_ci_generate_pkg_with_deps(tmpdir, mutable_mock_env_path,
def test_ci_generate_for_pr_pipeline(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env):
"""Test that PR pipelines do not include a final stage job for
@ -639,7 +631,7 @@ def test_ci_generate_for_pr_pipeline(tmpdir, mutable_mock_env_path,
def test_ci_generate_with_external_pkg(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env):
"""Make sure we do not generate jobs for external pkgs"""
@ -682,7 +674,7 @@ def test_ci_generate_with_external_pkg(tmpdir, mutable_mock_env_path,
assert not any('externaltool' in key for key in yaml_contents)
def test_ci_rebuild(tmpdir, mutable_mock_env_path, env_deactivate,
def test_ci_rebuild(tmpdir, mutable_mock_env_path,
install_mockery, mock_packages, monkeypatch,
mock_gnupghome, mock_fetch, project_dir_env):
project_dir_env(tmpdir.strpath)
@ -840,7 +832,7 @@ def mystrip(s):
env_cmd('deactivate')
def test_ci_nothing_to_rebuild(tmpdir, mutable_mock_env_path, env_deactivate,
def test_ci_nothing_to_rebuild(tmpdir, mutable_mock_env_path,
install_mockery, mock_packages, monkeypatch,
mock_fetch, project_dir_env):
project_dir_env(tmpdir.strpath)
@ -915,7 +907,7 @@ def fake_dl_method(spec, dest, require_cdashid, m_url=None):
@pytest.mark.disable_clean_stage_check
def test_push_mirror_contents(tmpdir, mutable_mock_env_path, env_deactivate,
def test_push_mirror_contents(tmpdir, mutable_mock_env_path,
install_mockery_mutable_config, mock_packages,
mock_fetch, mock_stage, mock_gnupghome,
project_dir_env):
@ -1086,7 +1078,7 @@ def faked(env, spec_file=None, packages=None, add_spec=True,
def test_ci_generate_override_runner_attrs(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env):
"""Test that we get the behavior we want with respect to the provision
@ -1231,7 +1223,7 @@ def test_ci_generate_override_runner_attrs(tmpdir, mutable_mock_env_path,
def test_ci_generate_with_workarounds(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env):
"""Make sure the post-processing cli workarounds do what they should"""
@ -1282,7 +1274,7 @@ def test_ci_generate_with_workarounds(tmpdir, mutable_mock_env_path,
@pytest.mark.disable_clean_stage_check
def test_ci_rebuild_index(tmpdir, mutable_mock_env_path, env_deactivate,
def test_ci_rebuild_index(tmpdir, mutable_mock_env_path,
install_mockery, mock_packages, mock_fetch,
mock_stage):
working_dir = tmpdir.join('working_dir')
@ -1336,7 +1328,7 @@ def test_ci_rebuild_index(tmpdir, mutable_mock_env_path, env_deactivate,
def test_ci_generate_bootstrap_prune_dag(
install_mockery_mutable_config, mock_packages, mock_fetch,
mock_archive, mutable_config, monkeypatch, tmpdir,
mutable_mock_env_path, env_deactivate, project_dir_env):
mutable_mock_env_path, project_dir_env):
"""Test compiler bootstrapping with DAG pruning. Specifically, make
sure that if we detect the bootstrapped compiler needs to be rebuilt,
we ensure the spec we want to build with that compiler is scheduled
@ -1468,7 +1460,7 @@ def fake_get_mirrors_for_spec(spec=None, full_hash_match=False,
def test_ci_subcommands_without_mirror(tmpdir, mutable_mock_env_path,
env_deactivate, mock_packages,
mock_packages,
install_mockery, project_dir_env):
"""Make sure we catch if there is not a mirror and report an error"""
project_dir_env(tmpdir.strpath)
@ -1546,7 +1538,7 @@ def test_ensure_only_one_temporary_storage():
def test_ci_generate_temp_storage_url(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env, mock_binary_index):
"""Verify correct behavior when using temporary-storage-url-prefix"""
@ -1602,7 +1594,7 @@ def test_ci_generate_temp_storage_url(tmpdir, mutable_mock_env_path,
def test_ci_generate_read_broken_specs_url(tmpdir, mutable_mock_env_path,
env_deactivate, install_mockery,
install_mockery,
mock_packages, monkeypatch,
project_dir_env):
"""Verify that `broken-specs-url` works as intended"""
@ -1658,7 +1650,7 @@ def test_ci_generate_read_broken_specs_url(tmpdir, mutable_mock_env_path,
assert(ex not in output)
def test_ci_reproduce(tmpdir, mutable_mock_env_path, env_deactivate,
def test_ci_reproduce(tmpdir, mutable_mock_env_path,
install_mockery, mock_packages, monkeypatch,
last_two_git_commits, project_dir_env, mock_binary_index):
project_dir_env(tmpdir.strpath)

View file

@ -14,8 +14,10 @@
import spack.cmd.env
import spack.environment as ev
import spack.environment.shell
import spack.hash_types as ht
import spack.modules
import spack.repo
import spack.util.spack_json as sjson
from spack.cmd.env import _env_create
from spack.main import SpackCommand, SpackCommandError
@ -52,13 +54,6 @@ def check_viewdir_removal(viewdir):
os.listdir(str(viewdir.join('.spack'))) == ['projections.yaml'])
@pytest.fixture()
def env_deactivate():
yield
ev._active_environment = None
os.environ.pop('SPACK_ENV', None)
def test_add():
e = ev.create('test')
e.add('mpileaks')
@ -213,8 +208,8 @@ def setup_error(pkg, env):
pkg = spack.repo.path.get_pkg_class("cmake-client")
monkeypatch.setattr(pkg, "setup_run_environment", setup_error)
with e:
pass
spack.environment.shell.activate(e)
_, err = capfd.readouterr()
assert "cmake-client had issues!" in err
@ -230,8 +225,9 @@ def test_activate_adds_transitive_run_deps_to_path(
with e:
install('depends-on-run-env')
cmds = ev.activate(e)
assert 'DEPENDENCY_ENV_VAR=1' in cmds
env_variables = {}
spack.environment.shell.activate(e).apply_modifications(env_variables)
assert env_variables['DEPENDENCY_ENV_VAR'] == '1'
def test_env_install_same_spec_twice(install_mockery, mock_fetch):
@ -574,15 +570,11 @@ def test_env_view_external_prefix(
e.install_all()
e.write()
env_modifications = e.add_default_view_to_shell('sh')
individual_modifications = env_modifications.split('\n')
def path_includes_fake_prefix(cmd):
return 'export PATH' in cmd and str(fake_bin) in cmd
assert any(
path_includes_fake_prefix(cmd) for cmd in individual_modifications
)
env_mod = spack.util.environment.EnvironmentModifications()
e.add_default_view_to_env(env_mod)
env_variables = {}
env_mod.apply_modifications(env_variables)
assert str(fake_bin) in env_variables['PATH']
def test_init_with_file_and_remove(tmpdir):
@ -630,7 +622,7 @@ def test_env_with_config():
for x in e._get_environment_specs())
def test_with_config_bad_include(env_deactivate, capfd):
def test_with_config_bad_include(capfd):
env_name = 'test_bad_include'
test_config = """\
spack:
@ -650,6 +642,7 @@ def test_with_config_bad_include(env_deactivate, capfd):
assert 'missing include' in err
assert '/no/such/directory' in err
assert 'no/such/file.yaml' in err
assert ev.active_environment() is None
def test_env_with_include_config_files_same_basename():
@ -1288,7 +1281,7 @@ def test_env_updates_view_force_remove(
def test_env_activate_view_fails(
tmpdir, mock_stage, mock_fetch, install_mockery, env_deactivate):
tmpdir, mock_stage, mock_fetch, install_mockery):
"""Sanity check on env activate to make sure it requires shell support"""
out = env('activate', 'test')
assert "To set up shell support" in out
@ -2039,8 +2032,7 @@ def test_view_link_all(tmpdir, mock_fetch, mock_packages, mock_archive,
def test_stack_view_activate_from_default(tmpdir, mock_fetch, mock_packages,
mock_archive, install_mockery,
env_deactivate):
mock_archive, install_mockery):
filename = str(tmpdir.join('spack.yaml'))
viewdir = str(tmpdir.join('view'))
with open(filename, 'w') as f:
@ -2072,8 +2064,7 @@ def test_stack_view_activate_from_default(tmpdir, mock_fetch, mock_packages,
def test_stack_view_no_activate_without_default(tmpdir, mock_fetch,
mock_packages, mock_archive,
install_mockery,
env_deactivate):
install_mockery):
filename = str(tmpdir.join('spack.yaml'))
viewdir = str(tmpdir.join('view'))
with open(filename, 'w') as f:
@ -2102,8 +2093,7 @@ def test_stack_view_no_activate_without_default(tmpdir, mock_fetch,
def test_stack_view_multiple_views(tmpdir, mock_fetch, mock_packages,
mock_archive, install_mockery,
env_deactivate):
mock_archive, install_mockery):
filename = str(tmpdir.join('spack.yaml'))
default_viewdir = str(tmpdir.join('default-view'))
combin_viewdir = str(tmpdir.join('combinatorial-view'))
@ -2150,7 +2140,7 @@ def test_stack_view_multiple_views(tmpdir, mock_fetch, mock_packages,
def test_env_activate_sh_prints_shell_output(
tmpdir, mock_stage, mock_fetch, install_mockery, env_deactivate
tmpdir, mock_stage, mock_fetch, install_mockery
):
"""Check the shell commands output by ``spack env activate --sh``.
@ -2171,7 +2161,7 @@ def test_env_activate_sh_prints_shell_output(
def test_env_activate_csh_prints_shell_output(
tmpdir, mock_stage, mock_fetch, install_mockery, env_deactivate
tmpdir, mock_stage, mock_fetch, install_mockery
):
"""Check the shell commands output by ``spack env activate --csh``."""
env('create', 'test', add_view=True)
@ -2188,8 +2178,7 @@ def test_env_activate_csh_prints_shell_output(
@pytest.mark.regression('12719')
def test_env_activate_default_view_root_unconditional(env_deactivate,
mutable_mock_env_path):
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)

View file

@ -195,7 +195,7 @@ def no_path_access(monkeypatch):
@pytest.fixture(scope='session', autouse=True)
def clean_user_environment():
spack_env_value = os.environ.pop(ev.spack_env_var, None)
with ev.deactivate_environment():
with ev.no_active_environment():
yield
if spack_env_value:
os.environ[ev.spack_env_var] = spack_env_value
@ -1386,11 +1386,11 @@ def get_rev():
@pytest.fixture()
def mutable_mock_env_path(tmpdir_factory):
"""Fixture for mocking the internal spack environments directory."""
saved_path = ev.env_path
saved_path = ev.environment.env_path
mock_path = tmpdir_factory.mktemp('mock-env-path')
ev.env_path = str(mock_path)
ev.environment.env_path = str(mock_path)
yield mock_path
ev.env_path = saved_path
ev.environment.env_path = saved_path
@pytest.fixture()

View file

@ -5,6 +5,8 @@
"""Test environment internals without CLI"""
import pytest
import spack.environment as ev
import spack.spec
@ -36,3 +38,11 @@ def test_hash_change_no_rehash_concrete(tmpdir, mock_packages, config):
assert read_in.concretized_order
assert read_in.concretized_order[0] in read_in.specs_by_hash
assert read_in.specs_by_hash[read_in.concretized_order[0]]._build_hash == new_hash
def test_activate_should_require_an_env():
with pytest.raises(TypeError):
ev.activate(env='name')
with pytest.raises(TypeError):
ev.activate(env=None)