diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | lib/spack/spack/cmd/env.py | 6 | ||||
-rw-r--r-- | lib/spack/spack/cmd/load.py | 3 | ||||
-rw-r--r-- | lib/spack/spack/cmd/unload.py | 3 | ||||
-rw-r--r-- | lib/spack/spack/environment.py | 29 | ||||
-rw-r--r-- | lib/spack/spack/util/environment.py | 2 | ||||
-rwxr-xr-x | share/spack/qa/run-shell-tests | 6 | ||||
-rwxr-xr-x | share/spack/qa/setup-env-test.fish | 395 | ||||
-rwxr-xr-x | share/spack/setup-env.fish | 723 | ||||
-rwxr-xr-x | share/spack/spack-completion.bash | 8 |
10 files changed, 1171 insertions, 6 deletions
diff --git a/.travis.yml b/.travis.yml index b59d1666d8..5d8b793758 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ jobs: - ninja-build - realpath - zsh + - fish env: [ TEST_SUITE=unit, COVERAGE=true ] - python: '3.8' os: linux @@ -73,6 +74,7 @@ addons: - ninja-build - patchelf - zsh + - fish update: true # ~/.ccache needs to be cached directly as Travis is not taking care of it diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py index 43c125e8f2..d3e825f1dd 100644 --- a/lib/spack/spack/cmd/env.py +++ b/lib/spack/spack/cmd/env.py @@ -52,6 +52,9 @@ def env_activate_setup_parser(subparser): shells.add_argument( '--csh', action='store_const', dest='shell', const='csh', help="print csh commands to activate the environment") + shells.add_argument( + '--fish', action='store_const', dest='shell', const='fish', + help="print fish commands to activate the environment") view_options = subparser.add_mutually_exclusive_group() view_options.add_argument( @@ -127,6 +130,9 @@ def env_deactivate_setup_parser(subparser): shells.add_argument( '--csh', action='store_const', dest='shell', const='csh', help="print csh commands to deactivate the environment") + shells.add_argument( + '--fish', action='store_const', dest='shell', const='fish', + help="print fish commands to activate the environment") def env_deactivate(args): diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py index 3ef485941f..3938602882 100644 --- a/lib/spack/spack/cmd/load.py +++ b/lib/spack/spack/cmd/load.py @@ -32,6 +32,9 @@ def setup_parser(subparser): shells.add_argument( '--csh', action='store_const', dest='shell', const='csh', help="print csh commands to load the package") + shells.add_argument( + '--fish', action='store_const', dest='shell', const='fish', + help="print fish commands to load the package") subparser.add_argument( '--first', diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py index d19a33102f..cbee2fc769 100644 --- a/lib/spack/spack/cmd/unload.py +++ b/lib/spack/spack/cmd/unload.py @@ -31,6 +31,9 @@ def setup_parser(subparser): shells.add_argument( '--csh', action='store_const', dest='shell', const='csh', help="print csh commands to activate the environment") + shells.add_argument( + '--fish', action='store_const', dest='shell', const='fish', + help="print fish commands to load the package") subparser.add_argument('-a', '--all', action='store_true', help='unload all loaded Spack packages.') diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py index 32bd8c962d..f7b50c30c9 100644 --- a/lib/spack/spack/environment.py +++ b/lib/spack/spack/environment.py @@ -115,7 +115,7 @@ def 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 (string): One of `sh`, `csh`. + shell (string): One of `sh`, `csh`, `fish`. prompt (string): string to add to the users prompt, or None Returns: @@ -141,6 +141,19 @@ def activate( 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) @@ -156,6 +169,12 @@ def activate( 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. + # if add_view and default_view_name in env.views: with spack.store.db.read_transaction(): cmds += env.add_default_view_to_shell(shell) @@ -167,7 +186,7 @@ def deactivate(shell='sh'): """Undo any configuration or repo settings modified by ``activate()``. Arguments: - shell (string): One of `sh`, `csh`. Shell style to use. + shell (string): One of `sh`, `csh`, `fish`. Shell style to use. Returns: (string): shell commands for `shell` to undo environment variables @@ -191,6 +210,12 @@ def deactivate(shell='sh'): 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' diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py index 3d69efa5ca..7e6d34e64d 100644 --- a/lib/spack/spack/util/environment.py +++ b/lib/spack/spack/util/environment.py @@ -32,12 +32,14 @@ system_dirs = [os.path.join(p, s) for s in suffixes for p in system_paths] + \ _shell_set_strings = { 'sh': 'export {0}={1};\n', 'csh': 'setenv {0} {1};\n', + 'fish': 'set -gx {0} {1};\n' } _shell_unset_strings = { 'sh': 'unset {0};\n', 'csh': 'unsetenv {0};\n', + 'fish': 'set -e {0};\n', } diff --git a/share/spack/qa/run-shell-tests b/share/spack/qa/run-shell-tests index 31c1c1548b..126e639186 100755 --- a/share/spack/qa/run-shell-tests +++ b/share/spack/qa/run-shell-tests @@ -25,6 +25,9 @@ check_dependencies $coverage git hg svn export PATH="$ORIGINAL_PATH" unset spack +# Convert QA_DIR to absolute path before changing directory +export QA_DIR=$(realpath $QA_DIR) + # Start in the spack root directory cd "$SPACK_ROOT" @@ -41,3 +44,6 @@ fi # Run the test scripts for their output (these will print nicely) zsh "$QA_DIR/setup-env-test.sh" dash "$QA_DIR/setup-env-test.sh" + +# Run fish tests +fish "$QA_DIR/setup-env-test.fish" diff --git a/share/spack/qa/setup-env-test.fish b/share/spack/qa/setup-env-test.fish new file mode 100755 index 0000000000..964d876fe9 --- /dev/null +++ b/share/spack/qa/setup-env-test.fish @@ -0,0 +1,395 @@ +#!/usr/bin/env fish +# +# Copyright 2013-2019 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) + +# +# This script tests that Spack's setup-env.fish init script works. +# + + +function allocate_testing_global -d "allocate global variables used for testing" + + # Colors for output + set -gx __spt_red '\033[1;31m' + set -gx __spt_cyan '\033[1;36m' + set -gx __spt_green '\033[1;32m' + set -gx __spt_reset '\033[0m' + + # counts of test successes and failures. + set -gx __spt_success 0 + set -gx __spt_errors 0 +end + + +function delete_testing_global -d "deallocate global varialbes used for testing" + + set -e __spt_red + set -e __spt_cyan + set -e __spt_green + set -e __spt_reset + + set -e __spt_success + set -e __spt_errors +end + +# ------------------------------------------------------------------------ +# Functions for color output. +# ------------------------------------------------------------------------ + + +function echo_red + printf "$__spt_red$argv$__spt_reset\n" +end + +function echo_green + printf "$__spt_green$argv$__spt_reset\n" +end + +function echo_msg + printf "$__spt_cyan$argv$__spt_reset\n" +end + + + +# ------------------------------------------------------------------------ +# Generic functions for testing fish code. +# ------------------------------------------------------------------------ + + +# Print out a header for a group of tests. +function title + + echo + echo_msg "$argv" + echo_msg "---------------------------------" + +end + +# echo FAIL in red text; increment failures +function fail + echo_red FAIL + set __spt_errors (math $__spt_errors+1) +end + +# echo SUCCESS in green; increment successes +function pass + echo_green SUCCESS + set __spt_success (math $__spt_success+1) +end + + +# +# Run a command and suppress output unless it fails. +# On failure, echo the exit code and output. +# +function spt_succeeds + printf "'$argv' succeeds ... " + + set -l output (eval $argv 2>&1) + + if test $status -ne 0 + fail + echo_red "Command failed with error $status" + if test -n "$output" + echo_msg "Output:" + echo "$output" + else + echo_msg "No output." + end + else + pass + end +end + + +# +# Run a command and suppress output unless it succeeds. +# If the command succeeds, echo the output. +# +function spt_fails + printf "'$argv' fails ... " + + set -l output (eval $argv 2>&1) + + if test $status -eq 0 + fail + echo_red "Command failed with error $status" + if test -n "$output" + echo_msg "Output:" + echo "$output" + else + echo_msg "No output." + end + else + pass + end +end + + +# +# Ensure that a string is in the output of a command. +# Suppresses output on success. +# On failure, echo the exit code and output. +# +function spt_contains + set -l target_string $argv[1] + set -l remaining_args $argv[2..-1] + + printf "'$remaining_args' output contains '$target_string' ... " + + set -l output (eval $remaining_args 2>&1) + + if not echo "$output" | string match -q -r ".*$target_string.*" + fail + echo_red "Command exited with error $status" + echo_red "'$target_string' was not in output." + if test -n "$output" + echo_msg "Output:" + echo "$output" + else + echo_msg "No output." + end + else + pass + end +end + + +# +# Ensure that a variable is set. +# +function is_set + printf "'$argv[1]' is set ... " + + if test -z "$$argv[1]" + fail + echo_msg "'$argv[1]' was not set!" + else + pass + end +end + + +# +# Ensure that a variable is not set. +# Fails and prints the value of the variable if it is set. +# +function is_not_set + printf "'$argv[1]' is not set ... " + + if test -n "$$argv[1]" + fail + echo_msg "'$argv[1]' was set!" + echo " $$argv[1]" + else + pass + end +end + + + +# ----------------------------------------------------------------------- +# Setup test environment and do some preliminary checks +# ----------------------------------------------------------------------- + +# Make sure no environment is active +set -e SPACK_ENV +true # ignore failing `set -e` + +# Source setup-env.sh before tests +set -gx QA_DIR (dirname (status --current-filename)) +source $QA_DIR/../setup-env.fish + + + +# ----------------------------------------------------------------------- +# Instead of invoking the module and cd commands, we print the arguments that +# Spack invokes the command with, so we can check that Spack passes the expected +# arguments in the tests below. +# +# We make that happen by defining the fish functions below. NOTE: these overwrite +# existing functions => define them last +# ----------------------------------------------------------------------- + + +function module + echo "module $argv" +end + +function cd + echo "cd $argv" +end + + +allocate_testing_global + + + +# ----------------------------------------------------------------------- +# Let the testing begin! +# ----------------------------------------------------------------------- + + +title "Testing setup-env.fish with $_sp_shell" + +# spack command is now available +spt_succeeds which spack + + +# create a fake mock package install and store its location for later +title "Setup" +echo "Creating a mock package installation" +spack -m install --fake a + +# create a test environment for testing environment commands +echo "Creating a mock environment" +spack env create spack_test_env + +# ensure that we uninstall b on exit +function spt_cleanup + + set trapped_error false + if test $status -ne 0 + set trapped_error true + end + + echo "Removing test environment before exiting." + spack env deactivate 2>&1 > /dev/null + spack env rm -y spack_test_env + + title "Cleanup" + echo "Removing test packages before exiting." + spack -m uninstall -yf b a + + echo + echo "$__spt_success tests succeeded." + echo "$__spt_errors tests failed." + + if test "$trapped_error" = false + echo "Exited due to an error." + end + + if test "$__spt_errors" -eq 0 + if test "$trapped_error" = false + pass + exit 0 + else + fail + exit 1 + end + else + fail + exit 1 + end + + delete_testing_global +end + +trap spt_cleanup EXIT + + + +# ----------------------------------------------------------------------- +# Test all spack commands with special env support +# ----------------------------------------------------------------------- +title 'Testing `spack`' +spt_contains 'usage: spack ' spack +spt_contains "usage: spack " spack -h +spt_contains "usage: spack " spack help +spt_contains "usage: spack " spack -H +spt_contains "usage: spack " spack help --all + +title 'Testing `spack cd`' +spt_contains "usage: spack cd " spack cd -h +spt_contains "usage: spack cd " spack cd --help +spt_contains "cd $b_install" spack cd -i b + +title 'Testing `spack module`' +spt_contains "usage: spack module " spack -m module -h +spt_contains "usage: spack module " spack -m module --help +spt_contains "usage: spack module " spack -m module + +title 'Testing `spack load`' +set _b_loc (spack -m location -i b) +set _b_ld $_b_loc"/lib" +set _a_loc (spack -m location -i a) +set _a_ld $_a_loc"/lib" + +spt_contains "set -gx LD_LIBRARY_PATH $_b_ld" spack -m load --only package --fish b +spt_succeeds spack -m load b +# test a variable MacOS clears and one it doesn't for recursive loads +spt_contains "set -gx LD_LIBRARY_PATH $_a_ld:$_b_ld" spack -m load --fish a +spt_contains "set -gx LIBRARY_PATH $_a_ld:$_b_ld" spack -m load --fish a +spt_succeeds spack -m load --only dependencies a +spt_succeeds spack -m load --only package a +spt_fails spack -m load d +spt_contains "usage: spack load " spack -m load -h +spt_contains "usage: spack load " spack -m load -h d +spt_contains "usage: spack load " spack -m load --help + +title 'Testing `spack unload`' +spack -m load b a # setup +# spt_contains "module unload $b_module" spack -m unload b +spt_succeeds spack -m unload b +spt_succeeds spack -m unload --all +spack -m unload --all # cleanup +spt_fails spack -m unload -l +# spt_contains "module unload -l --arg $b_module" spack -m unload -l --arg b +spt_fails spack -m unload d +spt_contains "usage: spack unload " spack -m unload -h +spt_contains "usage: spack unload " spack -m unload -h d +spt_contains "usage: spack unload " spack -m unload --help + +title 'Testing `spack env`' +spt_contains "usage: spack env " spack env -h +spt_contains "usage: spack env " spack env --help + +title 'Testing `spack env list`' +spt_contains " spack env list " spack env list -h +spt_contains " spack env list " spack env list --help + +title 'Testing `spack env activate`' +spt_contains "No such environment:" spack env activate no_such_environment +spt_contains "usage: spack env activate " spack env activate +spt_contains "usage: spack env activate " spack env activate -h +spt_contains "usage: spack env activate " spack env activate --help + +title 'Testing `spack env deactivate`' +spt_contains "Error: No environment is currently active" spack env deactivate +spt_contains "usage: spack env deactivate " spack env deactivate no_such_environment +spt_contains "usage: spack env deactivate " spack env deactivate -h +spt_contains "usage: spack env deactivate " spack env deactivate --help + +title 'Testing activate and deactivate together' +echo "Testing 'spack env activate spack_test_env'" +spack env activate spack_test_env +is_set SPACK_ENV + +echo "Testing 'spack env deactivate'" +spack env deactivate +is_not_set SPACK_ENV + +echo "Testing 'spack env activate spack_test_env'" +spack env activate spack_test_env +is_set SPACK_ENV + +echo "Testing 'despacktivate'" +despacktivate +is_not_set SPACK_ENV + +# +# NOTE: `--prompt` on fish does nothing => currently not implemented. +# + +# echo "Testing 'spack env activate --prompt spack_test_env'" +# spack env activate --prompt spack_test_env +# is_set SPACK_ENV +# is_set SPACK_OLD_PS1 +# +# echo "Testing 'despacktivate'" +# despacktivate +# is_not_set SPACK_ENV +# is_not_set SPACK_OLD_PS1 diff --git a/share/spack/setup-env.fish b/share/spack/setup-env.fish new file mode 100755 index 0000000000..a534f09ee9 --- /dev/null +++ b/share/spack/setup-env.fish @@ -0,0 +1,723 @@ +# Copyright 2013-2019 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) + + +################################################################################# +# +# This file is part of Spack and sets up the spack environment for the friendly +# interactive shell (fish). This includes module support, and it also puts spack +# in your path. The script also checks that at least module support exists, and +# provides suggestions if it doesn't. Source it like this: +# +# source /path/to/spack/share/spack/setup-env.fish +# +################################################################################# +# This is a wrapper around the spack command that forwards calls to 'spack load' +# and 'spack unload' to shell functions. This in turn allows them to be used to +# invoke environment modules functions. +# +# 'spack load' is smarter than just 'load' because it converts its arguments into +# a unique spack spec that is then passed to module commands. This allows the +# user to load packages without knowing all their installation details. +# +# e.g., rather than requiring a full spec for libelf, the user can type: +# +# spack load libelf +# +# This will first find the available libelf modules and load a matching one. If +# there are two versions of libelf, the user would need to be more specific, +# e.g.: +# +# spack load libelf@0.8.13 +# +# This is very similar to how regular spack commands work and it avoids the need +# to come up with a user-friendly naming scheme for spack dotfiles. +################################################################################# + + +# +# Test for STDERR-NOCARET feature: if this is off, fish will redirect stderr to +# a file named in the string after `^` +# + + +if status test-feature stderr-nocaret +else + echo "WARNING: you have not enabled the 'stderr-nocaret' feature." + echo "This means that you have to escape the caret (^) character when defining specs." + echo "Consider enabling stderr-nocaret: https://fishshell.com/docs/current/index.html#featureflags" +end + + + +# +# SPACK wrapper function, preprocessing arguments and flags. +# + + +function spack -d "wrapper for the `spack` command" + + +# +# DEFINE SUPPORT FUNCTIONS HERE +# + + +# +# ALLOCATE_SP_SHARED, and DELETE_SP_SHARED allocate (and delete) temporary +# global variables +# + + +function allocate_sp_shared -d "allocate shared (global variables)" + set -gx __sp_remaining_args + set -gx __sp_subcommand_args + set -gx __sp_module_args + set -gx __sp_stat + set -gx __sp_stdout + set -gx __sp_stderr +end + + + +function delete_sp_shared -d "deallocate shared (global variables)" + set -e __sp_remaining_args + set -e __sp_subcommand_args + set -e __sp_module_args + set -e __sp_stat + set -e __sp_stdout + set -e __sp_stderr +end + + + + +# +# STREAM_ARGS and SHIFT_ARGS: helper functions manipulating the `argv` array: +# -> STREAM_ARGS: echos the `argv` array element-by-element +# -> SHIFT_ARGS: echos the `argv` array element-by-element starting with the +# second element. If `argv` has only one element, echo the +# empty string `""`. +# NOTE: while `stream_args` is not strictly necessary, it adds a nice symmetry +# to `shift_args` +# + +function stream_args -d "echos args as a stream" + # return the elements of `$argv` as an array + # -> since we want to be able to call it as part of `set x (shift_args + # $x)`, we return these one-at-a-time using echo... this means that the + # sub-command stream will correctly concatenate the output into an array + for elt in $argv + echo $elt + end +end + + +function shift_args -d "simulates bash shift" + # + # Returns argv[2..-1] (as an array) + # -> if argv has only 1 element, then returns the empty string. This + # simulates the behavior of bash `shift` + # + + if test -z "$argv[2]" + # there are no more element, returning the empty string + echo "" + else + # return the next elements `$argv[2..-1]` as an array + # -> since we want to be able to call it as part of `set x (shift_args + # $x)`, we return these one-at-a-time using echo... this means that + # the sub-command stream will correctly concatenate the output into + # an array + for elt in $argv[2..-1] + echo $elt + end + end + +end + + + + +# +# CAPTURE_ALL: helper function used to capture stdout, stderr, and status +# -> CAPTURE_ALL: there is a bug in fish, that prevents stderr re-capture +# from nested command substitution: +# https://github.com/fish-shell/fish-shell/issues/6459 +# + +function capture_all + begin; + begin; + eval $argv[1] + set $argv[2] $status # read sets the `status` flag => capture here + end 2>| read -z __err + end 1>| read -z __out + + # output arrays + set $argv[3] (echo $__out | string split \n) + set $argv[4] (echo $__err | string split \n) + + return 0 +end + + + + +# +# GET_SP_FLAGS, and GET_MOD_ARGS: support functions for extracting arguments and +# flags. Note bash's `shift` operation is simulated by the `__sp_remaining_args` +# array which is roughly equivalent to `$@` in bash. +# + +function get_sp_flags -d "return leading flags" + # + # Accumulate initial flags for main spack command. NOTE: Sets the external + # array: `__sp_remaining_args` containing all unprocessed arguments. + # + + # initialize argument counter + set -l i 1 + + # iterate over elements (`elt`) in `argv` array + for elt in $argv + + # match element `elt` of `argv` array to check if it has a leading dash + if echo $elt | string match -r -q "^-" + # by echoing the current `elt`, the calling stream accumulates list + # of valid flags. NOTE that this can also be done by adding to an + # array, but fish functions can only return integers, so this is the + # most elegant solution. + echo $elt + else + # bash compatibility: stop when the match first fails. Upon failure, + # we pack the remainder of `argv` into a global `__sp_remaining_args` + # array (`i` tracks the index of the next element). + set __sp_remaining_args (stream_args $argv[$i..-1]) + return + end + + # increment argument counter: used in place of bash's `shift` command + set -l i (math $i+1) + + end + + # if all elements in `argv` are matched, make sure that `__sp_remaining_args` + # is deleted (this might be overkill...). + set -e __sp_remaining_args +end + + + +# +# CHECK_SP_FLAGS, CONTAINS_HELP_FLAGS, CHECK_ENV_ACTIVATE_FLAGS, and +# CHECK_ENV_DEACTIVATE_FLAGS: support functions for checking arguments and flags. +# + +function check_sp_flags -d "check spack flags for h/V flags" + # + # Check if inputs contain h or V flags. + # + + # combine argument array into single string (space seperated), to be passed + # to regular expression matching (`string match -r`) + set -l _a "$argv" + + # skip if called with blank input. Notes: [1] (cf. EOF) + if test -n "$_a" + if echo $_a | string match -r -q ".*h.*" + return 0 + end + if echo $_a | string match -r -q ".*V.*" + return 0 + end + end + + return 1 +end + + + +function check_env_activate_flags -d "check spack env subcommand flags for -h, --sh, --csh, or --fish" + # + # Check if inputs contain -h, --sh, --csh, or --fish + # + + # combine argument array into single string (space seperated), to be passed + # to regular expression matching (`string match -r`) + set -l _a "$argv" + + # skip if called with blank input. Notes: [1] (cf. EOF) + if test -n "$_a" + # looks for a single `-h` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *-h *" + return 0 + end + + # looks for a single `--sh` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *--sh *" + return 0 + end + + # looks for a single `--csh` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *--csh *" + return 0 + end + + # looks for a single `--fish` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *--fish *" + return 0 + end + + end + + return 1 +end + + +function check_env_deactivate_flags -d "check spack env subcommand flags for --sh, --csh, or --fish" + # + # Check if inputs contain -h, --sh, --csh, or --fish + # + + # combine argument array into single string (space seperated), to be passed + # to regular expression matching (`string match -r`) + set -l _a "$argv" + + # skip if called with blank input. Notes: [1] (cf. EOF) + if test -n "$_a" + + # TODO: should this crash (we're clearly using fish, not bash, here)? + # looks for a single `--sh` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *--sh *" + return 0 + end + + # TODO: should this crash (we're clearly using fish, not csh, here)? + # looks for a single `--csh` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *--csh *" + return 0 + end + + # looks for a single `--fish` (possibly surrounded by spaces) + if echo $_a | string match -r -q " *--fish *" + return 0 + end + + end + + return 1 +end + + + + +# +# SPACK RUNNER function, this does all the work! +# + + +function spack_runner -d "Runner function for the `spack` wrapper" + + + # + # Accumulate initial flags for main spack command + # + + set __sp_remaining_args # remaining (unparsed) arguments + set -l sp_flags (get_sp_flags $argv) # sets __sp_remaining_args + + + # + # h and V flags don't require further output parsing. + # + + if check_sp_flags $sp_flags + command spack $sp_flags $__sp_remaining_args + return 0 + end + + + # + # Isolate subcommand and subcommand specs. Notes: [1] (cf. EOF) + # + + set -l sp_subcommand "" + + if test -n "$__sp_remaining_args[1]" + set sp_subcommand $__sp_remaining_args[1] + set __sp_remaining_args (shift_args $__sp_remaining_args) # simulates bash shift + end + + set -l sp_spec $__sp_remaining_args + + + # + # Filter out cd, env, and load and unload. For any other commands, just run + # the spack command as is. + # + + switch $sp_subcommand + + # CASE: spack subcommand is `cd`: if the sub command arg is `-h`, nothing + # further needs to be done. Otherwise, test the location referring the + # subcommand and cd there (if it exists). + + case "cd" + + set -l sp_arg "" + + # Extract the first subcommand argument. Notes: [1] (cf. EOF) + if test -n "$__sp_remaining_args[1]" + set sp_arg $__sp_remaining_args[1] + set __sp_remaining_args (shift_args $__sp_remaining_args) # simulates bash shift + end + + # Notes: [2] (cf. EOF) + if test "x$sp_arg" = "x-h"; or test "x$sp_arg" = "x--help" + # nothing more needs to be done for `-h` or `--help` + command spack cd -h + else + # extract location using the subcommand (fish `(...)`) + set -l LOC (command spack location $sp_arg $__sp_remaining_args) + + # test location and cd if exists: + if test -d "$LOC" + cd $LOC + else + return 1 + end + + end + + return 0 + + + # CASE: spack subcommand is `env`. Here we get the spack runtime to + # supply the appropriate shell commands for setting the environment + # varibles. These commands are then run by fish (using the `capture_all` + # function, instead of a command substitution). + + case "env" + + set -l sp_arg "" + + # Extract the first subcommand argument. Notes: [1] (cf. EOF) + if test -n "$__sp_remaining_args[1]" + set sp_arg $__sp_remaining_args[1] + set __sp_remaining_args (shift_args $__sp_remaining_args) # simulates bash shift + end + + # Notes: [2] (cf. EOF) + if test "x$sp_arg" = "x-h"; or test "x$sp_arg" = "x--help" + # nothing more needs to be done for `-h` or `--help` + command spack env -h + else + switch $sp_arg + case "activate" + set -l _a (stream_args $__sp_remaining_args) + + if check_env_activate_flags $_a + # no args or args contain -h/--help, --sh, or --csh: just execute + command spack env activate $_a + else + # actual call to activate: source the output + set -l sp_env_cmd "command spack $sp_flags env activate --fish $__sp_remaining_args" + capture_all $sp_env_cmd __sp_stat __sp_stdout __sp_stderr + eval $__sp_stdout + if test -n "$__sp_stderr" + echo -s \n$__sp_stderr 1>&2 # current fish bug: handle stderr manually + end + end + + case "deactivate" + set -l _a (stream_args $__sp_remaining_args) + + if check_env_deactivate_flags $_a + # just execute the command if --sh, --csh, or --fish are provided + command spack env deactivate $_a + + # Test of further (unparsed arguments). Any other + # arguments are an error or help, so just run help + # -> TODO: This should throw and error but leave as is + # for compatibility with setup-env.sh + # -> Notes: [1] (cf. EOF). + else if test -n "$__sp_remaining_args" + command spack env deactivate -h + else + # no args: source the output of the command + set -l sp_env_cmd "command spack $sp_flags env deactivate --fish" + capture_all $sp_env_cmd __sp_stat __sp_stdout __sp_stderr + eval $__sp_stdout + if test $__sp_stat -ne 0 + if test -n "$__sp_stderr" + echo -s \n$__sp_stderr 1>&2 # current fish bug: handle stderr manually + end + return 1 + end + end + + case "*" + # if $__sp_remaining_args is empty, then don't include it + # as argument (otherwise it will be confused as a blank + # string input!) + if test -n "$__sp_remaining_args" + command spack env $sp_arg $__sp_remaining_args + else + command spack env $sp_arg + end + end + end + + + # CASE: spack subcommand is either `load`, or `unload`. These statements + # deal with the technical details of actually using modules. Especially + # to deal with the substituting latest version numbers to the module + # command. + + case "load" or "unload" + + set -l _a (stream_args $__sp_remaining_args) + + if check_env_activate_flags $_a + # no args or args contain -h/--help, --sh, or --csh: just execute + command spack $sp_flags $sp_subcommand $__sp_remaining_args + else + # actual call to activate: source the output + set -l sp_env_cmd "command spack $sp_flags $sp_subcommand --fish $__sp_remaining_args" + capture_all $sp_env_cmd __sp_stat __sp_stdout __sp_stderr + eval $__sp_stdout + if test $__sp_stat -ne 0 + if test -n "$__sp_stderr" + echo -s \n$__sp_stderr 1>&2 # current fish bug: handle stderr manually + end + return 1 + end + end + + + # CASE: Catch-all + + case "*" + command spack $argv + + end + + return 0 +end + + + + +# +# RUN SPACK_RUNNER HERE +# + + +# +# Allocate temporary global variables used for return extra arguments from +# functions. NOTE: remember to call delete_sp_shared whenever returning from +# this function. +# + +allocate_sp_shared + + +# +# Run spack command using the spack_runner. +# + +spack_runner $argv +# Capture state of spack_runner (returned below) +set -l stat $status + + +# +# Delete temprary global variabels allocated in `allocated_sp_shared`. +# + +delete_sp_shared + + + +return $stat + +end + + + +################################################################################# +# Prepends directories to path, if they exist. +# pathadd /path/to/dir # add to PATH +# or pathadd OTHERPATH /path/to/dir # add to OTHERPATH +################################################################################# +function spack_pathadd -d "Add path to specified variable (defaults to PATH)" + # + # Adds (existing only) paths to specified (defaults to PATH) + # variable. Does not warn attempting to add non-existing path. This is not a + # bug because the MODULEPATH setup tries add all possible compatible systems + # and therefore sp_multi_pathadd relies on this function failing silently. + # + + # If no variable name is supplied, just append to PATH otherwise append to + # that variable. + # -> Notes: [1] (cf. EOF). + if test -n "$argv[2]" + set pa_varname $argv[1] + set pa_new_path $argv[2] + else + true # this is a bit of a strange hack! Notes: [3] (cf EOF). + set pa_varname PATH + set pa_new_path $argv[1] + end + + set pa_oldvalue $$pa_varname + + # skip path is not existing directory + # -> Notes: [1] (cf. EOF). + if test -d "$pa_new_path" + + # combine argument array into single string (space seperated), to be + # passed to regular expression matching (`string match -r`) + set -l _a "$pa_oldvalue" + + # skip path if it is already contained in the variable + # note spaces in regular expression: we're matching to a space delimited + # list of paths + if not echo $_a | string match -q -r " *$pa_new_path *" + if test -n "$pa_oldvalue" + set $pa_varname $pa_new_path $pa_oldvalue + else + true # this is a bit of a strange hack! Notes: [3] (cf. EOF) + set $pa_varname $pa_new_path + end + end + end +end + + +function sp_multi_pathadd -d "Helper for adding module-style paths by incorporating compatible systems into pathadd" --inherit-variable _sp_compatible_sys_types + # + # Calls spack_pathadd in path inputs, adding all compatible system types + # (sourced from $_sp_compatible_sys_types) to input paths. + # + + for pth in $argv[2] + for systype in $_sp_compatible_sys_types + spack_pathadd $argv[1] "$pth/$systype" + end + end +end + + + +# +# Figure out where this file is. Below code only needs to work in fish +# +set -l sp_source_file (status -f) # name of current file + + + +# +# Find root directory and add bin to path. +# +set -l sp_share_dir (realpath (dirname $sp_source_file)) +set -l sp_prefix (realpath (dirname (dirname $sp_share_dir))) +spack_pathadd PATH "$sp_prefix/bin" +set -xg SPACK_ROOT $sp_prefix + + + +# +# No need to determine which shell is being used (obviously it's fish) +# +set -xg SPACK_SHELL "fish" +set -xg _sp_shell "fish" + + + + +# +# Check whether we need environment-variables (module) <= `use` is not available +# +set -l need_module "no" +if not functions -q use; and not functions -q module + set need_module "yes" +end + + + +# +# Make environment-modules available to shell +# +function sp_apply_shell_vars -d "applies expressions of the type `a='b'` as `set a b`" + + # convert `a='b' to array variable `a b` + set -l expr_token (string trim -c "'" (string split "=" $argv)) + + # run set command to takes, converting lists of type `a:b:c` to array + # variables `a b c` by splitting around the `:` character + set -xg $expr_token[1] (string split ":" $expr_token[2]) +end + + +if test "$need_module" = "yes" + set -l sp_shell_vars (command spack --print-shell-vars sh,modules) + + for sp_var_expr in $sp_shell_vars + sp_apply_shell_vars $sp_var_expr + end + + # _sp_module_prefix is set by spack --print-sh-vars + if test "$_sp_module_prefix" != "not_installed" + set -xg MODULE_PREFIX $_sp_module_prefix + spack_pathadd PATH "$MODULE_PREFIX/bin" + end + +else + + set -l sp_shell_vars (command spack --print-shell-vars sh) + + for sp_var_expr in $sp_shell_vars + sp_apply_shell_vars $sp_var_expr + end + +end + +if test "$need_module" = "yes" + function module -d "wrapper for the `module` command to point at Spack's modules instance" --inherit-variable MODULE_PREFIX + eval $MODULE_PREFIX/bin/modulecmd $SPACK_SHELL $argv + end +end + + + +# +# set module system roots +# + +# Search of MODULESPATHS by trying all possible compatible system types as +# module roots. +if test -z "$MODULEPATH" + set -gx MODULEPATH +end +sp_multi_pathadd MODULEPATH $_sp_tcl_roots + + + +# +# NOTES +# +# [1]: `test -n` requires exactly 1 argument. If `argv` is undefined, or if it +# is an array, `test -n $argv` is unpredictable. Instead, encapsulate +# `argv` in a string, and test the string. +# +# [2]: `test "$a" = "$b$` is dangerous if `a` and `b` contain flags at index 1, +# as `test $a` can be interpreted as `test $a[1] $a[2..-1]`. Solution is to +# prepend a non-flag character, eg: `test "x$a" = "x$b"`. +# +# [3]: When the test in the if statement fails, the `status` flag is set to 1. +# `true` here manuallt resets the value of `status` to 0. Since `set` +# passes `status` along, we thus avoid the function returning 1 by mistake. diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash index 830958fe42..11bd906ad0 100755 --- a/share/spack/spack-completion.bash +++ b/share/spack/spack-completion.bash @@ -732,14 +732,14 @@ _spack_env() { _spack_env_activate() { if $list_options then - SPACK_COMPREPLY="-h --help --sh --csh -v --with-view -V --without-view -d --dir -p --prompt" + SPACK_COMPREPLY="-h --help --sh --csh --fish -v --with-view -V --without-view -d --dir -p --prompt" else _environments fi } _spack_env_deactivate() { - SPACK_COMPREPLY="-h --help --sh --csh" + SPACK_COMPREPLY="-h --help --sh --csh --fish" } _spack_env_create() { @@ -997,7 +997,7 @@ _spack_list() { _spack_load() { if $list_options then - SPACK_COMPREPLY="-h --help -r --dependencies --sh --csh --first --only" + SPACK_COMPREPLY="-h --help -r --dependencies --sh --csh --fish --first --only" else _installed_packages fi @@ -1445,7 +1445,7 @@ _spack_uninstall() { _spack_unload() { if $list_options then - SPACK_COMPREPLY="-h --help --sh --csh -a --all" + SPACK_COMPREPLY="-h --help --sh --csh --fish -a --all" else _installed_packages fi |