Windows: Add PowerShell env support (#37951)
PowerShell requires explicit shell and env support in Spack. This is due to the distinct differences in shell interactions between cmd and pwsh. Add a doskey in pwsh piping 'spack' commands to a powershell script similar to the sh function 'spack'. Add support for PowerShell-specific shell interactions from Spack (set/unset shell variables).
This commit is contained in:
parent
25cc734452
commit
78f33bc002
11 changed files with 197 additions and 15 deletions
132
bin/spack.ps1
Normal file
132
bin/spack.ps1
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
# Copyright 2013-2023 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)
|
||||||
|
# #######################################################################
|
||||||
|
|
||||||
|
function Compare-CommonArgs {
|
||||||
|
$CMDArgs = $args[0]
|
||||||
|
# These aruments take precedence and call for no futher parsing of arguments
|
||||||
|
# invoke actual Spack entrypoint with that context and exit after
|
||||||
|
"--help", "-h", "--version", "-V" | ForEach-Object {
|
||||||
|
$arg_opt = $_
|
||||||
|
if(($CMDArgs) -and ([bool]($CMDArgs.Where({$_ -eq $arg_opt})))) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Read-SpackArgs {
|
||||||
|
$SpackCMD_params = @()
|
||||||
|
$SpackSubCommand = $NULL
|
||||||
|
$SpackSubCommandArgs = @()
|
||||||
|
$args_ = $args[0]
|
||||||
|
$args_ | ForEach-Object {
|
||||||
|
if (!$SpackSubCommand) {
|
||||||
|
if($_.SubString(0,1) -eq "-")
|
||||||
|
{
|
||||||
|
$SpackCMD_params += $_
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$SpackSubCommand = $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$SpackSubCommandArgs += $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $SpackCMD_params, $SpackSubCommand, $SpackSubCommandArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-SpackCD {
|
||||||
|
if (Compare-CommonArgs $SpackSubCommandArgs) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack cd -h
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$LOC = $(python $Env:SPACK_ROOT/bin/spack location $SpackSubCommandArgs)
|
||||||
|
if (($NULL -ne $LOC)){
|
||||||
|
if ( Test-Path -Path $LOC){
|
||||||
|
Set-Location $LOC
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-SpackEnv {
|
||||||
|
if (Compare-CommonArgs $SpackSubCommandArgs[0]) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack env -h
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$SubCommandSubCommand = $SpackSubCommandArgs[0]
|
||||||
|
$SubCommandSubCommandArgs = $SpackSubCommandArgs[1..$SpackSubCommandArgs.Count]
|
||||||
|
switch ($SubCommandSubCommand) {
|
||||||
|
"activate" {
|
||||||
|
if (Compare-CommonArgs $SubCommandSubCommandArgs) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack env activate $SubCommandSubCommandArgs
|
||||||
|
}
|
||||||
|
elseif ([bool]($SubCommandSubCommandArgs.Where({$_ -eq "--pwsh"}))) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack env activate $SubCommandSubCommandArgs
|
||||||
|
}
|
||||||
|
elseif (!$SubCommandSubCommandArgs) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack env activate $SubCommandSubCommandArgs
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$SpackEnv = $(python $Env:SPACK_ROOT/bin/spack $SpackCMD_params env activate "--pwsh" $SubCommandSubCommandArgs)
|
||||||
|
$ExecutionContext.InvokeCommand($SpackEnv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"deactivate" {
|
||||||
|
if ([bool]($SubCommandSubCommandArgs.Where({$_ -eq "--pwsh"}))) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack env deactivate $SubCommandSubCommandArgs
|
||||||
|
}
|
||||||
|
elseif($SubCommandSubCommandArgs) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default {python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-SpackLoad {
|
||||||
|
if (Compare-CommonArgs $SpackSubCommandArgs) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs
|
||||||
|
}
|
||||||
|
elseif ([bool]($SpackSubCommandArgs.Where({($_ -eq "--pwsh") -or ($_ -eq "--list")}))) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$SpackEnv = $(python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand "--pwsh" $SpackSubCommandArgs)
|
||||||
|
$ExecutionContext.InvokeCommand($SpackEnv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$SpackCMD_params, $SpackSubCommand, $SpackSubCommandArgs = Read-SpackArgs $args
|
||||||
|
|
||||||
|
if (Compare-CommonArgs $SpackCMD_params) {
|
||||||
|
python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process Spack commands with special conditions
|
||||||
|
# all other commands are piped directly to Spack
|
||||||
|
switch($SpackSubCommand)
|
||||||
|
{
|
||||||
|
"cd" {Invoke-SpackCD}
|
||||||
|
"env" {Invoke-SpackEnv}
|
||||||
|
"load" {Invoke-SpackLoad}
|
||||||
|
"unload" {Invoke-SpackLoad}
|
||||||
|
default {python $Env:SPACK_ROOT/bin/spack $SpackCMD_params $SpackSubCommand $SpackSubCommandArgs}
|
||||||
|
}
|
|
@ -36,7 +36,10 @@ def shell_init_instructions(cmd, equivalent):
|
||||||
" source %s/setup-env.fish" % spack.paths.share_path,
|
" source %s/setup-env.fish" % spack.paths.share_path,
|
||||||
"",
|
"",
|
||||||
color.colorize("@*c{For Windows batch:}"),
|
color.colorize("@*c{For Windows batch:}"),
|
||||||
" source %s/spack_cmd.bat" % spack.paths.share_path,
|
" %s\\spack_cmd.bat" % spack.paths.bin_path,
|
||||||
|
"",
|
||||||
|
color.colorize("@*c{For PowerShell:}"),
|
||||||
|
" %s\\setup-env.ps1" % spack.paths.share_path,
|
||||||
"",
|
"",
|
||||||
"Or, if you do not want to use shell support, run "
|
"Or, if you do not want to use shell support, run "
|
||||||
+ ("one of these" if shell_specific else "this")
|
+ ("one of these" if shell_specific else "this")
|
||||||
|
@ -50,6 +53,7 @@ def shell_init_instructions(cmd, equivalent):
|
||||||
equivalent.format(sh_arg="--csh ") + " # csh/tcsh",
|
equivalent.format(sh_arg="--csh ") + " # csh/tcsh",
|
||||||
equivalent.format(sh_arg="--fish") + " # fish",
|
equivalent.format(sh_arg="--fish") + " # fish",
|
||||||
equivalent.format(sh_arg="--bat ") + " # batch",
|
equivalent.format(sh_arg="--bat ") + " # batch",
|
||||||
|
equivalent.format(sh_arg="--pwsh") + " # powershell",
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
msg += [" " + equivalent]
|
msg += [" " + equivalent]
|
||||||
|
|
|
@ -86,6 +86,13 @@ def env_activate_setup_parser(subparser):
|
||||||
const="bat",
|
const="bat",
|
||||||
help="print bat commands to activate the environment",
|
help="print bat commands to activate the environment",
|
||||||
)
|
)
|
||||||
|
shells.add_argument(
|
||||||
|
"--pwsh",
|
||||||
|
action="store_const",
|
||||||
|
dest="shell",
|
||||||
|
const="pwsh",
|
||||||
|
help="print powershell commands to activate environment",
|
||||||
|
)
|
||||||
|
|
||||||
view_options = subparser.add_mutually_exclusive_group()
|
view_options = subparser.add_mutually_exclusive_group()
|
||||||
view_options.add_argument(
|
view_options.add_argument(
|
||||||
|
|
|
@ -42,6 +42,8 @@ def activate_header(env, shell, prompt=None):
|
||||||
cmds += 'set "SPACK_ENV=%s"\n' % env.path
|
cmds += 'set "SPACK_ENV=%s"\n' % env.path
|
||||||
# TODO: despacktivate
|
# TODO: despacktivate
|
||||||
# TODO: prompt
|
# TODO: prompt
|
||||||
|
elif shell == "pwsh":
|
||||||
|
cmds += "$Env:SPACK_ENV=%s\n" % env.path
|
||||||
else:
|
else:
|
||||||
if "color" in os.getenv("TERM", "") and prompt:
|
if "color" in os.getenv("TERM", "") and prompt:
|
||||||
prompt = colorize("@G{%s}" % prompt, color=True, enclose=True)
|
prompt = colorize("@G{%s}" % prompt, color=True, enclose=True)
|
||||||
|
@ -79,6 +81,8 @@ def deactivate_header(shell):
|
||||||
cmds += 'set "SPACK_ENV="\n'
|
cmds += 'set "SPACK_ENV="\n'
|
||||||
# TODO: despacktivate
|
# TODO: despacktivate
|
||||||
# TODO: prompt
|
# TODO: prompt
|
||||||
|
elif shell == "pwsh":
|
||||||
|
cmds += "Remove-Item Env:SPACK_ENV"
|
||||||
else:
|
else:
|
||||||
cmds += "if [ ! -z ${SPACK_ENV+x} ]; then\n"
|
cmds += "if [ ! -z ${SPACK_ENV+x} ]; then\n"
|
||||||
cmds += "unset SPACK_ENV; export SPACK_ENV;\n"
|
cmds += "unset SPACK_ENV; export SPACK_ENV;\n"
|
||||||
|
|
|
@ -35,12 +35,15 @@ def test_build_env_requires_a_spec(args):
|
||||||
_out_file = "env.out"
|
_out_file = "env.out"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("shell", ["pwsh", "bat"] if sys.platform == "win32" else ["bash"])
|
||||||
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
|
@pytest.mark.usefixtures("config", "mock_packages", "working_env")
|
||||||
def test_dump(tmpdir):
|
def test_dump(shell_as, shell, tmpdir):
|
||||||
with tmpdir.as_cwd():
|
with tmpdir.as_cwd():
|
||||||
build_env("--dump", _out_file, "zlib")
|
build_env("--dump", _out_file, "zlib")
|
||||||
with open(_out_file) as f:
|
with open(_out_file) as f:
|
||||||
if sys.platform == "win32":
|
if shell == "pwsh":
|
||||||
|
assert any(line.startswith("$Env:PATH") for line in f.readlines())
|
||||||
|
elif shell == "bat":
|
||||||
assert any(line.startswith('set "PATH=') for line in f.readlines())
|
assert any(line.startswith('set "PATH=') for line in f.readlines())
|
||||||
else:
|
else:
|
||||||
assert any(line.startswith("PATH=") for line in f.readlines())
|
assert any(line.startswith("PATH=") for line in f.readlines())
|
||||||
|
|
|
@ -1920,3 +1920,21 @@ def _func(spec_str, tests=False):
|
||||||
return concretized_specs_cache[key].copy()
|
return concretized_specs_cache[key].copy()
|
||||||
|
|
||||||
return _func
|
return _func
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def shell_as(shell):
|
||||||
|
if sys.platform != "win32":
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
if shell not in ("pwsh", "bat"):
|
||||||
|
raise RuntimeError("Shell must be one of supported Windows shells (pwsh|bat)")
|
||||||
|
try:
|
||||||
|
# fetch and store old shell type
|
||||||
|
_shell = os.environ.get("SPACK_SHELL", None)
|
||||||
|
os.environ["SPACK_SHELL"] = shell
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
# restore old shell if one was set
|
||||||
|
if _shell:
|
||||||
|
os.environ["SPACK_SHELL"] = _shell
|
||||||
|
|
|
@ -113,13 +113,16 @@ def test_path_put_first(prepare_environment_for_tests):
|
||||||
assert envutil.get_path("TEST_ENV_VAR") == expected
|
assert envutil.get_path("TEST_ENV_VAR") == expected
|
||||||
|
|
||||||
|
|
||||||
def test_dump_environment(prepare_environment_for_tests, tmpdir):
|
@pytest.mark.parametrize("shell", ["pwsh", "bat"] if sys.platform == "win32" else ["bash"])
|
||||||
|
def test_dump_environment(prepare_environment_for_tests, shell_as, shell, tmpdir):
|
||||||
test_paths = "/a:/b/x:/b/c"
|
test_paths = "/a:/b/x:/b/c"
|
||||||
os.environ["TEST_ENV_VAR"] = test_paths
|
os.environ["TEST_ENV_VAR"] = test_paths
|
||||||
dumpfile_path = str(tmpdir.join("envdump.txt"))
|
dumpfile_path = str(tmpdir.join("envdump.txt"))
|
||||||
envutil.dump_environment(dumpfile_path)
|
envutil.dump_environment(dumpfile_path)
|
||||||
with open(dumpfile_path, "r") as dumpfile:
|
with open(dumpfile_path, "r") as dumpfile:
|
||||||
if sys.platform == "win32":
|
if shell == "pwsh":
|
||||||
|
assert "$Env:TEST_ENV_VAR={}\n".format(test_paths) in list(dumpfile)
|
||||||
|
elif shell == "bat":
|
||||||
assert 'set "TEST_ENV_VAR={}"\n'.format(test_paths) in list(dumpfile)
|
assert 'set "TEST_ENV_VAR={}"\n'.format(test_paths) in list(dumpfile)
|
||||||
else:
|
else:
|
||||||
assert "TEST_ENV_VAR={0}; export TEST_ENV_VAR\n".format(test_paths) in list(dumpfile)
|
assert "TEST_ENV_VAR={0}; export TEST_ENV_VAR\n".format(test_paths) in list(dumpfile)
|
||||||
|
@ -164,11 +167,14 @@ def test_escape_double_quotes_in_shell_modifications():
|
||||||
|
|
||||||
to_validate.set("QUOTED_VAR", '"MY_VAL"')
|
to_validate.set("QUOTED_VAR", '"MY_VAL"')
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
cmds = to_validate.shell_modifications(shell="bat")
|
||||||
|
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
|
||||||
|
else:
|
||||||
cmds = to_validate.shell_modifications()
|
cmds = to_validate.shell_modifications()
|
||||||
|
|
||||||
if sys.platform != "win32":
|
|
||||||
assert 'export VAR="$PATH:$ANOTHER_PATH"' in cmds
|
assert 'export VAR="$PATH:$ANOTHER_PATH"' in cmds
|
||||||
assert r'export QUOTED_VAR="\"MY_VAL\""' in cmds
|
assert r'export QUOTED_VAR="\"MY_VAL\""' in cmds
|
||||||
else:
|
|
||||||
assert "export VAR=$PATH;$ANOTHER_PATH" in cmds
|
|
||||||
assert r'export QUOTED_VAR="MY_VAL"' in cmds
|
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
"csh": "setenv {0} {1};\n",
|
"csh": "setenv {0} {1};\n",
|
||||||
"fish": "set -gx {0} {1};\n",
|
"fish": "set -gx {0} {1};\n",
|
||||||
"bat": 'set "{0}={1}"\n',
|
"bat": 'set "{0}={1}"\n',
|
||||||
|
"pwsh": "$Env:{0}={1}\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
"csh": "unsetenv {0};\n",
|
"csh": "unsetenv {0};\n",
|
||||||
"fish": "set -e {0};\n",
|
"fish": "set -e {0};\n",
|
||||||
"bat": 'set "{0}="\n',
|
"bat": 'set "{0}="\n',
|
||||||
|
"pwsh": "Remove-Item Env:{0}\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,7 +174,9 @@ def path_put_first(var_name: str, directories: List[Path]):
|
||||||
|
|
||||||
|
|
||||||
def _win_env_var_to_set_line(var: str, val: str) -> str:
|
def _win_env_var_to_set_line(var: str, val: str) -> str:
|
||||||
return f'set "{var}={val}"'
|
is_pwsh = os.environ.get("SPACK_SHELL", None) == "pwsh"
|
||||||
|
env_set_phrase = f"$Env:{var}={val}" if is_pwsh else f'set "{var}={val}"'
|
||||||
|
return env_set_phrase
|
||||||
|
|
||||||
|
|
||||||
def _nix_env_var_to_source_line(var: str, val: str) -> str:
|
def _nix_env_var_to_source_line(var: str, val: str) -> str:
|
||||||
|
@ -693,7 +697,7 @@ def apply_modifications(self, env: Optional[MutableMapping[str, str]] = None):
|
||||||
|
|
||||||
def shell_modifications(
|
def shell_modifications(
|
||||||
self,
|
self,
|
||||||
shell: str = "sh",
|
shell: str = "sh" if sys.platform != "win32" else os.environ.get("SPACK_SHELL", "bat"),
|
||||||
explicit: bool = False,
|
explicit: bool = False,
|
||||||
env: Optional[MutableMapping[str, str]] = None,
|
env: Optional[MutableMapping[str, str]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|
|
@ -46,6 +46,10 @@ if ($null -eq $Env:EDITOR)
|
||||||
$Env:EDITOR = "notepad"
|
$Env:EDITOR = "notepad"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Set spack shell so we can detect powershell context
|
||||||
|
$Env:SPACK_SHELL="pwsh"
|
||||||
|
|
||||||
|
doskey /exename=powershell.exe spack=$Env:SPACK_ROOT\bin\spack.ps1 $args
|
||||||
|
|
||||||
Write-Output "*****************************************************************"
|
Write-Output "*****************************************************************"
|
||||||
Write-Output "**************** Spack Package Manager **************************"
|
Write-Output "**************** Spack Package Manager **************************"
|
||||||
|
|
|
@ -935,7 +935,7 @@ _spack_env() {
|
||||||
_spack_env_activate() {
|
_spack_env_activate() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help --sh --csh --fish --bat -v --with-view -V --without-view -p --prompt --temp -d --dir"
|
SPACK_COMPREPLY="-h --help --sh --csh --fish --bat --pwsh -v --with-view -V --without-view -p --prompt --temp -d --dir"
|
||||||
else
|
else
|
||||||
_environments
|
_environments
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in a new issue