From ca872f9c34f6b360d9d67a91294fa66452867e78 Mon Sep 17 00:00:00 2001 From: "John W. Parent" <45471568+johnwparent@users.noreply.github.com> Date: Fri, 1 Sep 2023 14:36:27 -0400 Subject: Windows: fix pwsh env activate/deactivate; load/unload (#39118) These commands are currently broken on powershell (Windows) due to improper use of the InvokeCommand commandlet and a lack of direct support for the `--pwsh` argument in `spack load`, `spack unload`, and `spack env deactivate`. --- bin/spack.ps1 | 23 +++++++++++++++++++---- lib/spack/spack/cmd/env.py | 7 +++++++ lib/spack/spack/cmd/load.py | 7 +++++++ lib/spack/spack/cmd/unload.py | 7 +++++++ lib/spack/spack/environment/shell.py | 4 ++-- lib/spack/spack/test/util/environment.py | 4 ++-- lib/spack/spack/util/environment.py | 13 ++++++------- share/spack/spack-completion.bash | 6 +++--- share/spack/spack-completion.fish | 12 +++++++++--- 9 files changed, 62 insertions(+), 21 deletions(-) diff --git a/bin/spack.ps1 b/bin/spack.ps1 index 39fe0167ca..d3f48dfc59 100644 --- a/bin/spack.ps1 +++ b/bin/spack.ps1 @@ -39,6 +39,20 @@ function Read-SpackArgs { return $SpackCMD_params, $SpackSubCommand, $SpackSubCommandArgs } +function Set-SpackEnv { + # This method is responsible + # for processing the return from $(spack ) + # which are returned as System.Object[]'s containing + # a list of env commands + # Invoke-Expression can only handle one command at a time + # so we iterate over the list to invoke the env modification + # expressions one at a time + foreach($envop in $args[0]){ + Invoke-Expression $envop + } +} + + function Invoke-SpackCD { if (Compare-CommonArgs $SpackSubCommandArgs) { python $Env:SPACK_ROOT/bin/spack cd -h @@ -79,7 +93,7 @@ function Invoke-SpackEnv { } else { $SpackEnv = $(python $Env:SPACK_ROOT/bin/spack $SpackCMD_params env activate "--pwsh" $SubCommandSubCommandArgs) - $ExecutionContext.InvokeCommand($SpackEnv) + Set-SpackEnv $SpackEnv } } "deactivate" { @@ -90,8 +104,8 @@ function Invoke-SpackEnv { python $Env:SPACK_ROOT/bin/spack env deactivate -h } else { - $SpackEnv = $(python $Env:SPACK_ROOT/bin/spack $SpackCMD_params env deactivate --pwsh) - $ExecutionContext.InvokeCommand($SpackEnv) + $SpackEnv = $(python $Env:SPACK_ROOT/bin/spack $SpackCMD_params env deactivate "--pwsh") + Set-SpackEnv $SpackEnv } } default {python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs} @@ -107,8 +121,9 @@ function Invoke-SpackLoad { python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs } else { + # python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand "--pwsh" $SpackSubCommandArgs $SpackEnv = $(python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand "--pwsh" $SpackSubCommandArgs) - $ExecutionContext.InvokeCommand($SpackEnv) + Set-SpackEnv $SpackEnv } } diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py index 40600b96af..8a2f129bda 100644 --- a/lib/spack/spack/cmd/env.py +++ b/lib/spack/spack/cmd/env.py @@ -239,6 +239,13 @@ def env_deactivate_setup_parser(subparser): const="bat", help="print bat commands to activate the environment", ) + shells.add_argument( + "--pwsh", + action="store_const", + dest="shell", + const="pwsh", + help="print pwsh 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 e4cc7443f8..e68fe48dce 100644 --- a/lib/spack/spack/cmd/load.py +++ b/lib/spack/spack/cmd/load.py @@ -52,6 +52,13 @@ def setup_parser(subparser): const="bat", help="print bat commands to load the package", ) + shells.add_argument( + "--pwsh", + action="store_const", + dest="shell", + const="pwsh", + help="print pwsh 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 667cd4804c..1fecdc5b33 100644 --- a/lib/spack/spack/cmd/unload.py +++ b/lib/spack/spack/cmd/unload.py @@ -51,6 +51,13 @@ def setup_parser(subparser): const="bat", help="print bat commands to load the package", ) + shells.add_argument( + "--pwsh", + action="store_const", + dest="shell", + const="pwsh", + help="print pwsh 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/shell.py b/lib/spack/spack/environment/shell.py index 1ae2acd3c4..380e49fa0f 100644 --- a/lib/spack/spack/environment/shell.py +++ b/lib/spack/spack/environment/shell.py @@ -43,7 +43,7 @@ def activate_header(env, shell, prompt=None): # TODO: despacktivate # TODO: prompt elif shell == "pwsh": - cmds += "$Env:SPACK_ENV=%s\n" % env.path + cmds += "$Env:SPACK_ENV='%s'\n" % env.path else: if "color" in os.getenv("TERM", "") and prompt: prompt = colorize("@G{%s}" % prompt, color=True, enclose=True) @@ -82,7 +82,7 @@ def deactivate_header(shell): # TODO: despacktivate # TODO: prompt elif shell == "pwsh": - cmds += "Remove-Item Env:SPACK_ENV" + cmds += "Set-Item -Path Env:SPACK_ENV\n" else: cmds += "if [ ! -z ${SPACK_ENV+x} ]; then\n" cmds += "unset SPACK_ENV; export SPACK_ENV;\n" diff --git a/lib/spack/spack/test/util/environment.py b/lib/spack/spack/test/util/environment.py index 481a58db47..b797331b77 100644 --- a/lib/spack/spack/test/util/environment.py +++ b/lib/spack/spack/test/util/environment.py @@ -172,8 +172,8 @@ def test_escape_double_quotes_in_shell_modifications(): assert r'set "VAR=$PATH;$ANOTHER_PATH"' in cmds assert r'set "QUOTED_VAR="MY_VAL"' in cmds cmds = to_validate.shell_modifications(shell="pwsh") - assert r"$Env:VAR=$PATH;$ANOTHER_PATH" in cmds - assert r'$Env:QUOTED_VAR="MY_VAL"' in cmds + assert "$Env:VAR='$PATH;$ANOTHER_PATH'" in cmds + assert "$Env:QUOTED_VAR='\"MY_VAL\"'" in cmds else: cmds = to_validate.shell_modifications() assert 'export VAR="$PATH:$ANOTHER_PATH"' in cmds diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py index 30322c320a..c18be76cc6 100644 --- a/lib/spack/spack/util/environment.py +++ b/lib/spack/spack/util/environment.py @@ -47,7 +47,7 @@ _SHELL_SET_STRINGS = { "csh": "setenv {0} {1};\n", "fish": "set -gx {0} {1};\n", "bat": 'set "{0}={1}"\n', - "pwsh": "$Env:{0}={1}\n", + "pwsh": "$Env:{0}='{1}'\n", } @@ -56,7 +56,7 @@ _SHELL_UNSET_STRINGS = { "csh": "unsetenv {0};\n", "fish": "set -e {0};\n", "bat": 'set "{0}="\n', - "pwsh": "Remove-Item Env:{0}\n", + "pwsh": "Set-Item -Path Env:{0}\n", } @@ -429,7 +429,7 @@ class RemovePath(NameValueModifier): def execute(self, env: MutableMapping[str, str]): tty.debug(f"RemovePath: {self.name}-{str(self.value)}", level=3) environment_value = env.get(self.name, "") - directories = environment_value.split(self.separator) if environment_value else [] + directories = environment_value.split(self.separator) directories = [ path_to_os_path(os.path.normpath(x)).pop() for x in directories @@ -724,11 +724,10 @@ class EnvironmentModifications: cmds += _SHELL_UNSET_STRINGS[shell].format(name) else: if sys.platform != "win32": - cmd = _SHELL_SET_STRINGS[shell].format( - name, double_quote_escape(new_env[name]) - ) + new_env_name = double_quote_escape(new_env[name]) else: - cmd = _SHELL_SET_STRINGS[shell].format(name, new_env[name]) + new_env_name = new_env[name] + cmd = _SHELL_SET_STRINGS[shell].format(name, new_env_name) cmds += cmd return cmds diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash index 63e1aa6d5c..d41b540a0e 100755 --- a/share/spack/spack-completion.bash +++ b/share/spack/spack-completion.bash @@ -1012,7 +1012,7 @@ _spack_env_activate() { } _spack_env_deactivate() { - SPACK_COMPREPLY="-h --help --sh --csh --fish --bat" + SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh" } _spack_env_create() { @@ -1304,7 +1304,7 @@ _spack_list() { _spack_load() { if $list_options then - SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --first --only --list" + SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh --first --only --list" else _installed_packages fi @@ -1922,7 +1922,7 @@ _spack_unit_test() { _spack_unload() { if $list_options then - SPACK_COMPREPLY="-h --help --sh --csh --fish --bat -a --all" + SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh -a --all" else _installed_packages fi diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish index 967ec96207..c4b9d47871 100755 --- a/share/spack/spack-completion.fish +++ b/share/spack/spack-completion.fish @@ -1442,7 +1442,7 @@ complete -c spack -n '__fish_spack_using_command env activate' -s d -l dir -r -f complete -c spack -n '__fish_spack_using_command env activate' -s d -l dir -r -d 'activate the environment in this directory' # spack env deactivate -set -g __fish_spack_optspecs_spack_env_deactivate h/help sh csh fish bat +set -g __fish_spack_optspecs_spack_env_deactivate h/help sh csh fish bat pwsh complete -c spack -n '__fish_spack_using_command env deactivate' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command env deactivate' -s h -l help -d 'show this help message and exit' complete -c spack -n '__fish_spack_using_command env deactivate' -l sh -f -a shell @@ -1453,6 +1453,8 @@ complete -c spack -n '__fish_spack_using_command env deactivate' -l fish -f -a s complete -c spack -n '__fish_spack_using_command env deactivate' -l fish -d 'print fish commands to activate the environment' complete -c spack -n '__fish_spack_using_command env deactivate' -l bat -f -a shell complete -c spack -n '__fish_spack_using_command env deactivate' -l bat -d 'print bat commands to activate the environment' +complete -c spack -n '__fish_spack_using_command env deactivate' -l pwsh -f -a shell +complete -c spack -n '__fish_spack_using_command env deactivate' -l pwsh -d 'print pwsh commands to activate the environment' # spack env create set -g __fish_spack_optspecs_spack_env_create h/help d/dir keep-relative without-view with-view= @@ -1972,7 +1974,7 @@ complete -c spack -n '__fish_spack_using_command list' -l update -r -f -a update complete -c spack -n '__fish_spack_using_command list' -l update -r -d 'write output to the specified file, if any package is newer' # spack load -set -g __fish_spack_optspecs_spack_load h/help sh csh fish bat first only= list +set -g __fish_spack_optspecs_spack_load h/help sh csh fish bat pwsh first only= list complete -c spack -n '__fish_spack_using_command_pos_remainder 0 load' -f -a '(__fish_spack_installed_specs)' complete -c spack -n '__fish_spack_using_command load' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command load' -s h -l help -d 'show this help message and exit' @@ -1984,6 +1986,8 @@ complete -c spack -n '__fish_spack_using_command load' -l fish -f -a shell complete -c spack -n '__fish_spack_using_command load' -l fish -d 'print fish commands to load the package' complete -c spack -n '__fish_spack_using_command load' -l bat -f -a shell complete -c spack -n '__fish_spack_using_command load' -l bat -d 'print bat commands to load the package' +complete -c spack -n '__fish_spack_using_command load' -l pwsh -f -a shell +complete -c spack -n '__fish_spack_using_command load' -l pwsh -d 'print pwsh commands to load the package' complete -c spack -n '__fish_spack_using_command load' -l first -f -a load_first complete -c spack -n '__fish_spack_using_command load' -l first -d 'load the first match if multiple packages match the spec' complete -c spack -n '__fish_spack_using_command load' -l only -r -f -a 'package dependencies' @@ -2845,7 +2849,7 @@ complete -c spack -n '__fish_spack_using_command unit-test' -l showlocals -f -a complete -c spack -n '__fish_spack_using_command unit-test' -l showlocals -d 'show local variable values in tracebacks' # spack unload -set -g __fish_spack_optspecs_spack_unload h/help sh csh fish bat a/all +set -g __fish_spack_optspecs_spack_unload h/help sh csh fish bat pwsh a/all complete -c spack -n '__fish_spack_using_command_pos_remainder 0 unload' -f -a '(__fish_spack_installed_specs)' complete -c spack -n '__fish_spack_using_command unload' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command unload' -s h -l help -d 'show this help message and exit' @@ -2857,6 +2861,8 @@ complete -c spack -n '__fish_spack_using_command unload' -l fish -f -a shell complete -c spack -n '__fish_spack_using_command unload' -l fish -d 'print fish commands to load the package' complete -c spack -n '__fish_spack_using_command unload' -l bat -f -a shell complete -c spack -n '__fish_spack_using_command unload' -l bat -d 'print bat commands to load the package' +complete -c spack -n '__fish_spack_using_command unload' -l pwsh -f -a shell +complete -c spack -n '__fish_spack_using_command unload' -l pwsh -d 'print pwsh commands to load the package' complete -c spack -n '__fish_spack_using_command unload' -s a -l all -f -a all complete -c spack -n '__fish_spack_using_command unload' -s a -l all -d 'unload all loaded Spack packages' -- cgit v1.2.3-70-g09d2