bug 1311729 - get a build working in WSL draft
authorTed Mielczarek <ted@mielczarek.org>
Fri, 28 Jul 2017 12:25:07 -0400
changeset 664871 1e45c016412a79289e9bc1bf314abf2851ea2d9f
parent 664870 227ab74c8d7879385fbf28c1a7d4d1244ab65936
child 664872 e3baad5e1bddd36ab635816a8ee1458442b0ee0b
push id79836
push userbmo:ted@mielczarek.org
push dateThu, 14 Sep 2017 14:29:42 +0000
bugs1311729
milestone57.0a1
bug 1311729 - get a build working in WSL MozReview-Commit-ID: JaUGWr6aai7
build/autoconf/sanitize.m4
build/moz.configure/init.configure
build/moz.configure/rust.configure
build/moz.configure/toolchain.configure
build/moz.configure/util.configure
build/moz.configure/windows.configure
js/src/old-configure.in
old-configure.in
python/mozbuild/mozbuild/configure/__init__.py
python/mozbuild/mozpack/path.py
toolkit/moz.configure
--- a/build/autoconf/sanitize.m4
+++ b/build/autoconf/sanitize.m4
@@ -118,11 +118,13 @@ if test -n "$MOZ_LLVM_HACKS"; then
 fi
 AC_SUBST(MOZ_NO_WLZDEFS)
 AC_SUBST(MOZ_CFLAGS_NSS)
 
 dnl ========================================================
 dnl = Test for whether the compiler is compatible with the
 dnl = given sanitize options.
 dnl ========================================================
-AC_TRY_LINK(,,,AC_MSG_ERROR([compiler is incompatible with sanitize options]))
+if test "$MOZ_ASAN" -o "$MOZ_MSAN" -o "$MOZ_TSAN" -o "$MOZ_UBSAN_INT_OVERFLOW"; then
+  AC_TRY_LINK(,,,AC_MSG_ERROR([compiler is incompatible with sanitize options]))
+fi
 
 ])
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -4,21 +4,33 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include('util.configure')
 include('checks.configure')
 
 option(env='DIST', nargs=1, help='DIST directory')
 
+@depends('--help')
+def host_is_wsl(help):
+    return host_is_wsl_check()
+
+set_config('HOST_IS_WSL', host_is_wsl)
+add_old_configure_assignment(
+    'HOST_IS_WSL', host_is_wsl)
+
+
 # Do not allow objdir == srcdir builds.
 # ==============================================================
-@depends('--help', 'DIST')
+@depends('--help', 'DIST', host_is_wsl)
 @imports(_from='os.path', _import='exists')
-def check_build_environment(help, dist):
+@imports('subprocess')
+@imports(_from='os', _import='environ')
+@imports(_from='os', _import='mkdir')
+def check_build_environment(help, dist, host_is_wsl):
     topobjdir = os.path.realpath(os.path.abspath('.'))
     topsrcdir = os.path.realpath(os.path.abspath(
         os.path.join(os.path.dirname(__file__), '..', '..')))
 
     if dist:
         dist = normsep(dist[0])
     else:
         dist = os.path.join(topobjdir, 'dist')
@@ -57,16 +69,38 @@ def check_build_environment(help, dist):
             '  *   A source tree build can confuse the separate objdir build.\n'
             '  *\n'
             '  *   To clean up the source tree:\n'
             '  *     1. cd %s\n'
             '  *     2. gmake distclean\n'
             '  ***'
             % ('\n  '.join(conflict_files), topsrcdir)
         )
+    # It would probably be nicer to do this after the host checks below, but this seems
+    # like a good place.
+    if host_is_wsl:
+        # We can't run MSVC, which is a native Windows program, against paths
+        # inside the WSL root, so make sure that topsrcdir/topobjdir are both on a
+        # mounted Windows drive.
+        for path in (result.topsrcdir, result.topobjdir):
+            fstype = subprocess.check_output(['/bin/findmnt', '-n', '-o', 'FSTYPE', '-T', path]).strip()
+            if fstype != 'drvfs':
+                die('\n'
+                    '  ***\n'
+                    '  * Cannot build in WSL with a source or object directory on a Linux partition\n'
+                    '  *   Found path: %s\n'
+                    '  *   Please put your source and object directories on a Windows drive.\n'
+                    '  ***'
+                    % (path, )
+                )
+        # Windows programs in WSL can't use /tmp/, so override it.
+        tmpdir = os.path.join(result.topobjdir, '_tmp')
+        if not exists(tmpdir):
+            mkdir(tmpdir)
+        environ['TEMP'] = tmpdir
 
     return result
 
 set_config('TOPSRCDIR', check_build_environment.topsrcdir)
 set_config('TOPOBJDIR', check_build_environment.topobjdir)
 set_config('MOZ_BUILD_ROOT', check_build_environment.topobjdir)
 set_config('DIST', check_build_environment.dist)
 
@@ -628,24 +662,27 @@ def help_host_target(help):
 
 @imports('subprocess')
 def config_sub(shell, triplet):
     config_sub = os.path.join(os.path.dirname(__file__), '..',
                               'autoconf', 'config.sub')
     return subprocess.check_output([shell, config_sub, triplet]).strip()
 
 
-@depends('--host', shell)
+@depends('--host', shell, host_is_wsl)
 @checking('for host system type', lambda h: h.alias)
 @imports('subprocess')
-def host(value, shell):
+def host(value, shell, host_is_wsl):
     if not value:
-        config_guess = os.path.join(os.path.dirname(__file__), '..',
-                                    'autoconf', 'config.guess')
-        host = subprocess.check_output([shell, config_guess]).strip()
+        if host_is_wsl:
+            host = 'x86_64-pc-mingw32'
+        else:
+            config_guess = os.path.join(os.path.dirname(__file__), '..',
+                                        'autoconf', 'config.guess')
+            host = subprocess.check_output([shell, config_guess]).strip()
     else:
         host = value[0]
 
     return split_triplet(config_sub(shell, host))
 
 host = help_host_target | host
 
 
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -1,19 +1,18 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-@imports(_from='os.path', _import='expanduser')
 def rustup_path(what):
     # rustup installs rustc/cargo into ~/.cargo/bin by default,
     # so look there if the binaries aren't in $PATH.
-    return [what, os.path.join(expanduser('~/.cargo/bin'), what)]
+    return [what, os.path.join(user_path('.cargo/bin'), what)]
 
 # Rust is required by `rust_compiler` below. We allow_missing here
 # to propagate failures to the better error message there.
 rustc = check_prog('RUSTC', rustup_path('rustc'), allow_missing=True)
 cargo = check_prog('CARGO', rustup_path('cargo'), allow_missing=True)
 
 @depends_if(rustc)
 @checking('rustc version', lambda info: info.version)
@@ -197,28 +196,28 @@ def rust_triple_alias(host_or_target):
 
             os.write(in_fd, source)
             os.close(in_fd)
 
             cmd = [
                 rustc,
                 '--crate-type', 'staticlib',
                 target_arg,
-                '-o', out_path,
-                in_path,
+                '-o', os.path.basename(out_path),
+                os.path.basename(in_path),
             ]
             def failed():
                 die(dedent('''\
                 Cannot compile for {} with {}
                 The target may be unsupported, or you may not have
                 a rust std library for that target installed. Try:
 
                   rustup target add {}
                 '''.format(host_or_target.alias, rustc, rustc_target)))
-            check_cmd_output(*cmd, onerror=failed)
+            check_cmd_output(*cmd, cwd=os.path.dirname(in_path), onerror=failed)
             if not os.path.exists(out_path) or os.path.getsize(out_path) == 0:
                 failed()
         finally:
             os.remove(in_path)
             os.remove(out_path)
 
         # This target is usable.
         return rustc_target
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -421,72 +421,72 @@ def check_compiler(compiler, language, t
         flags=flags,
     )
 
 
 @imports(_from='__builtin__', _import='open')
 @imports('json')
 @imports('subprocess')
 @imports('sys')
-def get_vc_paths(topsrcdir):
+def get_vc_paths(topsrcdir, host_is_wsl):
     def vswhere(args):
         encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
         return json.loads(subprocess.check_output([os.path.join(topsrcdir, 'build/win32/vswhere.exe'), '-format', 'json'] + args).decode(encoding, 'replace'))
 
     # Can't pass -requires with -legacy, so query each separately.
     # Legacy versions first (VS2015)
     for install in vswhere(['-legacy', '-version', '[14.0,15.0)']):
         version = Version(install['installationVersion'])
         # Skip anything older than VS2015.
         if version < '14':
             continue
         path = install['installationPath']
 
         yield (Version(install['installationVersion']), {
-            'x64': [os.path.join(path, r'VC\bin\amd64')],
+            'x64': [normalize_path(os.path.join(path, r'VC\bin\amd64'))],
             # The x64->x86 cross toolchain requires DLLs from the native x64 toolchain.
-            'x86': [os.path.join(path, r'VC\bin\amd64_x86'), os.path.join(path, r'VC\bin\amd64')],
+            'x86': [normalize_path(os.path.join(path, r'VC\bin\amd64_x86')), normalize_path(os.path.join(path, r'VC\bin\amd64'))],
         })
     # Then VS2017 and newer.
     for install in vswhere(['-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64']):
         path = install['installationPath']
-        tools_version = open(os.path.join(path, r'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt'), 'rb').read().strip()
-        tools_path = os.path.join(path, r'VC\Tools\MSVC', tools_version, r'bin\HostX64')
+        tools_version = open(normalize_path(os.path.join(path, r'VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt')), 'rb').read().strip()
+        tools_path = normalize_path(os.path.join(path, r'VC\Tools\MSVC', tools_version, r'bin\HostX64'))
         yield (Version(install['installationVersion']), {
-            'x64': [os.path.join(tools_path, 'x64')],
+            'x64': [normalize_path(os.path.join(tools_path, 'x64'))],
             # The x64->x86 cross toolchain requires DLLs from the native x64 toolchain.
-            'x86': [os.path.join(tools_path, 'x86'), os.path.join(tools_path, 'x64')],
+            'x86': [normalize_path(os.path.join(tools_path, 'x86')), normalize_path(os.path.join(tools_path, 'x64'))],
         })
 
 option('--with-visual-studio-version', nargs=1,
        choices=('2015', '2017'),
        help='Select a specific Visual Studio version to use')
 
 @depends('--with-visual-studio-version')
 def vs_major_version(value):
     if value:
         return {'2015': 14,
                 '2017': 15}[value[0]]
 
-@depends(host, target, vs_major_version, check_build_environment, '--with-visual-studio-version')
+@depends(host, target, vs_major_version, check_build_environment, '--with-visual-studio-version', host_is_wsl)
 @imports(_from='__builtin__', _import='sorted')
 @imports(_from='operator', _import='itemgetter')
 @imports('platform')
-def vc_compiler_path(host, target, vs_major_version, env, vs_release_name):
+def vc_compiler_path(host, target, vs_major_version, env, vs_release_name, host_is_wsl):
     if host.kernel != 'WINNT':
         return
     vc_target = {
         'x86': 'x86',
         'x86_64': 'x64',
         'arm': 'arm',
     }.get(target.cpu)
     if vc_target is None:
         return
 
-    all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0))
+    all_versions = sorted(get_vc_paths(env.topsrcdir, host_is_wsl), key=itemgetter(0))
     if not all_versions:
         return
     if vs_major_version:
         versions = [d for (v, d) in all_versions if v.major == vs_major_version]
         if not versions:
             die('Visual Studio %s could not be found!' % vs_release_name)
         data = versions[0]
     else:
@@ -521,17 +521,17 @@ def default_c_compilers(host_or_target):
 
     @depends(host_or_target, target, toolchain_prefix)
     def default_c_compilers(host_or_target, target, toolchain_prefix):
         gcc = ('gcc',)
         if toolchain_prefix and host_or_target is target:
             gcc = tuple('%sgcc' % p for p in toolchain_prefix) + gcc
 
         if host_or_target.kernel == 'WINNT':
-            return ('cl', 'clang-cl') + gcc + ('clang',)
+            return ('cl.exe', 'clang-cl.exe') + gcc + ('clang',)
         if host_or_target.kernel == 'Darwin':
             return ('clang',)
         return gcc + ('clang',)
 
     return default_c_compilers
 
 
 @template
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -73,16 +73,19 @@ def is_absolute_or_relative(path):
         return True
     return os.sep in path
 
 
 @imports(_import='mozpack.path', _as='mozpath')
 def normsep(path):
     return mozpath.normsep(path)
 
+@imports('platform')
+def host_is_wsl_check():
+    return platform.system() == 'Linux' and 'Microsoft' in platform.uname()[2]
 
 @imports('ctypes')
 @imports(_from='ctypes', _import='wintypes')
 @imports(_from='mozbuild.configure.constants', _import='WindowsBinaryType')
 def windows_binary_type(path):
     """Obtain the type of a binary on Windows.
 
     Returns WindowsBinaryType constant.
@@ -114,16 +117,18 @@ def get_GetShortPathNameW():
     GetShortPathNameW.restype = wintypes.DWORD
     return GetShortPathNameW
 
 
 @template
 @imports('ctypes')
 @imports('platform')
 @imports(_from='mozbuild.shellutil', _import='quote')
+@imports(_from='ntpath', _import='isabs')
+@imports(_from='ntpath', _import='splitdrive')
 def normalize_path():
     # Until the build system can properly handle programs that need quoting,
     # transform those paths into their short version on Windows (e.g.
     # c:\PROGRA~1...).
     if platform.system() == 'Windows':
         GetShortPathNameW = get_GetShortPathNameW()
 
         def normalize_path(path):
@@ -135,25 +140,68 @@ def normalize_path():
                 out = ctypes.create_unicode_buffer(size)
                 needed = GetShortPathNameW(path, out, size)
                 if size >= needed:
                     if ' ' in out.value:
                         die("GetShortPathName returned a long path name. "
                             "Are 8dot3 filenames disabled?")
                     return normsep(out.value)
                 size = needed
-
+    elif host_is_wsl_check():
+        def normalize_path(path):
+            if isabs(path):
+                drive, rest = splitdrive(path)
+                if not drive:
+                    # No drive letter, probably a posix path.
+                    return normsep(path)
+                return '/mnt/{}{}'.format(drive[0].lower(), normsep(rest))
+            else:
+                return normsep(path)
     else:
         def normalize_path(path):
             return normsep(path)
 
     return normalize_path
 
 normalize_path = normalize_path()
 
+# When building under WSL, map `path` to a Windows-native path. On other
+# platforms, this function is a no-op.
+def win_path():
+    if host_is_wsl_check():
+        def win_path(path):
+            if path.startswith('/'):
+                if not path.startswith('/mnt'):
+                    die("Can't convert a non-drvfs path to Windows format: %s" % path)
+                _, _, drive, rest = path.split('/', 3)
+                return drive + ':/' + rest
+            else:
+                return path
+    else:
+        def win_path(path):
+            return path
+    return win_path
+
+win_path = win_path()
+
+# Expand `path` relative to the user's home directory.
+@template
+@imports(_from='os.path', _import='expanduser')
+def user_path():
+    if host_is_wsl_check():
+        def user_path(path):
+            #TODO: would be nice to cache this value
+            home = check_cmd_output('cmd.exe', '/c', 'echo %USERPROFILE%').strip()
+            return normalize_path(os.path.join(home, path))
+    else:
+        def user_path(path):
+            return expanduser('~/' + path)
+    return user_path
+user_path = user_path()
+
 
 # Locates the given program using which, or returns the given path if it
 # exists.
 # The `paths` parameter may be passed to search the given paths instead of
 # $PATH.
 @imports(_from='which', _import='which')
 @imports(_from='which', _import='WhichError')
 @imports('itertools')
@@ -183,17 +231,22 @@ def find_program(file, paths=None):
         if paths:
             if not isinstance(paths, (list, tuple)):
                 die("Paths provided to find_program must be a list of strings, "
                     "not %r", paths)
             paths = list(itertools.chain(
                 *(p.split(pathsep) for p in paths if p)))
         return normalize_path(which(file, path=paths, exts=exts))
     except WhichError:
-        return None
+        if host_is_wsl_check() and not file.endswith('.exe'):
+            # Under WSL we might need a .exe, but not *all* the time, and
+            # which will refuse to use exts on what it thinks is Linux.
+            return find_program(file + '.exe', paths=paths)
+        else:
+            return None
 
 
 @imports('os')
 @imports('subprocess')
 @imports(_from='mozbuild.configure.util', _import='LineIO')
 @imports(_from='tempfile', _import='mkstemp')
 def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
     flags = flags or []
@@ -212,31 +265,54 @@ def try_invoke_compiler(compiler, langua
         source = source.encode('ascii', 'replace')
 
         log.debug('Creating `%s` with content:', path)
         with LineIO(lambda l: log.debug('| %s', l)) as out:
             out.write(source)
 
         os.write(fd, source)
         os.close(fd)
-        cmd = compiler + list(flags) + [path]
+        tmpd = os.path.dirname(path)
+        cmd = compiler + list(flags) + [os.path.basename(path)]
         kwargs = {'onerror': onerror}
-        return check_cmd_output(*cmd, **kwargs)
+        return check_cmd_output(*cmd, cwd=tmpd, **kwargs)
     finally:
         os.remove(path)
 
 
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
             result.append(i)
     return result
 
 
+# from mozbuild.util import ReadOnlyNamespace as namespace
+@imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
+def namespace(**kwargs):
+    return ReadOnlyNamespace(**kwargs)
+
+
+@imports('_winreg')
+def winreg_helper():
+    # Provide a subset of _winreg.
+    return namespace(**{k: getattr(_winreg, k)
+                        for k in ('OpenKey',
+                                  'EnumKey',
+                                  'EnumValue',
+                                  'KEY_READ',
+                                  'KEY_WOW64_32KEY',
+                                  'KEY_WOW64_64KEY',
+                                  'HKEY_LOCAL_MACHINE',
+                                  'HKEY_CURRENT_USER',
+                              )
+                    })
+
+
 # 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.
@@ -273,87 +349,114 @@ def unique_list(l):
 #      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='__builtin__', _import='OSError')
 @imports(_from='fnmatch', _import='fnmatch')
-def get_registry_values(pattern, get_32_and_64_bit=False):
-    def enum_helper(func, key):
-        i = 0
-        while True:
-            try:
-                yield func(key, i)
-            except WindowsError:
-                break
-            i += 1
+def get_registry_values():
+    if not host_is_wsl_check():
+        def get_registry_values(pattern, get_32_and_64_bit=False):
+            winreg = winreg_helper()
+            def enum_helper(func, key):
+                i = 0
+                while True:
+                    try:
+                        yield func(key, i)
+                    except OSError:
+                        break
+                    i += 1
 
-    def get_keys(key, pattern, access_mask):
-        try:
-            s = winreg.OpenKey(key, '\\'.join(pattern[:-1]), 0, access_mask)
-        except WindowsError:
-            return
-        for k in enum_helper(winreg.EnumKey, s):
-            if fnmatch(k, pattern[-1]):
+            def get_keys(key, pattern, access_mask):
+                try:
+                    s = winreg.OpenKey(key, '\\'.join(pattern[:-1]), 0, access_mask)
+                except OSError:
+                    return
+                for k in enum_helper(winreg.EnumKey, s):
+                    if fnmatch(k, pattern[-1]):
+                        try:
+                            yield k, winreg.OpenKey(s, k, 0, access_mask)
+                        except OSError:
+                            pass
+
+            def get_values(key, pattern, access_mask):
                 try:
-                    yield k, winreg.OpenKey(s, k, 0, access_mask)
-                except WindowsError:
-                    pass
+                    s = winreg.OpenKey(key, '\\'.join(pattern[:-1]), 0, access_mask)
+                except OSError:
+                    return
+                for k, v, t in enum_helper(winreg.EnumValue, s):
+                    if fnmatch(k, pattern[-1]):
+                        yield k, v
 
-    def get_values(key, pattern, access_mask):
-        try:
-            s = winreg.OpenKey(key, '\\'.join(pattern[:-1]), 0, access_mask)
-        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
 
-    def split_pattern(pattern):
-        subpattern = []
-        for p in pattern:
-            subpattern.append(p)
-            if '*' in p:
-                yield subpattern
-                subpattern = []
-        if subpattern:
-            yield subpattern
+            def get_all_values(keys, pattern, access_mask):
+                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, access_mask):
+                                yield matches + ((name, value) if want_name else (value,))
+                        else:
+                            for name, k in get_keys(base_key, p, access_mask):
+                                next_keys.append(matches + (name, k))
+                    keys = next_keys
 
-    def get_all_values(keys, pattern, access_mask):
-        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, access_mask):
-                        yield matches + ((name, value) if want_name else (value,))
-                else:
-                    for name, k in get_keys(base_key, p, access_mask):
-                        next_keys.append(matches + (name, k))
-            keys = next_keys
+            pattern = pattern.split('\\')
+            assert pattern[0].startswith('HKEY_')
+            keys = [(getattr(winreg, pattern[0]),)]
+            pattern = list(split_pattern(pattern[1:]))
+            if get_32_and_64_bit:
+                for match in get_all_values(keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY):
+                    yield match
+                for match in get_all_values(keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY):
+                    yield match
+            else:
+                for match in get_all_values(keys, pattern, winreg.KEY_READ):
+                    yield match
+    else: # host_is_wsl
+        #XXX: This doesn't actually implement the exact same logic as the native version above.
+        # notably, it doesn't handle wildcards in key names, only in value names
+        def get_registry_values(pattern, get_32_and_64_bit=False):
+            def run_reg(key, val, reg_32):
+                extra = ('/reg:32',) if reg_32 else ()
+                # Skip the leading empty line and the trailing empty line + query summary.
+                out = check_cmd_output('reg.exe', 'query', key, '/f', val, *extra, onerror=lambda: None)
+                if out is None:
+                    return
+                lines = out.splitlines()[2:-2]
+                for line in lines:
+                    (name, _, val) = line.split(None, 2)
+                    yield name, val
+            key, val = pattern.rsplit('\\', 1)
+            for name, val in run_reg(key, val, False):
+                yield name, val
+            if get_32_and_64_bit:
+                for name, val in run_reg(key, val, True):
+                    yield name, val
 
-    pattern = pattern.split('\\')
-    assert pattern[0].startswith('HKEY_')
-    keys = [(getattr(winreg, pattern[0]),)]
-    pattern = list(split_pattern(pattern[1:]))
-    if get_32_and_64_bit:
-        for match in get_all_values(keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY):
-            yield match
-        for match in get_all_values(keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY):
-            yield match
-    else:
-        for match in get_all_values(keys, pattern, winreg.KEY_READ):
-            yield match
+
+    return get_registry_values
+
+get_registry_values = get_registry_values()
 
 
 @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:
@@ -375,22 +478,16 @@ def deprecated_option(*args, **kwargs):
         def deprecated(value):
             if value.origin != 'default':
                 return func(value)
         return deprecated
 
     return decorator
 
 
-# from mozbuild.util import ReadOnlyNamespace as namespace
-@imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
-def namespace(**kwargs):
-    return ReadOnlyNamespace(**kwargs)
-
-
 # Turn an object into an object that can be used as an argument to @depends.
 # The given object can be a literal value, a function that takes no argument,
 # or, for convenience, a @depends function.
 @template
 @imports(_from='inspect', _import='isfunction')
 @imports(_from='mozbuild.configure', _import='SandboxDependsFunction')
 def dependable(obj):
     if isinstance(obj, SandboxDependsFunction):
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -28,53 +28,53 @@ option(env='WINDOWSSDKDIR', nargs=1,
 
 @depends('WINDOWSSDKDIR', host)
 def windows_sdk_dir(value, host):
     if value:
         return value
     if host.kernel != 'WINNT':
         return ()
 
-    return set(x[1] for x in get_registry_values(
+    return set(normalize_path(x[1]) for x in get_registry_values(
         r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots'
         r'\KitsRoot*', get_32_and_64_bit=True))
 
 # The Windows SDK 8.1 and 10 have different layouts. The former has
 # $SDK/include/$subdir, while the latter has $SDK/include/$version/$subdir.
 # The vcvars* scripts don't actually care about the version, they just take
 # the last alphanumerically.
 # The $SDK/lib directories always have version subdirectories, but while the
 # versions match the one in $SDK/include for SDK 10, it's "winv6.3" for SDK
 # 8.1.
 @imports('os')
 @imports('re')
 @imports(_from='__builtin__', _import='sorted')
-@imports(_from='__builtin__', _import='WindowsError')
+@imports(_from='__builtin__', _import='OSError')
 def get_sdk_dirs(sdk, subdir):
     def get_dirs_containing(sdk, stem, subdir):
         base = os.path.join(sdk, stem)
         try:
             subdirs = [d for d in os.listdir(base)
                        if os.path.isdir(os.path.join(base, d))]
-        except WindowsError:
+        except OSError:
             subdirs = []
         if not subdirs:
             return ()
         if subdir in subdirs:
             return (base,)
         # At this point, either we have an incomplete or invalid SDK directory,
         # or we exclusively have version numbers in subdirs.
         return tuple(os.path.join(base, s) for s in subdirs
                      if os.path.isdir(os.path.join(base, s, subdir)))
 
     def categorize(dirs):
         return {os.path.basename(d): d for d in dirs}
 
-    include_dirs = categorize(get_dirs_containing(sdk, 'include', subdir))
-    lib_dirs = categorize(get_dirs_containing(sdk, 'lib', subdir))
+    include_dirs = categorize(get_dirs_containing(sdk, 'Include', subdir))
+    lib_dirs = categorize(get_dirs_containing(sdk, 'Lib', subdir))
 
     if 'include' in include_dirs:
         include_dirs['winv6.3'] = include_dirs['include']
         del include_dirs['include']
 
     valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True)
     if valid_versions:
         return namespace(
@@ -104,17 +104,17 @@ def valid_windows_sdk_dir(compiler, wind
             check = dedent('''\
             #include <winsdkver.h>
             WINVER_MAXVER
             ''')
             um_dir = os.path.join(sdk.include, 'um')
             shared_dir = os.path.join(sdk.include, 'shared')
             result = try_preprocess(compiler.wrapper + [compiler.compiler] +
                                     compiler.flags +
-                                    ['-I', um_dir, '-I', shared_dir], 'C',
+                                    ['-I', win_path(um_dir), '-I', win_path(shared_dir)], 'C',
                                     check)
             if result:
                 maxver = result.splitlines()[-1]
                 try:
                     maxver = int(maxver, 0)
                 except:
                     pass
                 else:
@@ -225,49 +225,57 @@ def valid_ucrt_sdk_dir(windows_sdk_dir, 
 
     return namespace(
         path=sdk.path,
         include=sdk.include,
         lib=sdk.lib,
         version=version,
     )
 
+@imports('os')
+def find_path_upwards(path, search):
+    result = path
+    while True:
+        next, p = os.path.split(result)
+        if next == result:
+            return None
+        result = next
+        if p.lower() == search:
+            break
+    return result
 
 @depends(c_compiler)
-@imports('os')
 def vc_path(c_compiler):
     if c_compiler.type != 'msvc':
         return
     # Normally, we'd start from c_compiler.compiler, but for now, it's not the
     # ideal full path to the compiler. At least, we're guaranteed find_program
     # will get us the one we found in toolchain.configure.
     cl = find_program(c_compiler.compiler)
     result = os.path.dirname(cl)
-    while True:
-        next, p = os.path.split(result)
-        if next == result:
-            die('Cannot determine the Visual C++ directory the compiler (%s) '
-                'is in' % cl)
-        result = next
-        if p.lower() == 'bin':
-            break
+    result = find_path_upwards(result, 'bin')
+    if result is None:
+        die('Cannot determine the Visual C++ directory the compiler (%s) '
+            'is in' % cl)
     return result
 
 
 @depends(vc_path, c_compiler)
 @checking('for the Debug Interface Access SDK', lambda x: x or 'not found')
 @imports(_from='os.path', _import='isdir')
 def dia_sdk_dir(vc_path, c_compiler):
     if vc_path:
         if c_compiler.version < '19.10':
             path = os.path.join(os.path.dirname(vc_path), 'DIA SDK')
         else:
             # This would be easier if we had the installationPath that
             # get_vc_paths works with, since 'DIA SDK' is relative to that.
-            path = os.path.normpath(os.path.join(vc_path, r'..\..\..\..\DIA SDK'))
+            path = find_path_upwards(vc_path, 'vc')
+            if path:
+                path = os.path.normpath(os.path.join(path, 'DIA SDK'))
         if isdir(path):
             return path
 
 
 @depends(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
 @imports('os')
 def include_path(vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir):
     if not vc_path:
@@ -298,16 +306,22 @@ def include_path(vc_path, windows_sdk_di
         includes.append(os.path.join(dia_sdk_dir, 'include'))
     # Set in the environment for old-configure
     includes = os.pathsep.join(includes)
     os.environ['INCLUDE'] = includes
     return includes
 
 set_config('INCLUDE', include_path)
 
+@depends(include_path, host_is_wsl)
+@imports('os')
+def include_cflags(include_path, host_is_wsl):
+    if include_path and host_is_wsl:
+        return ['-I' + win_path(path) for path in include_path.split(os.pathsep)]
+    return []
 
 @depends(target, c_compiler, vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
 @imports('os')
 def lib_path(target, c_compiler, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir):
     if not vc_path:
         return
     sdk_target = {
         'x86': 'x86',
@@ -368,16 +382,17 @@ option(env='MT', nargs=1, help='Path to 
 @imports('platform')
 def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir):
     if not valid_windows_sdk_dir:
         return
 
     vc_host = {
         'x86': 'x86',
         'AMD64': 'x64',
+        'x86_64': 'x64',
     }.get(platform.machine())
 
     # From version 10.0.15063.0 onwards the bin path contains the version number.
     versioned_bin = ('bin' if valid_ucrt_sdk_dir.version < '10.0.15063.0'
                      else os.path.join('bin', str(valid_ucrt_sdk_dir.version)))
     result = [
         environ['PATH'],
         os.path.join(valid_windows_sdk_dir.path, versioned_bin, vc_host)
@@ -433,22 +448,22 @@ set_config('PATH', alter_path)
 check_prog('MAKECAB', ('makecab.exe',))
 
 @depends(c_compiler, using_sccache)
 def need_showincludes_prefix(info, using_sccache):
     # sccache does its own -showIncludes prefix checking.
     if info.type in ('clang-cl', 'msvc') and not using_sccache:
         return True
 
-@depends(c_compiler, when=need_showincludes_prefix)
+@depends(c_compiler, include_cflags, when=need_showincludes_prefix)
 @imports(_from='re', _import='compile', _as='re_compile')
-def msvc_showincludes_prefix(c_compiler):
+def msvc_showincludes_prefix(c_compiler, include_cflags):
     pattern = re_compile(br'^([^:]*:.*[ :] )(.*\\stdio.h)$')
     output = try_invoke_compiler([c_compiler.compiler], 'C', '#include <stdio.h>\n',
-                                 ['-nologo', '-c', '-Fonul', '-showIncludes'])
+                                 ['-nologo', '-c', '-Fonul', '-showIncludes'] + include_cflags)
     for line in output.splitlines():
         if line.endswith(b'\\stdio.h'):
             m = pattern.match(line)
             if m:
                 return m.group(1)
     # We should have found the prefix and returned earlier
     die('Cannot find cl -showIncludes prefix.')
 
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -91,17 +91,17 @@ if test "$COMPILE_ENVIRONMENT"; then
 #     - When we say $target, we mean $host, that is, the system on which
 #       Mozilla will be run.
 #     - When we say $host, we mean $build, that is, the system on which Mozilla
 #       is built.
 #     - $target (in its correct usage) is for compilers who generate code for a
 #       different platform than $host, so it would not be used by Mozilla.
 if test "$target" != "$host"; then
     MOZ_CROSS_COMPILER
-else
+elif test -z "$HOST_IS_WSL"; then
     AC_PROG_CC
     AC_PROG_CXX
     AC_PROG_RANLIB
     AC_CHECK_PROGS(AR, ar, :)
     AC_CHECK_PROGS(STRIP, strip, :)
     AC_CHECK_PROGS(WINDRES, windres, :)
     if test -z "$HOST_CC"; then
         HOST_CC='$(CC)'
@@ -144,27 +144,16 @@ case "$target" in
         # because this also forces narrowing to a single byte, which can be a
         # perf hit.  But this matters so little in practice (and often we want
         # that behavior) that it's better to turn it off.
         # _CRT_SECURE_NO_WARNINGS disables warnings about using MSVC-specific
         # secure CRT functions.
         # MSVC warning C4595 warns non-member operator new or delete functions
         # may not be declared inline, as of VS2015 Update 2.
         CXXFLAGS="$CXXFLAGS -wd4800 -wd4595 -D_CRT_SECURE_NO_WARNINGS"
-        AC_LANG_SAVE
-        AC_LANG_C
-        AC_TRY_COMPILE([#include <stdio.h>],
-            [ printf("Hello World\n"); ],,
-            AC_MSG_ERROR([\$(CC) test failed.  You must have MS VC++ in your path to build.]) )
-
-        AC_LANG_CPLUSPLUS
-        AC_TRY_COMPILE([#include <new.h>],
-            [ unsigned *test = new unsigned(42); ],,
-            AC_MSG_ERROR([\$(CXX) test failed.  You must have MS VC++ in your path to build.]) )
-        AC_LANG_RESTORE
 
         changequote(,)
         _MSVC_VER_FILTER='s|.*[^!-~]([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?).*|\1|p'
         changequote([,])
 
         _MSC_VER=`echo ${CC_VERSION} | cut -c 1-2,4-5`
 
         AC_DEFINE(_CRT_SECURE_NO_WARNINGS)
@@ -1429,17 +1418,17 @@ MOZ_ARG_ENABLE_STRING(optimize,
         MOZ_OPTIMIZE=2
     fi
 else
     MOZ_OPTIMIZE=
 fi ], MOZ_OPTIMIZE=1)
 
 MOZ_SET_FRAMEPTR_FLAGS
 
-if test "$COMPILE_ENVIRONMENT"; then
+if test -n "$COMPILE_ENVIRONMENT" -a -z "$HOST_IS_WSL"; then
 if test -n "$MOZ_OPTIMIZE"; then
     AC_MSG_CHECKING([for valid optimization flags])
     _SAVE_CFLAGS=$CFLAGS
     CFLAGS="$CFLAGS $MOZ_OPTIMIZE_FLAGS"
     AC_TRY_COMPILE([#include <stdio.h>],
         [printf("Hello World\n");],
         _results=yes,
         _results=no)
--- a/old-configure.in
+++ b/old-configure.in
@@ -107,17 +107,17 @@ dnl ====================================
 
 dnl AR_FLAGS set here so HOST_AR_FLAGS can be set correctly (see bug 538269)
 AR_FLAGS='crs $@'
 
 if test "$COMPILE_ENVIRONMENT"; then
 
 if test "$target" != "$host"; then
     MOZ_CROSS_COMPILER
-else
+elif test -z "$HOST_IS_WSL"; then
     AC_PROG_CC
     case "$target" in
     *-mingw*)
       # Work around the conftest.exe access problem on Windows
       sleep 2
     esac
     AC_PROG_CXX
     AC_PROG_RANLIB
@@ -149,31 +149,16 @@ dnl ====================================
 WINVER=601
 
 case "$target" in
 *-mingw*)
     if test "$GCC" != "yes"; then
         # Check to see if we are really running in a msvc environemnt
         _WIN32_MSVC=1
 
-        # Make sure compilers are valid
-        CFLAGS="$CFLAGS -TC -nologo"
-        CXXFLAGS="$CXXFLAGS -TP -nologo"
-        AC_LANG_SAVE
-        AC_LANG_C
-        AC_TRY_COMPILE([#include <stdio.h>],
-            [ printf("Hello World\n"); ],,
-            AC_MSG_ERROR([\$(CC) test failed.  You must have MS VC++ in your path to build.]) )
-
-        AC_LANG_CPLUSPLUS
-        AC_TRY_COMPILE([#include <new.h>],
-            [ unsigned *test = new unsigned(42); ],,
-            AC_MSG_ERROR([\$(CXX) test failed.  You must have MS VC++ in your path to build.]) )
-        AC_LANG_RESTORE
-
         changequote(,)
         _MSVC_VER_FILTER='s|.*[^!-~]([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?).*|\1|p'
         changequote([,])
 
         _MSC_VER=`echo ${CC_VERSION} | cut -c 1-2,4-5`
 
         AC_DEFINE(_CRT_SECURE_NO_WARNINGS)
         AC_DEFINE(_CRT_NONSTDC_NO_WARNINGS)
@@ -2512,19 +2497,25 @@ if test -n "$MOZ_WEBRTC"; then
         ;;
     *)
         dnl default to disabled for all others
         MOZ_WEBRTC=
         ;;
     esac
 fi
 
-AC_TRY_COMPILE([#include <linux/ethtool.h>],
-               [ struct ethtool_cmd cmd; cmd.speed_hi = 0; ],
-               MOZ_WEBRTC_HAVE_ETHTOOL_SPEED_HI=1)
+case "$target" in
+  *-linux*)
+    AC_TRY_COMPILE([#include <linux/ethtool.h>],
+                   [ struct ethtool_cmd cmd; cmd.speed_hi = 0; ],
+                   MOZ_WEBRTC_HAVE_ETHTOOL_SPEED_HI=1)
+    ;;
+  *)
+    ;;
+esac
 
 AC_SUBST(MOZ_WEBRTC_HAVE_ETHTOOL_SPEED_HI)
 
 # target_arch is from {ia32|x64|arm|ppc}
 case "$CPU_ARCH" in
 x86_64 | arm | aarch64 | x86 | ppc* | ia64)
     :
     ;;
@@ -3730,17 +3721,17 @@ MOZ_ARG_ENABLE_STRING(optimize,
         MOZ_OPTIMIZE=2
     fi
 else
     MOZ_OPTIMIZE=
 fi ], MOZ_OPTIMIZE=1)
 
 MOZ_SET_FRAMEPTR_FLAGS
 
-if test "$COMPILE_ENVIRONMENT"; then
+if test -n "$COMPILE_ENVIRONMENT" -a -z "$HOST_IS_WSL"; then
 if test -n "$MOZ_OPTIMIZE"; then
     AC_MSG_CHECKING([for valid C compiler optimization flags])
     _SAVE_CFLAGS=$CFLAGS
     CFLAGS="$CFLAGS $MOZ_OPTIMIZE_FLAGS"
     AC_TRY_COMPILE([#include <stdio.h>],
         [printf("Hello World\n");],
         _results=yes,
         _results=no)
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -994,11 +994,13 @@ class ConfigureSandbox(dict):
             closure
         ))
         @self.wraps(new_func)
         def wrapped(*args, **kwargs):
             if func in self._imports:
                 self._apply_imports(func, glob)
                 del self._imports[func]
             return new_func(*args, **kwargs)
+        # Allow wrapped functions to call themselves recursively.
+        glob[func.__name__] = wrapped
 
         self._prepared_functions.add(wrapped)
         return wrapped, glob
--- a/python/mozbuild/mozpack/path.py
+++ b/python/mozbuild/mozpack/path.py
@@ -15,21 +15,17 @@ Also contains a few additional utilities
 '''
 
 
 def normsep(path):
     '''
     Normalize path separators, by using forward slashes instead of whatever
     os.sep is.
     '''
-    if os.sep != '/':
-        path = path.replace(os.sep, '/')
-    if os.altsep and os.altsep != '/':
-        path = path.replace(os.altsep, '/')
-    return path
+    return path.replace('\\', '/')
 
 
 def relpath(path, start):
     rel = normsep(os.path.relpath(path, start))
     return '' if rel == '.' else rel
 
 
 def realpath(path):
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -730,18 +730,18 @@ with only_when(building_stylo_bindgen):
                 PATH may expose 'clang' as well, potentially altering your compiler,
                 which may not be what you intended.'''))
 
             check_minimum_llvm_config_version(llvm_config)
             libclang_arg = '--bindir' if host.os == 'WINNT' else '--libdir'
             libclang_path = invoke_llvm_config(llvm_config, libclang_arg)
             clang_path = os.path.join(invoke_llvm_config(llvm_config, '--bindir'),
                                       'clang')
-            libclang_path = normsep(libclang_path)
-            clang_path = normsep(clang_path)
+            libclang_path = normalize_path(libclang_path)
+            clang_path = normalize_path(clang_path)
 
             # Debian-based distros, at least, can have llvm-config installed
             # but not have other packages installed.  Since the user is trying
             # to use their system packages, we can't be more specific about what
             # they need.
             if not os.path.exists(libclang_path):
                 die(dedent('''\
                 The directory {} returned by `llvm-config {}` does not exist.
@@ -792,18 +792,18 @@ with only_when(building_stylo_bindgen):
 
         clang_resolved = find_program(clang_path)
         if not clang_resolved:
             die(dedent('''\
             The argument to --with-clang-path is not a file: {}
             '''.format(clang_path)))
 
         return namespace(
-            libclang_path=libclang_path,
-            clang_path=clang_resolved,
+            libclang_path=win_path(libclang_path),
+            clang_path=win_path(clang_resolved),
         )
 
     set_config('MOZ_LIBCLANG_PATH', bindgen_config_paths.libclang_path)
     set_config('MOZ_CLANG_PATH', bindgen_config_paths.clang_path)
     set_config('MOZ_STYLO_BINDGEN', depends_if('--enable-stylo-build-bindgen')(lambda _: True))
 
 set_config('MOZ_STYLO', stylo_config.build)
 set_define('MOZ_STYLO', stylo_config.build)