Windows: shell variables are case-insensitive (#36813)

If we modify both Path and PATH, on Windows they will clobber one
another. This PR updates the shell modification logic to automatically
convert variable names to upper-case on Windows.
This commit is contained in:
John W. Parent 2023-04-21 14:38:58 -04:00 committed by GitHub
parent 255c9ed5e9
commit d8451b0c3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 34 additions and 1 deletions

View File

@ -164,7 +164,7 @@ def setup_custom_environment(self, pkg, env):
out = out.decode("utf-16le", errors="replace") # novermin out = out.decode("utf-16le", errors="replace") # novermin
int_env = dict( int_env = dict(
(key.lower(), value) (key, value)
for key, _, value in (line.partition("=") for line in out.splitlines()) for key, _, value in (line.partition("=") for line in out.splitlines())
if key and value if key and value
) )

View File

@ -14,6 +14,7 @@
import re import re
import socket import socket
import sys import sys
from functools import wraps
from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple, Union from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple, Union
from llnl.util import tty from llnl.util import tty
@ -83,6 +84,28 @@ def double_quote_escape(s):
return '"' + s.replace('"', r"\"") + '"' return '"' + s.replace('"', r"\"") + '"'
def system_env_normalize(func):
"""Decorator wrapping calls to system env modifications,
converting all env variable names to all upper case on Windows, no-op
on other platforms before calling env modification method.
Windows, due to a DOS holdover, treats all env variable names case
insensitively, however Spack's env modification class does not,
meaning setting `Path` and `PATH` would be distinct env operations
for Spack, but would cause a collision when actually performing the
env modification operations on the env.
Normalize all env names to all caps to prevent this collision from the
Spack side."""
@wraps(func)
def case_insensitive_modification(self, name: str, *args, **kwargs):
if sys.platform == "win32":
name = name.upper()
return func(self, name, *args, **kwargs)
return case_insensitive_modification
def is_system_path(path: Path) -> bool: def is_system_path(path: Path) -> bool:
"""Returns True if the argument is a system path, False otherwise.""" """Returns True if the argument is a system path, False otherwise."""
return bool(path) and (os.path.normpath(path) in SYSTEM_DIRS) return bool(path) and (os.path.normpath(path) in SYSTEM_DIRS)
@ -466,6 +489,7 @@ def _trace(self) -> Optional[Trace]:
return Trace(filename=filename, lineno=lineno, context=current_context) return Trace(filename=filename, lineno=lineno, context=current_context)
@system_env_normalize
def set(self, name: str, value: str, *, force: bool = False): def set(self, name: str, value: str, *, force: bool = False):
"""Stores a request to set an environment variable. """Stores a request to set an environment variable.
@ -477,6 +501,7 @@ def set(self, name: str, value: str, *, force: bool = False):
item = SetEnv(name, value, trace=self._trace(), force=force) item = SetEnv(name, value, trace=self._trace(), force=force)
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def append_flags(self, name: str, value: str, sep: str = " "): def append_flags(self, name: str, value: str, sep: str = " "):
"""Stores a request to append 'flags' to an environment variable. """Stores a request to append 'flags' to an environment variable.
@ -488,6 +513,7 @@ def append_flags(self, name: str, value: str, sep: str = " "):
item = AppendFlagsEnv(name, value, separator=sep, trace=self._trace()) item = AppendFlagsEnv(name, value, separator=sep, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def unset(self, name: str): def unset(self, name: str):
"""Stores a request to unset an environment variable. """Stores a request to unset an environment variable.
@ -497,6 +523,7 @@ def unset(self, name: str):
item = UnsetEnv(name, trace=self._trace()) item = UnsetEnv(name, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def remove_flags(self, name: str, value: str, sep: str = " "): def remove_flags(self, name: str, value: str, sep: str = " "):
"""Stores a request to remove flags from an environment variable """Stores a request to remove flags from an environment variable
@ -508,6 +535,7 @@ def remove_flags(self, name: str, value: str, sep: str = " "):
item = RemoveFlagsEnv(name, value, separator=sep, trace=self._trace()) item = RemoveFlagsEnv(name, value, separator=sep, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def set_path(self, name: str, elements: List[str], separator: str = os.pathsep): def set_path(self, name: str, elements: List[str], separator: str = os.pathsep):
"""Stores a request to set an environment variable to a list of paths, """Stores a request to set an environment variable to a list of paths,
separated by a character defined in input. separated by a character defined in input.
@ -520,6 +548,7 @@ def set_path(self, name: str, elements: List[str], separator: str = os.pathsep):
item = SetPath(name, elements, separator=separator, trace=self._trace()) item = SetPath(name, elements, separator=separator, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def append_path(self, name: str, path: str, separator: str = os.pathsep): def append_path(self, name: str, path: str, separator: str = os.pathsep):
"""Stores a request to append a path to list of paths. """Stores a request to append a path to list of paths.
@ -531,6 +560,7 @@ def append_path(self, name: str, path: str, separator: str = os.pathsep):
item = AppendPath(name, path, separator=separator, trace=self._trace()) item = AppendPath(name, path, separator=separator, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def prepend_path(self, name: str, path: str, separator: str = os.pathsep): def prepend_path(self, name: str, path: str, separator: str = os.pathsep):
"""Stores a request to prepend a path to list of paths. """Stores a request to prepend a path to list of paths.
@ -542,6 +572,7 @@ def prepend_path(self, name: str, path: str, separator: str = os.pathsep):
item = PrependPath(name, path, separator=separator, trace=self._trace()) item = PrependPath(name, path, separator=separator, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def remove_path(self, name: str, path: str, separator: str = os.pathsep): def remove_path(self, name: str, path: str, separator: str = os.pathsep):
"""Stores a request to remove a path from a list of paths. """Stores a request to remove a path from a list of paths.
@ -553,6 +584,7 @@ def remove_path(self, name: str, path: str, separator: str = os.pathsep):
item = RemovePath(name, path, separator=separator, trace=self._trace()) item = RemovePath(name, path, separator=separator, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def deprioritize_system_paths(self, name: str, separator: str = os.pathsep): def deprioritize_system_paths(self, name: str, separator: str = os.pathsep):
"""Stores a request to deprioritize system paths in a path list, """Stores a request to deprioritize system paths in a path list,
otherwise preserving the order. otherwise preserving the order.
@ -564,6 +596,7 @@ def deprioritize_system_paths(self, name: str, separator: str = os.pathsep):
item = DeprioritizeSystemPaths(name, separator=separator, trace=self._trace()) item = DeprioritizeSystemPaths(name, separator=separator, trace=self._trace())
self.env_modifications.append(item) self.env_modifications.append(item)
@system_env_normalize
def prune_duplicate_paths(self, name: str, separator: str = os.pathsep): def prune_duplicate_paths(self, name: str, separator: str = os.pathsep):
"""Stores a request to remove duplicates from a path list, otherwise """Stores a request to remove duplicates from a path list, otherwise
preserving the order. preserving the order.