# -*- 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/. # Environment variables that impact the compilation step # ============================================================== option( env="HOST_CPPFLAGS", help="Extra flags for Preprocessing host sources", nargs=1, default="", ) option( env="HOST_CFLAGS", help="Extra flags for compiling host C sources", nargs=1, default="", ) option( env="HOST_CXXFLAGS", help="Extra flags for compiling host C++ sources", nargs=1, default="", ) option( env="HOST_LDFLAGS", help="Extra flags for linking host object files", nargs=1, default="", ) option( env="CPPFLAGS", help="Extra flags for preprocessing sources", nargs=1, default="", ) option( env="CFLAGS", help="Extra flags for compiling C sources", nargs=1, default="", ) option( env="CXXFLAGS", help="Extra flags for compiling C++ sources", nargs=1, default="", ) option( env="ASFLAGS", help="Extra flags for assembling sources", nargs=1, default="", ) option( env="LDFLAGS", help="Extra flags for linking object files", nargs=1, default="", ) option( env="LIBS", help="Extra libraries for linking object files", nargs=1, default="", ) option( env="MOZ_OPTIMIZE_FLAGS", help="Extra optimization flags", nargs=1, ) # Code optimization # ============================================================== option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags") @depends(target, when="MOZ_PGO") def forced_pgo_optimization_level(target): if target.kernel == "Linux" and target.os != "Android": return "-O3" @imports(_from="mozbuild.shellutil", _import="quote") def check_optimize_flags(src, flags): for flag in reversed(flags): if flag.startswith(("-O", "/O")): if flag[2:] == "0": die( f"Optimization enabled through {src} but last optimization flag is {flag} which disables optimizations" ) break else: die( f"Optimization enabled through {src} but no optimization flag found in {quote(*flags)}" ) return flags @depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS") @imports(_from="mozbuild.shellutil", _import="split") def configured_moz_optimize_flags(enable_optimize, env_flags): if len(enable_optimize): return check_optimize_flags("--enable-optimize", split(enable_optimize[0])) if len(env_flags): return check_optimize_flags("MOZ_OPTIMIZE_FLAGS", split(env_flags[0])) @depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS") def moz_optimize(enable_optimize, env_flags): return "1" if enable_optimize or env_flags else None set_config("MOZ_OPTIMIZE", moz_optimize) @depends( target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level ) @imports(_from="mozbuild.shellutil", _import="split") def moz_optimize_flags( target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level ): if configured_moz_optimize_flags: return configured_moz_optimize_flags if moz_optimize and forced_pgo_optimization_level: return [forced_pgo_optimization_level] if target.kernel == "Darwin": return ["-O3"] elif target.kernel in ("Linux", "WINNT"): return ["-O2"] else: return ["-O"] # 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.15 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.15 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.15"), help="Set the minimum MacOS version needed at runtime{|}", ) @depends_if("--enable-macos-target", developer_options) def macos_target(value, _): return value[0] @imports("plistlib") @imports(_from="__builtin__", _import="open") @imports(_from="__builtin__", _import="Exception") def get_sdk_settings(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 ) return obj @imports(_from="__builtin__", _import="Exception") def get_sdk_version_from_settings(sdk, settings): if "Version" not in settings: raise Exception( "Error finding Version information in SDKSettings.plist from the SDK: %s" % sdk ) return Version(settings["Version"]) def get_sdk_version(sdk): return get_sdk_version_from_settings(sdk, get_sdk_settings(sdk)) 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", ) def mac_sdk_min_version(): return "26.0" @depends( "--with-macos-sdk", host, bootstrap_path( "MacOSX{}.sdk".format(mac_sdk_min_version()), when=depends("--with-macos-sdk")(lambda x: not x), allow_failure=True, ), ) @imports(_from="__builtin__", _import="Exception") @imports(_from="os.path", _import="isdir") @imports(_from="os", _import="listdir") def macos_sdk(sdk, host, bootstrapped): if bootstrapped: sdk = [bootstrapped] 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 ) if version < Version(mac_sdk_min_version()): die( 'SDK version "%s" is too old. Please upgrade to at least %s. Try ' "updating your system Xcode." % (version, mac_sdk_min_version()) ) return sdk set_config("MACOS_SDK_DIR", macos_sdk) with only_when(target_is_ios): # iOS deployment target version # ============================================================== # This needs to happen before any compilation test is done. option( "--enable-ios-target", env="IPHONEOS_DEPLOYMENT_TARGET", nargs=1, default="18.4", help="Set the minimum iOS version needed at runtime", ) @depends_if("--enable-ios-target") def ios_target(value): return value[0] with only_when(target_is_ios): # iOS SDK # ======= option( "--with-ios-sdk", env="IPHONEOS_SDK_DIR", nargs=1, help="Location of platform SDK to use", ) @depends(target) def target_is_ios_simulator(target): # x86_64-apple-ios is simulator # aarch64-apple-ios is iphone # aarch64-apple-ios-sim is simulator return target.cpu == "x86_64" or target.raw_os == "ios-sim" def ios_sdk_min_version(): return "18.4" @depends(target_is_ios_simulator) def ios_sdk_name(target_is_ios_simulator): return "iPhone{}{}.sdk".format( "Simulator" if target_is_ios_simulator else "OS", ios_sdk_min_version(), ) @depends( "--with-ios-sdk", host, target, target_is_ios_simulator, bootstrap_path(ios_sdk_name, when=depends("--with-ios-sdk")(lambda x: not x)), ) @imports(_from="__builtin__", _import="Exception") @imports(_from="os.path", _import="isdir") @imports(_from="os", _import="listdir") def ios_sdk(sdk, host, target, target_is_ios_simulator, bootstrapped): if bootstrapped: sdk = [bootstrapped] sdk_name = "iphonesimulator" if target_is_ios_simulator else "iphoneos" if sdk: sdk = sdk[0] try: settings = get_sdk_settings(sdk) version = get_sdk_version_from_settings(sdk, settings) except Exception as e: die(e) elif host.os == "OSX": sdk = check_cmd_output( "xcrun", "--show-sdk-path", "--sdk", sdk_name, onerror=lambda: "" ).rstrip() if not sdk: die( "Could not find the iOS SDK. Please use --with-ios-sdk to give " "the path to a iOS 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(sdk_name): try: sdk = os.path.join(sdk_dir, d) settings = get_sdk_settings(sdk) version = get_sdk_version_from_settings(sdk, settings) versions.append((version, sdk, settings)) except Exception: pass version, sdk, settings = max(versions) else: die( "Need an iOS SDK when targeting iOS. Please use --with-ios-sdk " "to give the path to a iOS SDK." ) if not isdir(sdk): die( "SDK not found in %s. When using --with-ios-sdk, you must specify a " "valid SDK. SDKs are installed when the optional cross-development " "tools are selected during the Xcode installation." % sdk ) supported_targets = settings.get("SupportedTargets") if supported_targets: supported_archs = supported_targets.get(sdk_name, {}).get("Archs", []) cpu = { "aarch64": "arm64", }.get(target.cpu, str(target.cpu)) if cpu not in supported_archs: die("The SDK in %s does not support target %s" % (sdk, target.alias)) else: log.warning( "Cannot check whether the iOS SDK is for the right target, " "assuming it is." ) if version < Version(ios_sdk_min_version()): die( 'SDK version "%s" is too old. Please upgrade to at least %s. Try ' "updating your system Xcode." % (version, ios_sdk_min_version()) ) return sdk set_config("IPHONEOS_SDK_DIR", ios_sdk) # 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 # 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) # Compilers # ============================================================== include("compilers-util.configure") def try_preprocess( configure_cache, compiler, language, source, onerror=None, wrapper=[] ): return try_invoke_compiler( configure_cache, compiler, language, source, ["-E"], onerror, wrapper ) @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(configure_cache, 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(configure_cache, 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. `version` is compared against the version in the # "clang version string" column, and the resolved version is the # value from the "LLVM version" column. 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 < "14.3": version = Version("14.0.0") elif version < "15.0": version = Version("15.0.0") elif version < "16.0": version = Version("16.0.0") elif version < "17.0": version = Version("17.0.6") else: version = Version("19.1.4.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(configure_cache, compiler, language, target, android_version): info = get_compiler_info(configure_cache, 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 # C17 compiles as C23. c17_version = 201710 if info.language == "C" and info.language_version != c17_version: if info.type == "clang-cl": flags.append("-Xclang") flags.append("-std=gnu17") 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("-std:c++17") else: flags.append("-std=gnu++17") # Check compiler target # -------------------------------------------------------------------- has_target = False if target.os == "Android" and android_version: # This makes clang define __ANDROID_API__ and use versioned library # directories from the NDK. toolchain = "%s%d" % (target.toolchain, android_version) else: toolchain = target.toolchain 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 target.os == "iOS": target_flag = "--target=%s" % toolchain if target_flag not in compiler: flags.append(target_flag) 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" % 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" % 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" % toolchain) elif info.type == "clang": flags.append("--target=%s" % 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(_from="mozfile", _import="json") @imports("os") def get_vc_paths(host, 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)) variant = "arm64" if host.cpu == "aarch64" else "x86.x64" for install in vswhere( [ "-products", "*", "-requires", f"Microsoft.VisualStudio.Component.VC.Tools.{variant}", ] ): 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) @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( "WINSYSROOT", bootstrap_path( "vs", when=depends("WINSYSROOT", when=is_windows)(lambda x: not x), ), when=is_windows, ) def winsysroot(winsysroot, bootstrapped): if bootstrapped: return bootstrapped if winsysroot: return winsysroot[0] option( env="VC_PATH", nargs=1, when=is_windows, help="Path to the Microsoft Visual C/C++ compiler", ) @depends( host, build_environment, "VC_PATH", winsysroot, when=is_windows, ) @imports("os") @imports(_from="operator", _import="itemgetter") def vc_compiler_paths_for_version(host, env, vc_path, winsysroot): 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(host, env.topsrcdir), key=itemgetter(0)) if not all_versions: return # Choose the newest version. path = all_versions[-1][1] host_dir = { "x86_64": "Hostx64", "x86": "Hostx86", "aarch64": "Hostarm64", }.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, host, vc_compiler_paths_for_version, when=is_windows) def vc_compiler_path(target, host, paths): cpu = target.cpu if target.os == "WINNT" else host.cpu vc_target = { "x86": "x86", "x86_64": "x64", "arm": "arm", "aarch64": "arm64", }.get(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 @depends_if(vc_compiler_path, when=is_windows) def vc_compiler_version(vc_compiler_path): version = Version( os.path.basename( os.path.dirname(os.path.dirname(os.path.dirname(vc_compiler_path[0]))) ) ) # MSVC path with version 14.x is actually version 19.x if version.major == 14: return Version(f"19.{version.minor}") @depends_if(vc_compiler_version) def msvs_version(vc_compiler_version): # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to # be set for GYP on Windows. if vc_compiler_version >= Version("19.30"): return "2022" configure_error("Only Visual Studio 2022 or newer are supported") return "" set_config("MSVS_VERSION", msvs_version) with only_when(target_is_windows): @depends(target) def dxc_task_name(target): return "dxc-" + target.cpu + "-pc-windows-msvc" @depends_if(bootstrap_path(dxc_task_name)) def dxc_dll_path(bootstrap): return os.path.join(bootstrap, "dxcompiler.dll") clang_search_path = bootstrap_search_path("clang/bin") @depends( bootstrap_path("rustc/bin", when=moz_automation), original_path, ) @imports("os") @imports(_from="os", _import="environ") def rust_search_path(rust_path, original_path): result = [] # The order we search for a rust compiler is: # - bootstrap directory (only used by CI at the moment) # - rustup install directory ($CARGO_HOME/bin) # - $PATH 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 rust_path: result.append(rust_path) result.append(rustup_path) result.extend(original_path) return result # 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, clang_search_path, rust_search_path, target, original_path ) @imports("os") def altered_path( mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path ): altered_path = mozillabuild_bin_paths if target.kernel == "Darwin": # The rust compiler wants to execute dsymutil, but it does so in a # non-configurable way (https://github.com/rust-lang/rust/issues/52728) # so we add the clang path. path = clang_search_path else: path = original_path # cargo needs the rust search path to find cargo-$subcommand. path += rust_search_path for p in path: 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) @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": if host_or_target.abi: if host_or_target.abi == "msvc": supported = types = ("clang-cl",) elif host_or_target.abi == "mingw": supported = types = ("clang",) else: 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, ios_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, ios_sdk, path): version = None if sysroot_input: path = sysroot_input[0] elif host_or_target.os == "OSX" and macos_sdk: path = macos_sdk elif host_or_target.os == "iOS" and ios_sdk: path = ios_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, when=kwargs.get("when"), ) 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("10.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( configure_cache, compiler, provided_compiler, compiler_wrapper, host_or_target, sysroot, macos_target, ios_target, android_version, vc_compiler_version, 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( configure_cache, compiler, provided_compiler, compiler_wrapper, host_or_target, sysroot, macos_target, ios_target, android_version, vc_compiler_version, 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 provided_compiler: wrapper.extend(provided_compiler.wrapper) flags.extend(provided_compiler.flags) info = check_compiler( configure_cache, wrapper + [compiler] + flags, language, host_or_target, android_version, ) if host_or_target.os == "OSX" and macos_target: flags.append("-mmacosx-version-min=%s" % macos_target) if host_or_target.os == "iOS" and ios_target: flags.append("-mios-version-min=%s" % ios_target) # When not given an explicit compatibility version, clang-cl tries # to get one from MSVC, which might not even be the one used by the # build. And when it can't find one, its default might also not match # what the build is using. So if we were able to figure out the version # we're building with, explicitly use that. # This also means that, as a side effect, clang-cl will not try to find # MSVC, which saves a little overhead. if info.type == "clang-cl" and vc_compiler_version: flags.append(f"-fms-compatibility-version={vc_compiler_version}") if info.type == "clang" and language == "C++" and host_or_target.os == "OSX": flags.append("-stdlib=libc++") # 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( configure_cache, wrapper + [compiler] + flags, language, host_or_target, android_version, ) 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 winsysroot and host.os != "WINNT": overlay = os.path.join(winsysroot, "overlay.yaml") if os.path.exists(overlay): flags.extend(["-Xclang", "-ivfsoverlay", "-Xclang", overlay]) if (info.type, host_or_target.abi) in ( ("clang", "msvc"), ("clang-cl", "mingw"), ): raise FatalCheckError("Unknown compiler or compiler not supported.") # If you want to bump the version check here ensure the version # is known for Xcode in get_compiler_info. # This version can be no higher than the version used in # src/bootstrap/src/core/build_steps/llvm.rs's check_llvm_version in the source # code of the minimum supported Rust version. if info.type in ("clang", "clang-cl") and info.version < "17.0": raise FatalCheckError( "Only %s 17.0 or newer is supported (found version %s)." % (("clang/llvm" if info.type == "clang" else "clang-cl"), info.version) ) if host_or_target.kernel == "WASI" and info.type != "clang": raise FatalCheckError( "Only clang is supported for %s" % host_or_target.alias ) if host_or_target.os == "WINNT" and info.type == "gcc": raise FatalCheckError( "Firefox cannot be built with mingw-gcc and requires a mingw-clang toolchain to work." ) if info.flags: raise FatalCheckError("Unknown compiler or compiler not supported.") 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 # This excludes WASM_CC from the list. if var in ("CC", "CXX", "HOST_CC", "HOST_CXX"): # FIXME: we should return a plain list here. @depends_if(valid_compiler) @imports(_from="mozbuild.shellutil", _import="quote") def value(x): return quote(*x.wrapper, x.compiler, *x.flags) set_config(var, value) # 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) 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) 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, ) @template def windows_abi(host_or_target, c_compiler): @depends(host_or_target) def windows_abi(host_or_target): if host_or_target.os == "WINNT": return host_or_target.abi @depends(host_or_target, windows_abi) def need_windows_abi_from_compiler(host_or_target, windows_abi): return host_or_target.os == "WINNT" and windows_abi is None @depends(host_or_target, c_compiler, when=need_windows_abi_from_compiler) def windows_abi_from_compiler(host_or_target, c_compiler): if host_or_target.os == "WINNT": if c_compiler.type == "clang-cl": return "msvc" return "mingw" return windows_abi | windows_abi_from_compiler target_windows_abi = windows_abi(target, c_compiler) host_windows_abi = windows_abi(host, host_c_compiler) # Generic compiler-based conditions. building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc") building_with_gnu_compatible_cc = depends(c_compiler)( lambda info: info.type != "clang-cl" ) @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) # 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) # 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 lld-link 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 if present and the compiler is clang. Otherwise gold # is used if present otherwise, whatever the compiler uses by default. # - on release/official builds: whatever the compiler uses by default, except when the # compiler is clang, in which case lld is preferred when it's new enough. @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") option( "--enable-linker", nargs=1, help="Select the linker {bfd, gold, ld64, lld, lld-*, mold}", 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, stdcxx_compat, when=is_linker_option_enabled, ) host_or_target_str = "target" else: deps = depends( dependable(None), host_c_compiler, developer_options, dependable(None), host, stdcxx_compat, 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, stdcxx_compat ): 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 = None if linker: result = try_linker(linker) if result is None: die("Could not use {} as linker".format(linker)) if ( result is None and c_compiler.type == "clang" and ( ( target.kernel != "Darwin" and (developer_options or c_compiler.version >= "15.0") ) or ( target.kernel == "Darwin" and ( (developer_options and c_compiler.version >= "13.0") or c_compiler.version >= "15.0" ) ) ) ): result = try_linker("lld") if result is None and developer_options and not stdcxx_compat: result = try_linker("gold") if result is None: result = try_linker(None) if result is None: die("Failed to find an adequate linker") if stdcxx_compat and result.KIND == "gold": die("--enable-stdcxx-compat is not compatible with the gold 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) @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_sysroot, android_version, c_compiler, developer_options, ) else: deps = depends_if( select_linker_tmpl(host), host, host_sysroot, host_multiarch_dir, dependable(None), dependable(None), host_c_compiler, developer_options, ) @deps @imports("os") def linker_ldflags( linker, target, sysroot, multiarch_dir, android_sysroot, android_version, 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_sysroot: # BFD/gold linkers need a manual --rpath-link for indirect # dependencies. flags += [ "-Wl,--rpath-link={}/usr/lib/{}".format( android_sysroot, target.toolchain ), "-Wl,--rpath-link={}/usr/lib/{}/{}".format( android_sysroot, target.toolchain, android_version ), ] 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) 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) include("compile-checks.configure") include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm")) # Libstdc++ feature detection # ============================================================== using_msvc_stl = depends(cxx_compiler)(lambda c: c.type == "clang-cl") using_msvc_stl_202503_or_newer = try_compile( language="C++", includes=["new"], body="#if !defined(_MSVC_STL_UPDATE) || _MSVC_STL_UPDATE < 202503L\n#error 1\n#endif", when=using_msvc_stl, ) using_libstdcxx = try_compile( language="C++", includes=["new"], body="#ifndef __GLIBCXX__\n#error 1\n#endif", when=~using_msvc_stl, ) using_libcxx = try_compile( language="C++", includes=["new"], body="#ifndef _LIBCPP_VERSION\n#error 1\n#endif", when=~using_libstdcxx, ) using_libcxx_19_or_newer = try_compile( language="C++", includes=["new"], body="#if _LIBCPP_VERSION < 190000\n#error 1\n#endif", when=using_libcxx, ) @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) @imports(_from="mozbuild.shellutil", _import="split") 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 split(enable_debug_flags[0]) if env_debug_flags: return split(env_debug_flags[0]) return default_debug_flags set_config("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 in ("Darwin", "FreeBSD", "OpenBSD"): 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) # try harder, when checking for __thread support, see bug 521750 comment #33 and below # We pass linker_optimize_flags to the linker because if dead_strip is # enabled, the linker in xcode 4.1 will crash. Without this it would crash when # linking XUL. @depends(target, c_compiler) def check_thread(target, c_compiler): if target.cpu in ("mips32", "mips64"): # mips builds fail with TLS variables because of a binutils bug. # See bug 528687 return False if target.os == "Android": # The custom dynamic linker doesn't support TLS variables return False if target.kernel == "OpenBSD": # OpenBSD doesn't have TLS support, and the test succeeds with clang++ return False return c_compiler.type != "clang-cl" set_define( "HAVE_THREAD_TLS_KEYWORD", try_link( body="static __thread bool tlsIsMainThread = false; return tlsIsMainThread;", flags=linker_optimize_flags.ldflags, check_msg="for __thread keyword for TLS variables", when=check_thread, ), ) @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) # 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")), ) # TARGET_XPCOM_ABI # ============================================================== is_arm_eabi = c_compiler.try_compile( body=""" #if defined(__ARM_EABI__) return 0; #else #error Not ARM EABI. #endif""", check_msg="for ARM EABI", when=building_with_gnu_compatible_cc & depends(target.cpu)(lambda cpu: cpu == "arm"), ) @depends(target, is_arm_eabi, c_compiler) def target_xpcom_abi(target, is_arm_eabi, compiler): if compiler.type == "clang-cl": return f"{target.cpu}-msvc" elif target.cpu == "arm": target_compiler_abi = "eabi" if is_arm_eabi else "oabi" return f"{target.cpu}-{target_compiler_abi}-gcc3" else: return f"{target.cpu}-gcc3" set_config("TARGET_XPCOM_ABI", target_xpcom_abi) set_define("TARGET_XPCOM_ABI", depends(target_xpcom_abi)(lambda v: f'"{v}"')) # ASAN # ============================================================== option("--enable-address-sanitizer", help="Enable Address Sanitizer") @depends(when="--enable-address-sanitizer") def asan(): return True with only_when(asan): option( env="MOZ_CLANG_RT_ASAN_LIB_PATH", nargs=1, help="Path to clang runtime asan library", ) @depends( c_compiler, target, "MOZ_CLANG_RT_ASAN_LIB_PATH", ) @imports("os") @imports("glob") def clang_rt_asan_lib_path(c_compiler, target, clang_rt_asan_lib): if clang_rt_asan_lib: if os.path.exists(clang_rt_asan_lib[0]): return clang_rt_asan_lib[0] else: die( f"Specified MOZ_CLANG_RT_ASAN_LIB_PATH value '{clang_rt_asan_lib}' doesn't exist. " ) # Look for the ASan runtime binary if c_compiler.type == "clang-cl": cpu = {"x86": "i386"}.get(target.cpu, target.cpu) clang_rt_asan_lib = f"clang_rt.asan_dynamic-{cpu}.dll" subdir = "windows" elif target.os == "Android": cpu = {"x86": "i686"}.get(target.cpu, target.cpu) clang_rt_asan_lib = f"libclang_rt.asan-{cpu}-android.so" subdir = "linux" else: return search_path = os.path.join( os.path.dirname(c_compiler.compiler), "..", "lib", "clang", "*", "lib", subdir, clang_rt_asan_lib, ) if candidates := glob.glob(search_path): return candidates[0] die( f"Couldn't find {clang_rt_asan_lib}. " f"It should be available in the same location as {c_compiler.type}." ) set_config("MOZ_CLANG_RT_ASAN_LIB_PATH", clang_rt_asan_lib_path) @depends( c_compiler, target, compilation_flags, linker_flags, build_environment, when=asan, ) def asan_flags(c_compiler, target, compilation_flags, linker_flags, build_env): if c_compiler.type == "clang-cl": # Suppressing errors in recompiled code. if target.os == "WINNT": flag = f"-fsanitize-blacklist={build_env.topsrcdir}/build/sanitizers/asan_blacklist_win.txt" compilation_flags.cflags.append(flag) compilation_flags.cxxflags.append(flag) asan_flag = "-fsanitize=address" compilation_flags.cflags.append(asan_flag) compilation_flags.cxxflags.append(asan_flag) if c_compiler.type != "clang-cl": linker_flags.ldflags.extend([asan_flag, "-rdynamic"]) set_define("MOZ_ASAN", True, when=asan) set_config("MOZ_ASAN", True, when=asan) # MSAN # ============================================================== option("--enable-memory-sanitizer", help="Enable Memory Sanitizer") @depends(when="--enable-memory-sanitizer") def msan(): return True @depends(c_compiler, compilation_flags, linker_flags, when=msan) def msan_flags(c_compiler, compilation_flags, linker_flags): flags = ["-fsanitize=memory", "-fsanitize-memory-track-origins"] compilation_flags.cflags.extend(flags) compilation_flags.cxxflags.extend(flags) if c_compiler.type != "clang-cl": linker_flags.ldflags.extend(flags + ["-rdynamic"]) set_define("MOZ_MSAN", True, when=msan) set_config("MOZ_MSAN", True, when=msan) # TSAN # ============================================================== option("--enable-thread-sanitizer", help="Enable Thread Sanitizer") @depends(when="--enable-thread-sanitizer") def tsan(): return True @depends(c_compiler, compilation_flags, linker_flags, when=tsan) def tsan_flags(c_compiler, compilation_flags, linker_flags): flag = "-fsanitize=thread" compilation_flags.cflags.append(flag) compilation_flags.cxxflags.append(flag) if c_compiler.type != "clang-cl": linker_flags.ldflags.extend(["-fsanitize=thread", "-rdynamic"]) set_define("MOZ_TSAN", True, when=tsan) set_config("MOZ_TSAN", True, when=tsan) # UBSAN # ============================================================== option( "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer" ) @depends("--enable-undefined-sanitizer", moz_optimize) def ubsan(options, optimize): if not options: return default_checks = [ "bool", "bounds", "enum", "function", "integer-divide-by-zero", "pointer-overflow", "return", "vla-bound", ] # adding object-size generates a warning if -O0 is set if optimize: default_checks.append("object-size") checks = options if len(options) else default_checks return checks @depends( ubsan, c_compiler, compilation_flags, linker_flags, build_environment, when=ubsan ) @imports(_from="__builtin__", _import="open") @imports(_from="glob", _import="glob") @imports("shutil") def ubsan_flags(ubsan_checks, c_compiler, compilation_flags, linker_flags, build_env): ubsan_txt = os.path.join(build_env.topobjdir, "ubsan_blacklist.txt") with open(ubsan_txt, "w") as out_fd: for blacklist in glob( os.path.join( build_env.topsrcdir, "build", "sanitizers", "ubsan_*_blacklist.txt" ) ): with open(blacklist) as in_fd: shutil.copyfileobj(in_fd, out_fd) joined_ubsan_checks = ",".join(ubsan_checks) flags = [ f"-fsanitize={joined_ubsan_checks}", f"-fno-sanitize-recover={joined_ubsan_checks}", f"-fsanitize-blacklist={ubsan_txt}", ] compilation_flags.cflags.extend(flags) compilation_flags.cxxflags.extend(flags) if c_compiler.type != "clang-cl": linker_flags.ldflags.extend(["-fsanitize=undefined", "-rdynamic"]) 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 @depends( c_compiler, compilation_flags, linker_flags, build_environment, when=ub_signed_overflow_san, ) def ub_signed_overflow_san_flags( c_compiler, compilation_flags, linker_flags, build_env ): sanitizer_blacklist = os.path.join( build_env.topsrcdir, "build", "sanitizers", "ubsan_signed_overflow_blacklist.txt", ) flags = [ f"-fsanitize=signed-integer-overflow", f"-fsanitize-blacklist={sanitizer_blacklist}", ] compilation_flags.cflags.extend(flags) compilation_flags.cxxflags.extend(flags) if c_compiler.type != "clang-cl": linker_flags.ldflags.extend(["-fsanitize=signed-integer-overflow", "-rdynamic"]) 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 @depends( c_compiler, compilation_flags, linker_flags, build_environment, when=ub_unsigned_overflow_san, ) def ub_unsigned_overflow_san_flags( c_compiler, compilation_flags, linker_flags, build_env ): sanitizer_blacklist = os.path.join( build_env.topsrcdir, "build", "sanitizers", "ubsan_unsigned_overflow_blacklist.txt", ) flags = [ f"-fsanitize=unsigned-integer-overflow", f"-fsanitize-blacklist={sanitizer_blacklist}", ] compilation_flags.cflags.extend(flags) compilation_flags.cxxflags.extend(flags) if c_compiler.type != "clang-cl": linker_flags.ldflags.extend( ["-fsanitize=unsigned-integer-overflow", "-rdynamic"] ) # any_ubsan = ubsan | ub_signed_overflow_san | ub_unsigned_overflow_san set_define("MOZ_UBSAN", True, when=any_ubsan) set_config("MOZ_UBSAN", any_ubsan) # We only want to include windows.configure when we are compiling on # Windows, or for Windows. include("windows.configure", when=is_windows) # 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") # 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", "OpenBSD") 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) if (c_compiler.type == "clang" and c_compiler.version >= "16") or ( c_compiler.type == "gcc" and c_compiler.version >= "13" ): # Cannot use level 3 because we have many uses of the [0] GNU syntax. # Cannot use level 2 because sqlite3 and icu use the [1] GNU syntax. flags.append("-fstrict-flex-arrays=1") # 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, disable FORTIFY_SOURCE just to be safe if asan: flags.append("-D_FORTIFY_SOURCE=0") # 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, ) option( "--enable-stl-hardening", default=moz_debug, help="{Enable|Disable} C++ STL hardening", ) @depends( "--enable-stl-hardening", moz_debug, using_msvc_stl_202503_or_newer, using_libstdcxx, using_libcxx, using_libcxx_19_or_newer, ) def stl_hardening_flags( enabled, debug, using_msvc_stl_202503_or_newer, using_libstdcxx, using_libcxx, using_libcxx_19_or_newer, ): if not enabled: return if using_msvc_stl_202503_or_newer: return ["-D_MSVC_STL_HARDENING=1"] if using_libstdcxx: return ["-D_GLIBCXX_ASSERTIONS=1"] if using_libcxx: if using_libcxx_19_or_newer: if debug: return ["-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG"] else: return ["-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE"] else: return ["-D_LIBCPP_ENABLE_ASSERTIONS=1"] if enabled.origin != "default": die("C++ STL does not support hardening") set_config("MOZ_STL_HARDENING_FLAGS", stl_hardening_flags) # 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, 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) # Stack unwinding without frame pointers # ============================================================== have_unwind = check_symbol( "_Unwind_Backtrace", when=check_header("unwind.h", when=target_is_unix) ) # 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) @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)", ) set_config("ENABLE_CLANG_PLUGIN", True, when="--enable-clang-plugin") set_define("MOZ_CLANG_PLUGIN", True, when="--enable-clang-plugin") @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, ) @template def llvm_tool(name): @depends(host_c_compiler, c_compiler, bindgen_config_paths) def llvm_tool(host_c_compiler, c_compiler, bindgen_config_paths): 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 and bindgen_config_paths: clang = bindgen_config_paths.clang_path tool = name if clang: out = check_cmd_output( clang, "--print-prog-name=%s" % tool, onerror=lambda: None ) if out: tool = out.rstrip() return (tool,) return llvm_tool llvm_objdump = check_prog( "LLVM_OBJDUMP", llvm_tool("llvm-objdump"), what="llvm-objdump", when="--enable-compile-environment", paths=clang_search_path, ) # Force clang-cl compiler to treat input as C++ # ============================================================== add_flag("-TP", cxx_compiler, when=target_is_windows & ~building_with_gnu_compatible_cc) # Use the old libstdc++ ABI # ============================================================== 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, ) # Always included configuration file # ============================================================== @depends(c_compiler, build_environment, build_project) def defines_cpp_flags(c_compiler, build_environment, build_project): if build_project == "js": config_h = "js/src/js-confdefs.h" else: config_h = "mozilla-config.h" if c_compiler.type == "clang-cl": flag = "-FI" else: flag = "-include" return ["-DMOZILLA_CLIENT", flag, f"{build_environment.topobjdir}/{config_h}"] set_config("OS_COMPILE_CFLAGS", defines_cpp_flags) set_config("OS_COMPILE_CXXFLAGS", defines_cpp_flags) # 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", enable_snapshot_fuzzing) def enable_fuzzing(value, snapshot_fuzzing): if value or snapshot_fuzzing: 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) set_config("AFLFUZZ", enable_aflfuzzer) set_define("AFLFUZZ", enable_aflfuzzer) set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces) set_define("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, ) @depends(libfuzzer_flags, when=enable_libfuzzer) def sancov(libfuzzer_flags): return any( flag.startswith(head) for head in ("-fsanitize-coverage", "-fsanitize=fuzzer") for flag in libfuzzer_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) # Required for stand-alone (sanitizer-less) libFuzzer. # ============================================================== @depends(libfuzzer_flags, linker_flags, when=enable_libfuzzer) def add_libfuzzer_flags(libfuzzer_flags, linker_flags): linker_flags.ldflags.extend(libfuzzer_flags.use_flags) linker_flags.ldflags.append("-rdynamic") # The LLVM symbolizer is used by all sanitizers check_prog( "LLVM_SYMBOLIZER", ("llvm-symbolizer",), allow_missing=True, paths=clang_search_path, when=asan | msan | tsan | any_ubsan | enable_fuzzing, ) # 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 = ["$(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) target_ar = check_prog("AR", target_ar_config.names, paths=clang_search_path) set_config("AR_FLAGS", target_ar_config.flags) @depends(c_compiler, extra_toolchain_flags, target_ar, target_ar_config) @checking("whether ar supports response files") @imports("os") @imports(_from="__builtin__", _import="FileNotFoundError") @imports(_from="__builtin__", _import="open") @imports(_from="mozbuild.configure.util", _import="LineIO") def ar_supports_response_files(c_compiler, extra_toolchain_flags, ar, ar_config): lib_path = list_path = None with create_temporary_file(suffix=".o") as obj_path: if ( try_invoke_compiler( # No configure_cache because it would not create the # expected output file. None, [c_compiler.compiler] + c_compiler.flags, c_compiler.language, "void foo() {}", ["-c", "-o", obj_path] + (extra_toolchain_flags or []), wrapper=c_compiler.wrapper, onerror=lambda: None, ) is not None ): with create_temporary_file(suffix=".list") as list_path: with open(list_path, "w") as fd: fd.write(obj_path) log.debug("Creating `%s` with content:", list_path) log.debug("| %s", obj_path) with create_temporary_file(suffix=".a") as lib_path: os.remove(lib_path) ar_command = ( [ar] + [x.replace("$@", lib_path) for x in ar_config.flags] + ["@" + list_path] ) result = check_cmd_output(*ar_command, onerror=lambda: None) return result is not None set_config("AR_SUPPORTS_RESPONSE_FILE", True, when=ar_supports_response_files) host_ar_config = ar_config(host_c_compiler) 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) # We don't use it in the code, but it can be useful for debugging, so give # the user the option of enabling it. option("--enable-cpp-rtti", help="Enable C++ RTTI") @depends(compilation_flags, c_compiler, "--enable-cpp-rtti") def enable_cpp_rtti(compilation_flags, c_compiler, enable_rtti): if enable_rtti: return if c_compiler.type == "clang-cl": compilation_flags.cxxflags.append("-GR-") else: compilation_flags.cxxflags.append("-fno-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 @depends(target) def is_intel_target(target): return target.cpu in ("x86", "x86_64") @depends(target) def is_aarch64_target(target): return target.cpu == "aarch64" set_config("MMX_FLAGS", ["-mmmx"]) set_config("SSE_FLAGS", ["-msse"]) set_config("SSE2_FLAGS", ["-msse2"]) set_config("SSSE3_FLAGS", ["-mssse3"]) set_config("SSE4_2_FLAGS", ["-msse4.2"]) set_config("FMA_FLAGS", ["-mfma"]) set_config("AVX2_FLAGS", ["-mavx2"]) set_config( "AVXVNNI_FLAGS", ["-mavxvnni"], try_compile( check_msg="for -mavxvnni support", flags=["-mavxvnni"], when=is_intel_target ), ) set_config( "AVX512BW_FLAGS", ["-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], try_compile( check_msg="for -mavx512bw support", flags=["-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], when=is_intel_target, ), ) # AVX512VNNI can be based on either avx512bw or avx512vbmi. We choose the # former. set_config( "AVX512VNNI_FLAGS", ["-mavx512vnni", "-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], try_compile( check_msg="for -mavx512vnni support", flags=["-mavx512vnni", "-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"], when=is_intel_target, ), ) set_config( "NEON_I8MM_FLAGS", ["-march=armv8.2-a+i8mm"], try_compile( check_msg="for i8mm target feature", flags=["-march=armv8.2-a+i8mm"], when=is_aarch64_target, ), ) set_config( "SVE2_FLAGS", ["-march=armv9-a+sve2"], try_compile( check_msg="for ARM SVE2 target feature", flags=["-march=armv9-a+sve2"], when=is_aarch64_target, ), ) set_config( "DOTPROD_FLAGS", ["-march=armv8.2-a+dotprod"], try_compile( check_msg="for ARM dotprod target feature", flags=["-march=armv8.2-a+dotprod"], when=is_aarch64_target, ), ) # dtrace support ## option("--enable-dtrace", help="Build with dtrace support") dtrace = check_header( "sys/sdt.h", when="--enable-dtrace", onerror=lambda: die("dtrace enabled but sys/sdt.h not found"), ) set_config("HAVE_DTRACE", True, when=dtrace) set_define("INCLUDE_MOZILLA_DTRACE", True, when=dtrace)