env: add spack env activate/deactivate and shell support

- `spack env activate foo`: sets SPACK_ENV to the current active env name

- `spack env deactivate`: unsets SPACK_ENV, deactivates the environment

- added support to setup_env.sh and setup_env.csh

- other env commands work properly with SPACK_ENV, as with an environment
  arguments.

- command-line --env arguments take precedence over the active
  environment, if given.
This commit is contained in:
Todd Gamblin 2018-10-14 15:21:57 -07:00
parent 15c5c36eaf
commit d14f7b82bb
9 changed files with 280 additions and 65 deletions

View File

@ -8,21 +8,30 @@
from llnl.util import tty from llnl.util import tty
shell_init_instructions = [
"To initialize spack's shell commands:",
"",
" # for bash and zsh",
" . %s/setup-env.sh" % spack.paths.share_path,
"",
" # for csh and tcsh",
" setenv SPACK_ROOT %s" % spack.paths.prefix,
" source %s/setup-env.csh" % spack.paths.share_path, ""
]
def print_module_placeholder_help(): def print_module_placeholder_help():
""" """
For use by commands to tell user how to activate shell support. For use by commands to tell user how to activate shell support.
""" """
tty.msg("This command requires spack's shell integration.", "", msg = [
"To initialize spack's shell commands, you must run one of", "This command requires spack's shell integration.", ""
"the commands below. Choose the right command for your shell.", ] + shell_init_instructions + [
"", "For bash and zsh:",
" . %s/setup-env.sh" % spack.paths.share_path, "",
"For csh and tcsh:",
" setenv SPACK_ROOT %s" % spack.paths.prefix,
" source %s/setup-env.csh" % spack.paths.share_path, "",
"This exposes a 'spack' shell function, which you can use like", "This exposes a 'spack' shell function, which you can use like",
" $ spack load package-foo", "", " $ spack load package-foo", "",
"Running the Spack executable directly (for example, invoking", "Running the Spack executable directly (for example, invoking",
"./bin/spack) will bypass the shell function and print this", "./bin/spack) will bypass the shell function and print this",
"placeholder message, even if you have sourced one of the above", "placeholder message, even if you have sourced one of the above",
"shell integration scripts.") "shell integration scripts."
]
tty.msg(*msg)

View File

@ -30,6 +30,8 @@
#: List of subcommands of `spack env` #: List of subcommands of `spack env`
subcommands = [ subcommands = [
'activate',
'deactivate',
'create', 'create',
'destroy', 'destroy',
['list', 'ls'], ['list', 'ls'],
@ -46,6 +48,141 @@
] ]
def get_env(args, cmd_name):
"""Get target environment from args, or from environment variables.
This is used by a number of commands for handling the environment
argument.
Check whether an environment was passed via arguments, then whether
it was passed via SPACK_ENV. If an environment is found, read it in.
If not, print an error message referencing the calling command.
Arguments:
args (Namespace): argparse namespace wtih command arguments
cmd_name (str): name of calling command
"""
env = args.env
if not env:
env = os.environ.get('SPACK_ENV')
if not env:
tty.die(
'spack env %s requires an active environment or an argument'
% cmd_name)
return ev.read(env)
#
# env activate
#
def env_activate_setup_parser(subparser):
"""set the current environment"""
shells = subparser.add_mutually_exclusive_group()
shells.add_argument(
'--sh', action='store_const', dest='shell', const='sh',
help="print sh commands to activate the environment")
shells.add_argument(
'--csh', action='store_const', dest='shell', const='csh',
help="print csh commands to activate the environment")
subparser.add_argument(
metavar='env', dest='activate_env',
help='name of environment to activate')
def env_activate(args):
if not args.activate_env:
tty.die('spack env activate requires an environment name')
env = args.activate_env
if not args.shell:
msg = [
"This command works best with Spack's shell support",
""
] + spack.cmd.common.shell_init_instructions + [
'Or, if you want to use `spack env activate` without initializing',
'shell support, you can run one of these:',
'',
' eval `spack env activate --sh %s` # for bash/sh' % env,
' eval `spack env activate --csh %s` # for csh/tcsh' % env,
]
tty.msg(*msg)
return 1
if not ev.exists(env):
tty.die("No such environment: '%s'" % env)
env_name_prompt = '[%s] ' % env
if args.shell == 'csh':
# TODO: figure out how to make color work for csh
sys.stdout.write('''\
setenv SPACK_ENV %s;
setenv SPACK_OLD_PROMPT "${prompt}";
set prompt="%s ${prompt}";
alias despacktivate "spack env deactivate";
''' % (env, env_name_prompt))
else:
if 'color' in os.environ['TERM']:
env_name_prompt = colorize('@G{%s} ' % env_name_prompt, color=True)
sys.stdout.write('''\
export SPACK_ENV=%s;
if [ -z "${SPACK_OLD_PS1}" ]; then export SPACK_OLD_PS1="${PS1}"; fi;
export PS1="%s ${PS1}";
alias despacktivate='spack env deactivate'
''' % (env, env_name_prompt))
#
# env deactivate
#
def env_deactivate_setup_parser(subparser):
"""deactivate any active environment in the shell"""
shells = subparser.add_mutually_exclusive_group()
shells.add_argument(
'--sh', action='store_const', dest='shell', const='sh',
help="print sh commands to deactivate the environment")
shells.add_argument(
'--csh', action='store_const', dest='shell', const='csh',
help="print csh commands to deactivate the environment")
def env_deactivate(args):
if not args.shell:
msg = [
"This command works best with Spack's shell support",
""
] + spack.cmd.common.shell_init_instructions + [
'Or, if you want to use `spack env activate` without initializing',
'shell support, you can run one of these:',
'',
' eval `spack env deactivate --sh` # for bash/sh',
' eval `spack env deactivate --csh` # for csh/tcsh',
]
tty.msg(*msg)
return 1
if 'SPACK_ENV' not in os.environ:
tty.die('No environment is currently active.')
if args.shell == 'csh':
sys.stdout.write('''\
unsetenv SPACK_ENV;
set prompt="${SPACK_OLD_PROMPT}";
unsetenv SPACK_OLD_PROMPT;
unalias despacktivate;
''')
else:
sys.stdout.write('''\
unset SPACK_ENV; export SPACK_ENV;
export PS1="$SPACK_OLD_PS1";
unset SPACK_OLD_PS1; export SPACK_OLD_PS1;
unalias despacktivate;
''')
# #
# env create # env create
# #
@ -59,12 +196,12 @@ def env_create_setup_parser(subparser):
def env_create(args): def env_create(args):
if args.envfile: if args.envfile:
with open(args.envfile) as f: with open(args.envfile) as f:
_environment_create(args.env, f) _env_create(args.env, f)
else: else:
_environment_create(args.env) _env_create(args.env)
def _environment_create(name, env_yaml=None): def _env_create(name, env_yaml=None):
"""Create a new environment, with an optional yaml description. """Create a new environment, with an optional yaml description.
Arguments: Arguments:
@ -93,7 +230,7 @@ def env_destroy_setup_parser(subparser):
def env_destroy(args): def env_destroy(args):
for env in args.env: for env in args.env:
if not ev.exists(ev.root(env)): if not ev.exists(env):
tty.die("No such environment: '%s'" % env) tty.die("No such environment: '%s'" % env)
elif not os.access(ev.root(env), os.W_OK): elif not os.access(ev.root(env), os.W_OK):
tty.die("insufficient permissions to modify environment: '%s'" tty.die("insufficient permissions to modify environment: '%s'"
@ -152,10 +289,8 @@ def env_add_setup_parser(subparser):
def env_add(args): def env_add(args):
if not args.env: env = get_env(args, 'add')
tty.die('spack env unadd requires an active env or argument')
env = ev.read(args.env)
for spec in spack.cmd.parse_specs(args.specs): for spec in spack.cmd.parse_specs(args.specs):
if not env.add(spec): if not env.add(spec):
tty.msg("Package {0} was already added to {1}" tty.msg("Package {0} was already added to {1}"
@ -204,17 +339,14 @@ def env_concretize_setup_parser(subparser):
def env_concretize(args): def env_concretize(args):
if not args.env: env = get_env(args, 'status')
tty.die('spack env status requires an active env or argument') _env_concretize(env, use_repo=bool(args.exact_env), force=args.force)
environment = ev.read(args.env)
_environment_concretize(
environment, use_repo=bool(args.exact_env), force=args.force)
def _environment_concretize(environment, use_repo=False, force=False): def _env_concretize(env, use_repo=False, force=False):
"""Function body separated out to aid in testing.""" """Function body separated out to aid in testing."""
new_specs = environment.concretize(force=force) new_specs = env.concretize(force=force)
environment.write(dump_packages=new_specs) env.write(dump_packages=new_specs)
# REMOVE # REMOVE
@ -228,10 +360,7 @@ def env_install_setup_parser(subparser):
def env_install(args): def env_install(args):
if not args.env: env = get_env(args, 'status')
tty.die('spack env status requires an active env or argument')
env = ev.read(args.env)
env.install(args) env.install(args)
@ -246,11 +375,8 @@ def env_uninstall_setup_parser(subparser):
def env_uninstall(args): def env_uninstall(args):
if not args.env: env = get_env(args, 'uninstall')
tty.die('spack env uninstall requires an active env or argument') env.uninstall(args)
environment = ev.read(args.env)
environment.uninstall(args)
# #
@ -262,9 +388,9 @@ def env_relocate_setup_parser(subparser):
def env_relocate(args): def env_relocate(args):
environment = ev.read(args.env) env = get_env(args, 'relocate')
environment.reset_os_and_compiler(compiler=args.compiler) env.reset_os_and_compiler(compiler=args.compiler)
environment.write() env.write()
# #
@ -280,12 +406,10 @@ def env_status_setup_parser(subparser):
def env_status(args): def env_status(args):
if not args.env: env = get_env(args, 'status')
tty.die('spack env status requires an active env or argument')
# TODO? option to show packages w/ multiple instances? # TODO: option to show packages w/ multiple instances?
environment = ev.read(args.env) env.status(
environment.status(
sys.stdout, recurse_dependencies=args.recurse_dependencies, sys.stdout, recurse_dependencies=args.recurse_dependencies,
hashes=args.long or args.very_long, hashes=args.long or args.very_long,
hashlen=None if args.very_long else 7, hashlen=None if args.very_long else 7,
@ -302,11 +426,8 @@ def env_stage_setup_parser(subparser):
def env_stage(args): def env_stage(args):
if not args.env: env = get_env(args, 'stage')
tty.die('spack env loads requires an active env or argument') for spec in env.specs_by_hash.values():
environment = ev.read(args.env)
for spec in environment.specs_by_hash.values():
for dep in spec.traverse(): for dep in spec.traverse():
dep.package.do_stage() dep.package.do_stage()
@ -325,8 +446,7 @@ def env_loads_setup_parser(subparser):
def env_loads(args): def env_loads(args):
if not args.env: env = get_env(args, 'loads')
tty.die('spack env loads requires an active env or argument')
# Set the module types that have been selected # Set the module types that have been selected
module_type = args.module_type module_type = args.module_type
@ -334,13 +454,12 @@ def env_loads(args):
# If no selection has been made select all of them # If no selection has been made select all of them
module_type = 'tcl' module_type = 'tcl'
environment = ev.read(args.env)
recurse_dependencies = args.recurse_dependencies recurse_dependencies = args.recurse_dependencies
args.recurse_dependencies = False args.recurse_dependencies = False
loads_file = fs.join_path(environment.path, 'loads') loads_file = fs.join_path(env.path, 'loads')
with open(loads_file, 'w') as f: with open(loads_file, 'w') as f:
specs = environment._get_environment_specs( specs = env._get_environment_specs(
recurse_dependencies=recurse_dependencies) recurse_dependencies=recurse_dependencies)
spack.cmd.modules.loads(module_type, specs, args, f) spack.cmd.modules.loads(module_type, specs, args, f)
@ -360,7 +479,7 @@ def env_upgrade_setup_parser(subparser):
def env_upgrade(args): def env_upgrade(args):
env = ev.read(args.env) env = get_env(args, 'upgrade')
if os.path.exists(env.repos_path): if os.path.exists(env.repos_path):
repo_stage = tempfile.mkdtemp() repo_stage = tempfile.mkdtemp()

View File

@ -26,6 +26,10 @@
from spack.version import VersionList from spack.version import VersionList
#: environment variable used to indicate the active environment
spack_env_var = 'SPACK_ENV'
#: currently activated environment #: currently activated environment
active = None active = None

View File

@ -587,6 +587,10 @@ def main(argv=None):
env = args.env or args.exact_env env = args.env or args.exact_env
if env: if env:
spack.environment.activate(env, args.exact_env is not None) spack.environment.activate(env, args.exact_env is not None)
else:
env = os.environ.get(spack.environment.spack_env_var)
if env:
spack.environment.activate(env, False)
# make spack.config aware of any command line configuration scopes # make spack.config aware of any command line configuration scopes
if args.config_scopes: if args.config_scopes:

View File

@ -14,4 +14,4 @@ def test_cd():
out = cd() out = cd()
assert "To initialize spack's shell commands, you must run one of" in out assert "To initialize spack's shell commands:" in out

View File

@ -12,7 +12,7 @@
import spack.modules import spack.modules
import spack.environment as ev import spack.environment as ev
from spack.cmd.env import _environment_concretize, _environment_create from spack.cmd.env import _env_concretize, _env_create
from spack.version import Version from spack.version import Version
from spack.spec import Spec from spack.spec import Spec
from spack.main import SpackCommand from spack.main import SpackCommand
@ -170,7 +170,7 @@ def test_to_lockfile_dict():
def test_env_repo(): def test_env_repo():
e = ev.Environment('testx') e = ev.Environment('testx')
e.add('mpileaks') e.add('mpileaks')
_environment_concretize(e) _env_concretize(e)
package = e.repo.get(spack.spec.Spec('mpileaks')) package = e.repo.get(spack.spec.Spec('mpileaks'))
assert package.namespace == 'spack.pkg.builtin.mock' assert package.namespace == 'spack.pkg.builtin.mock'
@ -246,7 +246,7 @@ def test_env_with_config():
""" """
spack.package_prefs.PackagePrefs.clear_caches() spack.package_prefs.PackagePrefs.clear_caches()
_environment_create('test', test_config) _env_create('test', test_config)
e = ev.read('test') e = ev.read('test')
ev.prepare_config_scope(e) ev.prepare_config_scope(e)
@ -266,7 +266,7 @@ def test_env_with_included_config_file():
""" """
spack.package_prefs.PackagePrefs.clear_caches() spack.package_prefs.PackagePrefs.clear_caches()
_environment_create('test', test_config) _env_create('test', test_config)
e = ev.read('test') e = ev.read('test')
@ -296,7 +296,7 @@ def test_env_with_included_config_scope():
""" % config_scope_path """ % config_scope_path
spack.package_prefs.PackagePrefs.clear_caches() spack.package_prefs.PackagePrefs.clear_caches()
_environment_create('test', test_config) _env_create('test', test_config)
e = ev.read('test') e = ev.read('test')
@ -328,7 +328,7 @@ def test_env_config_precedence():
""" """
spack.package_prefs.PackagePrefs.clear_caches() spack.package_prefs.PackagePrefs.clear_caches()
_environment_create('test', test_config) _env_create('test', test_config)
e = ev.read('test') e = ev.read('test')

View File

@ -63,6 +63,47 @@ case cd:
cd `\spack location $_sp_arg $_sp_args` cd `\spack location $_sp_arg $_sp_args`
endif endif
breaksw breaksw
case env:
shift _sp_args # get rid of 'env'
set _sp_arg=""
[ $#_sp_args -gt 0 ] && set _sp_arg = ($_sp_args[1])
if ( "$_sp_arg" == "-h" ) then
\spack env -h
else
switch ($_sp_arg)
case activate:
set _sp_env_arg=""
[ $#_sp_args -gt 1 ] && set _sp_env_arg = ($_sp_args[2])
if ( "$_sp_env_arg" == "" || "$_sp_env_arg" =~ "-*" ) then
# no args or does not start with -: just execute
\spack $_sp_flags env $_sp_args
else
shift _sp_args # consume 'activate' or 'deactivate'
# actual call to activate: source the output
eval `\spack $_sp_flags env activate --csh $_sp_args`
endif
breaksw
case deactivate:
set _sp_env_arg=""
[ $#_sp_args -gt 1 ] && set _sp_env_arg = ($_sp_args[2])
if ( "$_sp_env_arg" != "" ) then
# with args: execute the command
\spack $_sp_flags env $_sp_args
else
# no args: source the output
eval `\spack $_sp_flags env deactivate --csh`
endif
breaksw
default:
echo default
\spack $_sp_flags env $_sp_args
breaksw
endsw
endif
case use: case use:
case unuse: case unuse:
case load: case load:
@ -113,3 +154,4 @@ endsw
_sp_end: _sp_end:
unset _sp_args _sp_full_spec _sp_modtype _sp_module_args unset _sp_args _sp_full_spec _sp_modtype _sp_module_args
unset _sp_sh_cmd _sp_spec _sp_subcommand _sp_flags unset _sp_sh_cmd _sp_spec _sp_subcommand _sp_flags
unset _sp_arg _sp_env_arg

View File

@ -28,5 +28,6 @@ if ($?SPACK_ROOT) then
_spack_pathadd DK_NODE "$_sp_dotkit_root/$_sp_sys_type" _spack_pathadd DK_NODE "$_sp_dotkit_root/$_sp_sys_type"
_spack_pathadd MODULEPATH "$_sp_tcl_root/$_sp_sys_type" _spack_pathadd MODULEPATH "$_sp_tcl_root/$_sp_sys_type"
else else
echo "ERROR: Sourcing spack setup-env.csh requires setting SPACK_ROOT to the root of your spack installation" echo "ERROR: Sourcing spack setup-env.csh requires setting SPACK_ROOT to "
echo " the root of your spack installation."
endif endif

View File

@ -89,6 +89,42 @@ function spack {
fi fi
return return
;; ;;
"env")
_sp_arg=""
if [ -n "$1" ]; then
_sp_arg="$1"
shift
fi
if [ "$_sp_arg" = "-h" ]; then
command spack env -h
else
case $_sp_arg in
activate)
if [ -z "$1" -o "${1#-}" != "$1" ]; then
# no args or does not start with -: just execute
command spack "${args[@]}"
else
# actual call to activate: source the output
eval $(command spack $_sp_flags env activate --sh "$@")
fi
;;
deactivate)
if [ -n "$1" ]; then
# with args: execute the command
command spack "${args[@]}"
else
# no args: source the output.
eval $(command spack $_sp_flags env deactivate --sh)
fi
;;
*)
command spack "${args[@]}"
;;
esac
fi
return
;;
"use"|"unuse"|"load"|"unload") "use"|"unuse"|"load"|"unload")
# Shift any other args for use off before parsing spec. # Shift any other args for use off before parsing spec.
_sp_subcommand_args="" _sp_subcommand_args=""