Bug 1289286 - Automatically find MSVC from the registry if it's not in $PATH. r?gps
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -395,16 +395,92 @@ def check_compiler(compiler, language, t
version=info.version,
target_cpu=info.cpu,
target_kernel=info.kernel,
target_endianness=info.endianness,
flags=flags,
)
+@imports(_from='collections', _import='defaultdict')
+@imports(_from='__builtin__', _import='sorted')
+def get_vc_paths(base):
+ vc = defaultdict(lambda: defaultdict(dict))
+ subkey = r'Microsoft\VisualStudio\VC\*\*\*\Compiler'
+ for v, h, t, p in get_registry_values(base + '\\' + subkey):
+ vc[v][h][t] = p
+ if not vc:
+ return
+ version, data = sorted(vc.iteritems(), key=lambda x: Version(x[0]))[-1]
+ return data
+
+
+@depends(host)
+@imports('platform')
+def vc_compiler_path(host):
+ if host.kernel != 'WINNT':
+ return
+ vc_host = {
+ 'x86': 'x86',
+ 'AMD64': 'x64',
+ }.get(platform.machine())
+ if vc_host is None:
+ return
+ vc_target = {
+ 'x86': 'x86',
+ 'x86_64': 'x64',
+ 'arm': 'arm',
+ }.get(host.cpu)
+ if vc_target is None:
+ return
+
+ base_key = r'HKEY_LOCAL_MACHINE\SOFTWARE'
+ data = get_vc_paths(base_key)
+ if not data:
+ data = get_vc_paths(base_key + r'\Wow6432Node')
+ if not data:
+ return
+
+ path = data.get(vc_host, {}).get(vc_target)
+ if not path and vc_host == 'x64':
+ vc_host = 'x86'
+ path = data.get(vc_host, {}).get(vc_target)
+ if not path:
+ return
+ path = os.path.dirname(path)
+ if vc_host != vc_target:
+ other_path = data.get(vc_host, {}).get(vc_host)
+ if other_path:
+ return (path, os.path.dirname(other_path))
+ return (path,)
+
+
+@depends(vc_compiler_path)
+@imports('os')
+def toolchain_search_path(vc_compiler_path):
+ if vc_compiler_path:
+ result = [os.environ.get('PATH')]
+ result.extend(vc_compiler_path)
+ return result
+
+
+# Normally, we'd just have CC, etc. set to absolute paths, but the build system
+# doesn't currently handle properly the case where the paths contain spaces.
+# Additionally, there's the issue described further below in valid_compiler().
+@depends(toolchain_search_path)
+@imports('os')
+def alter_path(toolchain_search_path):
+ if toolchain_search_path:
+ path = os.pathsep.join(toolchain_search_path)
+ os.environ['PATH'] = path
+ return path
+
+set_config('PATH', alter_path)
+
+
@template
def default_c_compilers(host_or_target):
'''Template defining the set of default C compilers for the host and
target platforms.
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure.
'''
assert host_or_target in (host, target)
@@ -536,17 +612,18 @@ def compiler(language, host_or_target, c
for k, v in other_compiler.__dict__.iteritems()
})
# Normally, we'd use `var` instead of `_var`, but the interaction with
# old-configure complicates things, and for now, we a) can't take the plain
# result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
# old-configure AC_SUBST it (because it's autoconf doing it, not us)
compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
- input=delayed_getattr(provided_compiler, 'compiler'))
+ input=delayed_getattr(provided_compiler, 'compiler'),
+ paths=toolchain_search_path)
@depends(compiler, provided_compiler, compiler_wrapper, host_or_target)
@checking('whether %s can be used' % what, lambda x: bool(x))
@imports(_from='mozbuild.shellutil', _import='quote')
def valid_compiler(compiler, provided_compiler, compiler_wrapper,
host_or_target):
wrapper = list(compiler_wrapper or ())
if provided_compiler:
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -87,16 +87,115 @@ def find_program(file, paths=None):
def unique_list(l):
result = []
for i in l:
if l not in result:
result.append(i)
return result
+
+# Get values out of the Windows registry. This function can only be called on
+# Windows.
+# The `pattern` argument is a string starting with HKEY_ and giving the full
+# "path" of the registry key to get the value for, with backslash separators.
+# The string can contains wildcards ('*').
+# The result of this functions is an enumerator yielding tuples for each
+# match. Each of these tuples contains the key name matching wildcards
+# followed by the value.
+#
+# Examples:
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\Installed Roots\KitsRoot*')
+# yields e.g.:
+# ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
+# ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\Installed Roots\KitsRoot8.1')
+# yields e.g.:
+# (r'C:\Program Files (x86)\Windows Kits\8.1\',)
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\*\KitsRoot*')
+# yields e.g.:
+# ('Installed Roots', 'KitsRoot81',
+# r'C:\Program Files (x86)\Windows Kits\8.1\')
+# ('Installed Roots', 'KitsRoot10',
+# r'C:\Program Files (x86)\Windows Kits\10\')
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'VisualStudio\VC\*\x86\*\Compiler')
+# yields e.g.:
+# ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
+# ('19.0', 'x64', r'C:\...\amd64\cl.exe')
+# ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
+@imports(_import='_winreg', _as='winreg')
+@imports(_from='__builtin__', _import='WindowsError')
+@imports(_from='fnmatch', _import='fnmatch')
+def get_registry_values(pattern):
+ def enum_helper(func, key):
+ i = 0
+ while True:
+ try:
+ yield func(key, i)
+ except WindowsError:
+ break
+ i += 1
+
+ def get_keys(key, pattern):
+ try:
+ s = winreg.OpenKey(key, '\\'.join(pattern[:-1]))
+ except WindowsError:
+ return
+ for k in enum_helper(winreg.EnumKey, s):
+ if fnmatch(k, pattern[-1]):
+ try:
+ yield k, winreg.OpenKey(s, k)
+ except WindowsError:
+ pass
+
+ def get_values(key, pattern):
+ try:
+ s = winreg.OpenKey(key, '\\'.join(pattern[:-1]))
+ except WindowsError:
+ return
+ for k, v, t in enum_helper(winreg.EnumValue, s):
+ if fnmatch(k, pattern[-1]):
+ yield k, v
+
+ def split_pattern(pattern):
+ subpattern = []
+ for p in pattern:
+ subpattern.append(p)
+ if '*' in p:
+ yield subpattern
+ subpattern = []
+ if subpattern:
+ yield subpattern
+
+ pattern = pattern.split('\\')
+ assert pattern[0].startswith('HKEY_')
+ keys = [(getattr(winreg, pattern[0]),)]
+ pattern = list(split_pattern(pattern[1:]))
+ for i, p in enumerate(pattern):
+ next_keys = []
+ for base_key in keys:
+ matches = base_key[:-1]
+ base_key = base_key[-1]
+ if i == len(pattern) - 1:
+ want_name = '*' in p[-1]
+ for name, value in get_values(base_key, p):
+ yield matches + ((name, value) if want_name else (value,))
+ else:
+ for name, k in get_keys(base_key, p):
+ next_keys.append(matches + (name, k))
+ keys = next_keys
+
+
@imports(_from='mozbuild.configure.util', _import='Version', _as='_Version')
def Version(v):
'A version number that can be compared usefully.'
return _Version(v)
# Denotes a deprecated option. Combines option() and @depends:
# @deprecated_option('--option')
# def option(value):