diff --git a/.travis.yml b/.travis.yml
index b59d1666d8bab132b3cebaa1eaf868308d8f167f..5d8b79375896c794858ed1582c8cf77abd801058 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 43c125e8f24c6d6d90d1b66a2cb5da6cedfa22d9..d3e825f1dd99d35af993050078483f7a9d90c9a8 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 3ef485941fe4b7bde7bda62bb27cf9caa438016e..3938602882371fe9c9b44f774f54b3682466b4dd 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 d19a33102f9fc138deac6df7f57b837e49428139..cbee2fc76960fa56c5dac51f0360f17168f79d4c 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 32bd8c962d596777e112c61e358def9a52d1e789..f7b50c30c9cdb04b416ddb672780357b801903bf 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 3d69efa5ca558d5772c30119bd7ddd18222d827d..7e6d34e64de768be30003967604ae8dd36f9b255 100644
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -32,12 +32,14 @@
 _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 31c1c1548b1fe7bcf64a225cb5295cac9519ef17..126e6391867349345878f7975796da651a80824c 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 0000000000000000000000000000000000000000..964d876fe923d6e81500ce66811d619017eb1bab
--- /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 0000000000000000000000000000000000000000..a534f09ee9a0a25fff77836c3baab617281624e7
--- /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 830958fe42775dd1293cb0053fbe474f260b4fbe..11bd906ad06c06a76d5dcf499f3477779f0766c8 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