Source code

Revision control

Copy as Markdown

Other Tools

# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Environment variables that impact the compilation step
# ==============================================================
option(
env="HOST_CPPFLAGS",
help="Extra flags for Preprocessing host sources.",
nargs=1,
default="",
)
option(
env="HOST_CFLAGS",
help="Extra flags for compiling host C sources.",
nargs=1,
default="",
)
option(
env="HOST_CXXFLAGS",
help="Extra flags for compiling host C++ sources.",
nargs=1,
default="",
)
option(
env="HOST_LDFLAGS",
help="Extra flags for linking host object files.",
nargs=1,
default="",
)
option(
env="CPPFLAGS",
help="Extra flags for preprocessing sources.",
nargs=1,
default="",
)
option(
env="CFLAGS",
help="Extra flags for compiling C sources.",
nargs=1,
default="",
)
option(
env="CXXFLAGS",
help="Extra flags for compiling C++ sources.",
nargs=1,
default="",
)
option(
env="ASFLAGS",
help="Extra flags for assembling sources.",
nargs=1,
default="",
)
option(
env="LDFLAGS",
help="Extra flags for linking object files.",
nargs=1,
default="",
)
option(
env="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"
@depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS")
@imports(_from="mozbuild.shellutil", _import="split")
def configured_moz_optimize_flags(enable_optimize, env_flags):
if len(enable_optimize):
return split(enable_optimize[0])
if len(env_flags):
return split(env_flags[0])
@depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS")
def moz_optimize(enable_optimize, env_flags):
return "1" if enable_optimize or env_flags else None
set_config("MOZ_OPTIMIZE", moz_optimize)
@depends(
target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level
)
@imports(_from="mozbuild.shellutil", _import="split")
def moz_optimize_flags(
target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level
):
if configured_moz_optimize_flags:
return configured_moz_optimize_flags
if moz_optimize and forced_pgo_optimization_level:
return [forced_pgo_optimization_level]
if target.kernel == "Darwin":
return ["-O3"]
elif target.kernel in ("Linux", "WINNT"):
return ["-O2"]
else:
return ["-O"]
# Android NDK
# ==============================================================
@depends("--disable-compile-environment", target)
def compiling_android(compile_env, target):
return compile_env and target.os == "Android"
include("android-ndk.configure", when=compiling_android)
with only_when(target_is_osx):
# MacOS deployment target version
# ==============================================================
# This needs to happen before any compilation test is done.
option(
"--enable-macos-target",
env="MACOSX_DEPLOYMENT_TARGET",
nargs=1,
default=depends(target, developer_options)
# We continue to target 10.15 on Intel, but can target 11.0 for
# aarch64 since the earliest hardware was released alongside 11.0.
# For local builds, we want to target 10.15 regardless of the
# underlying platform to catch any errors or warnings that wouldn't
# show up when targeting 11.0, since these would later show up on
# CI for Intel builds.
(lambda t, d: "11.0" if (t.cpu == "aarch64" and not d) else "10.15"),
help="Set the minimum MacOS version needed at runtime{|}",
)
@depends_if("--enable-macos-target", developer_options)
def macos_target(value, _):
return value[0]
@imports("plistlib")
@imports(_from="__builtin__", _import="open")
@imports(_from="__builtin__", _import="Exception")
def get_sdk_version(sdk):
with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist:
obj = plistlib.load(plist)
if not obj:
raise Exception(
"Error parsing SDKSettings.plist in the SDK directory: %s" % sdk
)
if "Version" not in obj:
raise Exception(
"Error finding Version information in SDKSettings.plist from the SDK: %s"
% sdk
)
return Version(obj["Version"])
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 "14.4"
@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_osx):
with only_when(cross_compiling):
option(
"--with-macos-private-frameworks",
env="MACOS_PRIVATE_FRAMEWORKS_DIR",
nargs=1,
help="Location of private frameworks to use",
)
@depends_if("--with-macos-private-frameworks")
@imports(_from="os.path", _import="isdir")
def macos_private_frameworks(value):
if value and not isdir(value[0]):
die(
"PrivateFrameworks not found not found in %s. When using "
"--with-macos-private-frameworks, you must specify a valid "
"directory",
value[0],
)
return value[0]
@depends(macos_private_frameworks, macos_sdk)
def macos_private_frameworks(value, sdk):
if value:
return value
return os.path.join(sdk or "/", "System/Library/PrivateFrameworks")
set_config("MACOS_PRIVATE_FRAMEWORKS_DIR", macos_private_frameworks)
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="17.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):
# MacOS SDK
# =========
option(
"--with-ios-sdk",
env="IPHONEOS_SDK_DIR",
nargs=1,
help="Location of platform SDK to use",
)
def ios_sdk_min_version():
return "17.4"
@depends(target)
def ios_sdk_name(target):
return "iPhone{}{}.sdk".format(
"Simulator" if target.raw_os == "ios-sim" else "OS",
ios_sdk_min_version(),
)
@depends(
"--with-ios-sdk",
host,
target,
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, 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_name = "iphonesimulator" if target.raw_os == "ios-sim" else "iphoneos"
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)
versions.append((get_sdk_version(sdk), sdk))
except Exception:
pass
version, sdk = 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
)
if version < Version(ios_sdk_min_version()):
die(
'SDK version "%s" is too old. Please upgrade to at least %s. Try '
"updating your system Xcode." % (version, ios_sdk_min_version())
)
return sdk
set_config("IPHONEOS_SDK_DIR", ios_sdk)
# GC rooting and hazard analysis.
# ==============================================================
option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis")
@depends("MOZ_HAZARD")
def hazard_analysis(value):
if value:
return True
# Cross-compilation related things.
# ==============================================================
option(
"--with-toolchain-prefix",
env="TOOLCHAIN_PREFIX",
nargs=1,
help="Prefix for the target toolchain",
)
@depends("--with-toolchain-prefix", host, target, cross_compiling)
def toolchain_prefix(value, host, target, cross_compiling):
if value:
return tuple(value)
# We don't want a toolchain prefix by default when building on mac for mac.
if cross_compiling and not (target.os == "OSX" and host.os == "OSX"):
return ("%s-" % target.toolchain, "%s-" % target.alias)
# Compilers
# ==============================================================
include("compilers-util.configure")
def try_preprocess(
configure_cache, compiler, language, source, onerror=None, wrapper=[]
):
return try_invoke_compiler(
configure_cache, compiler, language, source, ["-E"], onerror, wrapper
)
@imports(_from="mozbuild.configure.constants", _import="CompilerType")
@imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks")
@imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks")
@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
@imports(_from="textwrap", _import="dedent")
@imports(_from="__builtin__", _import="Exception")
def get_compiler_info(configure_cache, compiler, language):
"""Returns information about the given `compiler` (command line in the
form of a list or tuple), in the given `language`.
The returned information includes:
- the compiler type (clang-cl, clang or gcc)
- the compiler version
- the compiler supported language
- the compiler supported language version
"""
# Xcode clang versions are different from the underlying llvm version (they
# instead are aligned with the Xcode version). Fortunately, we can tell
# apart plain clang from Xcode clang, and convert the Xcode clang version
# into the more or less corresponding plain clang version.
check = dedent(
"""\
#if defined(_MSC_VER) && defined(__clang__) && defined(_MT)
%COMPILER "clang-cl"
%VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
#elif defined(__clang__)
%COMPILER "clang"
%VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
# ifdef __apple_build_version__
%XCODE 1
# endif
#elif defined(__GNUC__) && !defined(__MINGW32__)
%COMPILER "gcc"
%VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
#endif
#if __cplusplus
%cplusplus __cplusplus
#elif __STDC_VERSION__
%STDC_VERSION __STDC_VERSION__
#endif
"""
)
# While we're doing some preprocessing, we might as well do some more
# preprocessor-based tests at the same time, to check the toolchain
# matches what we want.
for name, preprocessor_checks in (
("CPU", CPU_preprocessor_checks),
("KERNEL", kernel_preprocessor_checks),
("OS", OS_preprocessor_checks),
):
for n, (value, condition) in enumerate(preprocessor_checks.items()):
check += dedent(
"""\
#%(if)s %(condition)s
%%%(name)s "%(value)s"
"""
% {
"if": "elif" if n else "if",
"condition": condition,
"name": name,
"value": value,
}
)
check += "#endif\n"
# Also check for endianness. The advantage of living in modern times is
# that all the modern compilers we support now have __BYTE_ORDER__ defined
# by the preprocessor.
check += dedent(
"""\
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
%ENDIANNESS "little"
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
%ENDIANNESS "big"
#endif
"""
)
result = try_preprocess(configure_cache, compiler, language, check)
if not result:
raise FatalCheckError("Unknown compiler or compiler not supported.")
# Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
# have non-ASCII characters. Treat the output as bytearray.
data = {}
for line in result.splitlines():
if line.startswith("%"):
k, _, v = line.partition(" ")
k = k.lstrip("%")
data[k] = v.replace(" ", "").lstrip('"').rstrip('"')
log.debug("%s = %s", k, data[k])
try:
type = CompilerType(data["COMPILER"])
except Exception:
raise FatalCheckError("Unknown compiler or compiler not supported.")
cplusplus = int(data.get("cplusplus", "0L").rstrip("L"))
stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L"))
version = data.get("VERSION")
if version:
version = Version(version)
if data.get("XCODE"):
# with enough granularity for major.minor version checks further
# down the line
if version < "9.1":
version = Version("4.0.0.or.less")
elif version < "10.0":
version = Version("5.0.2")
elif version < "10.0.1":
version = Version("6.0.1")
elif version < "11.0":
version = Version("7.0.0")
elif version < "11.0.3":
version = Version("8.0.0")
elif version < "12.0":
version = Version("9.0.0")
elif version < "12.0.5":
version = Version("10.0.0")
elif version < "13.0":
version = Version("11.1.0")
elif version < "13.0.1":
version = Version("12.0.0")
elif version < "14.0":
version = Version("13.0.0")
elif version < "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")
else:
version = Version("17.0.6.or.more")
return namespace(
type=type,
version=version,
cpu=data.get("CPU"),
kernel=data.get("KERNEL"),
endianness=data.get("ENDIANNESS"),
os=data.get("OS"),
language="C++" if cplusplus else "C",
language_version=cplusplus if cplusplus else stdc_version,
xcode=bool(data.get("XCODE")),
)
def same_arch_different_bits():
return (
("x86", "x86_64"),
("ppc", "ppc64"),
("sparc", "sparc64"),
)
@imports(_from="mozbuild.shellutil", _import="quote")
@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
def check_compiler(configure_cache, compiler, language, target, android_version):
info = get_compiler_info(configure_cache, compiler, language)
flags = []
# Check language standards
# --------------------------------------------------------------------
if language != info.language:
raise FatalCheckError(
"`%s` is not a %s compiler." % (quote(*compiler), language)
)
# Note: We do a strict version check because there sometimes are backwards
# incompatible changes in the standard, and not all code that compiles as
# C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for
# example)
if info.language == "C" and info.language_version != 199901:
if info.type == "clang-cl":
flags.append("-Xclang")
flags.append("-std=gnu99")
cxx17_version = 201703
if info.language == "C++":
if info.language_version != cxx17_version:
# MSVC headers include C++17 features, but don't guard them
# with appropriate checks.
if info.type == "clang-cl":
flags.append("-std:c++17")
else:
flags.append("-std=gnu++17")
# Check compiler target
# --------------------------------------------------------------------
has_target = False
if target.os == "Android" and android_version:
# This makes clang define __ANDROID_API__ and use versioned library
# directories from the NDK.
toolchain = "%s%d" % (target.toolchain, android_version)
else:
toolchain = target.toolchain
if info.type == "clang":
# Add the target explicitly when the target is aarch64 macosx, because
# the Xcode clang target is named differently, and we need to work around
# the target on the command line, even if the compiler would default to
# that.
if info.xcode and target.os == "OSX" and target.cpu == "aarch64":
if "--target=arm64-apple-darwin" not in compiler:
flags.append("--target=arm64-apple-darwin")
has_target = True
elif target.os == "iOS":
target_flag = "--target=%s" % toolchain
if target_flag not in compiler:
flags.append(target_flag)
has_target = True
elif (
not info.kernel
or info.kernel != target.kernel
or not info.endianness
or info.endianness != target.endianness
):
flags.append("--target=%s" % toolchain)
has_target = True
# Add target flag when there is an OS mismatch (e.g. building for Android on
# Linux). However, only do this if the target OS is in our whitelist, to
# keep things the same on other platforms.
elif target.os in OS_preprocessor_checks and (
not info.os or info.os != target.os
):
flags.append("--target=%s" % toolchain)
has_target = True
if not has_target and (not info.cpu or info.cpu != target.cpu):
same_arch = same_arch_different_bits()
if (target.cpu, info.cpu) in same_arch:
flags.append("-m32")
elif (info.cpu, target.cpu) in same_arch:
flags.append("-m64")
elif info.type == "clang-cl" and target.cpu == "aarch64":
flags.append("--target=%s" % toolchain)
elif info.type == "clang":
flags.append("--target=%s" % toolchain)
return namespace(
type=info.type,
version=info.version,
target_cpu=info.cpu,
target_kernel=info.kernel,
target_endianness=info.endianness,
target_os=info.os,
flags=flags,
)
@imports(_from="__builtin__", _import="open")
@imports("json")
@imports("os")
def get_vc_paths(host, topsrcdir):
def vswhere(args):
program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get(
"PROGRAMFILES"
)
if not program_files:
return []
vswhere = os.path.join(
program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe"
)
if not os.path.exists(vswhere):
return []
return json.loads(check_cmd_output(vswhere, "-format", "json", *args))
variant = "arm64" if host.cpu == "aarch64" else "x86.x64"
for install in vswhere(
[
"-products",
"*",
"-requires",
f"Microsoft.VisualStudio.Component.VC.Tools.{variant}",
]
):
path = install["installationPath"]
tools_version = (
open(
os.path.join(
path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
),
"r",
)
.read()
.strip()
)
tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version)
yield (Version(install["installationVersion"]), tools_path)
@depends(target, host)
def is_windows(target, host):
return host.kernel == "WINNT" or target.kernel == "WINNT"
# Calling this a sysroot is a little weird, but it's the terminology clang went
# with with its -winsysroot flag.
option(
env="WINSYSROOT",
nargs=1,
when=is_windows,
help='Path to a Windows "sysroot" (directory containing MSVC, SDKs)',
)
@depends(
"WINSYSROOT",
bootstrap_path(
"vs",
when=depends("WINSYSROOT", when=is_windows)(lambda x: not x),
),
when=is_windows,
)
def winsysroot(winsysroot, bootstrapped):
if bootstrapped:
return bootstrapped
if winsysroot:
return winsysroot[0]
option(
env="VC_PATH",
nargs=1,
when=is_windows,
help="Path to the Microsoft Visual C/C++ compiler",
)
@depends(
host,
build_environment,
"VC_PATH",
winsysroot,
when=is_windows,
)
@imports("os")
@imports(_from="operator", _import="itemgetter")
def vc_compiler_paths_for_version(host, env, vc_path, winsysroot):
if winsysroot:
if vc_path:
die("WINSYSROOT and VC_PATH cannot be set together.")
base_vc_path = os.path.join(winsysroot, "VC", "Tools", "MSVC")
versions = os.listdir(base_vc_path)
vc_path = [os.path.join(base_vc_path, str(max(Version(v) for v in versions)))]
if vc_path:
# Use an arbitrary version, it doesn't matter.
all_versions = [(Version("15"), vc_path[0])]
elif host.kernel != "WINNT":
# Don't try to do anything when VC_PATH is not set on cross-compiles.
return
else:
all_versions = sorted(get_vc_paths(host, env.topsrcdir), key=itemgetter(0))
if not all_versions:
return
# Choose the newest version.
path = all_versions[-1][1]
host_dir = {
"x86_64": "Hostx64",
"x86": "Hostx86",
"aarch64": "Hostarm64",
}.get(host.cpu)
if host_dir:
path = os.path.join(path, "bin", host_dir)
return {
"x64": [os.path.join(path, "x64")],
# The cross toolchains require DLLs from the native x64 toolchain.
"x86": [os.path.join(path, "x86"), os.path.join(path, "x64")],
"arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")],
}
@depends(target, host, vc_compiler_paths_for_version, when=is_windows)
def vc_compiler_path(target, host, paths):
cpu = target.cpu if target.os == "WINNT" else host.cpu
vc_target = {
"x86": "x86",
"x86_64": "x64",
"arm": "arm",
"aarch64": "arm64",
}.get(cpu)
if not paths:
return
return paths.get(vc_target)
@depends(vc_compiler_path, original_path)
@imports("os")
@imports(_from="os", _import="environ")
def vc_toolchain_search_path(vc_compiler_path, original_path):
result = list(original_path)
if vc_compiler_path:
# The second item, if there is one, is necessary to have in $PATH for
# Windows to load the required DLLs from there.
if len(vc_compiler_path) > 1:
environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:])
# The first item is where the programs are going to be
result.append(vc_compiler_path[0])
return result
@depends_if(vc_compiler_path, when=is_windows)
def vc_compiler_version(vc_compiler_path):
version = Version(
os.path.basename(
os.path.dirname(os.path.dirname(os.path.dirname(vc_compiler_path[0])))
)
)
# MSVC path with version 14.x is actually version 19.x
if version.major == 14:
return Version(f"19.{version.minor}")
@depends_if(vc_compiler_version)
def msvs_version(vc_compiler_version):
# clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to
# be set for GYP on Windows.
if vc_compiler_version >= Version("19.30"):
return "2022"
configure_error("Only Visual Studio 2022 or newer are supported")
return ""
set_config("MSVS_VERSION", msvs_version)
clang_search_path = bootstrap_search_path("clang/bin")
@depends(
bootstrap_search_path("rustc/bin", when="MOZ_AUTOMATION"),
original_path,
)
@imports("os")
@imports(_from="os", _import="environ")
def rust_search_path(rust_path, original_path):
result = list(rust_path or original_path)
# Also add the rustup install directory for cargo/rustc.
cargo_home = environ.get("CARGO_HOME", "")
if cargo_home:
cargo_home = os.path.abspath(cargo_home)
else:
cargo_home = os.path.expanduser(os.path.join("~", ".cargo"))
rustup_path = os.path.join(cargo_home, "bin")
result.insert(0, rustup_path)
return result
# Prepend the mozilla-build msys2 path, since otherwise we can get mismatched
# cygwin dll errors during configure if we get called from another msys2
# environment, see bug 1801826.
@depends(
mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path
)
@imports("os")
def altered_path(
mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path
):
altered_path = mozillabuild_bin_paths
if target.kernel == "Darwin":
# The rust compiler wants to execute dsymutil, but it does so in a
# so we add the clang path.
path = clang_search_path
else:
path = original_path
# cargo needs the rust search path to find cargo-$subcommand.
path += rust_search_path
for p in path:
if p not in altered_path:
altered_path.append(p)
return os.pathsep.join(altered_path)
set_config("PATH", altered_path)
# Compiler wrappers
# ==============================================================
option(
"--with-compiler-wrapper",
env="COMPILER_WRAPPER",
nargs=1,
help="Enable compiling with wrappers such as distcc and ccache",
)
option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache")
@depends_if("--with-ccache")
def ccache(value):
if len(value):
return value
# If --with-ccache was given without an explicit value, we default to
# 'ccache'.
return "ccache"
ccache = check_prog(
"CCACHE",
progs=(),
input=ccache,
paths=bootstrap_search_path(
"sccache", when=depends("CCACHE")(lambda c: len(c) and c[0] == "sccache")
),
allow_missing=True,
)
option(env="CCACHE_PREFIX", nargs=1, help="Compiler prefix to use when using ccache")
ccache_prefix = depends_if("CCACHE_PREFIX")(lambda prefix: prefix[0])
set_config("CCACHE_PREFIX", ccache_prefix)
# Distinguish ccache from sccache.
@depends_if(ccache)
def ccache_is_sccache(ccache):
return check_cmd_output(ccache, "--version").startswith("sccache")
@depends(ccache, ccache_is_sccache)
def using_ccache(ccache, ccache_is_sccache):
return ccache and not ccache_is_sccache
@depends_if(ccache, ccache_is_sccache)
def using_sccache(ccache, ccache_is_sccache):
return ccache and ccache_is_sccache
option(env="RUSTC_WRAPPER", nargs=1, help="Wrap rust compilation with given tool")
@depends(ccache, ccache_is_sccache, "RUSTC_WRAPPER")
@imports(_from="textwrap", _import="dedent")
@imports("os")
def check_sccache_version(ccache, ccache_is_sccache, rustc_wrapper):
sccache_min_version = Version("0.2.13")
def check_version(path):
out = check_cmd_output(path, "--version")
version = Version(out.rstrip().split()[-1])
if version < sccache_min_version:
die(
dedent(
"""\
sccache %s or later is required. sccache in use at %s has
version %s.
Please upgrade or acquire a new version with |./mach bootstrap|.
"""
),
sccache_min_version,
path,
version,
)
if ccache and ccache_is_sccache:
check_version(ccache)
if rustc_wrapper and (
os.path.splitext(os.path.basename(rustc_wrapper[0]))[0].lower() == "sccache"
):
check_version(rustc_wrapper[0])
set_config("MOZ_USING_CCACHE", using_ccache)
set_config("MOZ_USING_SCCACHE", using_sccache)
option(env="SCCACHE_VERBOSE_STATS", help="Print verbose sccache stats after build")
@depends(using_sccache, "SCCACHE_VERBOSE_STATS")
def sccache_verbose_stats(using_sccache, verbose_stats):
return using_sccache and bool(verbose_stats)
set_config("SCCACHE_VERBOSE_STATS", sccache_verbose_stats)
@depends("--with-compiler-wrapper", ccache)
@imports(_from="mozbuild.shellutil", _import="split", _as="shell_split")
def compiler_wrapper(wrapper, ccache):
if wrapper:
raw_wrapper = wrapper[0]
wrapper = shell_split(raw_wrapper)
wrapper_program = find_program(wrapper[0])
if not wrapper_program:
die(
"Cannot find `%s` from the given compiler wrapper `%s`",
wrapper[0],
raw_wrapper,
)
wrapper[0] = wrapper_program
if ccache:
if wrapper:
return tuple([ccache] + wrapper)
else:
return (ccache,)
elif wrapper:
return tuple(wrapper)
@dependable
def wasm():
return split_triplet("wasm32-wasi", allow_wasi=True)
@template
def default_c_compilers(host_or_target, other_c_compiler=None):
"""Template defining the set of default C compilers for the host and
target platforms.
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure.
`other_c_compiler` is the `target` C compiler when `host_or_target` is `host`.
"""
assert host_or_target in {host, target, wasm}
other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)
@depends(host_or_target, target, toolchain_prefix, *other_c_compiler)
def default_c_compilers(
host_or_target, target, toolchain_prefix, *other_c_compiler
):
if host_or_target.kernel == "WINNT":
if host_or_target.abi:
if host_or_target.abi == "msvc":
supported = types = ("clang-cl",)
elif host_or_target.abi == "mingw":
supported = types = ("clang",)
else:
supported = types = ("clang-cl", "clang")
elif host_or_target.kernel == "Darwin":
types = ("clang",)
supported = ("clang", "gcc")
elif host_or_target.kernel == "WASI":
supported = types = ("clang",)
else:
supported = types = ("clang", "gcc")
info = other_c_compiler[0] if other_c_compiler else None
if info and info.type in supported:
# When getting default C compilers for the host, we prioritize the
# same compiler as the target C compiler.
prioritized = info.compiler
if info.type == "gcc":
same_arch = same_arch_different_bits()
if (
target.cpu != host_or_target.cpu
and (target.cpu, host_or_target.cpu) not in same_arch
and (host_or_target.cpu, target.cpu) not in same_arch
):
# If the target C compiler is GCC, and it can't be used with
# -m32/-m64 for the host, it's probably toolchain-prefixed,
# so we prioritize a raw 'gcc' instead.
prioritized = info.type
if target.os != "WINNT" and host_or_target.os == "WINNT":
# When cross-compiling on Windows, don't prioritize. We'll fallback
# to checking for clang-cl first.
pass
else:
types = [prioritized] + [t for t in types if t != info.type]
gcc = ("gcc",)
if toolchain_prefix and host_or_target is target:
gcc = tuple("%sgcc" % p for p in toolchain_prefix) + gcc
result = []
for type in types:
if type == "gcc":
result.extend(gcc)
else:
result.append(type)
return tuple(result)
return default_c_compilers
@template
def default_cxx_compilers(c_compiler, other_c_compiler=None, other_cxx_compiler=None):
"""Template defining the set of default C++ compilers for the host and
target platforms.
`c_compiler` is the @depends function returning a Compiler instance for
the desired platform.
Because the build system expects the C and C++ compilers to be from the
same compiler suite, we derive the default C++ compilers from the C
compiler that was found if none was provided.
We also factor in the target C++ compiler when getting the default host
C++ compiler, using the target C++ compiler if the host and target C
compilers are the same.
"""
assert (other_c_compiler is None) == (other_cxx_compiler is None)
if other_c_compiler is not None:
other_compilers = (other_c_compiler, other_cxx_compiler)
else:
other_compilers = ()
@depends(c_compiler, *other_compilers)
def default_cxx_compilers(c_compiler, *other_compilers):
if other_compilers:
other_c_compiler, other_cxx_compiler = other_compilers
if other_c_compiler.compiler == c_compiler.compiler:
return (other_cxx_compiler.compiler,)
dir = os.path.dirname(c_compiler.compiler)
file = os.path.basename(c_compiler.compiler)
if c_compiler.type == "gcc":
return (os.path.join(dir, file.replace("gcc", "g++")),)
if c_compiler.type == "clang":
return (os.path.join(dir, file.replace("clang", "clang++")),)
return (c_compiler.compiler,)
return default_cxx_compilers
@template
def provided_program(env_var, when=None):
"""Template handling cases where a program can be specified either as a
path or as a path with applicable arguments.
"""
@depends_if(env_var, when=when)
@imports(_from="itertools", _import="takewhile")
@imports(_from="mozbuild.shellutil", _import="split", _as="shell_split")
def provided(cmd):
# Assume the first dash-prefixed item (and any subsequent items) are
# command-line options, the item before the dash-prefixed item is
# the program we're looking for, and anything before that is a wrapper
# of some kind (e.g. sccache).
cmd = shell_split(cmd[0])
without_flags = list(takewhile(lambda x: not x.startswith("-"), cmd))
return namespace(
wrapper=without_flags[:-1],
program=without_flags[-1],
flags=cmd[len(without_flags) :],
)
return provided
@template
def sysroot(host_or_target, target_sysroot=None):
assert target_sysroot or host_or_target is target
bootstrap_target_when = target_is_linux_or_wasi
if host_or_target is host:
host_or_target_str = "host"
opt = "--with-host-sysroot"
env = "HOST_SYSROOT"
when = depends(host)(lambda h: h.kernel == "Linux")
# Only bootstrap a host sysroot when using a bootstrapped target sysroot
# or when the target doesn't use a bootstrapped sysroot in the first place.
@depends(when, bootstrap_target_when, target_sysroot.bootstrapped)
def bootstrap_when(when, bootstrap_target_when, bootstrapped):
return when and (bootstrapped or not bootstrap_target_when)
else:
assert host_or_target is target
host_or_target_str = "target"
opt = "--with-sysroot"
env = "SYSROOT"
when = target_is_linux_or_wasi
bootstrap_when = bootstrap_target_when
option(
opt,
env=env,
nargs=1,
when=when,
help="Use the given sysroot directory for %s build" % host_or_target_str,
)
sysroot_input = depends(opt, when=when)(lambda x: x)
bootstrap_sysroot = depends(bootstrap_when, sysroot_input)(
# Only bootstrap when no flag was explicitly given (either --with or --without)
lambda bootstrap, input: bootstrap
and not input
and input.origin == "default"
)
@depends(
sysroot_input,
host_or_target,
macos_sdk,
ios_sdk,
bootstrap_path(
depends(host_or_target)(lambda t: "sysroot-{}".format(t.toolchain)),
when=bootstrap_sysroot,
),
)
@imports("os")
def sysroot(sysroot_input, host_or_target, macos_sdk, ios_sdk, path):
version = None
if sysroot_input:
path = sysroot_input[0]
elif host_or_target.os == "OSX" and macos_sdk:
path = macos_sdk
elif host_or_target.os == "iOS" and ios_sdk:
path = ios_sdk
if path:
# Find the version of libstdc++ headears in the sysroot
include = os.path.join(path, "usr/include/c++")
if os.path.isdir(include):
with os.scandir(include) as d:
version = max(Version(e.name) for e in d if e.is_dir())
log.info("Using %s sysroot in %s", host_or_target_str, path)
return namespace(
path=path,
bootstrapped=bool(path and not sysroot_input),
stdcxx_version=version,
)
return sysroot
target_sysroot = sysroot(target)
# Use `system_lib_option` instead of `option` for options that enable building
# with a system library for which the development headers are not available in
# the bootstrapped sysroots.
@template
def system_lib_option(name, *args, **kwargs):
option(name, *args, **kwargs)
@depends(
name,
target_sysroot.bootstrapped,
when=kwargs.get("when"),
)
def no_system_lib_in_sysroot(value, bootstrapped):
if bootstrapped and value:
die(
"%s is not supported with bootstrapped sysroot. "
"Drop the option, or use --without-sysroot or --disable-bootstrap",
value.format(name),
)
host_sysroot = sysroot(host, target_sysroot)
@template
def multiarch_dir(host_or_target):
sysroot = {
host: host_sysroot,
target: target_sysroot,
}[host_or_target]
@depends(host_or_target, when=sysroot.path)
def multiarch_dir(target):
if target.cpu == "x86":
# Turn e.g. i686-linux-gnu into i386-linux-gnu
return target.toolchain.replace(target.raw_cpu, "i386")
return target.toolchain
return multiarch_dir
target_multiarch_dir = multiarch_dir(target)
host_multiarch_dir = multiarch_dir(host)
def minimum_gcc_version():
return Version("8.1.0")
@template
def compiler(
language,
host_or_target,
c_compiler=None,
other_compiler=None,
other_c_compiler=None,
):
"""Template handling the generic base checks for the compiler for the
given `language` on the given platform (`host_or_target`).
`host_or_target` is either `host` or `target` (the @depends functions
from init.configure.
When the language is 'C++', `c_compiler` is the result of the `compiler`
template for the language 'C' for the same `host_or_target`.
When `host_or_target` is `host`, `other_compiler` is the result of the
`compiler` template for the same `language` for `target`.
When `host_or_target` is `host` and the language is 'C++',
`other_c_compiler` is the result of the `compiler` template for the
language 'C' for `target`.
"""
assert host_or_target in {host, target, wasm}
assert language in ("C", "C++")
assert language == "C" or c_compiler is not None
assert host_or_target is target or other_compiler is not None
assert language == "C" or host_or_target is target or other_c_compiler is not None
host_or_target_str = {
host: "host",
target: "target",
wasm: "wasm",
}[host_or_target]
sysroot = {
host: host_sysroot,
target: target_sysroot,
wasm: dependable(lambda: namespace(path=None)),
}[host_or_target]
multiarch_dir = {
host: host_multiarch_dir,
target: target_multiarch_dir,
wasm: never,
}[host_or_target]
var = {
("C", target): "CC",
("C++", target): "CXX",
("C", host): "HOST_CC",
("C++", host): "HOST_CXX",
("C", wasm): "WASM_CC",
("C++", wasm): "WASM_CXX",
}[language, host_or_target]
default_compilers = {
"C": lambda: default_c_compilers(host_or_target, other_compiler),
"C++": lambda: default_cxx_compilers(
c_compiler, other_c_compiler, other_compiler
),
}[language]()
what = "the %s %s compiler" % (host_or_target_str, language)
option(env=var, nargs=1, help="Path to %s" % what)
# Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
# HOST_CXX variables.
provided_compiler = provided_program(var)
# Normally, we'd use `var` instead of `_var`, but the interaction with
# old-configure complicates things, and for now, we a) can't take the plain
# result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
# old-configure AC_SUBST it (because it's autoconf doing it, not us)
compiler = check_prog(
"_%s" % var,
what=what,
progs=default_compilers,
input=provided_compiler.program,
paths=clang_search_path,
)
@depends(
configure_cache,
compiler,
provided_compiler,
compiler_wrapper,
host_or_target,
sysroot,
macos_target,
ios_target,
android_version,
vc_compiler_version,
multiarch_dir,
winsysroot,
host,
)
@checking("whether %s can be used" % what, lambda x: bool(x))
@imports(_from="mozbuild.shellutil", _import="quote")
@imports("os")
def valid_compiler(
configure_cache,
compiler,
provided_compiler,
compiler_wrapper,
host_or_target,
sysroot,
macos_target,
ios_target,
android_version,
vc_compiler_version,
multiarch_dir,
winsysroot,
host,
):
wrapper = list(compiler_wrapper or ())
flags = []
if sysroot.path:
if host_or_target.kernel == "Darwin":
# While --sysroot and -isysroot are roughly equivalent, when not using
# -isysroot on mac, clang takes the SDKROOT environment variable into
# consideration, which may be set by python and break things.
flags.extend(("-isysroot", sysroot.path))
else:
flags.extend(("--sysroot", sysroot.path))
if provided_compiler:
wrapper.extend(provided_compiler.wrapper)
flags.extend(provided_compiler.flags)
info = check_compiler(
configure_cache,
wrapper + [compiler] + flags,
language,
host_or_target,
android_version,
)
if host_or_target.os == "OSX" and macos_target:
flags.append("-mmacosx-version-min=%s" % macos_target)
if host_or_target.os == "iOS" and ios_target:
flags.append("-mios-version-min=%s" % ios_target)
# When not given an explicit compatibility version, clang-cl tries
# to get one from MSVC, which might not even be the one used by the
# build. And when it can't find one, its default might also not match
# what the build is using. So if we were able to figure out the version
# we're building with, explicitly use that.
# This also means that, as a side effect, clang-cl will not try to find
# MSVC, which saves a little overhead.
if info.type == "clang-cl" and vc_compiler_version:
flags.append(f"-fms-compatibility-version={vc_compiler_version}")
if info.type == "clang" and language == "C++" and host_or_target.os == "OSX":
flags.append("-stdlib=libc++")
# Check that the additional flags we got are enough to not require any
# more flags. If we get an exception, just ignore it; it's liable to be
# invalid command-line flags, which means the compiler we're checking
# doesn't support those command-line flags and will fail one or more of
# the checks below.
try:
if info.flags:
flags += info.flags
info = check_compiler(
configure_cache,
wrapper + [compiler] + flags,
language,
host_or_target,
android_version,
)
except FatalCheckError:
pass
if not info.target_cpu or info.target_cpu != host_or_target.cpu:
raise FatalCheckError(
"%s %s compiler target CPU (%s) does not match --%s CPU (%s)"
% (
host_or_target_str.capitalize(),
language,
info.target_cpu or "unknown",
host_or_target_str,
host_or_target.raw_cpu,
)
)
if not info.target_kernel or (info.target_kernel != host_or_target.kernel):
raise FatalCheckError(
"%s %s compiler target kernel (%s) does not match --%s kernel (%s)"
% (
host_or_target_str.capitalize(),
language,
info.target_kernel or "unknown",
host_or_target_str,
host_or_target.kernel,
)
)
if not info.target_endianness or (
info.target_endianness != host_or_target.endianness
):
raise FatalCheckError(
"%s %s compiler target endianness (%s) does not match --%s "
"endianness (%s)"
% (
host_or_target_str.capitalize(),
language,
info.target_endianness or "unknown",
host_or_target_str,
host_or_target.endianness,
)
)
# Compiler version checks
# ===================================================
# Check the compiler version here instead of in `compiler_version` so
# that the `checking` message doesn't pretend the compiler can be used
# to then bail out one line later.
if info.type == "gcc":
if host_or_target.os == "Android":
raise FatalCheckError(
"GCC is not supported on Android.\n"
"Please use clang from the Android NDK instead."
)
gcc_version = minimum_gcc_version()
if info.version < gcc_version:
raise FatalCheckError(
"Only GCC %d.%d or newer is supported (found version %s)."
% (gcc_version.major, gcc_version.minor, info.version)
)
# Force GCC to use the C++ headers from the sysroot, and to prefer the
# sysroot system headers to /usr/include.
# Non-Debian GCC also doesn't look at headers in multiarch directory.
if sysroot.bootstrapped and sysroot.stdcxx_version:
version = sysroot.stdcxx_version
for path in (
"usr/include/c++/{}".format(version),
"usr/include/{}/c++/{}".format(multiarch_dir, version),
"usr/include/{}".format(multiarch_dir),
"usr/include",
):
flags.extend(("-isystem", os.path.join(sysroot.path, path)))
if info.type == "clang-cl":
if info.version < "9.0.0":
raise FatalCheckError(
"Only clang-cl 9.0 or newer is supported (found version %s)"
% info.version
)
if winsysroot and host.os != "WINNT":
overlay = os.path.join(winsysroot, "overlay.yaml")
if os.path.exists(overlay):
overlay_flags = ["-Xclang", "-ivfsoverlay", "-Xclang", overlay]
if info.version >= "16.0" or (
# clang-cl 15 normally doesn't support the root-relative
# overlay we use, but the bootstrapped clang-cl 15 is patched
# to support it, so check we're using a patched version.
info.version >= "15.0"
and try_preprocess(
configure_cache,
[compiler] + flags + overlay_flags,
language,
"",
onerror=lambda: False,
wrapper=wrapper,
)
):
flags.extend(overlay_flags)
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.
if info.type == "clang" and info.version < "8.0":
raise FatalCheckError(
"Only clang/llvm 8.0 or newer is supported (found version %s)."
% info.version
)
if host_or_target.kernel == "WASI":
if info.type != "clang":
raise FatalCheckError(
"Only clang is supported for %s" % host_or_target.alias
)
if info.version < "8.0":
raise FatalCheckError(
"Only clang/llvm 8.0 or newer is supported for %s (found version %s)."
% (host_or_target.alias, info.version)
)
if host_or_target.os == "Android":
# Need at least clang 13 for compiler-rt/libunwind being the default.
if info.type == "clang" and info.version < "13.0":
raise FatalCheckError(
"Only clang/llvm 13.0 or newer is supported for %s (found version %s)."
% (host_or_target.alias, info.version)
)
if host_or_target.os == "WINNT" and info.type == "gcc":
raise FatalCheckError(
"Firefox cannot be built with mingw-gcc and requires a mingw-clang toolchain to work."
)
if info.flags:
raise FatalCheckError("Unknown compiler or compiler not supported.")
return namespace(
wrapper=wrapper,
compiler=compiler,
flags=flags,
type=info.type,
version=info.version,
language=language,
)
@depends(valid_compiler)
@checking("%s version" % what)
def compiler_version(compiler):
return compiler.version
if language == "C++":
@depends(valid_compiler, c_compiler)
def valid_compiler(compiler, c_compiler):
if compiler.type != c_compiler.type:
die(
"The %s C compiler is %s, while the %s C++ compiler is "
"%s. Need to use the same compiler suite.",
host_or_target_str,
c_compiler.type,
host_or_target_str,
compiler.type,
)
if compiler.version != c_compiler.version:
die(
"The %s C compiler is version %s, while the %s C++ "
"compiler is version %s. Need to use the same compiler "
"version.",
host_or_target_str,
c_compiler.version,
host_or_target_str,
compiler.version,
)
return compiler
# This excludes WASM_CC from the list.
if var in ("CC", "CXX", "HOST_CC", "HOST_CXX"):
# FIXME: we should return a plain list here.
@depends_if(valid_compiler)
@imports(_from="mozbuild.shellutil", _import="quote")
def value(x):
return quote(*x.wrapper, x.compiler, *x.flags)
set_config(var, value)
# Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
# old-configure to do some of its still existing checks.
if language == "C":
set_config("%s_TYPE" % var, valid_compiler.type)
set_config(
"%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v))
)
valid_compiler = compiler_class(valid_compiler, host_or_target)
def compiler_error():
raise FatalCheckError(
"Failed compiling a simple %s source with %s" % (language, what)
)
valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error)
set_config("%s_BASE_FLAGS" % var, valid_compiler.flags)
# Set CPP/CXXCPP for both the build system and old-configure. We don't
# need to check this works for preprocessing, because we already relied
# on $CC -E/$CXX -E doing preprocessing work to validate the compiler
# in the first place.
if host_or_target is target:
pp_var = {
"C": "CPP",
"C++": "CXXCPP",
}[language]
preprocessor = depends_if(valid_compiler)(
lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags)
)
set_config(pp_var, preprocessor)
if language == "C":
linker_var = {
target: "LD",
host: "HOST_LD",
}.get(host_or_target)
if linker_var:
@deprecated_option(env=linker_var, nargs=1)
def linker(value):
if value:
return value[0]
@depends(linker)
def unused_linker(linker):
if linker:
log.warning(
"The value of %s is not used by this build system." % linker_var
)
return valid_compiler
c_compiler = compiler("C", target)
cxx_compiler = compiler("C++", target, c_compiler=c_compiler)
host_c_compiler = compiler("C", host, other_compiler=c_compiler)
host_cxx_compiler = compiler(
"C++",
host,
c_compiler=host_c_compiler,
other_compiler=cxx_compiler,
other_c_compiler=c_compiler,
)
@template
def windows_abi(host_or_target, c_compiler):
@depends(host_or_target)
def windows_abi(host_or_target):
if host_or_target.os == "WINNT":
return host_or_target.abi
@depends(host_or_target, windows_abi)
def need_windows_abi_from_compiler(host_or_target, windows_abi):
return host_or_target.os == "WINNT" and windows_abi is None
@depends(host_or_target, c_compiler, when=need_windows_abi_from_compiler)
def windows_abi_from_compiler(host_or_target, c_compiler):
if host_or_target.os == "WINNT":
if c_compiler.type == "clang-cl":
return "msvc"
return "mingw"
return windows_abi | windows_abi_from_compiler
target_windows_abi = windows_abi(target, c_compiler)
host_windows_abi = windows_abi(host, host_c_compiler)
# Generic compiler-based conditions.
building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc")
building_with_gnu_compatible_cc = depends(c_compiler)(
lambda info: info.type != "clang-cl"
)
@depends(cxx_compiler, ccache_prefix)
@imports("os")
def cxx_is_icecream(info, ccache_prefix):
if (
os.path.islink(info.compiler)
and os.path.basename(os.readlink(info.compiler)) == "icecc"
):
return True
if ccache_prefix and os.path.basename(ccache_prefix) == "icecc":
return True
set_config("CXX_IS_ICECREAM", cxx_is_icecream)
# Libstdc++ compatibility hacks
# ==============================================================
#
@depends(target, host)
def target_or_host_is_linux(target, host):
return any(t.os == "GNU" and t.kernel == "Linux" for t in (target, host))
option(
"--enable-stdcxx-compat",
env="MOZ_STDCXX_COMPAT",
help="Enable compatibility with older libstdc++",
when=target_or_host_is_linux,
)
@depends("--enable-stdcxx-compat", when=target_or_host_is_linux)
def stdcxx_compat(value):
if value:
return True
set_config("MOZ_STDCXX_COMPAT", True, when=stdcxx_compat)
# Linker detection
# ==============================================================
# The policy is as follows:
# For Windows:
# - the linker is picked via the LINKER environment variable per windows.configure,
# but ought to be lld-link in any case.
# For macOS:
# - the linker is lld if the clang used is >= 15 (per LLVM version, not Xcode version).
# - the linker is also lld on local developer builds if the clang used is >= 13 (per LLVM
# version, not Xcode version)
# - otherwise the linker is ld64, either from XCode on macOS, or from cctools-ports when
# cross-compiling.
# For other OSes:
# - on local developer builds: lld if present and the compiler is clang. Otherwise gold
# is used if present otherwise, whatever the compiler uses by default.
# - on release/official builds: whatever the compiler uses by default, except when the
# compiler is clang, in which case lld is preferred when it's new enough.
@template
def is_not_winnt_or_sunos(host_or_target):
@depends(host_or_target)
def is_not_winnt_or_sunos(host_or_target):
if host_or_target.kernel not in ("WINNT", "SunOS"):
return True
return is_not_winnt_or_sunos
is_linker_option_enabled = is_not_winnt_or_sunos(target)
@deprecated_option("--enable-gold", env="MOZ_FORCE_GOLD", when=is_linker_option_enabled)
def enable_gold(value):
if value:
die("--enable-gold is deprecated, use --enable-linker=gold instead")
else:
die("--disable-gold is deprecated, use --enable-linker=something_else instead")
option(
"--enable-linker",
nargs=1,
help="Select the linker {bfd, gold, ld64, lld, lld-*, mold}",
when=is_linker_option_enabled,
)
# No-op to enable depending on --enable-linker from default_elfhack in
# toolkit/moz.configure.
@depends("--enable-linker", when=is_linker_option_enabled)
def enable_linker(linker):
return linker
@template
def select_linker_tmpl(host_or_target):
if host_or_target is target:
deps = depends(
"--enable-linker",
c_compiler,
developer_options,
extra_toolchain_flags,
target,
stdcxx_compat,
when=is_linker_option_enabled,
)
host_or_target_str = "target"
else:
deps = depends(
dependable(None),
host_c_compiler,
developer_options,
dependable(None),
host,
stdcxx_compat,
when=is_not_winnt_or_sunos(host_or_target),
)
host_or_target_str = "host"
@deps
@checking(f"for {host_or_target_str} linker", lambda x: x.KIND)
@imports("os")
@imports("shutil")
def select_linker(
linker, c_compiler, developer_options, toolchain_flags, target, stdcxx_compat
):
if linker:
linker = linker[0]
else:
linker = None
def is_valid_linker(linker):
if target.kernel == "Darwin":
valid_linkers = ("ld64", "lld")
else:
valid_linkers = ("bfd", "gold", "lld", "mold")
if linker in valid_linkers:
return True
if "lld" in valid_linkers and linker.startswith("lld-"):
return True
return False
if linker and not is_valid_linker(linker):
# Check that we are trying to use a supported linker
die("Unsupported linker " + linker)
# Check the kind of linker
version_check = ["-Wl,--version"]
cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags
def try_linker(linker):
# Generate the compiler flag
if linker == "ld64":
linker_flag = ["-fuse-ld=ld"]
elif linker:
linker_flag = ["-fuse-ld=" + linker]
else:
linker_flag = []
cmd = cmd_base + linker_flag + version_check
if toolchain_flags:
cmd += toolchain_flags
# ld64 doesn't have anything to print out a version. It does print out
# "ld64: For information on command line options please use 'man ld'."
# but that would require doing two attempts, one with --version, that
# would fail, and another with --help.
# Instead, abuse its LD_PRINT_OPTIONS feature to detect a message
# specific to it on stderr when it fails to process --version.
env = dict(os.environ)
env["LD_PRINT_OPTIONS"] = "1"
# Some locales might not print out the strings we are looking for, so
# ensure consistent output.
env["LC_ALL"] = "C"
retcode, stdout, stderr = get_cmd_output(*cmd, env=env)
if retcode == 1 and "Logging ld64 options" in stderr:
kind = "ld64"
elif retcode != 0:
return None
elif "mold" in stdout:
kind = "mold"
elif "GNU ld" in stdout:
# We are using the normal linker
kind = "bfd"
elif "GNU gold" in stdout:
kind = "gold"
elif "LLD" in stdout:
kind = "lld"
else:
kind = "unknown"
if kind == "unknown" or is_valid_linker(kind):
return namespace(
KIND=kind,
LINKER_FLAG=linker_flag,
)
result = None
if linker:
result = try_linker(linker)
if result is None:
die("Could not use {} as linker".format(linker))
if (
result is None
and c_compiler.type == "clang"
and (
(
target.kernel != "Darwin"
and (
developer_options
or host_or_target_str == "host"
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": <