# -*- Mode: python; c-basic-offset: 4; 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/. option( "--with-windows-version", nargs=1, default="603", help="Windows SDK version to target. Win 8.1 (603) is currently" "the minimum supported version.", ) @depends("--with-windows-version") @imports(_from="__builtin__", _import="ValueError") def valid_windows_version(value): if not value: die("Cannot build with --without-windows-version") try: version = int(value[0], 16) if version in (0x603,): return version except ValueError: pass die("Invalid value for --with-windows-version (%s)", value[0]) option(env="WINDOWSSDKDIR", nargs=1, help="Directory containing the Windows SDK") @depends("WINDOWSSDKDIR", host, c_compiler) def windows_sdk_dir(value, host, compiler): if value: return value # Ideally, we'd actually check for host/target ABI being MSVC, but # that's waiting for bug 1617793. if host.kernel != "WINNT" or compiler.type != "clang-cl": return () return set( 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="Exception") 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 Exception: 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)) 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( path=sdk, lib=lib_dirs[valid_versions[0]], include=include_dirs[valid_versions[0]], ) @imports(_from="mozbuild.shellutil", _import="quote") def valid_windows_sdk_dir_result(value): if value: return "0x%04x in %s" % (value.version, quote(value.path)) @depends(c_compiler, windows_sdk_dir, valid_windows_version, "WINDOWSSDKDIR") @checking("for Windows SDK", valid_windows_sdk_dir_result) @imports(_from="__builtin__", _import="sorted") @imports(_from="__builtin__", _import="Exception") @imports(_from="textwrap", _import="dedent") def valid_windows_sdk_dir( compiler, windows_sdk_dir, target_version, windows_sdk_dir_env ): # Ideally, we'd actually check for host/target ABI being MSVC, but # that's waiting for bug 1617793. if compiler.type != "clang-cl": return None if windows_sdk_dir_env: windows_sdk_dir_env = windows_sdk_dir_env[0] sdks = {} for d in windows_sdk_dir: sdk = get_sdk_dirs(d, "um") if sdk: check = dedent( """\ #include 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 + ["-X", "-I", um_dir, "-I", shared_dir], "C", check, onerror=lambda: "", ) if result: maxver = result.splitlines()[-1] try: maxver = int(maxver, 0) except Exception: pass else: sdks[d] = maxver, sdk continue if d == windows_sdk_dir_env: raise FatalCheckError( "Error while checking the version of the SDK in " "WINDOWSSDKDIR (%s). Please verify it contains a valid and " "complete SDK installation." % windows_sdk_dir_env ) valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) if valid_sdks: biggest_version, sdk = sdks[valid_sdks[0]] if not valid_sdks or biggest_version < target_version: if windows_sdk_dir_env: raise FatalCheckError( "You are targeting Windows version 0x%04x, but your SDK only " "supports up to version 0x%04x. Install and use an updated SDK, " "or target a lower version using --with-windows-version. " "Alternatively, try running the Windows SDK Configuration Tool " "and selecting a newer SDK. See " "https://developer.mozilla.org/En/Windows_SDK_versions for " "details on fixing this." % (target_version, biggest_version) ) raise FatalCheckError( "Cannot find a Windows SDK for version >= 0x%04x." % target_version ) return namespace( path=sdk.path, include=sdk.include, lib=sdk.lib, version=biggest_version, ) @imports(_from="mozbuild.shellutil", _import="quote") def valid_ucrt_sdk_dir_result(value): if value: return "%s in %s" % (value.version, quote(value.path)) @depends(windows_sdk_dir, "WINDOWSSDKDIR", c_compiler) @checking("for Universal CRT SDK", valid_ucrt_sdk_dir_result) @imports("os") @imports(_from="__builtin__", _import="sorted") @imports(_import="mozpack.path", _as="mozpath") def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env, compiler): # Ideally, we'd actually check for host/target ABI being MSVC, but # that's waiting for bug 1617793. if compiler.type != "clang-cl": return None if windows_sdk_dir_env: windows_sdk_dir_env = windows_sdk_dir_env[0] sdks = {} for d in windows_sdk_dir: sdk = get_sdk_dirs(d, "ucrt") if sdk: version = os.path.basename(sdk.include) # We're supposed to always find a version in the directory, because # the 8.1 SDK, which doesn't have a version in the directory, doesn't # contain the Universal CRT SDK. When the main SDK is 8.1, there # is, however, supposed to be a reduced install of the SDK 10 # with the UCRT. if version != "include": sdks[d] = Version(version), sdk continue if d == windows_sdk_dir_env: # When WINDOWSSDKDIR is set in the environment and we can't find the # Universal CRT SDK, chances are this is a start-shell-msvc*.bat # setup, where INCLUDE and LIB already contain the UCRT paths. ucrt_includes = [ p for p in os.environ.get("INCLUDE", "").split(";") if os.path.basename(p).lower() == "ucrt" ] ucrt_libs = [ p for p in os.environ.get("LIB", "").split(";") if os.path.basename(os.path.dirname(p)).lower() == "ucrt" ] if ucrt_includes and ucrt_libs: # Pick the first of each, since they are the ones that the # compiler would look first. Assume they contain the SDK files. include = os.path.dirname(ucrt_includes[0]) lib = os.path.dirname(os.path.dirname(ucrt_libs[0])) path = os.path.dirname(os.path.dirname(include)) version = os.path.basename(include) if version != "include" and mozpath.basedir(lib, [path]): sdks[d] = ( Version(version), namespace( path=path, include=include, lib=lib, ), ) continue raise FatalCheckError( "The SDK in WINDOWSSDKDIR (%s) does not contain the Universal " "CRT." % windows_sdk_dir_env ) valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) if not valid_sdks: raise FatalCheckError( "Cannot find the Universal CRT SDK. " "Please install it." ) version, sdk = sdks[valid_sdks[0]] minimum_ucrt_version = Version("10.0.17134.0") if version < minimum_ucrt_version: raise FatalCheckError( "Latest Universal CRT SDK version found %s" " and minimum required is %s. This or a later" " version can be installed using the Visual" " Studio installer." % (version, minimum_ucrt_version) ) return namespace( path=sdk.path, include=sdk.include, lib=sdk.lib, version=version, ) @depends(c_compiler, host_c_compiler, vc_toolchain_search_path) @imports("os") def vc_path(c_compiler, host_c_compiler, vc_toolchain_search_path): if c_compiler.type != "clang-cl" and host_c_compiler.type != "clang-cl": return # In clang-cl builds, we need the headers and libraries from an MSVC installation. vc_program = find_program("cl.exe", paths=vc_toolchain_search_path) if not vc_program: die("Cannot find a Visual C++ install for e.g. ATL headers.") result = os.path.dirname(vc_program) while True: next, p = os.path.split(result) if next == result: die( "Cannot determine the Visual C++ directory the compiler (%s) " "is in" % vc_program ) result = next if p.lower() == "bin": break return os.path.normpath(result) option(env="DIA_SDK_PATH", nargs=1, help="Path to the Debug Interface Access SDK") @depends(vc_path, "DIA_SDK_PATH") @checking("for the Debug Interface Access SDK", lambda x: x or "not found") @imports("os") def dia_sdk_dir(vc_path, dia_sdk_path): if dia_sdk_path: path = os.path.normpath(dia_sdk_path[0]) elif vc_path: # 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, "..", "..", "..", "..", "DIA SDK") ) else: return if os.path.exists(os.path.join(path, "include", "dia2.h")): 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: return atlmfc_dir = os.path.join(vc_path, "atlmfc", "include") if not os.path.isdir(atlmfc_dir): die( "Cannot find the ATL/MFC headers in the Visual C++ directory (%s). " "Please install them." % vc_path ) winrt_dir = os.path.join(windows_sdk_dir.include, "winrt") if not os.path.isdir(winrt_dir): die( "Cannot find the WinRT headers in the Windows SDK directory (%s). " "Please install them." % windows_sdk_dir.path ) includes = [] include_env = os.environ.get("INCLUDE") if include_env: includes.append(include_env) includes.extend( ( os.path.join(vc_path, "include"), atlmfc_dir, os.path.join(windows_sdk_dir.include, "shared"), os.path.join(windows_sdk_dir.include, "um"), winrt_dir, os.path.join(ucrt_sdk_dir.include, "ucrt"), ) ) if dia_sdk_dir: includes.append(os.path.join(dia_sdk_dir, "include")) # Set in the environment for old-configure includes = ";".join(includes) os.environ["INCLUDE"] = includes return includes set_config("INCLUDE", include_path) @template def dia_sdk_subdir(host_or_target, subdir): @depends(dia_sdk_dir, host_or_target, dependable(subdir)) def dia_sdk_subdir(dia_sdk_dir, target, subdir): if not dia_sdk_dir: return # For some reason the DIA SDK still uses the old-style targets # even in a newer MSVC. old_target = { "x86": "", "x86_64": "amd64", "arm": "arm", "aarch64": "arm64", }.get(target.cpu) if old_target is None: return # As old_target can be '', and os.path.join will happily use the empty # string, leading to a string ending with a backslash, that Make will # interpret as a "string continues on next line" indicator, use variable # args. old_target = (old_target,) if old_target else () return os.path.join(dia_sdk_dir, subdir, *old_target) return dia_sdk_subdir set_config("WIN_DIA_SDK_BIN_DIR", dia_sdk_subdir(host, "bin")) @template def lib_path_for(host_or_target): @depends( host_or_target, dependable(host_or_target is host), vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_subdir(host_or_target, "lib"), ) @imports("os") def lib_path( target, is_host, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_lib_dir ): if not vc_path: return sdk_target = { "x86": "x86", "x86_64": "x64", "arm": "arm", "aarch64": "arm64", }.get(target.cpu) # MSVC2017 switched to use the same target naming as the sdk. atlmfc_dir = os.path.join(vc_path, "atlmfc", "lib", sdk_target) if not os.path.isdir(atlmfc_dir): die( "Cannot find the ATL/MFC libraries in the Visual C++ directory " "(%s). Please install them." % vc_path ) libs = [] lib_env = os.environ.get("LIB") if lib_env and not is_host: libs.extend(lib_env.split(";")) libs.extend( ( os.path.join(vc_path, "lib", sdk_target), atlmfc_dir, os.path.join(windows_sdk_dir.lib, "um", sdk_target), os.path.join(ucrt_sdk_dir.lib, "ucrt", sdk_target), ) ) if dia_sdk_lib_dir: libs.append(dia_sdk_lib_dir) return libs return lib_path @depends_if(lib_path_for(target)) @imports("os") def lib_path(libs): # Set in the environment for old-configure libs = ";".join(libs) os.environ["LIB"] = libs return libs set_config("LIB", lib_path) lib_path_for_host = lib_path_for(host) @depends_if(lib_path_for_host) @imports(_from="mozbuild.shellutil", _import="quote") def host_linker_libpaths(libs): return ["-LIBPATH:%s" % quote(l) for l in libs] @depends_if(lib_path_for_host) @imports(_from="mozbuild.shellutil", _import="quote") def host_linker_libpaths_bat(libs): # .bat files need a different style of quoting. Batch quoting is actually # not defined, and up to applications to handle, so it's not really clear # what should be escaped and what not, but most paths should work just # fine without escaping. And we don't care about double-quotes possibly # having to be escaped because they're not allowed in file names on # Windows. return ['"-LIBPATH:%s"' % l for l in libs] set_config("HOST_LINKER_LIBPATHS", host_linker_libpaths) set_config("HOST_LINKER_LIBPATHS_BAT", host_linker_libpaths_bat) @depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host) @imports(_from="os", _import="environ") def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host): if not valid_windows_sdk_dir: return vc_host = { "x86": "x86", "x86_64": "x64", }.get(host.cpu) # 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), ] if vc_host == "x64": result.append(os.path.join(valid_windows_sdk_dir.path, versioned_bin, "x86")) return result option(env="LINKER", nargs=1, when=target_is_windows, help="Path to the linker") link = check_prog( "LINKER", ("lld-link",), input="LINKER", when=target_is_windows, paths=clang_search_path, ) option(env="HOST_LINKER", nargs=1, when=host_is_windows, help="Path to the host linker") host_link = check_prog( "HOST_LINKER", ("lld-link",), input="HOST_LINKER", when=host_is_windows, paths=clang_search_path, ) add_old_configure_assignment("LINKER", link)