Source code

Revision control

Copy as Markdown

Other Tools

# 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="AR_FLAGS",
help="Extra flags for creating static archives",
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="mozshellutil", _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="mozshellutil", _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="mozshellutil", _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)
@depends(target, host)
def is_windows(target, host):
return host.kernel == "WINNT" or target.kernel == "WINNT"
include("windows-toolchain.configure", when=is_windows)
@depends(android_toolchain_flags, windows_toolchain_flags)
def extra_toolchain_flags(android_toolchain_flags,
windows_toolchain_flags):
return (android_toolchain_flags or []) + (windows_toolchain_flags or [])
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.5"
@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"
set_config("IPHONEOS_IS_SIMULATOR", target_is_ios_simulator)
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, wrapper=[]):
"""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, wrapper=wrapper
)
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"):
# 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="mozshellutil", _import="quote")
@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
def check_compiler(
configure_cache, compiler, language, target, android_version, wrapper=[]
):
info = get_compiler_info(configure_cache, compiler, language, wrapper)
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")
cxx20_version = 202002
if info.language == "C++" and info.language_version != cxx20_version:
if info.type == "clang-cl":
flags.append("-std:c++20")
else:
flags.append("-std=gnu++20")
# gcc 10 defines C++20's __cplusplus as 201709L instead of 202002L.
# Redefine as C++20's final vesion for gcc 10 so modern C++20 code's
# __cplusplus version checks work as expected.
if info.type == "gcc" and info.version < Version("11.0.0"):
flags.append("-U__cplusplus")
flags.append("-D__cplusplus=202002L")
# 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)
elif target.kernel == "WASI" and info.type == "clang" and info.version >= Version("22.1"):
# The wasm32-wasi target was renamed to wasm32-wasip1 in LLVM 22.1.
toolchain = "wasm32-wasip1"
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
# 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,
)
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(
build_environment,
mozillabuild_bin_paths,
clang_search_path,
rust_search_path,
target,
original_path,
)
@imports("os")
@imports(_from="mach.util", _import="get_virtualenv_base_dir")
def altered_path(
build_env,
mozillabuild_bin_paths,
clang_search_path,
rust_search_path,
target,
original_path,
):
# Trim trailing empty entries so a stray `;;` in the input doesn't
# surface as a blank PATH entry.
def strip_trailing_empty_entries(path):
path = list(path)
while path and not path[-1]:
path.pop()
return path
if target.kernel == "Darwin":
# The rust compiler wants to execute dsymutil, but it does so in a
# so we add the clang path.
path = strip_trailing_empty_entries(clang_search_path)
else:
path = strip_trailing_empty_entries(original_path)
# cargo needs the rust search path to find cargo-$subcommand.
path.extend(strip_trailing_empty_entries(rust_search_path))
# Bug 2040992: Strip mach virtualenv entries so any prepended venvs
# (from intermediate ./mach activations along the build chain) don't
# end up in the captured PATH. This is safe because the build never
# invokes anything in those dirs.
venv_prefix = normalize_path_list_entry(
get_virtualenv_base_dir(build_env.topsrcdir)
)
seen = set()
altered_path = []
for p in mozillabuild_bin_paths + path:
normalized = normalize_path_list_entry(p)
if normalized in seen:
continue
if normalized.startswith(venv_prefix):
continue
seen.add(normalized)
altered_path.append(normalized)
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,
)
# Distinguish ccache from sccache and buildcache.
@depends_if(ccache)
def ccache_is_sccache(ccache):
return check_cmd_output(ccache, "--version").startswith("sccache")
@depends_if(ccache)
def ccache_is_buildcache(ccache):
return check_cmd_output(ccache, "--version").lower().startswith("buildcache")
@depends(ccache, ccache_is_sccache, ccache_is_buildcache)
def using_ccache(ccache, ccache_is_sccache, ccache_is_buildcache):
return ccache and not ccache_is_sccache and not ccache_is_buildcache
@depends_if(ccache, ccache_is_sccache)
def using_sccache(ccache, ccache_is_sccache):
return ccache and ccache_is_sccache
@depends_if(ccache, ccache_is_buildcache)
def using_buildcache(ccache, ccache_is_buildcache):
return ccache and ccache_is_buildcache
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)
set_config("MOZ_USING_BUILDCACHE", using_buildcache)
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(when=using_sccache)
def sccache_max_frame_length():
return "500000000"
option(
env="SCCACHE_CLIENT_SIDE",
choices=("true", "false"),
default="true",
help="Whether to enable sccache client side mode",
)
@depends("SCCACHE_CLIENT_SIDE", using_sccache)
def sccache_client_side(client_side, using_sccache):
if using_sccache:
return client_side[0]
# The base directories are handled further below, where the sysroot and SDK
# paths they contain are known.
option(
env="SCCACHE_BASEDIRS",
nargs=1,
help="Base directories sccache rewrites to relative paths for cache keys",
)
@depends("--with-compiler-wrapper", ccache)
@imports(_from="mozshellutil", _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)
set_config("COMPILER_WRAPPER", compiler_wrapper)
@dependable
def wasm():
return split_triplet("wasm32-wasi", allow_wasi=True)
@template
def default_c_compilers(host_or_target, other_c_compiler=None):
"""Template defining the set of default C compilers for the host and
target platforms.
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure.
`other_c_compiler` is the `target` C compiler when `host_or_target` is `host`.
"""
assert host_or_target in {host, target, wasm}
other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)
@depends(host_or_target, target, toolchain_prefix, *other_c_compiler)
def default_c_compilers(
host_or_target, target, toolchain_prefix, *other_c_compiler
):
if host_or_target.kernel == "WINNT":
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 == "clang-cl"
and "clang-cl" not in supported
and "clang" in supported
):
dir_path = os.path.dirname(info.compiler)
basename = os.path.basename(info.compiler)
clang_name = basename.replace("clang-cl", "clang")
info = namespace(compiler=os.path.join(dir_path, clang_name), type="clang")
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="mozshellutil", _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++ headers 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)
@depends(
build_environment,
target_sysroot.path,
valid_windows_sdk_dir,
vc_path,
when=using_sccache,
)
@imports("os")
def default_sccache_basedirs(build_env, sysroot_path, windows_sdk_dir, vc_path):
# sccache rewrites paths under these base directories to be relative
# before computing cache keys, so builds in different worktrees (or in
# automation versus locally) hit the same cache entries instead of
# missing on differing absolute paths. These are the same directories as
# the path remapping below: the object and source directories, plus the
# sysroot and SDKs.
basedirs = [build_env.topobjdir, build_env.topsrcdir]
if sysroot_path:
basedirs.append(sysroot_path)
if windows_sdk_dir:
basedirs.append(windows_sdk_dir.path)
if vc_path:
basedirs.append(vc_path)
return os.pathsep.join(basedirs)
@depends("SCCACHE_BASEDIRS", default_sccache_basedirs, using_sccache)
def sccache_basedirs(basedirs, default_basedirs, using_sccache):
if not using_sccache:
return
# Don't override a value the developer set explicitly.
return basedirs[0] if basedirs else default_basedirs
@template
def sccache_config(**kwargs):
"""Set the given configuration items, and return a @depends function that
puts them in the environment. sccache reads its configuration from the
environment, and its server only does so when it starts, which can be from
one of the compiler checks below, so the variables need to be set before
that happens."""
for name, value in kwargs.items():
set_config(name, value)
@depends(*kwargs.values())
@imports("os")
def sccache_environment(*values):
for name, value in zip(kwargs, values):
if value:
os.environ[name] = value
return sccache_environment
sccache_environment = sccache_config(
SCCACHE_BASEDIRS=sccache_basedirs,
SCCACHE_CLIENT_SIDE=sccache_client_side,
SCCACHE_MAX_FRAME_LENGTH=sccache_max_frame_length,
)
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,
sccache_environment,
)
@checking("whether %s can be used" % what, lambda x: bool(x))
@imports(_from="mozshellutil", _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,
sccache_environment,
):
# This is only a dependency for its side effect of setting the sccache
# variables in the environment, which needs to happen before the
# compiler wrapper is invoked. The statement below is only there to
# appease configure's lint, which rejects unused dependencies.
sccache_environment
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,
[compiler] + flags,
language,
host_or_target,
android_version,
wrapper=wrapper,
)
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,
[compiler] + flags,
language,
host_or_target,
android_version,
wrapper=wrapper,
)
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 < "19.0":
raise FatalCheckError(
"Only %s 19.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"):
set_config(
var,
depends_if(valid_compiler)(
lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)
),
)
# 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,
)
# 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"
)
# 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([]),
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 + 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=["version"],
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=["version"],
body="#ifndef __GLIBCXX__\n#error 1\n#endif",
when=~using_msvc_stl,
)
using_libcxx = try_compile(
language="C++",
includes=["version"],
body="#ifndef _LIBCPP_VERSION\n#error 1\n#endif",
when=~using_libstdcxx,
)
using_libcxx_19_or_newer = try_compile(
language="C++",
includes=["version"],
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.
flags = ("-gdwarf-4",)
if compiler_info.type == "clang":
# This improves debuginfo size.
flags += (
"-gsimple-template-names",
# clang will warn about this argument not being used in some compiler
# invocations that don't involve codegen (like clang-plugin checks or
# linking), and we don't have a great way of conditionally skipping it...
# context about this flag.
"--start-no-unused-arguments",
"-mllvm=-dwarf-linkage-names=Abstract",
"--end-no-unused-arguments",
)
return flags
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="mozshellutil", _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. "<x>=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)
# TODO: remove this once we target C++23, where clang always define it, see bug 1880762
set_define("_LIBCPP_REMOVE_TRANSITIVE_INCLUDES", True, when=using_libcxx)
@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
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 sorted(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 target.os not in ("WINNT", "OSX", "OpenBSD", "iOS")
and ((c_compiler.version >= "11.0.1" and target.cpu in ("x86", "x86_64", "ppc64", "s390x"))
or
(c_compiler.version >= "18.1.0" and target.cpu == "aarch64"))
):
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
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,
)
@depends(moz_debug, target)
def want_stl_hardening(debug, target):
return debug or target.os in ("WINNT", "OSX")
option(
"--enable-stl-hardening",
default=want_stl_hardening,
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"]
# Debug builds don't ship to users, so it is fine if STL hardening is
# silently skipped, unless it has been explicitly enabled.
if debug and enabled.origin == "default":
return
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)
add_linker_flag("--coverage", when=code_coverage & building_with_gnu_compatible_cc)
# 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 + 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, "AR_FLAGS")
@imports(_from="mozshellutil", _import="split")
def ar_config(toolchain_prefix, c_compiler, env_flags):
extra_ar_flags = tuple(split(env_flags[0]))
if c_compiler.type == "clang-cl":
return namespace(
names=("llvm-lib",),
flags=("-llvmlibthin", "-out:$@") + extra_ar_flags,
)
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",) + extra_ar_flags + ("$@",),
)
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,
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
# 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,
),
)
@depends(target, c_compiler)
def htmlaccel_config(target, c_compiler):
# Keep this is sync with the mozilla::htmlaccel::htmlaccelEnabled function.
#
# The code compiles on SSSE3, but AVX+BMI generates better code
# and has been available for 12 years at the time of landing this,
# so let's give the best code to users with reasonably recent hardware.
#
# Not enabled on 32-bit x86, due to lack of insight into what hardware is
# representative at this point in time and due to lack of such hardware
# for testing to see what config would actually be an optimization.
#
# aarch64 does not need extra flags.
#
# clang-cl doesn't tolerate -flax-vector-conversions but GCC requires it.
#
# -mavx2 doesn't change codegen vs. -mavx. AVX2 and BMI always co-occur
# in Intel CPUs, but there are AMD CPUs that have AVX and BMI without
# AVX2.
if target.cpu != "x86_64":
return []
if c_compiler.type == "gcc":
return ["-mavx", "-mbmi", "-flax-vector-conversions"]
return ["-mavx", "-mbmi"]
set_config("HTML_ACCEL_FLAGS", htmlaccel_config)
@depends(c_compiler, compilation_flags, coverage_cflags, linker_flags)
@imports(_from="mozshellutil", _import="split", _as="shellsplit")
def extra_linker_flags(compiler, compilation_flags, coverage_cflags, linker_flags):
if compiler.type != "clang-cl":
return
with create_temporary_file(suffix=".c") as path:
# On clang-cl builds only, we run the linker via separate commands rather
# than invoking it through the compiler. We want to figure out what flags
# the compiler would add to the linker command line based on the extra
# compiler flags we use (notably, for sanitizer)
# The -### argument to clang/clang-cl tells us what commands it would run
# internally, and the last of them is going to be the linker.
# What we want to do is compare the linker commands the compiler would
# invoke with a limited set of flags vs. all of them.
# Typically, that will give us some extra flags, including extra libs for
# the clang runtime.
path_stem = os.path.basename(path)[: -len(".c")]
def is_in_out_arg(arg):
return arg.startswith("-out:") or os.path.basename(arg).startswith(
path_stem
)
cmd = [
compiler.compiler,
path,
"-fuse-ld=lld",
"-MD",
"-###",
] + compiler.flags
retcode, stdout, stderr = get_cmd_output(*cmd)
if retcode != 0:
die("Error while running %s:\n%s" % (" ".join(cmd), stderr))
linker_command = shellsplit(stderr.splitlines()[-1])
linker_args = set(arg for arg in linker_command if not is_in_out_arg(arg))
cmd += compilation_flags.cflags + (coverage_cflags or [])
retcode, stdout, stderr = get_cmd_output(*cmd)
if retcode != 0:
die("Error while running %s:\n%s" % (" ".join(cmd), stderr))
linker_flags.ldflags.extend(
arg
for arg in shellsplit(stderr.splitlines()[-1])
if not is_in_out_arg(arg) and arg not in linker_args
)