# -*- 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/. # Code optimization # ============================================================== option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags") @depends("--enable-optimize") def moz_optimize(option): flags = None if len(option): val = "2" flags = option[0] elif option: val = "1" else: val = None return namespace( optimize=val, flags=flags, ) set_config("MOZ_OPTIMIZE", moz_optimize.optimize) add_old_configure_assignment("MOZ_OPTIMIZE", moz_optimize.optimize) add_old_configure_assignment("MOZ_CONFIGURE_OPTIMIZE_FLAGS", moz_optimize.flags) # Android NDK # ============================================================== @depends("--disable-compile-environment", target) def compiling_android(compile_env, target): return compile_env and target.os == "Android" include("android-ndk.configure", when=compiling_android) with only_when(target_is_osx): # MacOS deployment target version # ============================================================== # This needs to happen before any compilation test is done. option( "--enable-macos-target", env="MACOSX_DEPLOYMENT_TARGET", nargs=1, default=depends(target, developer_options) # We continue to target 10.12 on Intel, but can target 11.0 for # aarch64 since the earliest hardware was released alongside 11.0. # For local builds, we want to target 10.12 regardless of the # underlying platform to catch any errors or warnings that wouldn't # show up when targeting 11.0, since these would later show up on # CI for Intel builds. (lambda t, d: "11.0" if (t.cpu == "aarch64" and not d) else "10.12"), help="Set the minimum MacOS version needed at runtime{|}", ) @depends_if("--enable-macos-target", developer_options) def macos_target(value, _): return value[0] with only_when(host_is_osx | target_is_osx): # MacOS SDK # ========= option( "--with-macos-sdk", env="MACOS_SDK_DIR", nargs=1, help="Location of platform SDK to use", ) @imports("plistlib") @imports(_from="__builtin__", _import="open") @imports(_from="__builtin__", _import="Exception") def get_sdk_version(sdk): with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist: obj = plistlib.load(plist) if not obj: raise Exception( "Error parsing SDKSettings.plist in the SDK directory: %s" % sdk ) if "Version" not in obj: raise Exception( "Error finding Version information in SDKSettings.plist from the SDK: %s" % sdk ) return Version(obj["Version"]) @depends("--with-macos-sdk", host) @imports(_from="__builtin__", _import="Exception") @imports(_from="os.path", _import="isdir") @imports(_from="os", _import="listdir") def macos_sdk(sdk, host): # When we change the SDK we build with, please update the manual SDK # installation docs: # https://firefox-source-docs.mozilla.org/setup/macos_build.html#macos-sdk-is-unsupported sdk_min_version = Version("11.3") if sdk: sdk = sdk[0] try: version = get_sdk_version(sdk) except Exception as e: die(e) elif host.os == "OSX": sdk = check_cmd_output( "xcrun", "--show-sdk-path", onerror=lambda: "" ).rstrip() if not sdk: die( "Could not find the macOS SDK. Please use --with-macos-sdk to give " "the path to a macOS SDK." ) # Scan the parent directory xcrun returns for the most recent SDK. sdk_dir = os.path.dirname(sdk) versions = [] for d in listdir(sdk_dir): if d.lower().startswith("macos"): try: sdk = os.path.join(sdk_dir, d) versions.append((get_sdk_version(sdk), sdk)) except Exception: pass version, sdk = max(versions) else: die( "Need a macOS SDK when targeting macOS. Please use --with-macos-sdk " "to give the path to a macOS SDK." ) if not isdir(sdk): die( "SDK not found in %s. When using --with-macos-sdk, you must specify a " "valid SDK. SDKs are installed when the optional cross-development " "tools are selected during the Xcode/Developer Tools installation." % sdk ) sdk_installation_docs_url = "https://firefox-source-docs.mozilla.org/setup/macos_build.html#macos-sdk-is-unsupported" if version < sdk_min_version: die( 'SDK version "%s" is too old. Please upgrade to at least %s. Try ' "updating your system Xcode. If that's not sufficient, see the manual " "SDK installation docs: %s" % (version, sdk_min_version, sdk_installation_docs_url) ) return sdk set_config("MACOS_SDK_DIR", macos_sdk) with only_when(target_is_osx): with only_when(cross_compiling): option( "--with-macos-private-frameworks", env="MACOS_PRIVATE_FRAMEWORKS_DIR", nargs=1, help="Location of private frameworks to use", ) @depends_if("--with-macos-private-frameworks") @imports(_from="os.path", _import="isdir") def macos_private_frameworks(value): if value and not isdir(value[0]): die( "PrivateFrameworks not found not found in %s. When using " "--with-macos-private-frameworks, you must specify a valid " "directory", value[0], ) return value[0] @depends(macos_private_frameworks, macos_sdk) def macos_private_frameworks(value, sdk): if value: return value return os.path.join(sdk or "/", "System/Library/PrivateFrameworks") set_config("MACOS_PRIVATE_FRAMEWORKS_DIR", macos_private_frameworks) # GC rooting and hazard analysis. # ============================================================== option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis") @depends("MOZ_HAZARD") def hazard_analysis(value): if value: return True set_config("MOZ_HAZARD", hazard_analysis) # Cross-compilation related things. # ============================================================== option( "--with-toolchain-prefix", env="TOOLCHAIN_PREFIX", nargs=1, help="Prefix for the target toolchain", ) @depends("--with-toolchain-prefix", host, target, cross_compiling) def toolchain_prefix(value, host, target, cross_compiling): if value: return tuple(value) # We don't want a toolchain prefix by default when building on mac for mac. if cross_compiling and not (target.os == "OSX" and host.os == "OSX"): return ("%s-" % target.toolchain, "%s-" % target.alias) @depends(toolchain_prefix, target) def first_toolchain_prefix(toolchain_prefix, target): # Pass TOOLCHAIN_PREFIX down to the build system if it was given from the # command line/environment (in which case there's only one value in the tuple), # or when cross-compiling for Android or OSX. if toolchain_prefix and ( target.os in ("Android", "OSX") or len(toolchain_prefix) == 1 ): return toolchain_prefix[0] set_config("TOOLCHAIN_PREFIX", first_toolchain_prefix) add_old_configure_assignment("TOOLCHAIN_PREFIX", first_toolchain_prefix) # Compilers # ============================================================== include("compilers-util.configure") def try_preprocess(compiler, language, source, onerror=None): return try_invoke_compiler(compiler, language, source, ["-E"], onerror) @imports(_from="mozbuild.configure.constants", _import="CompilerType") @imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks") @imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks") @imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks") @imports(_from="textwrap", _import="dedent") @imports(_from="__builtin__", _import="Exception") def get_compiler_info(compiler, language): """Returns information about the given `compiler` (command line in the form of a list or tuple), in the given `language`. The returned information includes: - the compiler type (clang-cl, clang or gcc) - the compiler version - the compiler supported language - the compiler supported language version """ # Xcode clang versions are different from the underlying llvm version (they # instead are aligned with the Xcode version). Fortunately, we can tell # apart plain clang from Xcode clang, and convert the Xcode clang version # into the more or less corresponding plain clang version. check = dedent( """\ #if defined(_MSC_VER) && defined(__clang__) && defined(_MT) %COMPILER "clang-cl" %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__ #elif defined(__clang__) %COMPILER "clang" %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__ # ifdef __apple_build_version__ %XCODE 1 # endif #elif defined(__GNUC__) && !defined(__MINGW32__) %COMPILER "gcc" %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__ #endif #if __cplusplus %cplusplus __cplusplus #elif __STDC_VERSION__ %STDC_VERSION __STDC_VERSION__ #endif """ ) # While we're doing some preprocessing, we might as well do some more # preprocessor-based tests at the same time, to check the toolchain # matches what we want. for name, preprocessor_checks in ( ("CPU", CPU_preprocessor_checks), ("KERNEL", kernel_preprocessor_checks), ("OS", OS_preprocessor_checks), ): for n, (value, condition) in enumerate(preprocessor_checks.items()): check += dedent( """\ #%(if)s %(condition)s %%%(name)s "%(value)s" """ % { "if": "elif" if n else "if", "condition": condition, "name": name, "value": value, } ) check += "#endif\n" # Also check for endianness. The advantage of living in modern times is # that all the modern compilers we support now have __BYTE_ORDER__ defined # by the preprocessor. check += dedent( """\ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ %ENDIANNESS "little" #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ %ENDIANNESS "big" #endif """ ) result = try_preprocess(compiler, language, check) if not result: raise FatalCheckError("Unknown compiler or compiler not supported.") # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may # have non-ASCII characters. Treat the output as bytearray. data = {} for line in result.splitlines(): if line.startswith("%"): k, _, v = line.partition(" ") k = k.lstrip("%") data[k] = v.replace(" ", "").lstrip('"').rstrip('"') log.debug("%s = %s", k, data[k]) try: type = CompilerType(data["COMPILER"]) except Exception: raise FatalCheckError("Unknown compiler or compiler not supported.") cplusplus = int(data.get("cplusplus", "0L").rstrip("L")) stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L")) version = data.get("VERSION") if version: version = Version(version) if data.get("XCODE"): # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions # with enough granularity for major.minor version checks further # down the line if version < "9.1": version = Version("4.0.0.or.less") elif version < "10.0": version = Version("5.0.2") elif version < "10.0.1": version = Version("6.0.1") elif version < "11.0": version = Version("7.0.0") elif version < "11.0.3": version = Version("8.0.0") elif version < "12.0": version = Version("9.0.0") elif version < "12.0.5": version = Version("10.0.0") elif version < "13.0": version = Version("11.1.0") elif version < "13.0.1": version = Version("12.0.0") elif version < "14.0": version = Version("13.0.0") elif version < "15.0": version = Version("14.0.0") else: version = Version("14.0.0.or.more") return namespace( type=type, version=version, cpu=data.get("CPU"), kernel=data.get("KERNEL"), endianness=data.get("ENDIANNESS"), os=data.get("OS"), language="C++" if cplusplus else "C", language_version=cplusplus if cplusplus else stdc_version, xcode=bool(data.get("XCODE")), ) def same_arch_different_bits(): return ( ("x86", "x86_64"), ("ppc", "ppc64"), ("sparc", "sparc64"), ) @imports(_from="mozbuild.shellutil", _import="quote") @imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks") def check_compiler(compiler, language, target): info = get_compiler_info(compiler, language) flags = [] # Check language standards # -------------------------------------------------------------------- if language != info.language: raise FatalCheckError( "`%s` is not a %s compiler." % (quote(*compiler), language) ) # Note: We do a strict version check because there sometimes are backwards # incompatible changes in the standard, and not all code that compiles as # C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for # example) if info.language == "C" and info.language_version != 199901: if info.type == "clang-cl": flags.append("-Xclang") flags.append("-std=gnu99") cxx17_version = 201703 if info.language == "C++": if info.language_version != cxx17_version: # MSVC headers include C++17 features, but don't guard them # with appropriate checks. if info.type == "clang-cl": flags.append("-Xclang") flags.append("-std=c++17") else: flags.append("-std=gnu++17") # Check compiler target # -------------------------------------------------------------------- has_target = False if info.type == "clang": # Add the target explicitly when the target is aarch64 macosx, because # the Xcode clang target is named differently, and we need to work around # https://github.com/rust-lang/rust-bindgen/issues/1871 and # https://github.com/alexcrichton/cc-rs/issues/542 so we always want # the target on the command line, even if the compiler would default to # that. if info.xcode and target.os == "OSX" and target.cpu == "aarch64": if "--target=arm64-apple-darwin" not in compiler: flags.append("--target=arm64-apple-darwin") has_target = True elif ( not info.kernel or info.kernel != target.kernel or not info.endianness or info.endianness != target.endianness ): flags.append("--target=%s" % target.toolchain) has_target = True # Add target flag when there is an OS mismatch (e.g. building for Android on # Linux). However, only do this if the target OS is in our whitelist, to # keep things the same on other platforms. elif target.os in OS_preprocessor_checks and ( not info.os or info.os != target.os ): flags.append("--target=%s" % target.toolchain) has_target = True if not has_target and (not info.cpu or info.cpu != target.cpu): same_arch = same_arch_different_bits() if (target.cpu, info.cpu) in same_arch: flags.append("-m32") elif (info.cpu, target.cpu) in same_arch: flags.append("-m64") elif info.type == "clang-cl" and target.cpu == "aarch64": flags.append("--target=%s" % target.toolchain) elif info.type == "clang": flags.append("--target=%s" % target.toolchain) return namespace( type=info.type, version=info.version, target_cpu=info.cpu, target_kernel=info.kernel, target_endianness=info.endianness, target_os=info.os, flags=flags, ) @imports(_from="__builtin__", _import="open") @imports("json") @imports("os") def get_vc_paths(topsrcdir): def vswhere(args): program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get( "PROGRAMFILES" ) if not program_files: return [] vswhere = os.path.join( program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe" ) if not os.path.exists(vswhere): return [] return json.loads(check_cmd_output(vswhere, "-format", "json", *args)) for install in vswhere( [ "-products", "*", "-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" ), "r", ) .read() .strip() ) tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version) yield (Version(install["installationVersion"]), tools_path) option( "--with-visual-studio-version", nargs=1, choices=("2017",), when=host_is_windows, help="Select a specific Visual Studio version to use", ) @depends_if("--with-visual-studio-version", when=host_is_windows) def vs_version(value): return value[0] @depends(target, host) def is_windows(target, host): return host.kernel == "WINNT" or target.kernel == "WINNT" # Calling this a sysroot is a little weird, but it's the terminology clang went # with with its -winsysroot flag. option( env="WINSYSROOT", nargs=1, when=is_windows, help='Path to a Windows "sysroot" (directory containing MSVC, SDKs)', ) @depends_if("WINSYSROOT", when=is_windows) def winsysroot(winsysroot): return winsysroot[0] option( env="VC_PATH", nargs=1, when=is_windows, help="Path to the Microsoft Visual C/C++ compiler", ) @depends( host, vs_version, build_environment, "VC_PATH", winsysroot, when=is_windows, ) @imports("os") @imports(_from="operator", _import="itemgetter") def vc_compiler_paths_for_version(host, vs_version, env, vc_path, winsysroot): if (vc_path or winsysroot) and vs_version: var = "VC_PATH" if vc_path else "WINSYSROOT" die(f"{var} and --with-visual-studio-version cannot be used together.") if winsysroot: if vc_path: die("WINSYSROOT and VC_PATH cannot be set together.") base_vc_path = os.path.join(winsysroot, "VC", "Tools", "MSVC") versions = os.listdir(base_vc_path) vc_path = [os.path.join(base_vc_path, str(max(Version(v) for v in versions)))] if vc_path: # Use an arbitrary version, it doesn't matter. all_versions = [(Version("15"), vc_path[0])] elif host.kernel != "WINNT": # Don't try to do anything when VC_PATH is not set on cross-compiles. return else: all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0)) if not all_versions: return if vs_version: vs_major_version = {"2017": 15}[vs_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_version) path = versions[0] else: # Choose the newest version. path = all_versions[-1][1] host_dir = { "x86_64": "Hostx64", "x86": "Hostx86", }.get(host.cpu) if host_dir: path = os.path.join(path, "bin", host_dir) return { "x64": [os.path.join(path, "x64")], # The cross toolchains require DLLs from the native x64 toolchain. "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")], "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")], } @depends(target, vc_compiler_paths_for_version, when=is_windows) def vc_compiler_path(target, paths): vc_target = { "x86": "x86", "x86_64": "x64", "arm": "arm", "aarch64": "arm64", }.get(target.cpu) if not paths: return return paths.get(vc_target) @depends(vc_compiler_path, original_path) @imports("os") @imports(_from="os", _import="environ") def vc_toolchain_search_path(vc_compiler_path, original_path): result = list(original_path) if vc_compiler_path: # The second item, if there is one, is necessary to have in $PATH for # Windows to load the required DLLs from there. if len(vc_compiler_path) > 1: environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:]) # The first item is where the programs are going to be result.append(vc_compiler_path[0]) return result clang_search_path = bootstrap_search_path("clang/bin") @depends( bootstrap_search_path("rustc/bin", when="MOZ_AUTOMATION"), bootstrap_search_path_order, original_path, ) @imports("os") @imports(_from="os", _import="environ") def rust_search_path(rust_path, search_order, original_path): result = list(rust_path or original_path) # Also add the rustup install directory for cargo/rustc. cargo_home = environ.get("CARGO_HOME", "") if cargo_home: cargo_home = os.path.abspath(cargo_home) else: cargo_home = os.path.expanduser(os.path.join("~", ".cargo")) rustup_path = os.path.join(cargo_home, "bin") if search_order == "prepend": result.insert(0, rustup_path) else: result.append(rustup_path) return result # As a workaround until bug 1516228 and bug 1516253 are fixed, set the PATH # variable for the build to contain the toolchain search path. # # FIXME(bug 1802573): The two bugs above are fixed, do we still need that? # # Prepend the mozilla-build msys2 path, since otherwise we can get mismatched # cygwin dll errors during configure if we get called from another msys2 # environment, see bug 1801826. @depends(mozillabuild_bin_paths, vc_toolchain_search_path) @imports("os") def altered_path(mozillabuild_bin_paths, vc_toolchain_search_path): altered_path = mozillabuild_bin_paths + list(vc_toolchain_search_path) for p in os.environ["PATH"].split(os.pathsep): if p not in altered_path: altered_path.append(p) return os.pathsep.join(altered_path) set_config("PATH", altered_path) # Compiler wrappers # ============================================================== option( "--with-compiler-wrapper", env="COMPILER_WRAPPER", nargs=1, help="Enable compiling with wrappers such as distcc and ccache", ) option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache") @depends_if("--with-ccache") def ccache(value): if len(value): return value # If --with-ccache was given without an explicit value, we default to # 'ccache'. return "ccache" ccache = check_prog( "CCACHE", progs=(), input=ccache, paths=bootstrap_search_path( "sccache", when=depends("CCACHE")(lambda c: len(c) and c[0] == "sccache") ), allow_missing=True, ) option(env="CCACHE_PREFIX", nargs=1, help="Compiler prefix to use when using ccache") ccache_prefix = depends_if("CCACHE_PREFIX")(lambda prefix: prefix[0]) set_config("CCACHE_PREFIX", ccache_prefix) # Distinguish ccache from sccache. @depends_if(ccache) def ccache_is_sccache(ccache): return check_cmd_output(ccache, "--version").startswith("sccache") @depends(ccache, ccache_is_sccache) def using_ccache(ccache, ccache_is_sccache): return ccache and not ccache_is_sccache @depends_if(ccache, ccache_is_sccache) def using_sccache(ccache, ccache_is_sccache): return ccache and ccache_is_sccache option(env="RUSTC_WRAPPER", nargs=1, help="Wrap rust compilation with given tool") @depends(ccache, ccache_is_sccache, "RUSTC_WRAPPER") @imports(_from="textwrap", _import="dedent") @imports("os") def check_sccache_version(ccache, ccache_is_sccache, rustc_wrapper): sccache_min_version = Version("0.2.13") def check_version(path): out = check_cmd_output(path, "--version") version = Version(out.rstrip().split()[-1]) if version < sccache_min_version: die( dedent( """\ sccache %s or later is required. sccache in use at %s has version %s. Please upgrade or acquire a new version with |./mach bootstrap|. """ ), sccache_min_version, path, version, ) if ccache and ccache_is_sccache: check_version(ccache) if rustc_wrapper and ( os.path.splitext(os.path.basename(rustc_wrapper[0]))[0].lower() == "sccache" ): check_version(rustc_wrapper[0]) set_config("MOZ_USING_CCACHE", using_ccache) set_config("MOZ_USING_SCCACHE", using_sccache) option(env="SCCACHE_VERBOSE_STATS", help="Print verbose sccache stats after build") @depends(using_sccache, "SCCACHE_VERBOSE_STATS") def sccache_verbose_stats(using_sccache, verbose_stats): return using_sccache and bool(verbose_stats) set_config("SCCACHE_VERBOSE_STATS", sccache_verbose_stats) @depends("--with-compiler-wrapper", ccache) @imports(_from="mozbuild.shellutil", _import="split", _as="shell_split") def compiler_wrapper(wrapper, ccache): if wrapper: raw_wrapper = wrapper[0] wrapper = shell_split(raw_wrapper) wrapper_program = find_program(wrapper[0]) if not wrapper_program: die( "Cannot find `%s` from the given compiler wrapper `%s`", wrapper[0], raw_wrapper, ) wrapper[0] = wrapper_program if ccache: if wrapper: return tuple([ccache] + wrapper) else: return (ccache,) elif wrapper: return tuple(wrapper) @depends_if(compiler_wrapper) def using_compiler_wrapper(compiler_wrapper): return True set_config("MOZ_USING_COMPILER_WRAPPER", using_compiler_wrapper) @dependable def wasm(): return split_triplet("wasm32-wasi", allow_wasi=True) @template def default_c_compilers(host_or_target, other_c_compiler=None): """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. `other_c_compiler` is the `target` C compiler when `host_or_target` is `host`. """ assert host_or_target in {host, target, wasm} other_c_compiler = () if other_c_compiler is None else (other_c_compiler,) @depends(host_or_target, target, toolchain_prefix, *other_c_compiler) def default_c_compilers( host_or_target, target, toolchain_prefix, *other_c_compiler ): if host_or_target.kernel == "WINNT": supported = types = ("clang-cl", "clang") elif host_or_target.kernel == "Darwin": types = ("clang",) supported = ("clang", "gcc") elif host_or_target.kernel == "WASI": supported = types = ("clang",) else: supported = types = ("clang", "gcc") info = other_c_compiler[0] if other_c_compiler else None if info and info.type in supported: # When getting default C compilers for the host, we prioritize the # same compiler as the target C compiler. prioritized = info.compiler if info.type == "gcc": same_arch = same_arch_different_bits() if ( target.cpu != host_or_target.cpu and (target.cpu, host_or_target.cpu) not in same_arch and (host_or_target.cpu, target.cpu) not in same_arch ): # If the target C compiler is GCC, and it can't be used with # -m32/-m64 for the host, it's probably toolchain-prefixed, # so we prioritize a raw 'gcc' instead. prioritized = info.type if target.os != "WINNT" and host_or_target.os == "WINNT": # When cross-compiling on Windows, don't prioritize. We'll fallback # to checking for clang-cl first. pass else: types = [prioritized] + [t for t in types if t != info.type] gcc = ("gcc",) if toolchain_prefix and host_or_target is target: gcc = tuple("%sgcc" % p for p in toolchain_prefix) + gcc result = [] for type in types: if type == "gcc": result.extend(gcc) else: result.append(type) return tuple(result) return default_c_compilers @template def default_cxx_compilers(c_compiler, other_c_compiler=None, other_cxx_compiler=None): """Template defining the set of default C++ compilers for the host and target platforms. `c_compiler` is the @depends function returning a Compiler instance for the desired platform. Because the build system expects the C and C++ compilers to be from the same compiler suite, we derive the default C++ compilers from the C compiler that was found if none was provided. We also factor in the target C++ compiler when getting the default host C++ compiler, using the target C++ compiler if the host and target C compilers are the same. """ assert (other_c_compiler is None) == (other_cxx_compiler is None) if other_c_compiler is not None: other_compilers = (other_c_compiler, other_cxx_compiler) else: other_compilers = () @depends(c_compiler, *other_compilers) def default_cxx_compilers(c_compiler, *other_compilers): if other_compilers: other_c_compiler, other_cxx_compiler = other_compilers if other_c_compiler.compiler == c_compiler.compiler: return (other_cxx_compiler.compiler,) dir = os.path.dirname(c_compiler.compiler) file = os.path.basename(c_compiler.compiler) if c_compiler.type == "gcc": return (os.path.join(dir, file.replace("gcc", "g++")),) if c_compiler.type == "clang": return (os.path.join(dir, file.replace("clang", "clang++")),) return (c_compiler.compiler,) return default_cxx_compilers @template def provided_program(env_var, when=None): """Template handling cases where a program can be specified either as a path or as a path with applicable arguments. """ @depends_if(env_var, when=when) @imports(_from="itertools", _import="takewhile") @imports(_from="mozbuild.shellutil", _import="split", _as="shell_split") def provided(cmd): # Assume the first dash-prefixed item (and any subsequent items) are # command-line options, the item before the dash-prefixed item is # the program we're looking for, and anything before that is a wrapper # of some kind (e.g. sccache). cmd = shell_split(cmd[0]) without_flags = list(takewhile(lambda x: not x.startswith("-"), cmd)) return namespace( wrapper=without_flags[:-1], program=without_flags[-1], flags=cmd[len(without_flags) :], ) return provided @template def sysroot(host_or_target, target_sysroot=None): assert target_sysroot or host_or_target is target bootstrap_target_when = target_is_linux_or_wasi if host_or_target is host: host_or_target_str = "host" opt = "--with-host-sysroot" env = "HOST_SYSROOT" when = depends(host)(lambda h: h.kernel == "Linux") # Only bootstrap a host sysroot when using a bootstrapped target sysroot # or when the target doesn't use a bootstrapped sysroot in the first place. @depends(when, bootstrap_target_when, target_sysroot.bootstrapped) def bootstrap_when(when, bootstrap_target_when, bootstrapped): return when and (bootstrapped or not bootstrap_target_when) else: assert host_or_target is target host_or_target_str = "target" opt = "--with-sysroot" env = "SYSROOT" when = target_is_linux_or_wasi bootstrap_when = bootstrap_target_when option( opt, env=env, nargs=1, when=when, help="Use the given sysroot directory for %s build" % host_or_target_str, ) sysroot_input = depends(opt, when=when)(lambda x: x) bootstrap_sysroot = depends(bootstrap_when, sysroot_input)( # Only bootstrap when no flag was explicitly given (either --with or --without) lambda bootstrap, input: bootstrap and not input and input.origin == "default" ) @depends( sysroot_input, host_or_target, macos_sdk, bootstrap_path( depends(host_or_target)(lambda t: "sysroot-{}".format(t.toolchain)), when=bootstrap_sysroot, ), ) @imports("os") def sysroot(sysroot_input, host_or_target, macos_sdk, path): version = None if sysroot_input: path = sysroot_input[0] elif host_or_target.kernel == "Darwin" and macos_sdk: path = macos_sdk if path: # Find the version of libstdc++ headears in the sysroot include = os.path.join(path, "usr/include/c++") if os.path.isdir(include): with os.scandir(include) as d: version = max(Version(e.name) for e in d if e.is_dir()) log.info("Using %s sysroot in %s", host_or_target_str, path) return namespace( path=path, bootstrapped=bool(path and not sysroot_input), stdcxx_version=version, ) return sysroot target_sysroot = sysroot(target) # Use `system_lib_option` instead of `option` for options that enable building # with a system library for which the development headers are not available in # the bootstrapped sysroots. @template def system_lib_option(name, *args, **kwargs): option(name, *args, **kwargs) @depends(name, target_sysroot.bootstrapped) def no_system_lib_in_sysroot(value, bootstrapped): if bootstrapped and value: die( "%s is not supported with bootstrapped sysroot. " "Drop the option, or use --without-sysroot or --disable-bootstrap", value.format(name), ) host_sysroot = sysroot(host, target_sysroot) @template def multiarch_dir(host_or_target): sysroot = { host: host_sysroot, target: target_sysroot, }[host_or_target] @depends(host_or_target, when=sysroot.path) def multiarch_dir(target): if target.cpu == "x86": # Turn e.g. i686-linux-gnu into i386-linux-gnu return target.toolchain.replace(target.raw_cpu, "i386") return target.toolchain return multiarch_dir target_multiarch_dir = multiarch_dir(target) host_multiarch_dir = multiarch_dir(host) def minimum_gcc_version(): return Version("7.1.0") @template def compiler( language, host_or_target, c_compiler=None, other_compiler=None, other_c_compiler=None, ): """Template handling the generic base checks for the compiler for the given `language` on the given platform (`host_or_target`). `host_or_target` is either `host` or `target` (the @depends functions from init.configure. When the language is 'C++', `c_compiler` is the result of the `compiler` template for the language 'C' for the same `host_or_target`. When `host_or_target` is `host`, `other_compiler` is the result of the `compiler` template for the same `language` for `target`. When `host_or_target` is `host` and the language is 'C++', `other_c_compiler` is the result of the `compiler` template for the language 'C' for `target`. """ assert host_or_target in {host, target, wasm} assert language in ("C", "C++") assert language == "C" or c_compiler is not None assert host_or_target is target or other_compiler is not None assert language == "C" or host_or_target is target or other_c_compiler is not None host_or_target_str = { host: "host", target: "target", wasm: "wasm", }[host_or_target] sysroot = { host: host_sysroot, target: target_sysroot, wasm: dependable(lambda: namespace(path=None)), }[host_or_target] multiarch_dir = { host: host_multiarch_dir, target: target_multiarch_dir, wasm: never, }[host_or_target] var = { ("C", target): "CC", ("C++", target): "CXX", ("C", host): "HOST_CC", ("C++", host): "HOST_CXX", ("C", wasm): "WASM_CC", ("C++", wasm): "WASM_CXX", }[language, host_or_target] default_compilers = { "C": lambda: default_c_compilers(host_or_target, other_compiler), "C++": lambda: default_cxx_compilers( c_compiler, other_c_compiler, other_compiler ), }[language]() what = "the %s %s compiler" % (host_or_target_str, language) option(env=var, nargs=1, help="Path to %s" % what) # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/ # HOST_CXX variables. provided_compiler = provided_program(var) # 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=provided_compiler.program, paths=clang_search_path, ) @depends( compiler, provided_compiler, compiler_wrapper, host_or_target, sysroot, macos_target, multiarch_dir, winsysroot, host, ) @checking("whether %s can be used" % what, lambda x: bool(x)) @imports(_from="mozbuild.shellutil", _import="quote") @imports("os") def valid_compiler( compiler, provided_compiler, compiler_wrapper, host_or_target, sysroot, macos_target, multiarch_dir, winsysroot, host, ): wrapper = list(compiler_wrapper or ()) flags = [] if sysroot.path: if host_or_target.kernel == "Darwin": # While --sysroot and -isysroot are roughly equivalent, when not using # -isysroot on mac, clang takes the SDKROOT environment variable into # consideration, which may be set by python and break things. flags.extend(("-isysroot", sysroot.path)) else: flags.extend(("--sysroot", sysroot.path)) if host_or_target.os == "OSX" and macos_target: flags.append("-mmacosx-version-min=%s" % macos_target) if provided_compiler: wrapper.extend(provided_compiler.wrapper) flags.extend(provided_compiler.flags) info = check_compiler(wrapper + [compiler] + flags, language, host_or_target) if info.type == "clang" and language == "C++" and host_or_target.os == "OSX": flags.append("-stdlib=libc++") # Suppress Clang Argument Warnings if info.type in ("clang", "clang-cl"): flags.append("-Qunused-arguments") # Check that the additional flags we got are enough to not require any # more flags. If we get an exception, just ignore it; it's liable to be # invalid command-line flags, which means the compiler we're checking # doesn't support those command-line flags and will fail one or more of # the checks below. try: if info.flags: flags += info.flags info = check_compiler( wrapper + [compiler] + flags, language, host_or_target ) except FatalCheckError: pass if not info.target_cpu or info.target_cpu != host_or_target.cpu: raise FatalCheckError( "%s %s compiler target CPU (%s) does not match --%s CPU (%s)" % ( host_or_target_str.capitalize(), language, info.target_cpu or "unknown", host_or_target_str, host_or_target.raw_cpu, ) ) if not info.target_kernel or (info.target_kernel != host_or_target.kernel): raise FatalCheckError( "%s %s compiler target kernel (%s) does not match --%s kernel (%s)" % ( host_or_target_str.capitalize(), language, info.target_kernel or "unknown", host_or_target_str, host_or_target.kernel, ) ) if not info.target_endianness or ( info.target_endianness != host_or_target.endianness ): raise FatalCheckError( "%s %s compiler target endianness (%s) does not match --%s " "endianness (%s)" % ( host_or_target_str.capitalize(), language, info.target_endianness or "unknown", host_or_target_str, host_or_target.endianness, ) ) # Compiler version checks # =================================================== # Check the compiler version here instead of in `compiler_version` so # that the `checking` message doesn't pretend the compiler can be used # to then bail out one line later. if info.type == "gcc": if host_or_target.os == "Android": raise FatalCheckError( "GCC is not supported on Android.\n" "Please use clang from the Android NDK instead." ) gcc_version = minimum_gcc_version() if info.version < gcc_version: raise FatalCheckError( "Only GCC %d.%d or newer is supported (found version %s)." % (gcc_version.major, gcc_version.minor, info.version) ) # Force GCC to use the C++ headers from the sysroot, and to prefer the # sysroot system headers to /usr/include. # Non-Debian GCC also doesn't look at headers in multiarch directory. if sysroot.bootstrapped and sysroot.stdcxx_version: version = sysroot.stdcxx_version for path in ( "usr/include/c++/{}".format(version), "usr/include/{}/c++/{}".format(multiarch_dir, version), "usr/include/{}".format(multiarch_dir), "usr/include", ): flags.extend(("-isystem", os.path.join(sysroot.path, path))) if info.type == "clang-cl": if info.version < "8.0.0": raise FatalCheckError( "Only clang-cl 8.0 or newer is supported (found version %s)" % info.version ) if winsysroot and host.os != "WINNT": overlay = os.path.join(winsysroot, "overlay.yaml") if os.path.exists(overlay): overlay_flags = ["-Xclang", "-ivfsoverlay", "-Xclang", overlay] if info.version >= "16.0" or ( # clang-cl 15 normally doesn't support the root-relative # overlay we use, but the bootstrapped clang-cl 15 is patched # to support it, so check we're using a patched version. info.version >= "15.0" and try_preprocess( wrapper + [compiler] + flags + overlay_flags, language, "", onerror=lambda: False, ) ): flags.extend(overlay_flags) # If you want to bump the version check here ensure the version # is known for Xcode in get_compiler_info. if info.type == "clang" and info.version < "5.0": raise FatalCheckError( "Only clang/llvm 5.0 or newer is supported (found version %s)." % info.version ) if host_or_target.kernel == "WASI": if info.type != "clang": raise FatalCheckError( "Only clang is supported for %s" % host_or_target.alias ) if info.version < "8.0": raise FatalCheckError( "Only clang/llvm 8.0 or newer is supported for %s (found version %s)." % (host_or_target.alias, info.version) ) if info.flags: raise FatalCheckError("Unknown compiler or compiler not supported.") # Recent clang defaults to compiler-rt, but we want libgcc for compat # with the older NDKs we use. if host_or_target.os == "Android": flags.append("--rtlib=libgcc") return namespace( wrapper=wrapper, compiler=compiler, flags=flags, type=info.type, version=info.version, language=language, ) @depends(valid_compiler) @checking("%s version" % what) def compiler_version(compiler): return compiler.version if language == "C++": @depends(valid_compiler, c_compiler) def valid_compiler(compiler, c_compiler): if compiler.type != c_compiler.type: die( "The %s C compiler is %s, while the %s C++ compiler is " "%s. Need to use the same compiler suite.", host_or_target_str, c_compiler.type, host_or_target_str, compiler.type, ) if compiler.version != c_compiler.version: die( "The %s C compiler is version %s, while the %s C++ " "compiler is version %s. Need to use the same compiler " "version.", host_or_target_str, c_compiler.version, host_or_target_str, compiler.version, ) return compiler # Set CC/CXX/HOST_CC/HOST_CXX for old-configure, which needs the wrapper # and the flags that were part of the user input for those variables to # be provided. add_old_configure_assignment( var, depends_if(valid_compiler)( lambda x: list(x.wrapper) + [x.compiler] + list(x.flags) ), ) if host_or_target is target: add_old_configure_assignment( "ac_cv_prog_%s" % var, depends_if(valid_compiler)( lambda x: list(x.wrapper) + [x.compiler] + list(x.flags) ), ) # We check that it works in python configure already. add_old_configure_assignment("ac_cv_prog_%s_works" % var.lower(), "yes") add_old_configure_assignment( "ac_cv_prog_%s_cross" % var.lower(), depends(cross_compiling)(lambda x: "yes" if x else "no"), ) gcc_like = depends(valid_compiler.type)( lambda x: "yes" if x in ("gcc", "clang") else "no" ) add_old_configure_assignment("ac_cv_prog_%s_g" % var.lower(), gcc_like) if language == "C": add_old_configure_assignment("ac_cv_prog_gcc", gcc_like) if language == "C++": add_old_configure_assignment("ac_cv_prog_gxx", gcc_like) # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow # old-configure to do some of its still existing checks. if language == "C": set_config("%s_TYPE" % var, valid_compiler.type) add_old_configure_assignment("%s_TYPE" % var, valid_compiler.type) set_config( "%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v)) ) valid_compiler = compiler_class(valid_compiler, host_or_target) def compiler_error(): raise FatalCheckError( "Failed compiling a simple %s source with %s" % (language, what) ) valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error) set_config("%s_BASE_FLAGS" % var, valid_compiler.flags) # Set CPP/CXXCPP for both the build system and old-configure. We don't # need to check this works for preprocessing, because we already relied # on $CC -E/$CXX -E doing preprocessing work to validate the compiler # in the first place. if host_or_target is target: pp_var = { "C": "CPP", "C++": "CXXCPP", }[language] preprocessor = depends_if(valid_compiler)( lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags) ) set_config(pp_var, preprocessor) add_old_configure_assignment(pp_var, preprocessor) if language == "C": linker_var = { target: "LD", host: "HOST_LD", }.get(host_or_target) if linker_var: @deprecated_option(env=linker_var, nargs=1) def linker(value): if value: return value[0] @depends(linker) def unused_linker(linker): if linker: log.warning( "The value of %s is not used by this build system." % linker_var ) return valid_compiler c_compiler = compiler("C", target) cxx_compiler = compiler("C++", target, c_compiler=c_compiler) host_c_compiler = compiler("C", host, other_compiler=c_compiler) host_cxx_compiler = compiler( "C++", host, c_compiler=host_c_compiler, other_compiler=cxx_compiler, other_c_compiler=c_compiler, ) # Generic compiler-based conditions. building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc") @depends(cxx_compiler, ccache_prefix) @imports("os") def cxx_is_icecream(info, ccache_prefix): if ( os.path.islink(info.compiler) and os.path.basename(os.readlink(info.compiler)) == "icecc" ): return True if ccache_prefix and os.path.basename(ccache_prefix) == "icecc": return True set_config("CXX_IS_ICECREAM", cxx_is_icecream) # Linker detection # ============================================================== # The policy is as follows: # For Windows: # - the linker is picked via the LINKER environment variable per windows.configure, # but ought to be llvm-lld in any case. # For macOS: # - the linker is lld if the clang used is >= 15 (per LLVM version, not Xcode version). # - the linker is also lld on local developer builds if the clang used is >= 13 (per LLVM # version, not Xcode version) # - otherwise the linker is ld64, either from XCode on macOS, or from cctools-ports when # cross-compiling. # For other OSes: # - on local developer builds: lld is used if present. Otherwise gold is used if present # otherwise, BFD ld is used. # - on release/official builds: whatever the compiler uses by default, except on Android # (see enable_linker_default below). Usually what the compiler uses by default is BFD # ld, except with the Android NDK compiler, where the default varies depending on the # NDK version. The default also varies by platform and clang version. # lld is not used by default on Linux and Android because it introduces layout changes # that prevent elfhack from working. See e.g. # https://bugzilla.mozilla.org/show_bug.cgi?id=1563654#c2. @template def is_not_winnt_or_sunos(host_or_target): @depends(host_or_target) def is_not_winnt_or_sunos(host_or_target): if host_or_target.kernel not in ("WINNT", "SunOS"): return True return is_not_winnt_or_sunos is_linker_option_enabled = is_not_winnt_or_sunos(target) @deprecated_option("--enable-gold", env="MOZ_FORCE_GOLD", when=is_linker_option_enabled) def enable_gold(value): if value: die("--enable-gold is deprecated, use --enable-linker=gold instead") else: die("--disable-gold is deprecated, use --enable-linker=something_else instead") @depends(target, developer_options) def enable_linker_default(target, developer_options): # Recent versions of clang default to lld when targetting Android, but we don't # want that as the default for non developer builds (see above). # So we want to force the default to whatever it was with older versions of clang, # but with caveats/workarounds: # - x86-64 gold has bugs in how it lays out .note.* sections. See bug 1573820. # - x86-32 gold has a bug when assembly files are built. See bug 1651699. # That leaves us with aarch64 and armv7, which respectively defaulted to # bfd and gold. # On developer builds, select_linker will pick lld if it's not the default. if target.os == "Android" and not developer_options: return "bfd" if target.cpu in ("x86", "x86_64", "aarch64") else "gold" option( "--enable-linker", nargs=1, help="Select the linker {bfd, gold, ld64, lld, lld-*, mold}{|}", default=enable_linker_default, when=is_linker_option_enabled, ) # No-op to enable depending on --enable-linker from default_elfhack in # toolkit/moz.configure. @depends("--enable-linker", when=is_linker_option_enabled) def enable_linker(linker): return linker @template def select_linker_tmpl(host_or_target): if host_or_target is target: deps = depends( "--enable-linker", c_compiler, developer_options, extra_toolchain_flags, target, when=is_linker_option_enabled, ) host_or_target_str = "target" else: deps = depends( dependable(None), host_c_compiler, developer_options, dependable(None), host, when=is_not_winnt_or_sunos(host_or_target), ) host_or_target_str = "host" @deps @checking(f"for {host_or_target_str} linker", lambda x: x.KIND) @imports("os") @imports("shutil") def select_linker(linker, c_compiler, developer_options, toolchain_flags, target): if linker: linker = linker[0] else: linker = None def is_valid_linker(linker): if target.kernel == "Darwin": valid_linkers = ("ld64", "lld") else: valid_linkers = ("bfd", "gold", "lld", "mold") if linker in valid_linkers: return True if "lld" in valid_linkers and linker.startswith("lld-"): return True return False if linker and not is_valid_linker(linker): # Check that we are trying to use a supported linker die("Unsupported linker " + linker) # Check the kind of linker version_check = ["-Wl,--version"] cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags def try_linker(linker): # Generate the compiler flag if linker == "ld64": linker_flag = ["-fuse-ld=ld"] elif linker: linker_flag = ["-fuse-ld=" + linker] else: linker_flag = [] cmd = cmd_base + linker_flag + version_check if toolchain_flags: cmd += toolchain_flags # ld64 doesn't have anything to print out a version. It does print out # "ld64: For information on command line options please use 'man ld'." # but that would require doing two attempts, one with --version, that # would fail, and another with --help. # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message # specific to it on stderr when it fails to process --version. env = dict(os.environ) env["LD_PRINT_OPTIONS"] = "1" # Some locales might not print out the strings we are looking for, so # ensure consistent output. env["LC_ALL"] = "C" retcode, stdout, stderr = get_cmd_output(*cmd, env=env) if retcode == 1 and "Logging ld64 options" in stderr: kind = "ld64" elif retcode != 0: return None elif "mold" in stdout: kind = "mold" elif "GNU ld" in stdout: # We are using the normal linker kind = "bfd" elif "GNU gold" in stdout: kind = "gold" elif "LLD" in stdout: kind = "lld" else: kind = "unknown" if kind == "unknown" or is_valid_linker(kind): return namespace( KIND=kind, LINKER_FLAG=linker_flag, ) result = try_linker(linker) if result is None and linker: die("Could not use {} as linker".format(linker)) if ( linker is None and target.kernel == "Darwin" and c_compiler.type == "clang" and ( (developer_options and c_compiler.version >= "13.0") or c_compiler.version >= "15.0" ) ): result = try_linker("lld") elif ( linker is None and ( developer_options or (host_or_target_str == "host" and c_compiler.type == "clang") ) and (result is None or result.KIND in ("bfd", "gold")) ): # try and use lld if available. tried = try_linker("lld") if (result is None or result.KIND != "gold") and ( tried is None or tried.KIND != "lld" ): tried = try_linker("gold") if tried is None or tried.KIND != "gold": tried = None if tried: result = tried if result is None: die("Failed to find an adequate linker") # If an explicit linker was given, error out if what we found is different. if linker and not linker.startswith(result.KIND): die("Could not use {} as linker".format(linker)) return result return select_linker select_linker = select_linker_tmpl(target) set_config("LINKER_KIND", select_linker.KIND) @template def linker_ldflags_tmpl(host_or_target): if host_or_target is target: deps = depends_if( select_linker, target, target_sysroot, target_multiarch_dir, android_platform, c_compiler, developer_options, ) else: deps = depends_if( select_linker_tmpl(host), host, host_sysroot, host_multiarch_dir, dependable(None), host_c_compiler, developer_options, ) @deps @imports("os") def linker_ldflags( linker, target, sysroot, multiarch_dir, android_platform, c_compiler, developer_options, ): flags = list((linker and linker.LINKER_FLAG) or []) # rpath-link is irrelevant to wasm, see for more info https://github.com/emscripten-core/emscripten/issues/11076. if sysroot.path and multiarch_dir and target.os != "WASI": for d in ("lib", "usr/lib"): multiarch_lib_dir = os.path.join(sysroot.path, d, multiarch_dir) if os.path.exists(multiarch_lib_dir): # Non-Debian-patched binutils linkers (both BFD and gold) don't lookup # in multi-arch directories. flags.append("-Wl,-rpath-link,%s" % multiarch_lib_dir) # GCC also needs -L. if c_compiler.type == "gcc": flags.append("-L%s" % multiarch_lib_dir) if ( c_compiler.type == "gcc" and sysroot.bootstrapped and sysroot.stdcxx_version ): flags.append( "-L{}/usr/lib/gcc/{}/{}".format( sysroot.path, multiarch_dir, sysroot.stdcxx_version ) ) if android_platform: flags.append("-L{}/usr/lib".format(android_platform)) flags.append("-Wl,--rpath-link={}/usr/lib".format(android_platform)) flags.append("--sysroot") flags.append(android_platform) if ( developer_options and linker and linker.KIND == "lld" and target.kernel != "WINNT" ): flags.append("-Wl,-O0") return flags return linker_ldflags linker_ldflags = linker_ldflags_tmpl(target) add_old_configure_assignment("LINKER_LDFLAGS", linker_ldflags) add_old_configure_assignment("HOST_LINKER_LDFLAGS", linker_ldflags_tmpl(host)) # There's a wrinkle with MinGW: linker configuration is not enabled, so # `select_linker` is never invoked. Hard-code around it. @depends(select_linker, target, c_compiler) def gcc_use_gnu_ld(select_linker, target, c_compiler): if select_linker is not None and target.kernel != "Darwin": return select_linker.KIND in ("bfd", "gold", "lld", "mold") if target.kernel == "WINNT" and c_compiler.type == "clang": return True return None # GCC_USE_GNU_LD=1 means the linker is command line compatible with GNU ld. set_config("GCC_USE_GNU_LD", gcc_use_gnu_ld) add_old_configure_assignment("GCC_USE_GNU_LD", gcc_use_gnu_ld) @depends(c_compiler) def msvs_version(info): # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to # be set for GYP on Windows. if info.type == "clang-cl": return "2017" return "" set_config("MSVS_VERSION", msvs_version) include("compile-checks.configure") include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm")) @depends( have_64_bit, try_compile( body='static_assert(sizeof(void *) == 8, "")', check_msg="for 64-bit OS" ), ) def check_have_64_bit(have_64_bit, compiler_have_64_bit): if have_64_bit != compiler_have_64_bit: configure_error( "The target compiler does not agree with configure " "about the target bitness." ) @depends(cxx_compiler, target) def needs_libstdcxx_newness_check(cxx_compiler, target): # We only have to care about this on Linux and MinGW. if cxx_compiler.type == "clang-cl": return if target.kernel not in ("Linux", "WINNT"): return if target.os == "Android": return return True def die_on_old_libstdcxx(): die( "The libstdc++ in use is not new enough. Please run " "./mach bootstrap to update your compiler, or update your system " "libstdc++ installation." ) try_compile( includes=["cstddef"], body="\n".join( [ # _GLIBCXX_RELEASE showed up in libstdc++ 7. "#if defined(__GLIBCXX__) && !defined(_GLIBCXX_RELEASE)", "# error libstdc++ not new enough", "#endif", "#if defined(_GLIBCXX_RELEASE)", "# if _GLIBCXX_RELEASE < %d" % minimum_gcc_version().major, "# error libstdc++ not new enough", "# else", " (void) 0", "# endif", "#endif", ] ), check_msg="for new enough STL headers from libstdc++", when=needs_libstdcxx_newness_check, onerror=die_on_old_libstdcxx, ) @depends(c_compiler, target) def default_debug_flags(compiler_info, target): # Debug info is ON by default. if compiler_info.type == "clang-cl": return "-Z7" elif target.kernel == "WINNT" and compiler_info.type == "clang": return "-g -gcodeview" # The oldest versions of supported compilers default to DWARF-4, but # newer versions may default to DWARF-5 or newer (e.g. clang 14), which # Valgrind doesn't support. Force-use DWARF-4. return "-gdwarf-4" option(env="MOZ_DEBUG_FLAGS", nargs=1, help="Debug compiler flags") imply_option("--enable-debug-symbols", depends_if("--enable-debug")(lambda v: v)) option( "--disable-debug-symbols", nargs="?", help="Disable debug symbols using the given compiler flags", ) set_config("MOZ_DEBUG_SYMBOLS", depends_if("--enable-debug-symbols")(lambda _: True)) @depends("MOZ_DEBUG_FLAGS", "--enable-debug-symbols", default_debug_flags) def debug_flags(env_debug_flags, enable_debug_flags, default_debug_flags): # If MOZ_DEBUG_FLAGS is set, and --enable-debug-symbols is set to a value, # --enable-debug-symbols takes precedence. Note, the value of # --enable-debug-symbols may be implied by --enable-debug. if len(enable_debug_flags): return enable_debug_flags[0] if env_debug_flags: return env_debug_flags[0] return default_debug_flags set_config("MOZ_DEBUG_FLAGS", debug_flags) add_old_configure_assignment("MOZ_DEBUG_FLAGS", debug_flags) @depends(c_compiler, host) @imports( _from="mach.logging", _import="enable_blessed", _as="_enable_ansi_escape_codes" ) def color_cflags(info, host): # We could test compiling with flags. By why incur the overhead when # color support should always be present in a specific toolchain # version? # Code for auto-adding this flag to compiler invocations needs to # determine if an existing flag isn't already present. That is likely # using exact string matching on the returned value. So if the return # value changes to e.g. "=always", exact string match may fail and # multiple color flags could be added. So examine downstream consumers # before adding flags to return values. if info.type == "gcc": return "-fdiagnostics-color" elif info.type in ["clang", "clang-cl"]: if host.os == "WINNT" and _enable_ansi_escape_codes(): return "-fcolor-diagnostics -fansi-escape-codes" else: return "-fcolor-diagnostics" else: return "" set_config("COLOR_CFLAGS", color_cflags) # Some standard library headers (notably bionic on Android) declare standard # functions (e.g. getchar()) and also #define macros for those standard # functions. libc++ deals with this by doing something like the following # (explanatory comments added): # # #ifdef FUNC # // Capture the definition of FUNC. # inline _LIBCPP_INLINE_VISIBILITY int __libcpp_FUNC(...) { return FUNC(...); } # #undef FUNC # // Use a real inline definition. # inline _LIBCPP_INLINE_VISIBILITY int FUNC(...) { return _libcpp_FUNC(...); } # #endif # # _LIBCPP_INLINE_VISIBILITY is typically defined as: # # __attribute__((__visibility__("hidden"), __always_inline__)) # # Unfortunately, this interacts badly with our system header wrappers, as the: # # #pragma GCC visibility push(default) # # that they do prior to including the actual system header is treated by the # compiler as an explicit declaration of visibility on every function declared # in the header. Therefore, when the libc++ code above is encountered, it is # as though the compiler has effectively seen: # # int FUNC(...) __attribute__((__visibility__("default"))); # int FUNC(...) __attribute__((__visibility__("hidden"))); # # and the compiler complains about the mismatched visibility declarations. # # However, libc++ will only define _LIBCPP_INLINE_VISIBILITY if there is no # existing definition. We can therefore define it to the empty string (since # we are properly managing visibility ourselves) and avoid this whole mess. # Note that we don't need to do this with gcc, as libc++ detects gcc and # effectively does the same thing we are doing here. # # _LIBCPP_ALWAYS_INLINE needs a similar workarounds, since it too declares # hidden visibility. # # _LIBCPP_HIDE_FROM_ABI is a macro in libc++ versions in NDKs >=r19. It too # declares hidden visibility, but it also declares functions as excluded from # explicit instantiation (roughly: the function can be unused in the current # compilation, but does not then trigger an actual definition of the function; # it is assumed the real definition comes from elsewhere). We need to replicate # this setup. @depends(c_compiler, target) def libcxx_override_visibility(c_compiler, target): if c_compiler.type == "clang" and target.os == "Android": return namespace( empty="", hide_from_abi="__attribute__((__exclude_from_explicit_instantiation__))", ) set_define("_LIBCPP_INLINE_VISIBILITY", libcxx_override_visibility.empty) set_define("_LIBCPP_ALWAYS_INLINE", libcxx_override_visibility.empty) set_define("_LIBCPP_HIDE_FROM_ABI", libcxx_override_visibility.hide_from_abi) @depends(target, build_environment) def visibility_flags(target, env): if target.os != "WINNT": if target.kernel == "Darwin": return ("-fvisibility=hidden", "-fvisibility-inlines-hidden") return ( "-I%s/system_wrappers" % os.path.join(env.dist), "-include", "%s/config/gcc_hidden.h" % env.topsrcdir, ) @depends(target, visibility_flags) def wrap_system_includes(target, visibility_flags): if visibility_flags and target.kernel != "Darwin": return True set_define( "HAVE_VISIBILITY_HIDDEN_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None), ) set_define( "HAVE_VISIBILITY_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None) ) set_config("WRAP_SYSTEM_INCLUDES", wrap_system_includes) set_config("VISIBILITY_FLAGS", visibility_flags) @template def depend_cflags(host_or_target_c_compiler): @depends(host_or_target_c_compiler) def depend_cflags(host_or_target_c_compiler): if host_or_target_c_compiler.type != "clang-cl": return ["-MD", "-MP", "-MF $(MDDEPDIR)/$(@F).pp"] else: # clang-cl doesn't accept the normal -MD -MP -MF options that clang # does, but the underlying cc1 binary understands how to generate # dependency files. These options are based on analyzing what the # normal clang driver sends to cc1 when given the "correct" # dependency options. return [ "-Xclang", "-MP", "-Xclang", "-dependency-file", "-Xclang", "$(MDDEPDIR)/$(@F).pp", "-Xclang", "-MT", "-Xclang", "$@", ] return depend_cflags set_config("_DEPEND_CFLAGS", depend_cflags(c_compiler)) set_config("_HOST_DEPEND_CFLAGS", depend_cflags(host_c_compiler)) @depends(c_compiler) def preprocess_option(compiler): # The uses of PREPROCESS_OPTION depend on the spacing for -o/-Fi. if compiler.type in ("gcc", "clang"): return "-E -o " else: return "-P -Fi" set_config("PREPROCESS_OPTION", preprocess_option) # We only want to include windows.configure when we are compiling on # Windows, or for Windows. include("windows.configure", when=is_windows) # On Power ISA, determine compiler flags for VMX, VSX and VSX-3. set_config( "PPC_VMX_FLAGS", ["-maltivec"], when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), ) set_config( "PPC_VSX_FLAGS", ["-mvsx"], when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), ) set_config( "PPC_VSX3_FLAGS", ["-mvsx", "-mcpu=power9"], when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), ) # ASAN # ============================================================== option("--enable-address-sanitizer", help="Enable Address Sanitizer") @depends(when="--enable-address-sanitizer") def asan(): return True add_old_configure_assignment("MOZ_ASAN", asan) # MSAN # ============================================================== option("--enable-memory-sanitizer", help="Enable Memory Sanitizer") @depends(when="--enable-memory-sanitizer") def msan(): return True add_old_configure_assignment("MOZ_MSAN", msan) # TSAN # ============================================================== option("--enable-thread-sanitizer", help="Enable Thread Sanitizer") @depends(when="--enable-thread-sanitizer") def tsan(): return True add_old_configure_assignment("MOZ_TSAN", tsan) # UBSAN # ============================================================== option( "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer" ) @depends_if("--enable-undefined-sanitizer") def ubsan(options): default_checks = [ "bool", "bounds", "enum", "function", "integer-divide-by-zero", "object-size", "pointer-overflow", "return", "vla-bound", ] checks = options if len(options) else default_checks return ",".join(checks) add_old_configure_assignment("MOZ_UBSAN_CHECKS", ubsan) option( "--enable-signed-overflow-sanitizer", help="Enable UndefinedBehavior Sanitizer (Signed Integer Overflow Parts)", ) @depends(when="--enable-signed-overflow-sanitizer") def ub_signed_overflow_san(): return True add_old_configure_assignment("MOZ_SIGNED_OVERFLOW_SANITIZE", ub_signed_overflow_san) option( "--enable-unsigned-overflow-sanitizer", help="Enable UndefinedBehavior Sanitizer (Unsigned Integer Overflow Parts)", ) @depends(when="--enable-unsigned-overflow-sanitizer") def ub_unsigned_overflow_san(): return True add_old_configure_assignment("MOZ_UNSIGNED_OVERFLOW_SANITIZE", ub_unsigned_overflow_san) # Security Hardening # ============================================================== option( "--enable-hardening", env="MOZ_SECURITY_HARDENING", help="Enables security hardening compiler options", ) # This function is a bit confusing. It adds or removes hardening flags in # three stuations: if --enable-hardening is passed; if --disable-hardening # is passed, and if no flag is passed. # # At time of this comment writing, all flags are actually added in the # default no-flag case; making --enable-hardening the same as omitting the # flag. --disable-hardening will omit the security flags. (However, not all # possible security flags will be omitted by --disable-hardening, as many are # compiler-default options we do not explicitly enable.) @depends( "--enable-hardening", "--enable-address-sanitizer", "--enable-debug", "--enable-optimize", c_compiler, target, ) def security_hardening_cflags( hardening_flag, asan, debug, optimize, c_compiler, target ): compiler_is_gccish = c_compiler.type in ("gcc", "clang") mingw_clang = c_compiler.type == "clang" and target.os == "WINNT" flags = [] ldflags = [] trivial_auto_var_init = [] # WASI compiler doesn't support security hardening cflags if target.os == "WASI": return # ---------------------------------------------------------- # If hardening is explicitly enabled, or not explicitly disabled if hardening_flag.origin == "default" or hardening_flag: # FORTIFY_SOURCE ------------------------------------ # Require optimization for FORTIFY_SOURCE. See Bug 1417452 # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398 if compiler_is_gccish and optimize and not asan: flags.append("-U_FORTIFY_SOURCE") flags.append("-D_FORTIFY_SOURCE=2") if mingw_clang: # mingw-clang needs to link in ssp which is not done by default ldflags.append("-lssp") # fstack-protector ------------------------------------ # Enable only if hardening is not disabled and ASAN is # not on as ASAN will catch the crashes for us if compiler_is_gccish and not asan: flags.append("-fstack-protector-strong") ldflags.append("-fstack-protector-strong") if ( c_compiler.type == "clang" and c_compiler.version >= "11.0.1" and target.os not in ("WINNT", "OSX") and target.cpu in ("x86", "x86_64", "ppc64", "s390x") ): flags.append("-fstack-clash-protection") ldflags.append("-fstack-clash-protection") # ftrivial-auto-var-init ------------------------------ # Initialize local variables with a 0xAA pattern in clang builds. # Linux32 fails some xpcshell tests with -ftrivial-auto-var-init linux32 = target.kernel == "Linux" and target.cpu == "x86" if ( (c_compiler.type == "clang" or c_compiler.type == "clang-cl") and c_compiler.version >= "8" and not linux32 ): if c_compiler.type == "clang-cl": trivial_auto_var_init.append("-Xclang") trivial_auto_var_init.append("-ftrivial-auto-var-init=pattern") # Always enable on debug builds. if debug: flags.extend(trivial_auto_var_init) # ASLR ------------------------------------------------ # ASLR (dynamicbase) is enabled by default in clang-cl; but the # mingw-clang build requires it to be explicitly enabled if mingw_clang: ldflags.append("-Wl,--dynamicbase") # Control Flow Guard (CFG) ---------------------------- if ( c_compiler.type == "clang-cl" and c_compiler.version >= "8" and (target.cpu != "aarch64" or c_compiler.version >= "8.0.1") ): if target.cpu == "aarch64" and c_compiler.version >= "10.0.0": # The added checks in clang 10 make arm64 builds crash. (Bug 1639318) flags.append("-guard:cf,nochecks") else: flags.append("-guard:cf") # nolongjmp is needed because clang doesn't emit the CFG tables of # setjmp return addresses https://bugs.llvm.org/show_bug.cgi?id=40057 ldflags.append("-guard:cf,nolongjmp") # ---------------------------------------------------------- # If ASAN _is_ on, undefine FORTIFY_SOURCE just to be safe if asan: flags.append("-U_FORTIFY_SOURCE") # fno-common ----------------------------------------- # Do not merge variables for ASAN; can detect some subtle bugs if asan: # clang-cl does not recognize the flag, it must be passed down to clang if c_compiler.type == "clang-cl": flags.append("-Xclang") flags.append("-fno-common") return namespace( flags=flags, ldflags=ldflags, trivial_auto_var_init=trivial_auto_var_init, ) set_config("MOZ_HARDENING_CFLAGS", security_hardening_cflags.flags) set_config("MOZ_HARDENING_LDFLAGS", security_hardening_cflags.ldflags) set_config( "MOZ_TRIVIAL_AUTO_VAR_INIT", security_hardening_cflags.trivial_auto_var_init, ) # Intel Control-flow Enforcement Technology # ============================================================== # We keep this separate from the hardening flags above, because we want to be # able to easily remove the flags in the build files for certain executables. @depends(c_compiler, target) def cet_ldflags(c_compiler, target): ldflags = [] if ( c_compiler.type == "clang-cl" and c_compiler.version >= "11" and target.cpu == "x86_64" ): ldflags.append("-CETCOMPAT") return ldflags set_config("MOZ_CETCOMPAT_LDFLAGS", cet_ldflags) # Frame pointers # ============================================================== @depends(c_compiler) def frame_pointer_flags(compiler): if compiler.type == "clang-cl": return namespace( enable=["-Oy-"], disable=["-Oy"], ) return namespace( enable=["-fno-omit-frame-pointer", "-funwind-tables"], disable=["-fomit-frame-pointer", "-funwind-tables"], ) @depends( moz_optimize.optimize, moz_debug, target, "--enable-memory-sanitizer", "--enable-address-sanitizer", "--enable-undefined-sanitizer", ) def frame_pointer_default(optimize, debug, target, msan, asan, ubsan): return bool( not optimize or debug or msan or asan or ubsan or (target.os == "WINNT" and target.cpu in ("x86", "aarch64")) or target.os == "OSX" ) option( "--enable-frame-pointers", default=frame_pointer_default, help="{Enable|Disable} frame pointers", ) @depends("--enable-frame-pointers", frame_pointer_flags) def frame_pointer_flags(enable, flags): if enable: return flags.enable return flags.disable set_config("MOZ_FRAMEPTR_FLAGS", frame_pointer_flags) # Code Coverage # ============================================================== option("--enable-coverage", env="MOZ_CODE_COVERAGE", help="Enable code coverage") @depends("--enable-coverage") def code_coverage(value): if value: return True set_config("MOZ_CODE_COVERAGE", code_coverage) set_define("MOZ_CODE_COVERAGE", code_coverage) @depends(target, c_compiler, build_environment, when=code_coverage) @imports("os") @imports("re") @imports(_from="__builtin__", _import="open") def coverage_cflags(target, c_compiler, build_env): cflags = ["--coverage"] # clang 11 no longer accepts this flag (its behavior became the default) if c_compiler.type in ("clang", "clang-cl") and c_compiler.version < "11.0.0": cflags += [ "-Xclang", "-coverage-no-function-names-in-data", ] exclude = [] if target.os == "WINNT" and c_compiler.type == "clang-cl": # VS files exclude.append("^.*[vV][sS]20[0-9]{2}.*$") # Files in fetches directory. exclude.append("^.*[\\\\/]fetches[\\\\/].*$") elif target.os == "OSX": # Files in fetches directory. exclude.append("^.*/fetches/.*$") elif target.os == "GNU": # Files in fetches directory. exclude.append("^.*/fetches/.*$") # Files in /usr/ exclude.append("^/usr/.*$") if exclude: exclude = ";".join(exclude) cflags += [ f"-fprofile-exclude-files={exclude}", ] response_file_path = os.path.join(build_env.topobjdir, "code_coverage_cflags") with open(response_file_path, "w") as f: f.write(" ".join(cflags)) return ["@{}".format(response_file_path)] set_config("COVERAGE_CFLAGS", coverage_cflags) # Assembler detection # ============================================================== option(env="AS", nargs=1, help="Path to the assembler") @depends(target, c_compiler) def as_info(target, c_compiler): if c_compiler.type == "clang-cl": ml = { "x86": "ml.exe", "x86_64": "ml64.exe", "aarch64": "armasm64.exe", }.get(target.cpu) return namespace(type="masm", names=(ml,)) # When building with anything but clang-cl, we just use the C compiler as the assembler. return namespace(type="gcc", names=(c_compiler.compiler,)) # One would expect the assembler to be specified merely as a program. But in # cases where the assembler is passed down into js/, it can be specified in # the same way as CC: a program + a list of argument flags. We might as well # permit the same behavior in general, even though it seems somewhat unusual. # So we have to do the same sort of dance as we did above with # `provided_compiler`. provided_assembler = provided_program("AS") assembler = check_prog( "_AS", input=provided_assembler.program, what="the assembler", progs=as_info.names, paths=vc_toolchain_search_path, ) @depends(as_info, assembler, provided_assembler, c_compiler) def as_with_flags(as_info, assembler, provided_assembler, c_compiler): if provided_assembler: return provided_assembler.wrapper + [assembler] + provided_assembler.flags if as_info.type == "masm": return assembler assert as_info.type == "gcc" # Need to add compiler wrappers and flags as appropriate. return c_compiler.wrapper + [assembler] + c_compiler.flags set_config("AS", as_with_flags) @depends(assembler, c_compiler, extra_toolchain_flags) @imports("subprocess") @imports(_from="os", _import="devnull") def gnu_as(assembler, c_compiler, toolchain_flags): # clang uses a compatible GNU assembler. if c_compiler.type == "clang": return True if c_compiler.type == "gcc": cmd = [assembler] + c_compiler.flags if toolchain_flags: cmd += toolchain_flags cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"] # We don't actually have to provide any input on stdin, `Popen.communicate` will # close the stdin pipe. # clang will error if it uses its integrated assembler for this target, # so handle failures gracefully. if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""): return True set_config("GNU_AS", gnu_as) add_old_configure_assignment("GNU_AS", gnu_as) @depends(as_info, target) def as_dash_c_flag(as_info, target): # armasm64 doesn't understand -c. if as_info.type == "masm" and target.cpu == "aarch64": return "" else: return "-c" set_config("AS_DASH_C_FLAG", as_dash_c_flag) @depends(as_info, target) def as_outoption(as_info, target): # The uses of ASOUTOPTION depend on the spacing for -o/-Fo. if as_info.type == "masm" and target.cpu != "aarch64": return "-Fo" return "-o " set_config("ASOUTOPTION", as_outoption) # clang plugin handling # ============================================================== option( "--enable-clang-plugin", env="ENABLE_CLANG_PLUGIN", help="Enable building with the Clang plugin (gecko specific static analyzers)", ) add_old_configure_assignment( "ENABLE_CLANG_PLUGIN", depends_if("--enable-clang-plugin")(lambda _: True) ) @depends(host_c_compiler, c_compiler, when="--enable-clang-plugin") def llvm_config(host_c_compiler, c_compiler): clang = None for compiler in (host_c_compiler, c_compiler): if compiler and compiler.type == "clang": clang = compiler.compiler break elif compiler and compiler.type == "clang-cl": clang = os.path.join(os.path.dirname(compiler.compiler), "clang") break if not clang: die("Cannot --enable-clang-plugin when not building with clang") llvm_config = "llvm-config" out = check_cmd_output(clang, "--print-prog-name=llvm-config", onerror=lambda: None) if out: llvm_config = out.rstrip() return (llvm_config,) llvm_config = check_prog( "LLVM_CONFIG", llvm_config, what="llvm-config", when="--enable-clang-plugin", paths=clang_search_path, ) add_old_configure_assignment("LLVM_CONFIG", llvm_config) option( "--enable-clang-plugin-alpha", env="ENABLE_CLANG_PLUGIN_ALPHA", help="Enable static analysis with clang-plugin alpha checks.", ) @depends("--enable-clang-plugin", "--enable-clang-plugin-alpha") def check_clang_plugin_alpha(enable_clang_plugin, enable_clang_plugin_alpha): if enable_clang_plugin_alpha: if enable_clang_plugin: return True die("Cannot enable clang-plugin alpha checkers without --enable-clang-plugin.") add_old_configure_assignment("ENABLE_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha) set_define("MOZ_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha) option( "--enable-mozsearch-plugin", env="ENABLE_MOZSEARCH_PLUGIN", help="Enable building with the mozsearch indexer plugin", ) add_old_configure_assignment( "ENABLE_MOZSEARCH_PLUGIN", depends_if("--enable-mozsearch-plugin")(lambda _: True) ) # Libstdc++ compatibility hacks # ============================================================== # @depends(target, host) def target_or_host_is_linux(target, host): return any(t.os == "GNU" and t.kernel == "Linux" for t in (target, host)) option( "--enable-stdcxx-compat", env="MOZ_STDCXX_COMPAT", help="Enable compatibility with older libstdc++", when=target_or_host_is_linux, ) @depends("--enable-stdcxx-compat", when=target_or_host_is_linux) def stdcxx_compat(value): if value: return True set_config("MOZ_STDCXX_COMPAT", True, when=stdcxx_compat) add_flag( "-D_GLIBCXX_USE_CXX11_ABI=0", cxx_compiler, when=stdcxx_compat, ) add_flag( "-D_GLIBCXX_USE_CXX11_ABI=0", host_cxx_compiler, when=stdcxx_compat, ) # Support various fuzzing options # ============================================================== option("--enable-fuzzing", help="Enable fuzzing support") @depends(build_project) def js_build(build_project): return build_project == "js" option( "--enable-js-fuzzilli", when=js_build, help="Enable fuzzilli support for the JS engine", ) option( "--enable-snapshot-fuzzing", help="Enable experimental snapshot fuzzing support", ) imply_option("--enable-fuzzing", True, when="--enable-snapshot-fuzzing") @depends("--enable-snapshot-fuzzing") def enable_snapshot_fuzzing(value): if value: return True @depends("--enable-fuzzing") def enable_fuzzing(value): if value: return True @depends("--enable-js-fuzzilli", when=js_build) def enable_js_fuzzilli(value): if value: return True @depends(enable_fuzzing, enable_snapshot_fuzzing) def check_aflfuzzer(fuzzing, snapshot_fuzzing): if fuzzing and not snapshot_fuzzing: return True @depends( try_compile( body="__AFL_COMPILER;", check_msg="for AFL compiler", when=check_aflfuzzer ) ) def enable_aflfuzzer(afl): if afl: return True @depends(enable_fuzzing, enable_aflfuzzer, enable_snapshot_fuzzing, c_compiler, target) def enable_libfuzzer(fuzzing, afl, snapshot_fuzzing, c_compiler, target): if ( fuzzing and not afl and not snapshot_fuzzing and c_compiler.type == "clang" and target.os != "Android" ): return True @depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer, enable_js_fuzzilli) def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer, enable_js_fuzzilli): if fuzzing and (afl or libfuzzer) and not enable_js_fuzzilli: return True set_config("FUZZING", enable_fuzzing) set_define("FUZZING", enable_fuzzing) set_config("LIBFUZZER", enable_libfuzzer) set_define("LIBFUZZER", enable_libfuzzer) add_old_configure_assignment("LIBFUZZER", enable_libfuzzer) set_config("AFLFUZZ", enable_aflfuzzer) set_define("AFLFUZZ", enable_aflfuzzer) set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces) set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces) add_old_configure_assignment("FUZZING_INTERFACES", enable_fuzzing_interfaces) set_config("FUZZING_JS_FUZZILLI", enable_js_fuzzilli) set_define("FUZZING_JS_FUZZILLI", enable_js_fuzzilli) set_config("FUZZING_SNAPSHOT", enable_snapshot_fuzzing) set_define("FUZZING_SNAPSHOT", enable_snapshot_fuzzing) @depends( c_compiler.try_compile( flags=["-fsanitize=fuzzer-no-link"], when=enable_fuzzing, check_msg="whether the C compiler supports -fsanitize=fuzzer-no-link", ), tsan, enable_js_fuzzilli, ) def libfuzzer_flags(value, tsan, enable_js_fuzzilli): if tsan: # With ThreadSanitizer, we should not use any libFuzzer instrumentation because # it is incompatible (e.g. there are races on global sanitizer coverage counters). # Instead we use an empty set of flags here but still build the fuzzing targets. # With this setup, we can still run files through these targets in TSan builds, # e.g. those obtained from regular fuzzing. # This code can be removed once libFuzzer has been made compatible with TSan. # # Also, this code needs to be kept in sync with certain gyp files, currently: # - dom/media/webrtc/transport/third_party/nICEr/nicer.gyp return namespace(no_link_flag_supported=False, use_flags=[]) if enable_js_fuzzilli: # Fuzzilli comes with its own trace-pc interceptors and flag requirements. no_link_flag_supported = False use_flags = ["-fsanitize-coverage=trace-pc-guard", "-g"] elif value: no_link_flag_supported = True # recommended for (and only supported by) clang >= 6 use_flags = ["-fsanitize=fuzzer-no-link"] else: no_link_flag_supported = False use_flags = ["-fsanitize-coverage=trace-pc-guard,trace-cmp"] return namespace( no_link_flag_supported=no_link_flag_supported, use_flags=use_flags, ) set_config("HAVE_LIBFUZZER_FLAG_FUZZER_NO_LINK", libfuzzer_flags.no_link_flag_supported) set_config("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags) add_old_configure_assignment("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags) # Shared library building # ============================================================== # XXX: The use of makefile constructs in these variables is awful. @depends(target, c_compiler) def make_shared_library(target, compiler): if target.os == "WINNT": if compiler.type == "gcc": return namespace( mkshlib=["$(CXX)", "$(DSO_LDOPTS)", "-o", "$@"], mkcshlib=["$(CC)", "$(DSO_LDOPTS)", "-o", "$@"], ) elif compiler.type == "clang": return namespace( mkshlib=[ "$(CXX)", "$(DSO_LDOPTS)", "-Wl,-pdb,$(LINK_PDBFILE)", "-o", "$@", ], mkcshlib=[ "$(CC)", "$(DSO_LDOPTS)", "-Wl,-pdb,$(LINK_PDBFILE)", "-o", "$@", ], ) else: linker = [ "$(LINKER)", "-NOLOGO", "-DLL", "-OUT:$@", "-PDB:$(LINK_PDBFILE)", "$(DSO_LDOPTS)", ] return namespace( mkshlib=linker, mkcshlib=linker, ) cc = ["$(CC)", "$(COMPUTED_C_LDFLAGS)"] cxx = ["$(CXX)", "$(COMPUTED_CXX_LDFLAGS)"] flags = ["$(PGO_CFLAGS)", "$(DSO_LDOPTS)"] output = ["-o", "$@"] if target.kernel == "Darwin": soname = [] elif target.os == "NetBSD": soname = ["-Wl,-soname,$(DSO_SONAME)"] else: assert compiler.type in ("gcc", "clang") soname = ["-Wl,-h,$(DSO_SONAME)"] return namespace( mkshlib=cxx + flags + soname + output, mkcshlib=cc + flags + soname + output, ) set_config("MKSHLIB", make_shared_library.mkshlib) set_config("MKCSHLIB", make_shared_library.mkcshlib) @depends(c_compiler, toolchain_prefix, when=target_is_windows) def rc_names(c_compiler, toolchain_prefix): if c_compiler.type in ("gcc", "clang"): return tuple("%s%s" % (p, "windres") for p in ("",) + (toolchain_prefix or ())) return ("llvm-rc",) check_prog("RC", rc_names, paths=clang_search_path, when=target_is_windows) @template def ar_config(c_compiler, toolchain_prefix=None): if not toolchain_prefix: toolchain_prefix = dependable(None) @depends(toolchain_prefix, c_compiler) def ar_config(toolchain_prefix, c_compiler): if c_compiler.type == "clang-cl": return namespace( names=("llvm-lib",), flags=("-llvmlibthin", "-out:$@"), ) names = tuple("%s%s" % (p, "ar") for p in (toolchain_prefix or ()) + ("",)) if c_compiler.type == "clang": # Get the llvm-ar path as per the output from clang --print-prog-name=llvm-ar # so that we directly get the one under the clang directory, rather than one # that might be in /usr/bin and that might point to one from a different version # of clang. out = check_cmd_output( c_compiler.compiler, "--print-prog-name=llvm-ar", onerror=lambda: None ) llvm_ar = out.rstrip() if out else "llvm-ar" names = (llvm_ar,) + names return namespace( names=names, flags=("crs", "$@"), ) return ar_config target_ar_config = ar_config(c_compiler, toolchain_prefix) ar = check_prog("AR", target_ar_config.names, paths=clang_search_path) add_old_configure_assignment("AR", ar) set_config("AR_FLAGS", target_ar_config.flags) host_ar_config = ar_config(host_c_compiler) host_ar = check_prog("HOST_AR", host_ar_config.names, paths=clang_search_path) @depends(toolchain_prefix, c_compiler) def nm_names(toolchain_prefix, c_compiler): names = tuple("%s%s" % (p, "nm") for p in (toolchain_prefix or ()) + ("",)) if c_compiler.type == "clang": # Get the llvm-nm path as per the output from clang --print-prog-name=llvm-nm # so that we directly get the one under the clang directory, rather than one # that might be in /usr/bin and that might point to one from a different version # of clang. out = check_cmd_output( c_compiler.compiler, "--print-prog-name=llvm-nm", onerror=lambda: None ) llvm_nm = out.rstrip() if out else "llvm-nm" names = (llvm_nm,) + names return names check_prog("NM", nm_names, paths=clang_search_path, when=target_has_linux_kernel) option("--enable-cpp-rtti", help="Enable C++ RTTI") add_old_configure_assignment("_MOZ_USE_RTTI", "1", when="--enable-cpp-rtti") option( "--enable-path-remapping", nargs="*", choices=("c", "rust"), help="Enable remapping source and object paths in compiled outputs.", ) @depends("--enable-path-remapping") def path_remapping(value): if len(value): return value if bool(value): return ["c", "rust"] return [] @depends( target, build_environment, target_sysroot.path, valid_windows_sdk_dir, vc_path, when="--enable-path-remapping", ) def path_remappings(target, build_env, sysroot_path, windows_sdk_dir, vc_path): win = target.kernel == "WINNT" # The prefix maps are processed in the order they're specified on the # command line. Therefore, to accommodate object directories in the source # directory, it's important that we map the topobjdir before the topsrcdir, # 'cuz we might have /src/obj/=/o/ and /src/=/s/. The various other # directories might be subdirectories of topsrcdir as well, so they come # earlier still. path_remappings = [] # We will have only one sysroot or SDK, so all can have the same mnemonic: K # for "kit" (since S is taken for "source"). See # https://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html # for how to use the Windows `subst` command to map these in debuggers and # IDEs. if sysroot_path: path_remappings.append((sysroot_path, "k:/" if win else "/sysroot/")) if windows_sdk_dir: path_remappings.append( (windows_sdk_dir.path, "k:/" if win else "/windows_sdk/") ) if vc_path: path_remappings.append((vc_path, "v:/" if win else "/vc/")) path_remappings += [ (build_env.topobjdir, "o:/" if win else "/topobjdir/"), (build_env.topsrcdir, "s:/" if win else "/topsrcdir/"), ] path_remappings = [ (normsep(old).rstrip("/") + "/", new) for old, new in path_remappings ] # It is tempting to sort these, but we want the order to be the same across # machines so that we can share cache hits. Therefore we reject bad # configurations rather than trying to make the configuration good. for i in range(len(path_remappings) - 1): p = path_remappings[i][0] for q, _ in path_remappings[i + 1 :]: if q.startswith(p): die(f"Cannot remap paths because {p} is an ancestor of {q}") return path_remappings set_config("MMX_FLAGS", ["-mmmx"]) set_config("SSE_FLAGS", ["-msse"]) set_config("SSE2_FLAGS", ["-msse2"]) set_config("SSSE3_FLAGS", ["-mssse3"])