Bug 1289286 - Automatically find MSVC from the registry if it's not in $PATH. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 22 Jul 2016 15:51:34 +0900
changeset 306717 60de9112cad61ed4ca812931e4fa55257167b853
parent 306716 aadff7c91cd6aa6a1a75108129af3a0bce00f4d4
child 306718 aef31d82969314decfd0df7aa8d1a3f4a9037b1d
push id30836
push usermh@glandium.org
push dateTue, 26 Jul 2016 21:45:35 +0000
treeherderautoland@60de9112cad6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgps
bugs1289286
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1289286 - Automatically find MSVC from the registry if it's not in $PATH. r=gps
build/moz.configure/toolchain.configure
build/moz.configure/util.configure
--- 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):