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
|
||||
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]:
|
||||
|
|
|
@ -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 @@ def __init__(self):
|
|||
|
||||
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
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
@ -68,8 +69,19 @@ def _gather_subkey_info(self):
|
|||
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 @@ def __init__(self, key, root_key=HKEY.HKEY_CURRENT_USER):
|
|||
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 @@ def _valid_reg_check(self):
|
|||
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 @@ def get_subkeys(self):
|
|||
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(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
|
||||
Returns None or RegistryKey object corresponding to requested key name
|
||||
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:
|
||||
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
|
||||
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)
|
||||
|
||||
if not recursive:
|
||||
return self.get_subkey(subkey_name)
|
||||
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
|
||||
|
||||
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.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):
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue