MSVC: detection from registry (#38500)
Typically MSVC is detected via the VSWhere program. However, this may not be available, or may be installed in an unpredictable location. This PR adds an additional approach via Windows Registry queries to determine VS install location root. Additionally: * Construct vs_install_paths after class-definition time (move it to variable-access time). * Skip over keys for which a user does not have read permissions when performing searches (previously the presence of these keys would have caused an error, regardless of whether they were needed). * Extend helper functionality with option for regex matching on registry keys vs. exact string matching. * Some internal refactoring: remove boolean parameters in some cases where the function was always called with the same value (e.g. `find_subkey`)
This commit is contained in:
parent
9e01199e13
commit
148dce96ed
3 changed files with 169 additions and 56 deletions
|
@ -269,7 +269,7 @@ def find_windows_compiler_root_paths() -> List[str]:
|
||||||
|
|
||||||
At the moment simply returns location of VS install paths from VSWhere
|
At the moment simply returns location of VS install paths from VSWhere
|
||||||
But should be extended to include more information as relevant"""
|
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
|
@staticmethod
|
||||||
def find_windows_compiler_cmake_paths() -> List[str]:
|
def find_windows_compiler_cmake_paths() -> List[str]:
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from spack.error import SpackError
|
from spack.error import SpackError
|
||||||
|
from spack.util import windows_registry as winreg
|
||||||
from spack.version import Version
|
from spack.version import Version
|
||||||
|
|
||||||
from ._operating_system import OperatingSystem
|
from ._operating_system import OperatingSystem
|
||||||
|
@ -31,8 +33,17 @@ class WindowsOs(OperatingSystem):
|
||||||
10.
|
10.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Find MSVC directories using vswhere
|
def __init__(self):
|
||||||
comp_search_paths = []
|
plat_ver = windows_version()
|
||||||
|
if plat_ver < Version("10"):
|
||||||
|
raise SpackError("Spack is not supported on Windows versions older than 10")
|
||||||
|
super().__init__("windows{}".format(plat_ver), plat_ver)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vs_install_paths(self):
|
||||||
vs_install_paths = []
|
vs_install_paths = []
|
||||||
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
|
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
|
||||||
if root:
|
if root:
|
||||||
|
@ -52,27 +63,49 @@ class WindowsOs(OperatingSystem):
|
||||||
**extra_args,
|
**extra_args,
|
||||||
).strip()
|
).strip()
|
||||||
vs_install_paths = paths.split("\n")
|
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):
|
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
|
||||||
pass
|
pass
|
||||||
if comp_search_paths:
|
return vs_install_paths
|
||||||
compiler_search_paths = comp_search_paths
|
|
||||||
|
|
||||||
def __init__(self):
|
@property
|
||||||
plat_ver = windows_version()
|
def msvc_paths(self):
|
||||||
if plat_ver < Version("10"):
|
return [os.path.join(path, "VC", "Tools", "MSVC") for path in self.vs_install_paths]
|
||||||
raise SpackError("Spack is not supported on Windows versions older than 10")
|
|
||||||
super().__init__("windows{}".format(plat_ver), plat_ver)
|
|
||||||
|
|
||||||
def __str__(self):
|
@property
|
||||||
return self.name
|
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
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@ -68,8 +69,19 @@ def _gather_subkey_info(self):
|
||||||
sub_keys, _, _ = winreg.QueryInfoKey(self.hkey)
|
sub_keys, _, _ = winreg.QueryInfoKey(self.hkey)
|
||||||
for i in range(sub_keys):
|
for i in range(sub_keys):
|
||||||
sub_name = winreg.EnumKey(self.hkey, i)
|
sub_name = winreg.EnumKey(self.hkey, i)
|
||||||
|
try:
|
||||||
sub_handle = winreg.OpenKeyEx(self.hkey, sub_name, access=winreg.KEY_READ)
|
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))
|
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):
|
def _gather_value_info(self):
|
||||||
"""Compose all values for this key into a dict of form value name: RegistryValue Object"""
|
"""Compose all values for this key into a dict of form value name: RegistryValue Object"""
|
||||||
|
@ -161,6 +173,15 @@ def __init__(self, key, root_key=HKEY.HKEY_CURRENT_USER):
|
||||||
self.root = root_key
|
self.root = root_key
|
||||||
self._reg = None
|
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
|
@contextmanager
|
||||||
def invalid_reg_ref_error_handler(self):
|
def invalid_reg_ref_error_handler(self):
|
||||||
try:
|
try:
|
||||||
|
@ -193,6 +214,10 @@ def _valid_reg_check(self):
|
||||||
return False
|
return False
|
||||||
return True
|
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
|
@property
|
||||||
def reg(self):
|
def reg(self):
|
||||||
if not self._reg:
|
if not self._reg:
|
||||||
|
@ -218,51 +243,106 @@ def get_subkeys(self):
|
||||||
with self.invalid_reg_ref_error_handler():
|
with self.invalid_reg_ref_error_handler():
|
||||||
return self.reg.subkeys
|
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):
|
def get_values(self):
|
||||||
if not self._valid_reg_check():
|
if not self._valid_reg_check():
|
||||||
raise RegistryError("Cannot query values from invalid key %s" % self.key)
|
raise RegistryError("Cannot query values from invalid key %s" % self.key)
|
||||||
with self.invalid_reg_ref_error_handler():
|
with self.invalid_reg_ref_error_handler():
|
||||||
return self.reg.values
|
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
|
"""Perform simple BFS of subkeys, returning the key
|
||||||
that successfully triggers the stop condition.
|
that successfully triggers the stop condition.
|
||||||
Args:
|
Args:
|
||||||
stop_condition: lambda or function pointer that takes a single argument
|
stop_condition: lambda or function pointer that takes a single argument
|
||||||
a key and returns a boolean value based on that key
|
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:
|
Return:
|
||||||
the key if stop_condition is triggered, or None if not
|
the key if stop_condition is triggered, or None if not
|
||||||
"""
|
"""
|
||||||
|
collection = []
|
||||||
if not self._valid_reg_check():
|
if not self._valid_reg_check():
|
||||||
raise RegistryError("Cannot query values from invalid key %s" % self.key)
|
raise RegistryError("Cannot query values from invalid key %s" % self.key)
|
||||||
with self.invalid_reg_ref_error_handler():
|
with self.invalid_reg_ref_error_handler():
|
||||||
queue = self.reg.subkeys
|
queue = self.reg.subkeys
|
||||||
for key in queue:
|
for key in queue:
|
||||||
if stop_condition(key):
|
if stop_condition(key):
|
||||||
|
if collect_all_matching:
|
||||||
|
collection.append(key)
|
||||||
|
else:
|
||||||
return key
|
return key
|
||||||
queue.extend(key.subkeys)
|
queue.extend(key.subkeys)
|
||||||
return None
|
return collection if collection else None
|
||||||
|
|
||||||
def find_subkey(self, subkey_name, recursive=True):
|
def _find_subkey_s(self, search_key, collect_all_matching=False):
|
||||||
"""If non recursive, this method is the same as get subkey with error handling
|
"""Retrieve one or more keys regex matching `search_key`.
|
||||||
Otherwise perform a BFS of subkeys until desired key is found
|
One key will be returned unless `collect_all_matching` is enabled,
|
||||||
Returns None or RegistryKey object corresponding to requested key name
|
in which case call matches are returned.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
subkey_name (str): string representing subkey to be searched for
|
search_key (str): regex string represeting a subkey name structure
|
||||||
recursive (bool): optional argument, if True, subkey need not be a direct
|
to be matched against.
|
||||||
sub key of this registry entry, and this method will
|
Cannot be provided alongside `direct_subkey`
|
||||||
search all subkeys recursively.
|
collect_all_matching (bool): No-op if `direct_subkey` is specified
|
||||||
Default is True
|
|
||||||
Return:
|
Return:
|
||||||
the desired subkey as a RegistryKey object, or none
|
the desired subkey as a RegistryKey object, or none
|
||||||
"""
|
"""
|
||||||
|
return self._traverse_subkeys(search_key, collect_all_matching=collect_all_matching)
|
||||||
|
|
||||||
if not recursive:
|
def find_subkey(self, subkey_name):
|
||||||
return self.get_subkey(subkey_name)
|
"""Perform a BFS of subkeys until desired key is found
|
||||||
|
Returns None or RegistryKey object corresponding to requested key name
|
||||||
|
|
||||||
else:
|
Args:
|
||||||
return self._traverse_subkeys(lambda x: x.name == subkey_name)
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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):
|
def find_value(self, val_name, recursive=True):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue