diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/detection/common.py | 2 | ||||
-rwxr-xr-x | lib/spack/spack/operating_systems/windows_os.py | 107 | ||||
-rw-r--r-- | lib/spack/spack/util/windows_registry.py | 114 |
3 files changed, 168 insertions, 55 deletions
diff --git a/lib/spack/spack/detection/common.py b/lib/spack/spack/detection/common.py index 0e873c3f55..6fba021b33 100644 --- a/lib/spack/spack/detection/common.py +++ b/lib/spack/spack/detection/common.py @@ -269,7 +269,7 @@ class WindowsCompilerExternalPaths: At the moment simply returns location of VS install paths from VSWhere But should be extended to include more information as relevant""" - return list(winOs.WindowsOs.vs_install_paths) + return list(winOs.WindowsOs().vs_install_paths) @staticmethod def find_windows_compiler_cmake_paths() -> List[str]: diff --git a/lib/spack/spack/operating_systems/windows_os.py b/lib/spack/spack/operating_systems/windows_os.py index 0c3930e99c..fa767d71fb 100755 --- a/lib/spack/spack/operating_systems/windows_os.py +++ b/lib/spack/spack/operating_systems/windows_os.py @@ -5,10 +5,12 @@ import glob import os +import pathlib import platform import subprocess from spack.error import SpackError +from spack.util import windows_registry as winreg from spack.version import Version from ._operating_system import OperatingSystem @@ -31,43 +33,6 @@ class WindowsOs(OperatingSystem): 10. """ - # Find MSVC directories using vswhere - comp_search_paths = [] - vs_install_paths = [] - root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") - if root: - try: - extra_args = {"encoding": "mbcs", "errors": "strict"} - paths = subprocess.check_output( # type: ignore[call-overload] # novermin - [ - os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), - "-prerelease", - "-requires", - "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", - "-property", - "installationPath", - "-products", - "*", - ], - **extra_args, - ).strip() - vs_install_paths = paths.split("\n") - msvc_paths = [os.path.join(path, "VC", "Tools", "MSVC") for path in vs_install_paths] - for p in msvc_paths: - comp_search_paths.extend(glob.glob(os.path.join(p, "*", "bin", "Hostx64", "x64"))) - if os.getenv("ONEAPI_ROOT"): - comp_search_paths.extend( - glob.glob( - os.path.join( - str(os.getenv("ONEAPI_ROOT")), "compiler", "*", "windows", "bin" - ) - ) - ) - except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): - pass - if comp_search_paths: - compiler_search_paths = comp_search_paths - def __init__(self): plat_ver = windows_version() if plat_ver < Version("10"): @@ -76,3 +41,71 @@ class WindowsOs(OperatingSystem): def __str__(self): return self.name + + @property + def vs_install_paths(self): + vs_install_paths = [] + root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") + if root: + try: + extra_args = {"encoding": "mbcs", "errors": "strict"} + paths = subprocess.check_output( # type: ignore[call-overload] # novermin + [ + os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), + "-prerelease", + "-requires", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", + "installationPath", + "-products", + "*", + ], + **extra_args, + ).strip() + vs_install_paths = paths.split("\n") + except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): + pass + return vs_install_paths + + @property + def msvc_paths(self): + return [os.path.join(path, "VC", "Tools", "MSVC") for path in self.vs_install_paths] + + @property + def compiler_search_paths(self): + # First Strategy: Find MSVC directories using vswhere + _compiler_search_paths = [] + for p in self.msvc_paths: + _compiler_search_paths.extend(glob.glob(os.path.join(p, "*", "bin", "Hostx64", "x64"))) + if os.getenv("ONEAPI_ROOT"): + _compiler_search_paths.extend( + glob.glob( + os.path.join(str(os.getenv("ONEAPI_ROOT")), "compiler", "*", "windows", "bin") + ) + ) + # Second strategy: Find MSVC via the registry + msft = winreg.WindowsRegistryView( + "SOFTWARE\\WOW6432Node\\Microsoft", winreg.HKEY.HKEY_LOCAL_MACHINE + ) + vs_entries = msft.find_subkeys(r"VisualStudio_.*") + vs_paths = [] + + def clean_vs_path(path): + path = path.split(",")[0].lstrip("@") + return str((pathlib.Path(path).parent / "..\\..").resolve()) + + for entry in vs_entries: + try: + val = entry.get_subkey("Capabilities").get_value("ApplicationDescription").value + vs_paths.append(clean_vs_path(val)) + except FileNotFoundError as e: + if hasattr(e, "winerror"): + if e.winerror == 2: + pass + else: + raise + else: + raise + + _compiler_search_paths.extend(vs_paths) + return _compiler_search_paths diff --git a/lib/spack/spack/util/windows_registry.py b/lib/spack/spack/util/windows_registry.py index 5cc0edd8bf..cfc1672456 100644 --- a/lib/spack/spack/util/windows_registry.py +++ b/lib/spack/spack/util/windows_registry.py @@ -8,6 +8,7 @@ Utility module for dealing with Windows Registry. """ import os +import re import sys from contextlib import contextmanager @@ -68,8 +69,19 @@ class RegistryKey: sub_keys, _, _ = winreg.QueryInfoKey(self.hkey) for i in range(sub_keys): sub_name = winreg.EnumKey(self.hkey, i) - sub_handle = winreg.OpenKeyEx(self.hkey, sub_name, access=winreg.KEY_READ) - self._keys.append(RegistryKey(os.path.join(self.path, sub_name), sub_handle)) + try: + sub_handle = winreg.OpenKeyEx(self.hkey, sub_name, access=winreg.KEY_READ) + self._keys.append(RegistryKey(os.path.join(self.path, sub_name), sub_handle)) + except OSError as e: + if hasattr(e, "winerror"): + if e.winerror == 5: + # This is a permission error, we can't read this key + # move on + pass + else: + raise + else: + raise def _gather_value_info(self): """Compose all values for this key into a dict of form value name: RegistryValue Object""" @@ -161,6 +173,15 @@ class WindowsRegistryView: self.root = root_key self._reg = None + class KeyMatchConditions: + @staticmethod + def regex_matcher(subkey_name): + return lambda x: re.match(subkey_name, x.name) + + @staticmethod + def name_matcher(subkey_name): + return lambda x: subkey_name == x.name + @contextmanager def invalid_reg_ref_error_handler(self): try: @@ -193,6 +214,10 @@ class WindowsRegistryView: return False return True + def _regex_match_subkeys(self, subkey): + r_subkey = re.compile(subkey) + return [key for key in self.get_subkeys() if r_subkey.match(key.name)] + @property def reg(self): if not self._reg: @@ -218,51 +243,106 @@ class WindowsRegistryView: with self.invalid_reg_ref_error_handler(): return self.reg.subkeys + def get_matching_subkeys(self, subkey_name): + """Returns all subkeys regex matching subkey name + + Note: this method obtains only direct subkeys of the given key and does not + desced to transtitve subkeys. For this behavior, see `find_matching_subkeys`""" + self._regex_match_subkeys(subkey_name) + def get_values(self): if not self._valid_reg_check(): raise RegistryError("Cannot query values from invalid key %s" % self.key) with self.invalid_reg_ref_error_handler(): return self.reg.values - def _traverse_subkeys(self, stop_condition): + def _traverse_subkeys(self, stop_condition, collect_all_matching=False): """Perform simple BFS of subkeys, returning the key that successfully triggers the stop condition. Args: stop_condition: lambda or function pointer that takes a single argument a key and returns a boolean value based on that key + collect_all_matching: boolean value, if True, the traversal collects and returns + all keys meeting stop condition. If false, once stop + condition is met, the key that triggered the condition ' + is returned. Return: the key if stop_condition is triggered, or None if not """ + collection = [] if not self._valid_reg_check(): raise RegistryError("Cannot query values from invalid key %s" % self.key) with self.invalid_reg_ref_error_handler(): queue = self.reg.subkeys for key in queue: if stop_condition(key): - return key + if collect_all_matching: + collection.append(key) + else: + return key queue.extend(key.subkeys) - return None + return collection if collection else None + + def _find_subkey_s(self, search_key, collect_all_matching=False): + """Retrieve one or more keys regex matching `search_key`. + One key will be returned unless `collect_all_matching` is enabled, + in which case call matches are returned. + + Args: + search_key (str): regex string represeting a subkey name structure + to be matched against. + Cannot be provided alongside `direct_subkey` + collect_all_matching (bool): No-op if `direct_subkey` is specified + Return: + the desired subkey as a RegistryKey object, or none + """ + return self._traverse_subkeys(search_key, collect_all_matching=collect_all_matching) - def find_subkey(self, subkey_name, recursive=True): - """If non recursive, this method is the same as get subkey with error handling - Otherwise perform a BFS of subkeys until desired key is found + def find_subkey(self, subkey_name): + """Perform a BFS of subkeys until desired key is found Returns None or RegistryKey object corresponding to requested key name Args: - subkey_name (str): string representing subkey to be searched for - recursive (bool): optional argument, if True, subkey need not be a direct - sub key of this registry entry, and this method will - search all subkeys recursively. - Default is True + subkey_name (str) Return: the desired subkey as a RegistryKey object, or none + + For more details, see the WindowsRegistryView._find_subkey_s method docstring """ + return self._find_subkey_s( + WindowsRegistryView.KeyMatchConditions.name_matcher(subkey_name) + ) - if not recursive: - return self.get_subkey(subkey_name) + def find_matching_subkey(self, subkey_name): + """Perform a BFS of subkeys until a key matching subkey name regex is found + Returns None or the first RegistryKey object corresponding to requested key name - else: - return self._traverse_subkeys(lambda x: x.name == subkey_name) + Args: + subkey_name (str) + Return: + the desired subkey as a RegistryKey object, or none + + For more details, see the WindowsRegistryView._find_subkey_s method docstring + """ + return self._find_subkey_s( + WindowsRegistryView.KeyMatchConditions.regex_matcher(subkey_name) + ) + + def find_subkeys(self, subkey_name): + """Exactly the same as find_subkey, except this function tries to match + a regex to multiple keys + + Args: + subkey_name (str) + Return: + the desired subkeys as a list of RegistryKey object, or none + + For more details, see the WindowsRegistryView._find_subkey_s method docstring + """ + kwargs = {"collect_all_matching": True} + return self._find_subkey_s( + WindowsRegistryView.KeyMatchConditions.regex_matcher(subkey_name), **kwargs + ) def find_value(self, val_name, recursive=True): """ |