summaryrefslogtreecommitdiffstats
path: root/build/moz.configure
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--build/moz.configure/android-ndk.configure307
-rw-r--r--build/moz.configure/android-sdk.configure157
-rw-r--r--build/moz.configure/arm.configure305
-rw-r--r--build/moz.configure/bindgen.configure403
-rw-r--r--build/moz.configure/bootstrap.configure326
-rw-r--r--build/moz.configure/checks.configure206
-rw-r--r--build/moz.configure/compile-checks.configure309
-rw-r--r--build/moz.configure/compilers-util.configure148
-rw-r--r--build/moz.configure/flags.configure145
-rw-r--r--build/moz.configure/headers.configure119
-rw-r--r--build/moz.configure/init.configure1307
-rw-r--r--build/moz.configure/java.configure76
-rw-r--r--build/moz.configure/keyfiles.configure68
-rw-r--r--build/moz.configure/lto-pgo.configure355
-rw-r--r--build/moz.configure/memory.configure105
-rw-r--r--build/moz.configure/node.configure90
-rw-r--r--build/moz.configure/nspr.configure115
-rw-r--r--build/moz.configure/nss.configure29
-rw-r--r--build/moz.configure/old.configure354
-rw-r--r--build/moz.configure/pkg.configure208
-rw-r--r--build/moz.configure/rust.configure786
-rw-r--r--build/moz.configure/toolchain.configure3194
-rw-r--r--build/moz.configure/update-programs.configure171
-rw-r--r--build/moz.configure/util.configure517
-rw-r--r--build/moz.configure/warnings.configure320
-rw-r--r--build/moz.configure/windows.configure481
26 files changed, 10601 insertions, 0 deletions
diff --git a/build/moz.configure/android-ndk.configure b/build/moz.configure/android-ndk.configure
new file mode 100644
index 0000000000..333a5ea499
--- /dev/null
+++ b/build/moz.configure/android-ndk.configure
@@ -0,0 +1,307 @@
+# -*- 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/.
+
+
+@depends(toolchains_base_dir, "--help")
+@imports(_from="os.path", _import="isdir")
+@imports(_from="mozboot.android", _import="NDK_VERSION")
+def default_android_ndk_root(toolchains_base_dir, _):
+ for ndk in ("android-ndk-%s" % NDK_VERSION, "android-ndk"):
+ path = os.path.join(toolchains_base_dir, ndk)
+ if isdir(path):
+ return path
+
+
+option(
+ "--with-android-ndk",
+ nargs=1,
+ default=default_android_ndk_root,
+ help="location where the Android NDK can be found{|}",
+)
+
+option("--with-android-toolchain", nargs=1, help="location of the Android toolchain")
+
+option(
+ "--with-android-lldb-server", nargs=1, help="location of the Android LLDB server"
+)
+
+option(
+ "--with-android-googlevr-sdk", nargs=1, help="location of the Android GoogleVR SDK"
+)
+
+
+@depends(target)
+def min_android_version(target):
+ if target.cpu in ["aarch64", "x86_64"]:
+ # 64-bit support was added in API 21.
+ return "21"
+ return "16"
+
+
+option(
+ "--with-android-version",
+ nargs=1,
+ help="android platform version{|}",
+ default=min_android_version,
+)
+
+
+@depends("--with-android-version", min_android_version)
+@imports(_from="__builtin__", _import="ValueError")
+def android_version(value, min_version):
+ if not value:
+ # Someone has passed --without-android-version.
+ die("--with-android-version cannot be disabled.")
+
+ try:
+ version = int(value[0])
+ except ValueError:
+ die("--with-android-version expects an integer value")
+
+ if version < int(min_version):
+ die(
+ "--with-android-version must be at least %s (got %s)", min_version, value[0]
+ )
+
+ return version
+
+
+@depends("--with-android-ndk")
+@imports(_from="os.path", _import="isdir")
+def ndk(value):
+ if value:
+ if not isdir(value[0]):
+ die(
+ "The path you specified with --with-android-ndk (%s) is not "
+ "a directory" % value[0]
+ )
+ return value[0]
+
+ die(
+ "You must specify --with-android-ndk=/path/to/ndk when targeting Android, "
+ "or try |mach bootstrap|."
+ )
+
+
+set_config("ANDROID_NDK", ndk)
+
+
+@depends(ndk)
+@checking("for android ndk version")
+@imports(_from="__builtin__", _import="open")
+@imports(_from="mozboot.android", _import="NDK_VERSION")
+@imports(_from="mozboot.android", _import="get_ndk_version")
+@imports(_from="mozboot.android", _import="GetNdkVersionError")
+def ndk_version(ndk):
+ if not ndk:
+ # Building 'js/src' for non-Android.
+ return
+
+ try:
+ major, minor, human = get_ndk_version(ndk)
+ except GetNdkVersionError as e:
+ die(str(e))
+
+ if NDK_VERSION != human:
+ die(
+ "The only supported version of the NDK is %s (have %s)\n"
+ "Please run |mach bootstrap| "
+ "to install the correct NDK." % (NDK_VERSION, human)
+ )
+ return namespace(
+ major=major,
+ minor=minor,
+ )
+
+
+set_config("ANDROID_NDK_MAJOR_VERSION", ndk_version.major)
+set_config("ANDROID_NDK_MINOR_VERSION", ndk_version.minor)
+
+
+@imports(_from="os.path", _import="isdir")
+@imports(_from="mozbuild.shellutil", _import="quote")
+def host_dir(host, base_dir):
+ dir_format = "%s/%s-%s"
+ host_kernel = "windows" if host.kernel == "WINNT" else host.kernel.lower()
+
+ dir = dir_format % (base_dir, host_kernel, host.cpu)
+ log.debug("Trying %s" % quote(dir))
+ if not isdir(dir) and host.cpu == "x86_64":
+ dir = dir_format % (base_dir, host_kernel, "x86")
+ log.debug("Trying %s" % quote(dir))
+ if not isdir(dir) and host.kernel == "Darwin" and host.cpu == "aarch64":
+ dir = dir_format % (base_dir, host_kernel, "x86_64")
+ log.debug("Trying %s" % quote(dir))
+ if isdir(dir):
+ return dir
+
+
+@depends(host, ndk, "--with-android-toolchain")
+@checking("for the Android toolchain directory", lambda x: x or "not found")
+def android_toolchain(host, ndk, toolchain):
+ if not ndk:
+ return
+ if toolchain:
+ return toolchain[0]
+
+ toolchain = host_dir(host, os.path.join(ndk, "toolchains", "llvm", "prebuilt"))
+ if toolchain:
+ return toolchain
+ die("You have to specify --with-android-toolchain=" "/path/to/ndk/toolchain.")
+
+
+@depends(target, android_toolchain)
+@checking("for android sysroot directory")
+@imports(_from="os.path", _import="isdir")
+def android_sysroot(target, android_toolchain):
+ if target.os != "Android":
+ return
+
+ search_dirs = [
+ os.path.join(android_toolchain, "sysroot"),
+ ]
+
+ for sysroot_dir in search_dirs:
+ if isdir(sysroot_dir):
+ return sysroot_dir
+
+ die(
+ "Android sysroot directory not found in %s."
+ % str([sysroot_dir for sysroot_dir in search_dirs])
+ )
+
+
+@depends(target, host, ndk, "--with-android-lldb-server")
+@checking("for the Android LLDB server", lambda x: x or "not found")
+@imports(_from="os", _import="listdir")
+@imports(_from="os.path", _import="isdir")
+@imports(_from="os.path", _import="isfile")
+@imports(_from="mozbuild.shellutil", _import="quote")
+def android_lldb_server(target, host, ndk, lldb):
+ if not ndk:
+ return
+ if lldb:
+ return lldb[0]
+ else:
+ clang_format = "toolchains/llvm/prebuilt/%s-%s/lib64/clang"
+ llvm_lib = "lib/linux"
+
+ host_kernel = "windows" if host.kernel == "WINNT" else host.kernel.lower()
+ clang_path = os.path.join(ndk, clang_format % (host_kernel, host.cpu))
+ if not isdir(clang_path) and host.kernel == "Darwin" and host.cpu == "aarch64":
+ clang_path = os.path.join(ndk, clang_format % (host_kernel, "x86_64"))
+ log.debug("Listing subdirectories of %s" % quote(clang_path))
+ clang_subdirs = [
+ x for x in listdir(clang_path) if isdir(os.path.join(clang_path, x))
+ ]
+ log.debug("Got %r" % clang_subdirs)
+ if len(clang_subdirs) != 1:
+ die(
+ "Could not resolve lldb-server in %s. Please specify --with-android-lldb-server=/path/to/android/lldb-server"
+ % quote(clang_path)
+ )
+ log.debug("Found version %s" % quote(clang_subdirs[0]))
+
+ if target.cpu == "x86":
+ target_cpu = "i386"
+ else:
+ target_cpu = target.cpu
+
+ full_path = os.path.join(
+ clang_path, clang_subdirs[0], llvm_lib, target_cpu, "lldb-server"
+ )
+ log.debug("Trying %s" % quote(full_path))
+
+ if isfile(full_path):
+ return full_path
+ die("Please specify --with-android-lldb-server=/path/to/android/lldb-server")
+
+
+set_config("ANDROID_LLDB_SERVER", android_lldb_server)
+
+
+option(
+ env="STLPORT_LIBS",
+ nargs=1,
+ help="Options linker should pass for standard C++ library",
+)
+
+
+@depends("STLPORT_LIBS", ndk)
+@imports(_from="os.path", _import="isfile")
+def stlport_libs(value, ndk):
+ if value and len(value):
+ return value.split()
+ if not ndk:
+ return
+
+ return ["-static-libstdc++"]
+
+
+set_config("STLPORT_LIBS", stlport_libs)
+
+
+@depends(android_sysroot, android_toolchain)
+def extra_toolchain_flags(android_sysroot, toolchain_dir):
+ if not android_sysroot:
+ return []
+ flags = [
+ "--sysroot={}".format(android_sysroot),
+ "--gcc-toolchain={}".format(toolchain_dir),
+ ]
+ return flags
+
+
+add_old_configure_assignment("extra_android_flags", extra_toolchain_flags)
+
+
+@depends(extra_toolchain_flags)
+def bindgen_cflags_android(toolchain_flags):
+ return toolchain_flags
+
+
+@depends("--with-android-googlevr-sdk", target)
+@checking("for GoogleVR SDK", lambda x: x.result)
+@imports(_from="os.path", _import="exists")
+@imports(_from="os.path", _import="abspath")
+def googlevr_sdk(value, target):
+ if not value:
+ return namespace(result="Not specified")
+ path = abspath(value[0])
+ if not exists(path):
+ die("Could not find GoogleVR SDK %s", path)
+ include = "%s/libraries/headers/" % path
+ if "arm" == target.cpu:
+ arch = "armeabi-v7a"
+ elif "aarch64" == target.cpu:
+ arch = "arm64-v8a"
+ elif "x86" == target.cpu:
+ arch = "x86"
+ else:
+ die("Unsupported GoogleVR cpu architecture %s" % target.cpu)
+
+ libs = "{0}/libraries/jni/{1}/".format(path, arch)
+
+ if not exists(libs):
+ die(
+ "Could not find GoogleVR NDK at %s. Did you try running "
+ "'./gradlew :extractNdk' in %s?",
+ libs,
+ path,
+ )
+
+ return namespace(
+ result=path,
+ include=include,
+ libs=libs,
+ enabled=True,
+ )
+
+
+set_define("MOZ_ANDROID_GOOGLE_VR", googlevr_sdk.enabled)
+set_config("MOZ_ANDROID_GOOGLE_VR", googlevr_sdk.enabled)
+set_config("MOZ_ANDROID_GOOGLE_VR_INCLUDE", googlevr_sdk.include)
+set_config("MOZ_ANDROID_GOOGLE_VR_LIBS", googlevr_sdk.libs)
diff --git a/build/moz.configure/android-sdk.configure b/build/moz.configure/android-sdk.configure
new file mode 100644
index 0000000000..b7daecbaf9
--- /dev/null
+++ b/build/moz.configure/android-sdk.configure
@@ -0,0 +1,157 @@
+# -*- 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/.
+
+# Ensure Android SDK and build-tools versions depending on mobile target.
+
+
+@depends(host, toolchains_base_dir, "--help")
+@imports(_from="os.path", _import="isdir")
+def default_android_sdk_root(host, toolchains_base_dir, _):
+ sdk_basename = {
+ "Darwin": "android-sdk-macosx",
+ "Linux": "android-sdk-linux",
+ "WINNT": "android-sdk-windows",
+ }.get(host.kernel, "android-sdk")
+ for sdk_basename in (sdk_basename, "android-sdk"):
+ path = os.path.join(toolchains_base_dir, sdk_basename)
+ if isdir(path):
+ return path
+
+
+option(
+ "--with-android-sdk",
+ nargs=1,
+ default=default_android_sdk_root,
+ help="location where the Android SDK can be found (like ~/.mozbuild/android-sdk-linux){|}",
+)
+
+
+@depends("--with-android-sdk")
+@imports(_from="os.path", _import="isdir")
+def android_sdk_root(value):
+ if value:
+ if not isdir(value[0]):
+ die(
+ "The path you specified with --with-android-sdk (%s) is not "
+ "a directory" % value[0]
+ )
+ return value[0]
+
+ die(
+ "You must specify --with-android-sdk=/path/to/sdk when targeting Android, "
+ "or try |mach bootstrap|."
+ )
+
+
+@depends("--enable-geckoview-lite")
+def android_sdk_version(geckoview_lite):
+ # We support Android SDK version 21 and up by default (16 in lite mode).
+ # See the --enable-android-min-sdk option below.
+ #
+ # Warning: Before increasing the with-android-min-sdk value, please note several places in
+ # and out of tree have to be changed. Otherwise, places like Treeherder or archive.mozilla.org
+ # will advertise a bad API level. This may confuse people. As an example, please look at
+ # bug 1384482.
+ # If you think you can't handle the whole set of changes, please reach out to the Release
+ # Engineering team.
+ return namespace(
+ build_tools_version="33.0.1",
+ target_sdk_version="33",
+ min_sdk_version="16" if geckoview_lite else "21",
+ )
+
+
+option(
+ "--with-android-min-sdk",
+ default=android_sdk_version.min_sdk_version,
+ help="Impose a minimum Firefox for Android SDK version",
+)
+
+
+@depends("--with-android-min-sdk", android_sdk_version.target_sdk_version)
+@imports(_from="__builtin__", _import="ValueError")
+def valid_android_min_sdk(min_sdk_version, target_sdk_version):
+ if not min_sdk_version:
+ die("--without-android-min-sdk is not a valid option")
+ try:
+ if int(min_sdk_version[0]) > int(target_sdk_version):
+ die(
+ "--with-android-min-sdk is expected to be less than {}".format(
+ target_sdk_version
+ )
+ )
+ except ValueError:
+ die("--with-android-min-sdk takes a numerical value")
+ return min_sdk_version[0]
+
+
+set_config("MOZ_ANDROID_MIN_SDK_VERSION", valid_android_min_sdk)
+
+
+@depends(android_sdk_root, android_sdk_version)
+@checking("for Android build-tools")
+@imports(_from="os.path", _import="exists")
+@imports(_from="os.path", _import="isdir")
+def android_build_tools(sdk_root, sdk_version):
+ android_build_tools_base = os.path.join(sdk_root, "build-tools")
+ version = sdk_version.build_tools_version
+ if isdir(os.path.join(android_build_tools_base, version)):
+ tools = os.path.join(android_build_tools_base, version)
+ for zipalign in ("zipalign", "zipalign.exe"):
+ if exists(os.path.join(tools, zipalign)):
+ return [tools]
+
+ die(
+ "You must install the Android build-tools version %s. "
+ "Try |mach bootstrap|. (Looked for %s/%s)"
+ % (version, android_build_tools_base, version)
+ )
+
+
+@depends(android_sdk_root)
+@checking("for Android platform-tools")
+@imports(_from="os.path", _import="exists")
+@imports(_from="os.path", _import="isdir")
+def android_platform_tools(sdk_root):
+ tools = os.path.join(sdk_root, "platform-tools")
+ for adb in ("adb", "adb.exe"):
+ if exists(os.path.join(tools, adb)):
+ return [tools]
+
+ die(
+ "You must install the Android platform-tools. Try |mach bootstrap|. (Looked for %s)"
+ % tools
+ )
+
+
+@depends(android_sdk_root)
+def android_emulator_path(sdk_root):
+ return [os.path.join(sdk_root, "emulator")]
+
+
+@template
+def check_android_tools(tool, tool_dir):
+ check = check_prog(
+ tool.upper(), (tool, tool + ".exe"), paths=tool_dir, allow_missing=True
+ )
+
+ @depends(check)
+ def require_tool(result):
+ if result is None:
+ die("The program %s was not found. Try |mach bootstrap|" % tool)
+ return result
+
+ return require_tool
+
+
+check_android_tools("zipalign", android_build_tools)
+check_android_tools("adb", android_platform_tools)
+check_android_tools("emulator", android_emulator_path)
+
+set_config("ANDROID_SDK_ROOT", android_sdk_root)
+
+set_config("ANDROID_BUILD_TOOLS_VERSION", android_sdk_version.build_tools_version)
+set_config("ANDROID_TARGET_SDK", android_sdk_version.target_sdk_version)
diff --git a/build/moz.configure/arm.configure b/build/moz.configure/arm.configure
new file mode 100644
index 0000000000..5d26f4d732
--- /dev/null
+++ b/build/moz.configure/arm.configure
@@ -0,0 +1,305 @@
+# -*- 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/.
+
+
+@depends(target.os)
+def arm_option_defaults(os):
+ if os == "Android":
+ arch = "armv7-a"
+ thumb = "yes"
+ fpu = "neon"
+ float_abi = "softfp"
+ else:
+ arch = thumb = fpu = float_abi = "toolchain-default"
+ return namespace(
+ arch=arch,
+ thumb=thumb,
+ fpu=fpu,
+ float_abi=float_abi,
+ )
+
+
+# Note: '{...|}' in the help of all options with a non-constant default to
+# make the lint happy. The first arm is always going to be used, because a
+# default is always returned. The lint is fooled by this file being
+# conditional. If it weren't conditional, the lint wouldn't ask for '{|}' to
+# be there.
+option(
+ "--with-arch",
+ nargs=1,
+ default=arm_option_defaults.arch,
+ help="{Use specific CPU features (-march=type). Resets thumb, fpu, "
+ "float-abi, etc. defaults when set|}",
+)
+
+
+@depends("--with-arch")
+def arch_option(value):
+ if value:
+ if value[0] != "toolchain-default":
+ return ["-march={}".format(value[0])]
+ return []
+
+
+option(
+ "--with-thumb",
+ choices=("yes", "no", "toolchain-default"),
+ default=arm_option_defaults.thumb,
+ nargs="?",
+ help="{Use Thumb instruction set (-mthumb)|}",
+)
+
+
+def normalize_arm_option(value):
+ if value:
+ if len(value):
+ if value[0] == "yes":
+ return True
+ elif value[0] == "no":
+ return False
+ else:
+ return value[0]
+ return True
+ return False
+
+
+@depends("--with-thumb")
+def thumb_option(value):
+ value = normalize_arm_option(value)
+ if value is True:
+ return ["-mthumb"]
+ if value is False:
+ return ["-marm"]
+ return []
+
+
+option(
+ "--with-thumb-interwork",
+ choices=("yes", "no", "toolchain-default"),
+ default="toolchain-default",
+ nargs="?",
+ help="Use Thumb/ARM instuctions interwork (-mthumb-interwork)",
+)
+
+
+@depends("--with-thumb-interwork")
+def thumb_interwork_option(value):
+ value = normalize_arm_option(value)
+ if value is True:
+ return ["-mthumb-interwork"]
+ if value is False:
+ return ["-mno-thumb-interwork"]
+ return []
+
+
+option(
+ "--with-fpu",
+ nargs=1,
+ default=arm_option_defaults.fpu,
+ help="{Use specific FPU type (-mfpu=type)|}",
+)
+
+
+@depends("--with-fpu")
+def fpu_option(value):
+ if value:
+ if value[0] != "toolchain-default":
+ return ["-mfpu={}".format(value[0])]
+ return []
+
+
+option(
+ "--with-float-abi",
+ nargs=1,
+ default=arm_option_defaults.float_abi,
+ help="{Use specific arm float ABI (-mfloat-abi=type)|}",
+)
+
+
+@depends("--with-float-abi")
+def float_abi_option(value):
+ if value:
+ if value[0] != "toolchain-default":
+ return ["-mfloat-abi={}".format(value[0])]
+ return []
+
+
+option(
+ "--with-soft-float",
+ choices=("yes", "no", "toolchain-default"),
+ default="toolchain-default",
+ nargs="?",
+ help="Use soft float library (-msoft-float)",
+)
+
+
+@depends("--with-soft-float")
+def soft_float_option(value):
+ value = normalize_arm_option(value)
+ if value is True:
+ return ["-msoft-float"]
+ if value is False:
+ return ["-mno-soft-float"]
+ return []
+
+
+check_and_add_flag(
+ "-mno-unaligned-access", when=depends(target.os)(lambda os: os == "Android")
+)
+
+
+# The set of flags that clang understands
+@depends(
+ arch_option,
+ thumb_option,
+ fpu_option,
+ float_abi_option,
+ soft_float_option,
+)
+def all_clang_arm_flags(arch, thumb, fpu, float_abi, soft_float):
+ return arch + thumb + fpu + float_abi + soft_float
+
+
+# All the flags the compiler understands. When the compiler is clang, this
+# still includes unsupported flags, but we live it to configure to fail
+# during a compiler check. These checks aren't available for clang as used
+# by bindgen, so we keep the separate set of flags for clang for bindgen.
+@depends(all_clang_arm_flags, thumb_interwork_option)
+def all_arm_flags(flags, interwork):
+ return flags + interwork
+
+
+add_old_configure_assignment("_ARM_FLAGS", all_arm_flags)
+add_old_configure_assignment("_THUMB_FLAGS", thumb_option)
+
+
+@depends(configure_cache, c_compiler, all_arm_flags)
+@checking("ARM version support in compiler", lambda x: x.arm_arch)
+@imports(_from="textwrap", _import="dedent")
+def arm_target(configure_cache, compiler, all_arm_flags):
+ # We're going to preprocess the following source to figure out some details
+ # about the arm target options we have enabled.
+ source = dedent(
+ """\
+ %ARM_ARCH __ARM_ARCH
+ #if __thumb2__
+ %THUMB2 yes
+ #else
+ %THUMB2 no
+ #endif
+ // Confusingly, the __SOFTFP__ preprocessor variable indicates the
+ // "softfloat" ABI, not the "softfp" ABI.
+ #if __SOFTFP__
+ %FLOAT_ABI soft
+ #elif __ARM_PCS_VFP
+ %FLOAT_ABI hard
+ #else
+ %FLOAT_ABI softfp
+ #endif
+ // There is more subtlety to it than this preprocessor test, but MOZ_FPU doesn't
+ // need to be too fine-grained.
+ #if __ARM_NEON
+ %FPU neon
+ #elif __ARM_VFPV2__ || __ARM_FP == 12
+ %FPU vfpv2
+ #elif __ARM_VFPV3__
+ %FPU vfpv3
+ #elif __ARM_VFPV4__ || __ARM_FP == 14
+ %FPU vfpv4
+ #elif __ARM_FPV5__
+ %FPU fp-armv8
+ #endif
+ """
+ )
+ result = try_invoke_compiler(
+ configure_cache,
+ [compiler.compiler] + compiler.flags,
+ compiler.language,
+ source,
+ ["-E"] + all_arm_flags,
+ wrapper=compiler.wrapper,
+ )
+ # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
+ # have non-ASCII characters. Treat the output as bytearray.
+ data = {"fpu": None} # fpu may not get a value from the preprocessor.
+ for line in result.splitlines():
+ if line.startswith("%"):
+ k, _, v = line.partition(" ")
+ k = k.lstrip("%").lower()
+ if k == "arm_arch":
+ data[k] = int(v)
+ else:
+ data[k] = {
+ "yes": True,
+ "no": False,
+ }.get(v, v)
+ log.debug("%s = %s", k, data[k])
+
+ return namespace(**data)
+
+
+@depends(arm_target.arm_arch, when=depends(target.os)(lambda os: os == "Android"))
+def armv7(arch):
+ if arch < 7:
+ die("Android/armv6 and earlier are not supported")
+
+
+set_config("MOZ_THUMB2", True, when=arm_target.thumb2)
+set_define("MOZ_THUMB2", True, when=arm_target.thumb2)
+
+
+have_arm_simd = c_compiler.try_compile(
+ body='asm("uqadd8 r1, r1, r2");', check_msg="for ARM SIMD support in compiler"
+)
+
+set_config("HAVE_ARM_SIMD", have_arm_simd)
+set_define("HAVE_ARM_SIMD", have_arm_simd)
+
+have_arm_neon = c_compiler.try_compile(
+ body='asm(".fpu neon\\n vadd.i8 d0, d0, d0");',
+ check_msg="for ARM NEON support in compiler",
+)
+
+set_config("HAVE_ARM_NEON", have_arm_neon)
+set_define("HAVE_ARM_NEON", have_arm_neon)
+
+
+# We don't need to build NEON support if we're targetting a non-NEON device.
+# This matches media/webrtc/trunk/webrtc/build/common.gypi.
+@depends(arm_target.arm_arch, when=have_arm_neon)
+def build_arm_neon(arm_arch):
+ return arm_arch >= 7
+
+
+set_config("BUILD_ARM_NEON", True, when=build_arm_neon)
+set_define("BUILD_ARM_NEON", True, when=build_arm_neon)
+
+
+set_config("ARM_ARCH", depends(arm_target.arm_arch)(lambda x: str(x)))
+set_config("MOZ_FPU", arm_target.fpu)
+
+
+@depends(arm_target)
+def neon_flags(arm_target):
+ # Building with -mfpu=neon requires either the "softfp" or the
+ # "hardfp" ABI. Depending on the compiler's default target, and the
+ # CFLAGS, the default ABI might be neither, in which case it is the
+ # "softfloat" ABI.
+ # The "softfloat" ABI is binary-compatible with the "softfp" ABI, so
+ # we can safely mix code built with both ABIs. So, if we detect
+ # that compiling uses the "softfloat" ABI, force the use of the
+ # "softfp" ABI instead.
+ flags = ["-mfpu=neon"]
+ if arm_target.float_abi == "soft":
+ flags.append("-mfloat-abi=softfp")
+ if arm_target.arm_arch < 7:
+ # clang needs to be forced to at least armv7 for -mfpu=neon to do
+ # something.
+ flags.append("-march=armv7-a")
+ return tuple(flags)
+
+
+set_config("NEON_FLAGS", neon_flags)
diff --git a/build/moz.configure/bindgen.configure b/build/moz.configure/bindgen.configure
new file mode 100644
index 0000000000..5ce66fe9af
--- /dev/null
+++ b/build/moz.configure/bindgen.configure
@@ -0,0 +1,403 @@
+# -*- 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/.
+
+
+@depends(build_project, "--enable-smoosh")
+def cbindgen_is_needed(build_project, js_enable_smoosh):
+ if build_project != "js":
+ # cbindgen is needed by the style system build and webrender.
+ return True
+
+ # cbindgen is needed by SmooshMonkey.
+ return js_enable_smoosh
+
+
+option(env="CBINDGEN", nargs=1, when=cbindgen_is_needed, help="Path to cbindgen")
+
+
+@imports(_from="textwrap", _import="dedent")
+def check_cbindgen_version(cbindgen, fatal=False):
+ log.debug("trying cbindgen: %s" % cbindgen)
+
+ cbindgen_min_version = Version("0.24.3")
+
+ # cbindgen x.y.z
+ version = Version(check_cmd_output(cbindgen, "--version").strip().split(" ")[1])
+ log.debug("%s has version %s" % (cbindgen, version))
+ if version >= cbindgen_min_version:
+ return True
+ if not fatal:
+ return False
+
+ die(
+ dedent(
+ """\
+ cbindgen version {} is too old. At least version {} is required.
+
+ Please update using 'cargo install cbindgen --force' or running
+ './mach bootstrap', after removing the existing executable located at
+ {}.
+ """.format(
+ version, cbindgen_min_version, cbindgen
+ )
+ )
+ )
+
+
+# Similar behavior to what check_prog does.
+has_cbindgen_input = depends("CBINDGEN", when=cbindgen_is_needed)(lambda x: x)
+bootstrap_cbindgen = depends(cbindgen_is_needed, has_cbindgen_input)(
+ lambda n, i: n and not i
+)
+
+
+@depends_if(
+ "CBINDGEN",
+ bootstrap_search_path("cbindgen", when=bootstrap_cbindgen),
+ rust_search_path,
+ when=cbindgen_is_needed,
+)
+@checking("for cbindgen")
+@imports(_from="textwrap", _import="dedent")
+def cbindgen(cbindgen_override, bootstrap_search_path, rust_search_path):
+ if cbindgen_override:
+ check_cbindgen_version(cbindgen_override[0], fatal=True)
+ return cbindgen_override[0]
+
+ candidates = []
+ for path in bootstrap_search_path + rust_search_path:
+ candidate = find_program("cbindgen", [path])
+ if not candidate:
+ continue
+ if check_cbindgen_version(candidate):
+ return candidate
+ candidates.append(candidate)
+
+ if not candidates:
+ raise FatalCheckError(
+ dedent(
+ """\
+ Cannot find cbindgen. Please run `mach bootstrap`,
+ `cargo install cbindgen`, ensure that `cbindgen` is on your PATH,
+ or point at an executable with `CBINDGEN`.
+ """
+ )
+ )
+ check_cbindgen_version(candidates[0], fatal=True)
+
+
+set_config("CBINDGEN", cbindgen)
+
+# Bindgen can use rustfmt to format Rust file, but it's not required.
+option(env="RUSTFMT", nargs=1, help="Path to the rustfmt program")
+
+rustfmt = check_prog(
+ "RUSTFMT",
+ ["rustfmt"],
+ paths=rust_search_path,
+ input="RUSTFMT",
+ allow_missing=True,
+)
+
+
+option(
+ "--with-libclang-path",
+ nargs=1,
+ help="Absolute path to a directory containing Clang/LLVM libraries for bindgen (version 3.9.x or above)",
+)
+option(
+ "--with-clang-path",
+ nargs=1,
+ help="Absolute path to a Clang binary for bindgen (version 3.9.x or above)",
+)
+
+
+@depends(
+ "--with-clang-path",
+ configure_cache,
+ c_compiler,
+ cxx_compiler,
+ clang_search_path,
+ target,
+ target_sysroot.path,
+ android_version,
+)
+@checking("for clang for bindgen", lambda x: x.path if x else "not found")
+def bindgen_clang_compiler(
+ clang_path,
+ configure_cache,
+ c_compiler,
+ cxx_compiler,
+ clang_search_path,
+ target,
+ sysroot_path,
+ android_version,
+):
+ # When the target compiler is clang, use that, including flags.
+ if cxx_compiler.type == "clang":
+ if clang_path and clang_path[0] not in (
+ c_compiler.compiler,
+ cxx_compiler.compiler,
+ ):
+ die(
+ "--with-clang-path is not valid when the target compiler is %s",
+ cxx_compiler.type,
+ )
+ return namespace(
+ path=cxx_compiler.compiler,
+ flags=cxx_compiler.flags,
+ )
+ # When the target compiler is clang-cl, use clang in the same directory,
+ # and figure the right flags to use.
+ if cxx_compiler.type == "clang-cl":
+ if clang_path and os.path.dirname(clang_path[0]) != os.path.dirname(
+ cxx_compiler.compiler
+ ):
+ die(
+ "--with-clang-path must point to clang in the same directory "
+ "as the target compiler"
+ )
+ if not clang_path:
+ clang_path = [os.path.join(os.path.dirname(cxx_compiler.compiler), "clang")]
+
+ clang_path = find_program(
+ clang_path[0] if clang_path else "clang++", clang_search_path
+ )
+ if not clang_path:
+ return
+ # Hack before bug 1617793: if the compiler is clang-cl, hack the target
+ if cxx_compiler.type == "clang-cl":
+ target = split_triplet("%s-pc-windows-msvc" % target.raw_cpu)
+ flags = []
+ if sysroot_path:
+ flags.extend(("--sysroot", sysroot_path))
+ info = check_compiler(
+ configure_cache, [clang_path] + flags, "C++", target, android_version
+ )
+ # Usually, one check_compiler pass would be enough, but when cross-compiling
+ # and the host and target don't use the same default C++ standard, we don't
+ # get the --std flag, so try again. This is the same thing as valid_compiler()
+ # does in toolchain.configure.
+ if info.flags:
+ flags += info.flags
+ info = check_compiler(
+ configure_cache, [clang_path] + flags, "C++", target, android_version
+ )
+ return namespace(
+ path=clang_path,
+ flags=flags + info.flags,
+ )
+
+
+@depends("--with-libclang-path", bindgen_clang_compiler, host_library_name_info, host)
+@checking("for libclang for bindgen", lambda x: x if x else "not found")
+@imports("glob")
+@imports(_from="os", _import="pathsep")
+@imports(_from="os.path", _import="split", _as="pathsplit")
+@imports("re")
+def bindgen_libclang_path(libclang_path, clang, library_name_info, host):
+ if not clang:
+ if libclang_path:
+ die(
+ "--with-libclang-path is not valid without a clang compiler "
+ "for bindgen"
+ )
+ return
+
+ # Try to ensure that the clang shared library that bindgen is going
+ # to look for is actually present. The files that we search for
+ # mirror the logic in clang-sys/build.rs.
+ libclang_choices = []
+ if host.os == "WINNT":
+ libclang_choices.append("libclang.dll")
+ libclang_choices.append(
+ "%sclang%s" % (library_name_info.dll.prefix, library_name_info.dll.suffix)
+ )
+ if host.kernel == "Linux":
+ libclang_choices.append("libclang.so.*")
+
+ if host.os == "OpenBSD":
+ libclang_choices.append("libclang.so.*.*")
+
+ candidates = []
+ if not libclang_path:
+ # Try to find libclang_path based on clang search dirs.
+ clang_search_dirs = check_cmd_output(clang.path, "-print-search-dirs")
+ for line in clang_search_dirs.splitlines():
+ name, _, value = line.partition(": =")
+ if host.os == "WINNT" and name == "programs":
+ # On Windows, libclang.dll is in bin/ rather than lib/,
+ # so scan the programs search dirs.
+ # To make matters complicated, clang before version 9 uses `:`
+ # separate between paths (and `;` in newer versions)
+ if pathsep in value:
+ candidates.extend(value.split(pathsep))
+ else:
+ for part in value.split(":"):
+ # Assume that if previous "candidate" was of length 1,
+ # it's a drive letter and the current part is the rest of
+ # the corresponding full path.
+ if candidates and len(candidates[-1]) == 1:
+ candidates[-1] += ":" + part
+ else:
+ candidates.append(part)
+ elif host.os != "WINNT" and name == "libraries":
+ # On other platforms, use the directories from the libraries
+ # search dirs that looks like $something/clang/$version.
+ for dir in value.split(pathsep):
+ dir, version = pathsplit(dir)
+ if re.match(r"[0-9.]+", version):
+ dir, name = pathsplit(dir)
+ if name == "clang":
+ candidates.append(dir)
+ else:
+ candidates.append(libclang_path[0])
+
+ for dir in candidates:
+ for pattern in libclang_choices:
+ log.debug('Trying "%s" in "%s"', pattern, dir)
+ libs = glob.glob(os.path.join(dir, pattern))
+ if libs:
+ return libs[0]
+
+
+@depends(bindgen_clang_compiler, bindgen_libclang_path, build_project)
+def bindgen_config_paths(clang, libclang, build_project):
+ # XXX: we want this code to be run for both Gecko and JS, but we don't
+ # necessarily want to force a bindgen/Rust dependency on JS just yet.
+ # Actually, we don't want to force an error if we're not building the
+ # browser generally. We therefore whitelist the projects that require
+ # bindgen facilities at this point and leave it at that.
+ if build_project in ("browser", "mobile/android"):
+ if not clang:
+ die(
+ "Could not find clang to generate run bindings for C/C++. "
+ "Please install the necessary packages, run `mach bootstrap`, "
+ "or use --with-clang-path to give the location of clang."
+ )
+
+ if not libclang:
+ die(
+ "Could not find libclang to generate rust bindings for C/C++. "
+ "Please install the necessary packages, run `mach bootstrap`, "
+ "or use --with-libclang-path to give the path containing it."
+ )
+
+ if clang and libclang:
+ return namespace(
+ libclang=libclang,
+ libclang_path=os.path.dirname(libclang),
+ clang_path=clang.path,
+ clang_flags=clang.flags,
+ )
+
+
+@depends(bindgen_config_paths.libclang, when=bindgen_config_paths)
+@checking("that libclang is new enough", lambda s: "yes" if s else "no")
+@imports(_from="ctypes", _import="CDLL")
+@imports(_from="textwrap", _import="dedent")
+def min_libclang_version(libclang):
+ try:
+ lib = CDLL(libclang)
+ # We want at least 5.0. The API we test below is enough for that.
+ # Just accessing it should throw if not found.
+ fun = lib.clang_getAddressSpace
+ return True
+ except:
+ die(
+ dedent(
+ """\
+ The libclang located at {} is too old (need at least 5.0).
+
+ Please make sure to update it or point to a newer libclang using
+ --with-libclang-path.
+ """.format(
+ libclang
+ )
+ )
+ )
+ return False
+
+
+set_config("MOZ_LIBCLANG_PATH", bindgen_config_paths.libclang_path)
+set_config("MOZ_CLANG_PATH", bindgen_config_paths.clang_path)
+
+
+@depends(
+ target,
+ target_is_unix,
+ cxx_compiler,
+ bindgen_cflags_android,
+ bindgen_config_paths.clang_flags,
+ all_clang_arm_flags,
+)
+def basic_bindgen_cflags(
+ target, is_unix, compiler_info, android_cflags, clang_flags, all_arm_flags
+):
+ args = [
+ "-x",
+ "c++",
+ "-fno-sized-deallocation",
+ "-fno-aligned-new",
+ "-DTRACING=1",
+ "-DIMPL_LIBXUL",
+ "-DMOZILLA_INTERNAL_API",
+ "-DRUST_BINDGEN",
+ ]
+
+ if is_unix:
+ args += ["-DOS_POSIX=1"]
+
+ if target.os == "Android":
+ args += android_cflags
+
+ args += {
+ "Android": ["-DOS_ANDROID=1"],
+ "DragonFly": ["-DOS_BSD=1", "-DOS_DRAGONFLY=1"],
+ "FreeBSD": ["-DOS_BSD=1", "-DOS_FREEBSD=1"],
+ "GNU": ["-DOS_LINUX=1"],
+ "NetBSD": ["-DOS_BSD=1", "-DOS_NETBSD=1"],
+ "OpenBSD": ["-DOS_BSD=1", "-DOS_OPENBSD=1"],
+ "OSX": ["-DOS_MACOSX=1"],
+ "SunOS": ["-DOS_SOLARIS=1"],
+ "WINNT": [
+ "-DOS_WIN=1",
+ "-DWIN32=1",
+ ],
+ }.get(target.os, [])
+
+ if compiler_info.type == "clang-cl":
+ args += [
+ # To enable the builtin __builtin_offsetof so that CRT wouldn't
+ # use reinterpret_cast in offsetof() which is not allowed inside
+ # static_assert().
+ "-D_CRT_USE_BUILTIN_OFFSETOF",
+ # Enable hidden attribute (which is not supported by MSVC and
+ # thus not enabled by default with a MSVC-compatibile build)
+ # to exclude hidden symbols from the generated file.
+ "-DHAVE_VISIBILITY_HIDDEN_ATTRIBUTE=1",
+ ]
+
+ return args + (clang_flags or []) + (all_arm_flags or [])
+
+
+option(
+ env="BINDGEN_CFLAGS",
+ nargs=1,
+ help="Options bindgen should pass to the C/C++ parser",
+)
+
+
+@depends(basic_bindgen_cflags, "BINDGEN_CFLAGS")
+@checking("bindgen cflags", lambda s: s if s else "no")
+def bindgen_cflags(base_flags, extra_flags):
+ flags = base_flags
+ if extra_flags and len(extra_flags):
+ flags += extra_flags[0].split()
+ return " ".join(flags)
+
+
+set_config("BINDGEN_SYSTEM_FLAGS", bindgen_cflags)
diff --git a/build/moz.configure/bootstrap.configure b/build/moz.configure/bootstrap.configure
new file mode 100644
index 0000000000..5abb27f469
--- /dev/null
+++ b/build/moz.configure/bootstrap.configure
@@ -0,0 +1,326 @@
+# -*- 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/.
+
+option(
+ env="MOZ_FETCHES_DIR",
+ nargs=1,
+ when="MOZ_AUTOMATION",
+ help="Directory containing fetched artifacts",
+)
+
+
+@depends("MOZ_FETCHES_DIR", when="MOZ_AUTOMATION")
+def moz_fetches_dir(value):
+ if value:
+ return value[0]
+
+
+@depends(vcs_checkout_type, milestone.is_nightly, "MOZ_AUTOMATION")
+def bootstrap_default(vcs_checkout_type, is_nightly, automation):
+ if automation:
+ return False
+ # We only enable if building off a VCS checkout of central.
+ if is_nightly and vcs_checkout_type:
+ return True
+
+
+option(
+ "--enable-bootstrap",
+ nargs="*",
+ default=bootstrap_default,
+ help="{Automatically bootstrap or update some toolchains|Disable bootstrap or update of toolchains}",
+)
+
+
+@depends_if("--enable-bootstrap")
+def enable_bootstrap(bootstrap):
+ include = set()
+ exclude = set()
+ for item in bootstrap:
+ if item.startswith("-"):
+ exclude.add(item.lstrip("-"))
+ else:
+ include.add(item)
+
+ def match(name):
+ if name in exclude:
+ return False
+ if include and name in include:
+ return True
+ return not bool(include)
+
+ return match
+
+
+@depends(developer_options, "--enable-bootstrap", moz_fetches_dir)
+def bootstrap_search_path_order(developer_options, bootstrap, moz_fetches_dir):
+ if moz_fetches_dir:
+ log.debug("Prioritizing MOZ_FETCHES_DIR in toolchain path.")
+ return "prepend"
+
+ if bootstrap:
+ log.debug(
+ "Prioritizing mozbuild state dir in toolchain paths because "
+ "bootstrap mode is enabled."
+ )
+ return "maybe-prepend"
+
+ if developer_options:
+ log.debug(
+ "Prioritizing mozbuild state dir in toolchain paths because "
+ "you are not building in release mode."
+ )
+ return "prepend"
+
+ log.debug(
+ "Prioritizing system over mozbuild state dir in "
+ "toolchain paths because you are building in "
+ "release mode."
+ )
+ return "append"
+
+
+toolchains_base_dir = moz_fetches_dir | mozbuild_state_path
+
+
+@dependable
+@imports("os")
+@imports(_from="os", _import="environ")
+def original_path():
+ return environ["PATH"].split(os.pathsep)
+
+
+@depends(host, when="--enable-bootstrap")
+@imports("os")
+@imports("traceback")
+@imports(_from="mozbuild.toolchains", _import="toolchain_task_definitions")
+@imports(_from="__builtin__", _import="Exception")
+def bootstrap_toolchain_tasks(host):
+ prefix = {
+ ("x86_64", "GNU", "Linux"): "linux64",
+ ("x86_64", "OSX", "Darwin"): "macosx64",
+ ("aarch64", "OSX", "Darwin"): "macosx64-aarch64",
+ ("x86_64", "WINNT", "WINNT"): "win64",
+ ("aarch64", "WINNT", "WINNT"): "win64-aarch64",
+ }.get((host.cpu, host.os, host.kernel))
+ try:
+ tasks = toolchain_task_definitions()
+ except Exception as e:
+ message = traceback.format_exc()
+ log.warning(str(e))
+ log.debug(message)
+ return None
+
+ def task_data(t):
+ result = {
+ "index": t.optimization["index-search"],
+ "artifact": t.attributes["toolchain-artifact"],
+ }
+ command = t.attributes.get("toolchain-command")
+ if command:
+ result["command"] = command
+ return result
+
+ # We only want to use toolchains annotated with "local-toolchain". We also limit the
+ # amount of data to what we use, so that trace logs can be more useful.
+ tasks = {
+ k: task_data(t)
+ for k, t in tasks.items()
+ if t.attributes.get("local-toolchain") and "index-search" in t.optimization
+ }
+
+ return namespace(prefix=prefix, tasks=tasks)
+
+
+@template
+def bootstrap_path(path, **kwargs):
+ when = kwargs.pop("when", None)
+ if kwargs:
+ configure_error("bootstrap_path only takes `when` as a keyword argument")
+
+ @depends(
+ enable_bootstrap,
+ toolchains_base_dir,
+ moz_fetches_dir,
+ bootstrap_toolchain_tasks,
+ build_environment,
+ dependable(path),
+ when=when,
+ )
+ @imports("os")
+ @imports("subprocess")
+ @imports("sys")
+ @imports(_from="mozbuild.util", _import="ensureParentDir")
+ @imports(_from="importlib", _import="import_module")
+ @imports(_from="shutil", _import="rmtree")
+ @imports(_from="__builtin__", _import="open")
+ @imports(_from="__builtin__", _import="Exception")
+ def bootstrap_path(
+ bootstrap, toolchains_base_dir, moz_fetches_dir, tasks, build_env, path
+ ):
+ if not path:
+ return
+ path_parts = path.split("/")
+ path_prefix = ""
+ # Small hack until clang-tidy stops being a separate toolchain in a
+ # weird location.
+ if path_parts[0] == "clang-tools":
+ path_prefix = path_parts.pop(0)
+
+ def try_bootstrap(exists):
+ if not tasks:
+ return False
+ prefixes = [""]
+ if tasks.prefix:
+ prefixes.insert(0, "{}-".format(tasks.prefix))
+ for prefix in prefixes:
+ label = "toolchain-{}{}".format(prefix, path_parts[0])
+ task = tasks.tasks.get(label)
+ if task:
+ break
+ log.debug("Trying to bootstrap %s", label)
+ if not task:
+ return False
+ task_index = task["index"]
+ log.debug("Resolved %s to %s", label, task_index[0])
+ task_index = task_index[0].split(".")[-1]
+ artifact = task["artifact"]
+ # `mach artifact toolchain` doesn't support authentication for
+ # private artifacts. Some toolchains may provide a command that can be
+ # used for local production of the artifact.
+ command = None
+ if not artifact.startswith("public/"):
+ command = task.get("command")
+ if not command:
+ log.debug("Cannot bootstrap %s: not a public artifact", label)
+ return False
+ index_file = os.path.join(toolchains_base_dir, "indices", path_parts[0])
+ try:
+ with open(index_file) as fh:
+ index = fh.read().strip()
+ except Exception:
+ # On automation, if there's an artifact in MOZ_FETCHES_DIR, we assume it's
+ # up-to-date.
+ index = task_index if moz_fetches_dir else None
+ if index == task_index and exists:
+ log.debug("%s is up-to-date", label)
+ return True
+ # Manually import with import_module so that we can gracefully disable bootstrap
+ # when e.g. building from a js standalone tarball, that doesn't contain the
+ # taskgraph code. In those cases, `mach artifact toolchain --from-build` would
+ # also fail.
+ task_id = None
+ if not command:
+ try:
+ IndexSearch = import_module(
+ "gecko_taskgraph.optimize.strategies"
+ ).IndexSearch
+ except Exception:
+ log.debug("Cannot bootstrap %s: missing taskgraph module", label)
+ return False
+ task_id = IndexSearch().should_replace_task(
+ task, {}, None, task["index"]
+ )
+ if task_id:
+ # If we found the task in the index, use the `mach artifact toolchain`
+ # fast path.
+ command = [
+ "artifact",
+ "toolchain",
+ "--from-task",
+ f"{task_id}:{artifact}",
+ ]
+ elif command:
+ # For private local toolchains, run the associated command.
+ command = (
+ [
+ "python",
+ "--virtualenv",
+ "build",
+ os.path.join(
+ build_env.topsrcdir,
+ "taskcluster/scripts/misc",
+ command["script"],
+ ),
+ ]
+ + command["arguments"]
+ + [path_parts[0]]
+ )
+ # Clean up anything that was bootstrapped previously before going
+ # forward. In other cases, that's taken care of by mach artifact toolchain.
+ rmtree(
+ os.path.join(toolchains_base_dir, path_prefix, path_parts[0]),
+ ignore_errors=True,
+ )
+ else:
+ # Otherwise, use the slower path, which will print a better error than
+ # we would be able to.
+ command = ["artifact", "toolchain", "--from-build", label]
+
+ log.info(
+ "%s bootstrapped toolchain in %s",
+ "Updating" if exists else "Installing",
+ os.path.join(toolchains_base_dir, path_prefix, path_parts[0]),
+ )
+ os.makedirs(os.path.join(toolchains_base_dir, path_prefix), exist_ok=True)
+ subprocess.run(
+ [
+ sys.executable,
+ os.path.join(build_env.topsrcdir, "mach"),
+ "--log-no-times",
+ ]
+ + command,
+ cwd=os.path.join(toolchains_base_dir, path_prefix),
+ check=True,
+ )
+ ensureParentDir(index_file)
+ with open(index_file, "w") as fh:
+ fh.write(task_index)
+ return True
+
+ path = os.path.join(toolchains_base_dir, path_prefix, *path_parts)
+ if bootstrap and bootstrap(path_parts[0]):
+ try:
+ if not try_bootstrap(os.path.exists(path)):
+ # If there aren't toolchain artifacts to use for this build,
+ # don't return a path.
+ return None
+ except Exception as e:
+ log.error("%s", e)
+ die("If you can't fix the above, retry with --disable-bootstrap.")
+ # We re-test whether the path exists because it may have been created by
+ # try_bootstrap. Automation will not have gone through the bootstrap
+ # process, but we want to return the path if it exists.
+ if os.path.exists(path):
+ return path
+
+ return bootstrap_path
+
+
+@template
+def bootstrap_search_path(path, paths=original_path, **kwargs):
+ @depends(
+ enable_bootstrap,
+ bootstrap_path(path, **kwargs),
+ bootstrap_search_path_order,
+ paths,
+ original_path,
+ )
+ def bootstrap_search_path(bootstrap, path, order, paths, original_path):
+ if paths is None:
+ paths = original_path
+ if not path:
+ return paths
+ if order == "maybe-prepend":
+ if bootstrap(path.split("/")[0]):
+ order = "prepend"
+ else:
+ order = "append"
+ if order == "prepend":
+ return [path] + paths
+ return paths + [path]
+
+ return bootstrap_search_path
diff --git a/build/moz.configure/checks.configure b/build/moz.configure/checks.configure
new file mode 100644
index 0000000000..73e8b0a886
--- /dev/null
+++ b/build/moz.configure/checks.configure
@@ -0,0 +1,206 @@
+# -*- 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/.
+
+# Templates implementing some generic checks.
+# ==============================================================
+
+# Declare some exceptions. This is cumbersome, but since we shouldn't need a
+# lot of them, let's stack them all here. When adding a new one, put it in the
+# _declare_exceptions template, and add it to the return statement. Then
+# destructure in the assignment below the function declaration.
+
+
+@template
+@imports(_from="__builtin__", _import="Exception")
+def _declare_exceptions():
+ class FatalCheckError(Exception):
+ """An exception to throw from a function decorated with @checking.
+ It will result in calling die() with the given message.
+ Debugging messages emitted from the decorated function will also be
+ printed out."""
+
+ return (FatalCheckError,)
+
+
+(FatalCheckError,) = _declare_exceptions()
+
+del _declare_exceptions
+
+# Helper to display "checking" messages
+# @checking('for foo')
+# def foo():
+# return 'foo'
+# is equivalent to:
+# def foo():
+# log.info('checking for foo... ')
+# ret = foo
+# log.info(ret)
+# return ret
+# This can be combined with e.g. @depends:
+# @depends(some_option)
+# @checking('for something')
+# def check(value):
+# ...
+# An optional callback can be given, that will be used to format the returned
+# value when displaying it.
+
+
+@template
+def checking(what, callback=None):
+ def decorator(func):
+ def wrapped(*args, **kwargs):
+ log.info("checking %s... ", what)
+ with log.queue_debug():
+ error, ret = None, None
+ try:
+ ret = func(*args, **kwargs)
+ except FatalCheckError as e:
+ error = str(e)
+ display_ret = callback(ret) if callback else ret
+ if display_ret is True:
+ log.info("yes")
+ elif display_ret is False or display_ret is None:
+ log.info("no")
+ else:
+ log.info(display_ret)
+ if error is not None:
+ die(error)
+ return ret
+
+ return wrapped
+
+ return decorator
+
+
+# Template to check for programs in $PATH.
+# - `var` is the name of the variable that will be set with `set_config` when
+# the program is found.
+# - `progs` is a list (or tuple) of program names that will be searched for.
+# It can also be a reference to a @depends function that returns such a
+# list. If the list is empty and there is no input, the check is skipped.
+# - `what` is a human readable description of what is being looked for. It
+# defaults to the lowercase version of `var`.
+# - `input` is a string reference to an existing option or a reference to a
+# @depends function resolving to explicit input for the program check.
+# The default is to create an option for the environment variable `var`.
+# This argument allows to use a different kind of option (possibly using a
+# configure flag), or doing some pre-processing with a @depends function.
+# - `allow_missing` indicates whether not finding the program is an error.
+# - `paths` is a list of paths or @depends function returning a list of paths
+# that will cause the given path(s) to be searched rather than $PATH. Input
+# paths may either be individual paths or delimited by os.pathsep, to allow
+# passing $PATH (for example) as an element.
+# - `bootstrap` is a path relative to the bootstrap root path (e.g ~/.mozbuild)
+# where to find the program if it's bootstrapped.
+# - `validate` is a callback function that takes a path and returns True if
+# the program at that location is appropriate or not, or False if not.
+# when the callback returns False, check_prog ignores the program and goes
+# on to the next from the `progs` list.
+#
+# - `bootstrap_search_path` is not an argument that users of the template are
+# supposed to pass. See the override of check_prog in top-level moz.configure.
+#
+# The simplest form is:
+# check_prog('PROG', ('a', 'b'))
+# This will look for 'a' or 'b' in $PATH, and set_config PROG to the one
+# it can find. If PROG is already set from the environment or command line,
+# use that value instead.
+@template
+@imports(_from="mozbuild.shellutil", _import="quote")
+def check_prog(
+ var,
+ progs,
+ what=None,
+ input=None,
+ allow_missing=False,
+ paths=None,
+ allow_spaces=False,
+ bootstrap=None,
+ when=None,
+ validate=None,
+ bootstrap_search_path=None,
+):
+ if input is not None:
+ # Wrap input with type checking and normalization.
+ @depends(input, when=when)
+ def input(value):
+ if not value:
+ return
+ if isinstance(value, str):
+ return (value,)
+ if isinstance(value, (tuple, list)) and len(value) == 1:
+ return value
+ configure_error(
+ "input must resolve to a tuple or a list with a "
+ "single element, or a string"
+ )
+
+ else:
+ option(
+ env=var,
+ nargs=1,
+ when=when,
+ help="Path to %s" % (what or "the %s program" % var.lower()),
+ )
+ input = var
+ what = what or var.lower()
+
+ # Trick to make a @depends function out of an immediate value.
+ progs = dependable(progs)
+ paths = dependable(paths)
+ allow_missing = dependable(allow_missing)
+
+ if bootstrap:
+ if input is var:
+ # A when is needed when depending on an option, so normalize
+ # to a function that can used without.
+ has_input = depends(input, when=when)(lambda x: x)
+ else:
+ has_input = input
+ # We don't want to bootstrap when an explicit value was given as input.
+ if when:
+ bootstrap_when = depends(when, has_input)(lambda w, i: w and not i)
+ else:
+ bootstrap_when = depends(has_input)(lambda i: not i)
+ paths = bootstrap_search_path(bootstrap, paths, when=bootstrap_when)
+
+ # Avoid displaying the "Checking for" message when the inputs are such
+ # that we don't actually want anything to be checked. It is a bit
+ # convoluted because of how `when` works.
+ # We first wrap all the inputs except allow_missing (which doesn't count
+ # for whether to display the "Checking for" message).
+ @depends_if(input, progs, paths, when=when)
+ def inputs(input, progs, paths):
+ if progs is None:
+ progs = ()
+
+ if not isinstance(progs, (tuple, list)):
+ configure_error("progs must resolve to a list or tuple!")
+
+ return namespace(value=input, progs=progs, paths=paths)
+
+ @depends(inputs, allow_missing, when=inputs)
+ @checking("for %s" % what, lambda x: quote(x) if x else "not found")
+ def check(inputs, allow_missing):
+ value = inputs.value
+ progs = inputs.progs
+ paths = inputs.paths
+
+ for prog in value or progs:
+ log.debug("%s: Looking for %s", var.lower(), quote(prog))
+ result = find_program(prog, paths, allow_spaces)
+ if validate and result and not validate(result):
+ log.debug("%s: %s found but didn't work", var.lower(), quote(result))
+ continue
+ if result:
+ return result
+
+ if not allow_missing or value:
+ raise FatalCheckError("Cannot find %s" % what)
+
+ set_config(var, check)
+
+ return check
diff --git a/build/moz.configure/compile-checks.configure b/build/moz.configure/compile-checks.configure
new file mode 100644
index 0000000000..d40a6b40af
--- /dev/null
+++ b/build/moz.configure/compile-checks.configure
@@ -0,0 +1,309 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+# Generates a test program and attempts to compile it. In case of failure, the
+# resulting check will return None. If the test program succeeds, it will return
+# the output of the test program.
+# - `includes` are the includes (as file names) that will appear at the top of
+# the generated test program.
+# - `body` is the code that will appear in the main function of the generated
+# test program. `return 0;` is appended to the function body automatically.
+# - `language` is the language selection, so that the appropriate compiler is
+# used.
+# - `flags` are the flags to be passed to the compiler, in addition to `-c`.
+# - `check_msg` is the message to be printed to accompany compiling the test
+# program.
+@template
+def try_compile(
+ includes=None,
+ body="",
+ language="C++",
+ flags=None,
+ check_msg=None,
+ when=None,
+ onerror=lambda: None,
+):
+ compiler = {
+ "C": c_compiler,
+ "C++": cxx_compiler,
+ }[language]
+
+ return compiler.try_compile(
+ includes, body, flags, check_msg, when=when, onerror=onerror
+ )
+
+
+# Checks for the presence of the given header on the target system by compiling
+# a test program including that header. The return value of the template is a
+# check function returning True if the header is present, and None if it is not.
+# The value of this check function is also used to set a variable (with set_define)
+# corresponding to the checked header. For instance, HAVE_MALLOC_H will be set in
+# defines if check_header if called with 'malloc.h' as input and malloc.h is
+# present on the target.
+# - `header` is the header, as a file name, to check for.
+# - `language` is the language selection, so that the appropriate compiler is
+# used.
+# - `flags` are the flags to be passed to the compiler, in addition to `-c`.
+# - `includes` are additional includes, as file names, to appear before the
+# header checked for.
+# - `when` is a depends function that if present will make performing the check
+# conditional on the value of that function.
+@template
+def check_header(
+ header, language="C++", flags=None, includes=None, when=None, onerror=lambda: None
+):
+ if when is None:
+ when = always
+
+ if includes:
+ includes = includes[:]
+ else:
+ includes = []
+ includes.append(header)
+
+ have_header = try_compile(
+ includes=includes,
+ language=language,
+ flags=flags,
+ check_msg="for %s" % header,
+ when=when,
+ onerror=onerror,
+ )
+ header_var = "HAVE_%s" % (
+ header.upper().replace("-", "_").replace("/", "_").replace(".", "_")
+ )
+ set_define(header_var, have_header)
+ return have_header
+
+
+# A convenience wrapper for check_header for checking multiple headers.
+# returns an array of the resulting checks in order corresponding to the
+# provided headers.
+# - `headers` are the headers to be checked.
+# - `kwargs` are keyword arguments passed verbatim to check_header.
+
+
+@template
+def check_headers(*headers, **kwargs):
+ checks = []
+ for header in headers:
+ checks.append(check_header(header, **kwargs))
+ return checks
+
+
+@depends(linker_ldflags, target.kernel)
+def check_symbol_flags(linker_ldflags, kernel):
+ if kernel == "WINNT":
+ # The build doesn't use the compiler to link things as of writing,
+ # but some compilation checks do. When using clang-cl, the only
+ # linker we really support is lld.link, but clang-cl defaults to
+ # link.exe (even when cross-compiling). So we force the use of
+ # lld.link for the linkage checks.
+ return ["-fuse-ld=lld"]
+ return linker_ldflags
+
+
+# Checks for the presence of the given symbol on the target system by compiling
+# a test program. The return value of the template is a check function
+# returning True if the symbol can be found, and None if it is not.
+@template
+def check_symbol(symbol, language="C", flags=None, when=None, onerror=lambda: None):
+ if when is None:
+ when = always
+
+ compiler, extern_c = {
+ "C": (c_compiler, ""),
+ "C++": (cxx_compiler, 'extern "C" '),
+ }[language]
+
+ # Stolen from autoconf 2.13 ; might be irrelevant now, but it doesn't hurt to
+ # keep using a char return type.
+ comment = [
+ "/* Override any gcc2 internal prototype to avoid an error. */",
+ "/* We use char because int might match the return type of a gcc2",
+ " builtin and then its argument prototype would still apply. */",
+ ]
+
+ if flags:
+
+ @depends(check_symbol_flags, dependable(flags))
+ def flags(base_flags, extra_flags):
+ if base_flags and extra_flags:
+ return base_flags + list(extra_flags)
+ if extra_flags:
+ return extra_flags
+ return base_flags
+
+ else:
+ flags = check_symbol_flags
+
+ return compiler.try_run(
+ header=comment + ["%schar %s();" % (extern_c, symbol)],
+ body="%s();" % symbol,
+ flags=flags,
+ check_msg="for %s" % symbol,
+ when=when,
+ onerror=onerror,
+ )
+
+
+# Determine whether to add a given flag to the given lists of flags for C or
+# C++ compilation.
+# - `flag` is the flag to test
+# - `flags_collection` is a @depends function for a namespace of lists of
+# C/C++ compiler flags to add to.
+# - `test_flags` is a list of flags to pass to the compiler instead of merely
+# passing `flag`. This is especially useful for checking warning flags. If
+# this list is empty, `flag` will be passed on its own.
+# - `compiler` (optional) is the compiler to test against (c_compiler or
+# cxx_compiler, from toolchain.configure). When omitted, both compilers
+# are tested; the list of flags added to is dependent on the compiler tested.
+# - `when` (optional) is a @depends function or option name conditioning
+# when the warning flag is wanted.
+# - `check`, when not set, skips checking whether the flag is supported and
+# adds it to the list of flags unconditionally.
+@template
+def check_and_add_flags(
+ flag, flags_collection, test_flags, compiler=None, when=None, check=True
+):
+ if compiler is not None:
+ compilers = (compiler,)
+ else:
+ compilers = (c_compiler, cxx_compiler)
+
+ if when is None:
+ when = always
+
+ results = []
+
+ if test_flags:
+ flags = test_flags
+ else:
+ flags = [flag]
+
+ for c in compilers:
+ assert c in {c_compiler, cxx_compiler, host_c_compiler, host_cxx_compiler}
+ lang, list_of_flags = {
+ c_compiler: ("C", flags_collection.cflags),
+ cxx_compiler: ("C++", flags_collection.cxxflags),
+ host_c_compiler: ("host C", flags_collection.host_cflags),
+ host_cxx_compiler: ("host C++", flags_collection.host_cxxflags),
+ }[c]
+
+ result = when
+
+ if check:
+
+ @depends(c, dependable(flags))
+ def flags(c, flags):
+ # Don't error out just because clang complains about other things.
+ if c.type in ("clang", "clang-cl"):
+ flags += ["-Wno-error=unused-command-line-argument"]
+
+ return flags
+
+ result = c.try_compile(
+ flags=flags,
+ when=result,
+ check_msg="whether the %s compiler supports %s" % (lang, flag),
+ )
+
+ @depends(result, list_of_flags)
+ def maybe_add_flag(result, list_of_flags):
+ if result:
+ list_of_flags.append(flag)
+
+ results.append(result)
+
+ return tuple(results)
+
+
+@dependable
+def warnings_flags():
+ return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[])
+
+
+# Tests whether GCC or clang support the given warning flag, and if it is,
+# add it to the list of warning flags for the build.
+# - `warning` is the warning flag (e.g. -Wfoo)
+# - `compiler` (optional) is the compiler to test against (c_compiler or
+# cxx_compiler, from toolchain.configure). When omitted, both compilers
+# are tested.
+# - `when` (optional) is a @depends function or option name conditioning
+# when the warning flag is wanted.
+# - `check`, when not set, skips checking whether the flag is supported and
+# adds it to the list of warning flags unconditionally. This is only meant
+# for add_warning().
+@template
+def check_and_add_warning(warning, compiler=None, when=None, check=True):
+ # GCC and clang will fail if given an unknown warning option like
+ # -Wfoobar. But later versions won't fail if given an unknown negated
+ # warning option like -Wno-foobar. So when we are checking for support
+ # of a negated warning option, we actually test the positive form, but
+ # add the negated form to the flags variable.
+ if warning.startswith("-Wno-") and not warning.startswith("-Wno-error="):
+ flags = ["-Werror", "-W" + warning[5:]]
+ elif warning.startswith("-Werror="):
+ flags = [warning]
+ else:
+ flags = ["-Werror", warning]
+
+ return check_and_add_flags(
+ warning, warnings_flags, flags, compiler=compiler, when=when, check=check
+ )
+
+
+# Add the given warning to the list of warning flags for the build.
+# - `warning` is the warning flag (e.g. -Wfoo)
+# - `compiler` (optional) is the compiler to add the flag for (c_compiler or
+# cxx_compiler, from toolchain.configure). When omitted, the warning flag
+# is added for both compilers.
+# - `when` (optional) is a @depends function or option name conditioning
+# when the warning flag is wanted.
+
+
+@template
+def add_warning(warning, compiler=None, when=None):
+ check_and_add_warning(warning, compiler, when, check=False)
+
+
+# Like the warning checks above, but for general compilation flags.
+@dependable
+def compilation_flags():
+ return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[])
+
+
+# Tests whether GCC or clang support the given compilation flag; if the flag
+# is supported, add it to the list of compilation flags for the build.
+# - `flag` is the flag to test
+# - `compiler` (optional) is the compiler to test against (c_compiler or
+# cxx_compiler, from toolchain.configure). When omitted, both compilers
+# are tested.
+# - `when` (optional) is a @depends function or option name conditioning
+# when the warning flag is wanted.
+# - `check`, when not set, skips checking whether the flag is supported and
+# adds it to the list of flags unconditionally. This is only meant for
+# add_flag().
+@template
+def check_and_add_flag(flag, compiler=None, when=None, check=True):
+ flags = ["-Werror", flag]
+
+ return check_and_add_flags(
+ flag, compilation_flags, flags, compiler=compiler, when=when, check=check
+ )
+
+
+# Add the given flag to the list of flags for the build.
+# - `flag` is the flag (e.g. -fno-sized-deallocation)
+# - `compiler` (optional) is the compiler to add the flag for (c_compiler or
+# cxx_compiler, from toolchain.configure). When omitted, the flag is added
+# for both compilers.
+# - `when` (optional) is a @depends function or option name conditioning
+# when the flag is wanted.
+@template
+def add_flag(warning, compiler=None, when=None):
+ check_and_add_flag(warning, compiler, when, check=False)
diff --git a/build/moz.configure/compilers-util.configure b/build/moz.configure/compilers-util.configure
new file mode 100644
index 0000000000..0f4c4fb2ee
--- /dev/null
+++ b/build/moz.configure/compilers-util.configure
@@ -0,0 +1,148 @@
+# -*- 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/.
+
+
+@template
+@imports("textwrap")
+@imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
+def compiler_class(compiler, host_or_target):
+ is_target = host_or_target is target
+
+ class Compiler(SandboxDependsFunction):
+ # Generates a test program and attempts to compile it. In case of
+ # failure, the resulting check will return None. If the test program
+ # succeeds, it will return the output of the test program.
+ # - `includes` are the includes (as file names) that will appear at the
+ # top of the generated test program.
+ # - `body` is the code that will appear in the main function of the
+ # generated test program. `return 0;` is appended to the function
+ # body automatically.
+ # - `flags` are the flags to be passed to the compiler, in addition to
+ # `-c`.
+ # - `check_msg` is the message to be printed to accompany compiling the
+ # test program.
+ def try_compile(
+ self,
+ includes=None,
+ body="",
+ flags=None,
+ check_msg=None,
+ when=None,
+ onerror=lambda: None,
+ ):
+ @depends(dependable(flags))
+ def flags(flags):
+ flags = list(flags or [])
+ flags.append("-c")
+ return flags
+
+ @depends(dependable(includes))
+ def header(includes):
+ includes = includes or []
+ return ["#include <%s>" % f for f in includes]
+
+ return self.try_run(
+ header=header,
+ body=body,
+ flags=flags,
+ check_msg=check_msg,
+ when=when,
+ onerror=onerror,
+ )
+
+ # Generates a test program and run the compiler against it. In case of
+ # failure, the resulting check will return None.
+ # - `header` is code that will appear at the top of the generated test
+ # program.
+ # - `body` is the code that will appear in the main function of the
+ # generated test program. `return 0;` is appended to the function
+ # body automatically.
+ # - `flags` are the flags to be passed to the compiler.
+ # - `check_msg` is the message to be printed to accompany compiling the
+ # test program.
+ # - `onerror` is a function called when the check fails.
+ def try_run(
+ self,
+ header=None,
+ body="",
+ flags=None,
+ check_msg=None,
+ when=None,
+ onerror=lambda: None,
+ ):
+ source = textwrap.dedent(
+ """\
+ int
+ main(void)
+ {
+ %s
+ ;
+ return 0;
+ }
+ """
+ % body
+ )
+
+ if check_msg:
+
+ def checking_fn(fn):
+ return checking(check_msg)(fn)
+
+ else:
+
+ def checking_fn(fn):
+ return fn
+
+ # We accept onerror being a @depends function that returns a callable.
+ # So, create a similar @depends function when it's not already one.
+ if not isinstance(onerror, SandboxDependsFunction):
+ onerror = dependable(lambda: onerror)
+
+ @depends(
+ self,
+ dependable(flags),
+ extra_toolchain_flags,
+ dependable(header),
+ onerror,
+ configure_cache,
+ when=when,
+ )
+ @checking_fn
+ def func(
+ compiler,
+ flags,
+ extra_flags,
+ header,
+ onerror,
+ configure_cache,
+ ):
+ flags = list(flags or [])
+ if is_target:
+ flags += extra_flags or []
+ header = header or ""
+ if isinstance(header, (list, tuple)):
+ header = "\n".join(header)
+ if header:
+ header += "\n"
+
+ if (
+ try_invoke_compiler(
+ configure_cache,
+ [compiler.compiler] + compiler.flags,
+ compiler.language,
+ header + source,
+ flags,
+ onerror=onerror,
+ wrapper=compiler.wrapper,
+ )
+ is not None
+ ):
+ return True
+
+ return func
+
+ compiler.__class__ = Compiler
+ return compiler
diff --git a/build/moz.configure/flags.configure b/build/moz.configure/flags.configure
new file mode 100644
index 0000000000..d5526dc930
--- /dev/null
+++ b/build/moz.configure/flags.configure
@@ -0,0 +1,145 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# We support C++14, but we don't want to enable the sized deallocation
+# facilities in C++14 yet.
+check_and_add_flag("-fno-sized-deallocation", compiler=cxx_compiler)
+# Likewise for C++17 and aligned allocation. It's not immediately obvious
+# from the clang and GCC documentation, but they both support this.
+check_and_add_flag("-fno-aligned-new", compiler=cxx_compiler)
+
+# Please keep these last in this file.
+add_old_configure_assignment("_COMPILATION_CFLAGS", compilation_flags.cflags)
+add_old_configure_assignment("_COMPILATION_CXXFLAGS", compilation_flags.cxxflags)
+add_old_configure_assignment("_COMPILATION_HOST_CFLAGS", compilation_flags.host_cflags)
+add_old_configure_assignment(
+ "_COMPILATION_HOST_CXXFLAGS", compilation_flags.host_cxxflags
+)
+
+
+option(
+ "--disable-new-pass-manager",
+ help="Use the legacy LLVM pass manager in clang builds",
+)
+
+
+@depends(
+ "--enable-new-pass-manager",
+ c_compiler,
+ host,
+ target,
+ "MOZ_PGO",
+ enable_fuzzing,
+ ubsan,
+)
+def pass_manager(enabled, compiler, host, target, pgo, enable_fuzzing, ubsan):
+ if compiler.type not in ("clang", "clang-cl"):
+ return None
+
+ # As of clang 13, the default pass manager is the new one.
+ if compiler.version >= "13.0.0":
+ if enabled:
+ return namespace(flags=None, enabled=True)
+ if compiler.type == "clang":
+ return namespace(flags=["-flegacy-pass-manager"], enabled=False)
+ if compiler.type == "clang-cl":
+ return namespace(flags=["-Xclang", "-flegacy-pass-manager"], enabled=False)
+
+ if not enabled:
+ if compiler.version >= "15.0.0":
+ die("--disable-new-pass-manager is only supported with clang < 15")
+ return None
+ if compiler.version < "9.0.0":
+ if enabled.origin != "default":
+ die("--enable-new-pass-manager is only supported with clang >= 9")
+ return None
+
+ if host.os == "OSX":
+ # Some native Mac builds hang with the new pass manager. Given the
+ # inability to test in CI, don't take the risk of further breakage.
+ if enabled.origin != "default":
+ die(
+ "--enable-new-pass-manager causes problems on mac hosts with clang < 13"
+ )
+ return None
+ if target.os == "OSX" and not pgo:
+ # Also disable when cross-compiling to Mac, because plain-ish opt
+ # builds hang. Variants like asan and ccov work fine, but it would be
+ # too tedious to test them all here. PGO is the only thing that matters
+ # enough to make an exception for.
+ if enabled.origin != "default":
+ die(
+ "--enable-new-pass-manager causes problems on mac builds with clang < 13"
+ )
+ return None
+ if enable_fuzzing and compiler.version < "10.0.0":
+ # Clang 9 does not seem to play well with libFuzzer
+ if enabled.origin != "default":
+ die(
+ "--enable-new-pass-manager causes problems on fuzzing builds with clang < 10"
+ )
+ return None
+ if ubsan and compiler.version == "10.0.0":
+ # Clang 10.0.0 hangs with some ubsan-inserted code constructs.
+ # This was fixed in 10.0.1 (https://llvm.org/pr45835)
+ if enabled.origin != "default":
+ die(
+ "--enable-new-pass-manager causes problems with ubsan builds with clang 10.0.0"
+ )
+ return None
+ if compiler.type == "clang":
+ return namespace(flags=["-fexperimental-new-pass-manager"], enabled=True)
+ elif compiler.type == "clang-cl":
+ return namespace(
+ flags=["-Xclang", "-fexperimental-new-pass-manager"], enabled=True
+ )
+
+
+set_config("MOZ_PASS_MANAGER_FLAGS", pass_manager.flags)
+
+
+# Try to make builds more reproducible and allow sharing built artifacts across
+# source and object directories by using -ffile-prefix-map and friends. To
+# "unwind" the prefix maps, use:
+#
+# (gdb) set substitute-path /topsrcdir/ $topsrcdir/
+#
+# (lldb) settings set target.source-map /topobjdir/ $topobjdir/
+#
+# See, for example, https://lldb.llvm.org/use/map.html.
+@depends(
+ path_remapping,
+ path_remappings,
+ c_compiler,
+)
+@imports(_from="os", _import="sep")
+def file_prefix_map_flags(path_remapping, path_remappings, compiler):
+ if "c" not in path_remapping:
+ return []
+
+ if (compiler.type == "gcc" and compiler.version < "8.1") or (
+ compiler.type in ("clang", "clang-cl") and compiler.version < "10.0.0"
+ ):
+ die(
+ f"Compiler of type {compiler.type} and version {compiler.version} "
+ "does not support --enable-path-remapping."
+ )
+
+ flags = []
+ for old, new in path_remappings:
+ # We would prefer to use just -ffile-prefix-map, but clang-cl doesn't
+ # seem to recognize it.
+ for flag in ("-fdebug-prefix-map", "-fmacro-prefix-map"):
+ flag = f"{flag}={old}={new}"
+ if compiler.type in ("gcc", "clang"):
+ flags.append(flag)
+ elif compiler.type == "clang-cl":
+ flags.extend(["-Xclang", flag])
+
+ return flags
+
+
+set_config("MOZ_FILE_PREFIX_MAP_FLAGS", file_prefix_map_flags)
diff --git a/build/moz.configure/headers.configure b/build/moz.configure/headers.configure
new file mode 100644
index 0000000000..5332c7365f
--- /dev/null
+++ b/build/moz.configure/headers.configure
@@ -0,0 +1,119 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Check for headers defining standard int types.
+check_header("stdint.h")
+have_inttypes = check_header("inttypes.h")
+
+# Assume we have ansi C header files available.
+set_define("STDC_HEADERS", True)
+
+set_config("HAVE_INTTYPES_H", have_inttypes)
+
+building_linux = depends(target)(lambda target: target.kernel == "Linux")
+
+have_malloc = check_header("malloc.h")
+
+check_header("alloca.h")
+
+add_old_configure_assignment("HAVE_MALLOC_H", have_malloc)
+
+check_headers(
+ "sys/byteorder.h",
+ "getopt.h",
+ "unistd.h",
+ "nl_types.h",
+ "cpuid.h",
+ "fts.h",
+)
+
+# These are all the places some variant of statfs can be hiding.
+check_headers(
+ "sys/statvfs.h",
+ "sys/statfs.h",
+ "sys/vfs.h",
+ "sys/mount.h",
+)
+
+# Quota support
+# Check for both the header and quotactl() because Android headers can have the
+# header but not quotactl().
+set_define(
+ "HAVE_SYS_QUOTA_H",
+ try_compile(
+ includes=["sys/quota.h"],
+ body="quotactl(0, nullptr, 0, (caddr_t)nullptr);",
+ check_msg="for sys/quota.h",
+ ),
+)
+check_header("linux/quota.h", includes=["sys/socket.h"], when=building_linux)
+
+# SCTP support - needs various network include headers
+check_headers(
+ "linux/if_addr.h",
+ "linux/rtnetlink.h",
+ includes=["sys/socket.h"],
+ when=building_linux,
+)
+
+check_header("sys/queue.h")
+
+check_headers(
+ "sys/types.h",
+ "netinet/in.h",
+ "byteswap.h",
+)
+
+# memfd_create(2) -- Note that older versions of the Linux man-pages
+# project incorrectly cite <sys/memfd.h>, which doesn't exist; this
+# was fixed in the man-pages-5.00 release.
+set_define(
+ "HAVE_MEMFD_CREATE",
+ try_compile(
+ includes=["sys/mman.h"],
+ body='memfd_create("", 0);',
+ check_msg="for memfd_create in sys/mman.h",
+ ),
+)
+
+# TODO: Move these checks to file specific to --enable-project=js.
+have_perf_event_h = check_header("linux/perf_event.h", when=building_linux)
+
+option(
+ "--with-linux-headers",
+ help="location where the Linux kernel headers can be found",
+ nargs=1,
+)
+
+passed_linux_header_flags = depends_if("--with-linux-headers")(
+ lambda v: ["-I%s" % v[0]]
+)
+
+
+@depends(
+ try_compile(
+ includes=["asm/unistd.h"],
+ body="return sizeof(__NR_perf_event_open);",
+ flags=passed_linux_header_flags,
+ check_msg="for perf_event_open system call",
+ ),
+ when=have_perf_event_h,
+)
+def have_perf_event_open(have_perf_event_open):
+ if have_perf_event_open:
+ return True
+
+
+set_config("HAVE_LINUX_PERF_EVENT_H", have_perf_event_open)
+
+
+@depends(passed_linux_header_flags, have_perf_event_open)
+def linux_headers_includes(passed_linux_header_flags, have_perf_event_open):
+ if have_perf_event_open and passed_linux_header_flags:
+ return passed_linux_header_flags[0]
+
+
+set_config("LINUX_HEADERS_INCLUDES", linux_headers_includes)
diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure
new file mode 100644
index 0000000000..53bbc4203f
--- /dev/null
+++ b/build/moz.configure/init.configure
@@ -0,0 +1,1307 @@
+# -*- 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/.
+
+include("util.configure")
+include("checks.configure")
+
+# Make `toolkit` available when toolkit/moz.configure is not included.
+toolkit = dependable(None)
+# Likewise with `bindgen_config_paths` when
+# build/moz.configure/bindgen.configure is not included.
+bindgen_config_paths = dependable(None)
+
+
+@depends("--help")
+def build_environment(_):
+ topobjdir = os.path.realpath(".")
+ topsrcdir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+ dist = os.path.join(topobjdir, "dist")
+
+ return namespace(
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ dist=dist,
+ )
+
+
+@depends(build_environment)
+@imports(_import="json")
+@imports(_from="pathlib", _import="Path")
+def configure_cache(build_environment):
+ """
+ This cache is used to cache the results of compiler invocations
+ between different runs of configure and save them to the disk, which
+ results in a significant speed up of subsequent configure runs (15%-50%).
+
+ It is not currently thread safe because it is just a simple dictionary
+ wrapped in a dummy class (ConfigureCache). It could be improved to be
+ thread safe in the future, if we ever make configure parallelized, but
+ for now there is no advantage to doing so.
+ """
+
+ class ConfigureCache(dict):
+ pass
+
+ cache_file = Path(build_environment.topobjdir) / "configure.cache"
+ if cache_file.exists():
+ with cache_file.open() as f:
+ cache_data = json.load(f)
+ else:
+ cache_data = {}
+
+ cache = ConfigureCache(cache_data)
+ cache.version_checked_compilers = set()
+
+ return cache
+
+
+set_config("TOPSRCDIR", build_environment.topsrcdir)
+set_config("TOPOBJDIR", build_environment.topobjdir)
+set_config("DIST", build_environment.dist)
+
+add_old_configure_assignment("_topsrcdir", build_environment.topsrcdir)
+add_old_configure_assignment("_objdir", build_environment.topobjdir)
+add_old_configure_assignment("DIST", build_environment.dist)
+
+option(env="MOZ_AUTOMATION", help="Enable options for automated builds")
+set_config("MOZ_AUTOMATION", depends_if("MOZ_AUTOMATION")(lambda x: True))
+
+
+option(env="OLD_CONFIGURE", nargs=1, help="Path to the old configure script")
+
+option(env="MOZCONFIG", nargs=1, help="Mozconfig location")
+
+
+# Read user mozconfig
+# ==============================================================
+# Note: the dependency on --help is only there to always read the mozconfig,
+# even when --help is passed. Without this dependency, the function wouldn't
+# be called when --help is passed, and the mozconfig wouldn't be read.
+
+
+@depends("MOZCONFIG", "OLD_CONFIGURE", build_environment, "--help")
+@imports(_from="mozbuild.mozconfig", _import="MozconfigLoader")
+@imports(_from="mozboot.mozconfig", _import="find_mozconfig")
+@imports("os")
+def mozconfig(mozconfig, old_configure, build_env, help):
+ # Don't read the mozconfig for the js configure (yay backwards
+ # compatibility)
+ # While the long term goal is that js and top-level use the same configure
+ # and the same overall setup, including the possibility to use mozconfigs,
+ # figuring out what we want to do wrt mozconfig vs. command line and
+ # environment variable is not a clear-cut case, and it's more important to
+ # fix the immediate problem mozconfig causes to js developers by
+ # "temporarily" returning to the previous behavior of not loading the
+ # mozconfig for the js configure.
+ # Separately to the immediate problem for js developers, there is also the
+ # need to not load a mozconfig when running js configure as a subconfigure.
+ # Unfortunately, there is no direct way to tell whether the running
+ # configure is the js configure. The indirect way is to look at the
+ # OLD_CONFIGURE path, which points to js/src/old-configure.
+ # I expect we'll have figured things out for mozconfigs well before
+ # old-configure dies.
+ if (
+ old_configure
+ and os.path.dirname(os.path.abspath(old_configure[0])).endswith("/js/src")
+ or (mozconfig and mozconfig[0] == os.devnull)
+ ):
+ return {"path": None}
+
+ topsrcdir = build_env.topsrcdir
+ loader = MozconfigLoader(topsrcdir)
+ mozconfig = mozconfig[0] if mozconfig else None
+ mozconfig = find_mozconfig(topsrcdir, env={"MOZCONFIG": mozconfig})
+ mozconfig = loader.read_mozconfig(mozconfig)
+
+ return mozconfig
+
+
+set_config("MOZCONFIG", depends(mozconfig)(lambda m: m["path"]))
+
+
+# Mozilla-Build
+# ==============================================================
+option(env="MOZILLABUILD", nargs=1, help="Path to Mozilla Build (Windows-only)")
+
+option(env="CONFIG_SHELL", nargs=1, help="Path to a POSIX shell")
+
+# It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
+# but the end goal being that the configure script would go away...
+
+
+@depends("CONFIG_SHELL", "MOZILLABUILD")
+@checking("for a shell")
+@imports("sys")
+@imports(_from="pathlib", _import="Path")
+def shell(value, mozillabuild):
+ if value:
+ return find_program(value[0])
+ shell = "sh"
+ if mozillabuild:
+ if (Path(mozillabuild[0]) / "msys2").exists():
+ shell = mozillabuild[0] + "/msys2/usr/bin/sh"
+ else:
+ shell = mozillabuild[0] + "/msys/bin/sh"
+ if sys.platform == "win32":
+ shell = shell + ".exe"
+ return find_program(shell)
+
+
+# This defines a reasonable shell for when running with --help.
+# If one was passed in the environment, though, fall back to that.
+@depends("--help", "CONFIG_SHELL")
+def help_shell(help, shell):
+ if help and not shell:
+ return "sh"
+
+
+shell = help_shell | shell
+
+
+# Python 3
+# ========
+@dependable
+@checking("for Python 3", callback=lambda x: "%s (%s)" % (x.path, x.str_version))
+@imports("sys")
+@imports(_from="mach.site", _import="PythonVirtualenv")
+@imports(_from="os.path", _import="realpath")
+def virtualenv_python3():
+ return namespace(
+ # sys.executable is currently not updated for in-process activations. However,
+ # sys.prefix is, so we can calculate the python executable's path from there.
+ path=normsep(PythonVirtualenv(realpath(sys.prefix)).python_path),
+ str_version=".".join(str(i) for i in sys.version_info[0:3]),
+ )
+
+
+set_config("PYTHON3", virtualenv_python3.path)
+set_config("PYTHON3_VERSION", virtualenv_python3.str_version)
+add_old_configure_assignment("PYTHON3", virtualenv_python3.path)
+
+
+# Inject mozconfig options
+# ==============================================================
+# All options defined above this point can't be injected in mozconfig_options
+# below, so collect them.
+
+
+@template
+def early_options():
+ @depends("--help")
+ @imports("__sandbox__")
+ def early_options(_):
+ return set(option.env for option in __sandbox__._options.values() if option.env)
+
+ return early_options
+
+
+early_options = early_options()
+
+
+@depends(mozconfig, early_options, "MOZ_AUTOMATION", "--help")
+# This gives access to the sandbox. Don't copy this blindly.
+@imports("__sandbox__")
+@imports("os")
+def mozconfig_options(mozconfig, early_options, automation, help):
+ if mozconfig["path"]:
+ if "MOZ_AUTOMATION_MOZCONFIG" in mozconfig["env"]["added"]:
+ if not automation:
+ log.error(
+ "%s directly or indirectly includes an in-tree " "mozconfig.",
+ mozconfig["path"],
+ )
+ log.error(
+ "In-tree mozconfigs make strong assumptions about "
+ "and are only meant to be used by Mozilla "
+ "automation."
+ )
+ die("Please don't use them.")
+ helper = __sandbox__._helper
+ log.info("Adding configure options from %s" % mozconfig["path"])
+ for arg in mozconfig["configure_args"]:
+ log.info(" %s" % arg)
+ # We could be using imply_option() here, but it has other
+ # contraints that don't really apply to the command-line
+ # emulation that mozconfig provides.
+ helper.add(arg, origin="mozconfig", args=helper._args)
+
+ def add(key, value):
+ if key.isupper():
+ arg = "%s=%s" % (key, value)
+ log.info(" %s" % arg)
+ if key not in early_options:
+ helper.add(arg, origin="mozconfig", args=helper._args)
+
+ for key, value in mozconfig["env"]["added"].items():
+ add(key, value)
+ os.environ[key] = value
+ for key, (_, value) in mozconfig["env"]["modified"].items():
+ add(key, value)
+ os.environ[key] = value
+ for key, value in mozconfig["vars"]["added"].items():
+ add(key, value)
+ for key, (_, value) in mozconfig["vars"]["modified"].items():
+ add(key, value)
+
+
+@depends(build_environment, "--help")
+@imports(_from="os.path", _import="exists")
+def js_package(build_env, help):
+ return not exists(os.path.join(build_env.topsrcdir, "browser"))
+
+
+# Source checkout and version control integration.
+# ================================================
+
+
+@depends(build_environment, "MOZ_AUTOMATION", js_package, "--help")
+@checking("for vcs source checkout")
+@imports("os")
+def vcs_checkout_type(build_env, automation, js_package, help):
+ if os.path.exists(os.path.join(build_env.topsrcdir, ".hg")):
+ return "hg"
+ elif os.path.exists(os.path.join(build_env.topsrcdir, ".git")):
+ return "git"
+ elif automation and not js_package and not help:
+ raise FatalCheckError(
+ "unable to resolve VCS type; must run "
+ "from a source checkout when MOZ_AUTOMATION "
+ "is set"
+ )
+
+
+# Resolve VCS binary for detected repository type.
+
+
+# TODO remove hg.exe once bug 1382940 addresses ambiguous executables case.
+hg = check_prog(
+ "HG",
+ (
+ "hg.exe",
+ "hg",
+ ),
+ allow_missing=True,
+ when=depends(vcs_checkout_type)(lambda x: x == "hg"),
+)
+git = check_prog(
+ "GIT",
+ ("git",),
+ allow_missing=True,
+ when=depends(vcs_checkout_type)(lambda x: x == "git"),
+)
+
+
+@depends_if(hg)
+@checking("for Mercurial version")
+@imports("os")
+@imports("re")
+def hg_version(hg):
+ # HGPLAIN in Mercurial 1.5+ forces stable output, regardless of set
+ # locale or encoding.
+ env = dict(os.environ)
+ env["HGPLAIN"] = "1"
+
+ out = check_cmd_output(hg, "--version", env=env)
+
+ match = re.search(r"Mercurial Distributed SCM \(version ([^\)]+)", out)
+
+ if not match:
+ raise FatalCheckError("unable to determine Mercurial version: %s" % out)
+
+ # The version string may be "unknown" for Mercurial run out of its own
+ # source checkout or for bad builds. But LooseVersion handles it.
+
+ return Version(match.group(1))
+
+
+# Resolve Mercurial config items so other checks have easy access.
+# Do NOT set this in the config because it may contain sensitive data
+# like API keys.
+
+
+@depends_all(build_environment, hg, hg_version)
+@imports("os")
+def hg_config(build_env, hg, version):
+ env = dict(os.environ)
+ env["HGPLAIN"] = "1"
+
+ # Warnings may get sent to stderr. But check_cmd_output() ignores
+ # stderr if exit code is 0. And the command should always succeed if
+ # `hg version` worked.
+ out = check_cmd_output(hg, "config", env=env, cwd=build_env.topsrcdir)
+
+ config = {}
+
+ for line in out.strip().splitlines():
+ key, value = [s.strip() for s in line.split("=", 1)]
+ config[key] = value
+
+ return config
+
+
+@depends_if(git)
+@checking("for Git version")
+@imports("re")
+def git_version(git):
+ out = check_cmd_output(git, "--version").rstrip()
+
+ match = re.search("git version (.*)$", out)
+
+ if not match:
+ raise FatalCheckError("unable to determine Git version: %s" % out)
+
+ return Version(match.group(1))
+
+
+# Only set VCS_CHECKOUT_TYPE if we resolved the VCS binary.
+# Require resolved VCS info when running in automation so automation's
+# environment is more well-defined.
+
+
+@depends(vcs_checkout_type, hg_version, git_version, "MOZ_AUTOMATION")
+def exposed_vcs_checkout_type(vcs_checkout_type, hg, git, automation):
+ if vcs_checkout_type == "hg":
+ if hg:
+ return "hg"
+
+ if automation:
+ raise FatalCheckError("could not resolve Mercurial binary info")
+
+ elif vcs_checkout_type == "git":
+ if git:
+ return "git"
+
+ if automation:
+ raise FatalCheckError("could not resolve Git binary info")
+ elif vcs_checkout_type:
+ raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type)
+
+
+set_config("VCS_CHECKOUT_TYPE", exposed_vcs_checkout_type)
+
+# Obtain a Repository interface for the current VCS repository.
+
+
+@depends(build_environment, exposed_vcs_checkout_type, hg, git)
+@imports(_from="mozversioncontrol", _import="get_repository_object")
+def vcs_repository(build_env, vcs_checkout_type, hg, git):
+ if vcs_checkout_type == "hg":
+ return get_repository_object(build_env.topsrcdir, hg=hg)
+ elif vcs_checkout_type == "git":
+ return get_repository_object(build_env.topsrcdir, git=git)
+ elif vcs_checkout_type:
+ raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type)
+
+
+@depends_if(vcs_repository)
+@checking("for sparse checkout")
+def vcs_sparse_checkout(repo):
+ return repo.sparse_checkout_present()
+
+
+set_config("VCS_SPARSE_CHECKOUT", vcs_sparse_checkout)
+
+# The application/project to build
+# ==============================================================
+option(
+ "--enable-application",
+ nargs=1,
+ env="MOZ_BUILD_APP",
+ help="Application to build. Same as --enable-project.",
+)
+
+
+@depends("--enable-application")
+def application(app):
+ if app:
+ return app
+
+
+imply_option("--enable-project", application)
+
+
+@depends(build_environment, js_package)
+def default_project(build_env, js_package):
+ if js_package or build_env.topobjdir.endswith("/js/src"):
+ return "js"
+ return "browser"
+
+
+option("--enable-project", nargs=1, default=default_project, help="Project to build")
+
+
+# Artifact builds
+# ==============================================================
+
+option(
+ "--enable-artifact-builds",
+ env="MOZ_ARTIFACT_BUILDS",
+ help="Download and use prebuilt binary artifacts.",
+)
+
+
+@depends("--enable-artifact-builds")
+def artifact_builds(value):
+ if value:
+ return True
+
+
+set_config("MOZ_ARTIFACT_BUILDS", artifact_builds)
+
+# Host and target systems
+# ==============================================================
+option("--host", nargs=1, help="Define the system type performing the build")
+
+option(
+ "--target",
+ nargs=1,
+ help="Define the system type where the resulting executables will be " "used",
+)
+
+
+@imports(_from="mozbuild.configure.constants", _import="Abi")
+@imports(_from="mozbuild.configure.constants", _import="CPU")
+@imports(_from="mozbuild.configure.constants", _import="CPU_bitness")
+@imports(_from="mozbuild.configure.constants", _import="Endianness")
+@imports(_from="mozbuild.configure.constants", _import="Kernel")
+@imports(_from="mozbuild.configure.constants", _import="OS")
+@imports(_from="__builtin__", _import="ValueError")
+def split_triplet(triplet, allow_wasi=False):
+ # The standard triplet is defined as
+ # CPU_TYPE-VENDOR-OPERATING_SYSTEM
+ # There is also a quartet form:
+ # CPU_TYPE-VENDOR-KERNEL-OPERATING_SYSTEM
+ # But we can consider the "KERNEL-OPERATING_SYSTEM" as one.
+ # Additionally, some may omit "unknown" when the vendor
+ # is not specified and emit
+ # CPU_TYPE-OPERATING_SYSTEM
+ vendor = "unknown"
+ parts = triplet.split("-", 2)
+ if len(parts) == 3:
+ cpu, vendor, os = parts
+ elif len(parts) == 2:
+ cpu, os = parts
+ else:
+ raise ValueError("Unexpected triplet string: %s" % triplet)
+
+ # Autoconf uses config.sub to validate and canonicalize those triplets,
+ # but the granularity of its results has never been satisfying to our
+ # use, so we've had our own, different, canonicalization. We've also
+ # historically not been very consistent with how we use the canonicalized
+ # values. Hopefully, this will help us make things better.
+ # The tests are inherited from our decades-old autoconf-based configure,
+ # which can probably be improved/cleaned up because they are based on a
+ # mix of uname and config.guess output, while we now only use the latter,
+ # which presumably has a cleaner and leaner output. Let's refine later.
+ raw_os = os = os.replace("/", "_")
+ abi = None
+ sub_configure_alias = triplet
+ if "android" in os:
+ canonical_os = "Android"
+ canonical_kernel = "Linux"
+ elif os.startswith("linux"):
+ canonical_os = "GNU"
+ canonical_kernel = "Linux"
+ elif os.startswith("kfreebsd") and os.endswith("-gnu"):
+ canonical_os = "GNU"
+ canonical_kernel = "kFreeBSD"
+ elif os.startswith("gnu"):
+ canonical_os = canonical_kernel = "GNU"
+ elif os.startswith("mingw") or os in ("windows-msvc", "windows-gnu"):
+ canonical_os = canonical_kernel = "WINNT"
+ if not os.startswith("mingw"):
+ if os == "windows-msvc":
+ abi = "msvc"
+ elif os == "windows-gnu":
+ abi = "mingw"
+ # Many things down the line are looking for the string "mingw32"
+ # until they are all fixed, we pretend that's the raw os we had
+ # in the first place, even when we didn't.
+ sub_configure_alias = sub_configure_alias[: -len(os)] + "mingw32"
+ raw_os = "mingw32"
+ elif os.startswith("darwin"):
+ canonical_kernel = "Darwin"
+ canonical_os = "OSX"
+ elif os.startswith("dragonfly"):
+ canonical_os = canonical_kernel = "DragonFly"
+ elif os.startswith("freebsd"):
+ canonical_os = canonical_kernel = "FreeBSD"
+ elif os.startswith("netbsd"):
+ canonical_os = canonical_kernel = "NetBSD"
+ elif os.startswith("openbsd"):
+ canonical_os = canonical_kernel = "OpenBSD"
+ elif os.startswith("solaris"):
+ canonical_os = canonical_kernel = "SunOS"
+ elif os.startswith("wasi") and allow_wasi:
+ canonical_os = canonical_kernel = "WASI"
+ else:
+ raise ValueError("Unknown OS: %s" % os)
+
+ # The CPU granularity is probably not enough. Moving more things from
+ # old-configure will tell us if we need more
+ if cpu.endswith("86") or (cpu.startswith("i") and "86" in cpu):
+ canonical_cpu = "x86"
+ endianness = "little"
+ elif cpu in ("x86_64", "ia64"):
+ canonical_cpu = cpu
+ endianness = "little"
+ elif cpu in ("s390", "s390x"):
+ canonical_cpu = cpu
+ endianness = "big"
+ elif cpu in ("powerpc64", "ppc64", "powerpc64le", "ppc64le"):
+ canonical_cpu = "ppc64"
+ endianness = "little" if "le" in cpu else "big"
+ elif cpu in ("powerpc", "ppc", "rs6000") or cpu.startswith("powerpc"):
+ canonical_cpu = "ppc"
+ endianness = "big"
+ elif cpu in ("Alpha", "alpha", "ALPHA"):
+ canonical_cpu = "Alpha"
+ endianness = "little"
+ elif cpu.startswith("hppa") or cpu == "parisc":
+ canonical_cpu = "hppa"
+ endianness = "big"
+ elif cpu.startswith("sparc64") or cpu.startswith("sparcv9"):
+ canonical_cpu = "sparc64"
+ endianness = "big"
+ elif cpu.startswith("sparc") or cpu == "sun4u":
+ canonical_cpu = "sparc"
+ endianness = "big"
+ elif cpu.startswith("arm"):
+ canonical_cpu = "arm"
+ endianness = "big" if cpu.startswith(("armeb", "armbe")) else "little"
+ elif cpu in ("m68k"):
+ canonical_cpu = "m68k"
+ endianness = "big"
+ elif cpu in ("mips", "mipsel"):
+ canonical_cpu = "mips32"
+ endianness = "little" if "el" in cpu else "big"
+ elif cpu in ("mips64", "mips64el"):
+ canonical_cpu = "mips64"
+ endianness = "little" if "el" in cpu else "big"
+ elif cpu.startswith("aarch64"):
+ canonical_cpu = "aarch64"
+ endianness = "little"
+ elif cpu in ("riscv64", "riscv64gc"):
+ canonical_cpu = "riscv64"
+ endianness = "little"
+ elif cpu.startswith("loongarch64"):
+ canonical_cpu = "loongarch64"
+ endianness = "little"
+ elif cpu == "sh4":
+ canonical_cpu = "sh4"
+ endianness = "little"
+ elif cpu == "wasm32" and allow_wasi:
+ canonical_cpu = "wasm32"
+ endianness = "little"
+ else:
+ raise ValueError("Unknown CPU type: %s" % cpu)
+
+ # Toolchains, most notably for cross compilation may use cpu-os
+ # prefixes. We need to be more specific about the LLVM target on Mac
+ # so cross-language LTO will work correctly.
+
+ if os.startswith("darwin"):
+ toolchain = "%s-apple-%s" % (cpu, os)
+ else:
+ toolchain = "%s-%s" % (cpu, os)
+
+ return namespace(
+ alias=triplet,
+ cpu=CPU(canonical_cpu),
+ bitness=CPU_bitness[canonical_cpu],
+ kernel=Kernel(canonical_kernel),
+ os=OS(canonical_os),
+ endianness=Endianness(endianness),
+ # For now, only report the Windows ABI.
+ abi=abi and Abi(abi),
+ raw_cpu=cpu,
+ raw_os=raw_os,
+ toolchain=toolchain,
+ vendor=vendor,
+ sub_configure_alias=sub_configure_alias,
+ )
+
+
+# This defines a fake target/host namespace for when running with --help
+# If either --host or --target is passed on the command line, then fall
+# back to the real deal.
+@depends("--help", "--host", "--target")
+def help_host_target(help, host, target):
+ if help and not host and not target:
+ return namespace(
+ alias="unknown-unknown-unknown",
+ cpu="unknown",
+ bitness="unknown",
+ kernel="unknown",
+ os="unknown",
+ endianness="unknown",
+ abi="unknown",
+ raw_cpu="unknown",
+ raw_os="unknown",
+ toolchain="unknown-unknown",
+ )
+
+
+def config_sub(shell, triplet):
+ config_sub = os.path.join(os.path.dirname(__file__), "..", "autoconf", "config.sub")
+ # Config.sub doesn't like the *-windows-msvc/*-windows-gnu triplets, so
+ # munge those before and after calling config.sub.
+ suffix = None
+ munging = {
+ "-windows-msvc": "-mingw32",
+ "-windows-gnu": "-mingw32",
+ }
+ for check_suffix, replacement in munging.items():
+ if triplet.endswith(check_suffix):
+ suffix = check_suffix
+ triplet = triplet[: -len(suffix)] + replacement
+ break
+ result = check_cmd_output(shell, config_sub, triplet).strip()
+ if suffix:
+ assert result.endswith(replacement)
+ result = result[: -len(replacement)] + suffix
+ return result
+
+
+@depends("--host", shell)
+@checking("for host system type", lambda h: h.alias)
+@imports("os")
+@imports("sys")
+@imports(_from="__builtin__", _import="ValueError")
+def real_host(value, shell):
+ if not value and sys.platform == "win32":
+ arch = os.environ.get("PROCESSOR_ARCHITEW6432") or os.environ.get(
+ "PROCESSOR_ARCHITECTURE"
+ )
+ if arch == "AMD64":
+ return split_triplet("x86_64-pc-windows-msvc")
+ elif arch == "x86":
+ return split_triplet("i686-pc-windows-msvc")
+
+ if not value:
+ config_guess = os.path.join(
+ os.path.dirname(__file__), "..", "autoconf", "config.guess"
+ )
+
+ # Ensure that config.guess is determining the host triplet, not the target
+ # triplet
+ env = os.environ.copy()
+ env.pop("CC_FOR_BUILD", None)
+ env.pop("HOST_CC", None)
+ env.pop("CC", None)
+
+ host = check_cmd_output(shell, config_guess, env=env).strip()
+ try:
+ return split_triplet(host)
+ except ValueError:
+ pass
+ else:
+ host = value[0]
+
+ host = config_sub(shell, host)
+
+ try:
+ return split_triplet(host)
+ except ValueError as e:
+ die(e)
+
+
+host = help_host_target | real_host
+
+
+@depends("--target", real_host, shell, "--enable-project", "--enable-application")
+@checking("for target system type", lambda t: t.alias)
+@imports(_from="__builtin__", _import="ValueError")
+def real_target(value, host, shell, project, application):
+ # Because --enable-project is implied by --enable-application, and
+ # implied options are not currently handled during --help, which is
+ # used get the build target in mozbuild.base, we manually check
+ # whether --enable-application was given, and fall back to
+ # --enable-project if not. Both can't be given contradictory values
+ # under normal circumstances, so it's fine.
+ if application:
+ project = application[0]
+ elif project:
+ project = project[0]
+ if not value:
+ if project == "mobile/android":
+ if host.raw_os == "mingw32":
+ log.warning(
+ "Building Firefox for Android on Windows is not fully "
+ "supported. See https://bugzilla.mozilla.org/show_bug.cgi?"
+ "id=1169873 for details."
+ )
+ return split_triplet("arm-unknown-linux-androideabi")
+ return host
+ # If --target was only given a cpu arch, expand it with the
+ # non-cpu part of the host. For mobile/android, expand it with
+ # unknown-linux-android.
+ target = value[0]
+ if "-" not in target:
+ if project == "mobile/android":
+ rest = "unknown-linux-android"
+ if target.startswith("arm"):
+ rest += "eabi"
+ else:
+ cpu, rest = host.alias.split("-", 1)
+ target = "-".join((target, rest))
+ try:
+ return split_triplet(target)
+ except ValueError:
+ pass
+
+ try:
+ return split_triplet(config_sub(shell, target), allow_wasi=(project == "js"))
+ except ValueError as e:
+ die(e)
+
+
+target = help_host_target | real_target
+
+
+@depends(host, target)
+@checking("whether cross compiling")
+def cross_compiling(host, target):
+ return host != target
+
+
+set_config("CROSS_COMPILE", cross_compiling)
+set_define("CROSS_COMPILE", cross_compiling)
+
+
+@depends(target)
+def have_64_bit(target):
+ if target.bitness == 64:
+ return True
+
+
+set_config("HAVE_64BIT_BUILD", have_64_bit)
+set_define("HAVE_64BIT_BUILD", have_64_bit)
+add_old_configure_assignment("HAVE_64BIT_BUILD", have_64_bit)
+
+# Some third-party code bases depend on this being set for big-endians.
+set_define(
+ "WORDS_BIGENDIAN", True, when=depends(target.endianness)(lambda e: e == "big")
+)
+
+
+# Autoconf needs these set
+
+
+@depends(host)
+def host_for_sub_configure(host):
+ return "--host=%s" % host.sub_configure_alias
+
+
+@depends(target)
+def target_for_sub_configure(target):
+ return "--target=%s" % target.sub_configure_alias
+
+
+# These variables are for compatibility with the current moz.builds and
+# old-configure. Eventually, we'll want to canonicalize better.
+@depends(target)
+def target_variables(target):
+ if target.kernel == "kFreeBSD":
+ os_target = "GNU/kFreeBSD"
+ os_arch = "GNU_kFreeBSD"
+ elif target.kernel == "Darwin" or (target.kernel == "Linux" and target.os == "GNU"):
+ os_target = target.kernel
+ os_arch = target.kernel
+ else:
+ os_target = target.os
+ os_arch = target.kernel
+
+ return namespace(
+ OS_TARGET=os_target,
+ OS_ARCH=os_arch,
+ INTEL_ARCHITECTURE=target.cpu in ("x86", "x86_64") or None,
+ )
+
+
+set_config("OS_TARGET", target_variables.OS_TARGET)
+add_old_configure_assignment("OS_TARGET", target_variables.OS_TARGET)
+set_config("OS_ARCH", target_variables.OS_ARCH)
+add_old_configure_assignment("OS_ARCH", target_variables.OS_ARCH)
+set_config("CPU_ARCH", target.cpu)
+add_old_configure_assignment("CPU_ARCH", target.cpu)
+set_config("INTEL_ARCHITECTURE", target_variables.INTEL_ARCHITECTURE)
+set_config("TARGET_CPU", target.raw_cpu)
+set_config("TARGET_OS", target.os)
+set_config("TARGET_RAW_OS", target.raw_os)
+set_config("TARGET_ENDIANNESS", target.endianness)
+
+
+@depends(host)
+def host_variables(host):
+ if host.kernel == "kFreeBSD":
+ os_arch = "GNU_kFreeBSD"
+ else:
+ os_arch = host.kernel
+ return namespace(
+ HOST_OS_ARCH=os_arch,
+ )
+
+
+set_config("HOST_CPU_ARCH", host.cpu)
+set_config("HOST_OS_ARCH", host_variables.HOST_OS_ARCH)
+add_old_configure_assignment("HOST_OS_ARCH", host_variables.HOST_OS_ARCH)
+set_config("HOST_ALIAS", host.alias)
+
+
+@depends(target)
+def target_is_windows(target):
+ if target.kernel == "WINNT":
+ return True
+
+
+@depends(host)
+def host_is_windows(host):
+ if host.kernel == "WINNT":
+ return True
+
+
+set_define("_WINDOWS", target_is_windows)
+set_define("WIN32", target_is_windows)
+set_define("XP_WIN", target_is_windows)
+
+
+@depends(target)
+def target_is_unix(target):
+ if target.kernel != "WINNT":
+ return True
+
+
+set_define("XP_UNIX", target_is_unix)
+
+
+@depends(target)
+def target_is_darwin(target):
+ if target.kernel == "Darwin":
+ return True
+
+
+set_define("XP_DARWIN", target_is_darwin)
+
+
+@depends(target)
+def target_is_osx(target):
+ if target.kernel == "Darwin" and target.os == "OSX":
+ return True
+
+
+@depends(host)
+def host_is_osx(host):
+ if host.os == "OSX":
+ return True
+
+
+set_define("XP_MACOSX", target_is_osx)
+
+
+@depends(target)
+def target_has_linux_kernel(target):
+ if target.kernel == "Linux":
+ return True
+
+
+set_define("XP_LINUX", target_has_linux_kernel)
+
+
+@depends(target)
+def target_is_linux_or_wasi(target):
+ if (target.kernel == "Linux" and target.os == "GNU") or target.kernel == "WASI":
+ return True
+
+
+@depends(target)
+def target_is_android(target):
+ if target.os == "Android":
+ return True
+
+
+set_define("ANDROID", target_is_android)
+
+
+@depends(target)
+def target_is_openbsd(target):
+ if target.kernel == "OpenBSD":
+ return True
+
+
+set_define("XP_OPENBSD", target_is_openbsd)
+
+
+@depends(target)
+def target_is_netbsd(target):
+ if target.kernel == "NetBSD":
+ return True
+
+
+set_define("XP_NETBSD", target_is_netbsd)
+
+
+@depends(target)
+def target_is_freebsd(target):
+ if target.kernel == "FreeBSD":
+ return True
+
+
+set_define("XP_FREEBSD", target_is_freebsd)
+
+
+@depends(target)
+def target_is_solaris(target):
+ if target.kernel == "SunOS":
+ return True
+
+
+set_define("XP_SOLARIS", target_is_solaris)
+
+
+@depends(target)
+def target_is_sparc(target):
+ if target.cpu == "sparc64":
+ return True
+
+
+set_define("SPARC64", target_is_sparc)
+
+
+@depends(target, when=target_is_android)
+def android_cpu_arch(target):
+ d = {
+ "aarch64": "arm64-v8a",
+ "arm": "armeabi-v7a",
+ "x86": "x86",
+ "x86_64": "x86_64",
+ }
+ if target.cpu not in d:
+ die(f"Cannot determine android_cpu_arch: unknown target.cpu: {target.cpu}")
+ return d[target.cpu]
+
+
+set_config("ANDROID_CPU_ARCH", android_cpu_arch)
+
+
+@depends("--enable-project", build_environment, "--help")
+@imports(_from="os.path", _import="exists")
+def include_project_configure(project, build_env, help):
+ if not project:
+ die("--enable-project is required.")
+
+ base_dir = build_env.topsrcdir
+ path = os.path.join(base_dir, project[0], "moz.configure")
+ if not exists(path):
+ die("Cannot find project %s", project[0])
+ return path
+
+
+@depends("--enable-project")
+def build_project(project):
+ return project[0]
+
+
+set_config("MOZ_BUILD_APP", build_project)
+set_define("MOZ_BUILD_APP", build_project)
+add_old_configure_assignment("MOZ_BUILD_APP", build_project)
+
+
+option(env="MOZILLA_OFFICIAL", help="Build an official release")
+
+
+@depends("MOZILLA_OFFICIAL")
+def mozilla_official(official):
+ if official:
+ return True
+
+
+set_config("MOZILLA_OFFICIAL", mozilla_official)
+set_define("MOZILLA_OFFICIAL", mozilla_official)
+add_old_configure_assignment("MOZILLA_OFFICIAL", mozilla_official)
+
+
+# Allow specifying custom paths to the version files used by the milestone() function below.
+option(
+ "--with-version-file-path",
+ nargs=1,
+ help="Specify a custom path to app version files instead of auto-detecting",
+ default=None,
+)
+
+
+@depends("--with-version-file-path")
+def version_path(path):
+ return path
+
+
+# Allow to easily build nightly with a release / beta configuration so that we
+# can have the same options we'd have on a release version.
+# This is useful for performance profiling, as there are things that we don't
+# enable in release (like the background hang monitor) that can affect
+# performance.
+option(
+ "--as-milestone",
+ help="Build with another milestone configuration (e.g., as release)",
+ choices=("early-beta", "late-beta", "release"),
+ default=None,
+)
+
+
+# set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in
+# The logic works like this:
+# - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
+# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
+# - otherwise, we're building Release/Beta (define RELEASE_OR_BETA)
+@depends(
+ build_environment,
+ build_project,
+ version_path,
+ "--as-milestone",
+ "--help",
+)
+@imports(_from="__builtin__", _import="open")
+@imports("os")
+@imports("re")
+def milestone(build_env, build_project, version_path, as_milestone, _):
+ versions = []
+ paths = ["config/milestone.txt"]
+ if build_project == "js":
+ paths = paths * 3
+ else:
+ paths += [
+ "browser/config/version.txt",
+ "browser/config/version_display.txt",
+ ]
+ if version_path:
+ version_path = version_path[0]
+ else:
+ version_path = os.path.join(build_project, "config")
+ for f in ("version.txt", "version_display.txt"):
+ f = os.path.join(version_path, f)
+ if not os.path.exists(os.path.join(build_env.topsrcdir, f)):
+ break
+ paths.append(f)
+
+ for p in paths:
+ with open(os.path.join(build_env.topsrcdir, p), "r") as fh:
+ content = fh.read().splitlines()
+ if not content:
+ die("Could not find a version number in {}".format(p))
+ versions.append(content[-1])
+
+ is_early_beta_or_earlier = None
+ if as_milestone:
+ if "a1" not in versions[0]:
+ # We could make this work with some effort
+ die("--as-milestone only works on nightly builds")
+ as_milestone = as_milestone[0]
+ as_milestone_flag = "" if as_milestone == "release" else "b1"
+ versions = [v.replace("a1", as_milestone_flag) for v in versions]
+ if as_milestone == "early-beta":
+ is_early_beta_or_earlier = True
+
+ milestone, firefox_version, firefox_version_display = versions[:3]
+
+ # version.txt content from the project directory if there is one, otherwise
+ # the firefox version.
+ app_version = versions[3] if len(versions) > 3 else firefox_version
+ # version_display.txt content from the project directory if there is one,
+ # otherwise version.txt content from the project directory, otherwise the
+ # firefox version for display.
+ app_version_display = versions[-1] if len(versions) > 3 else firefox_version_display
+
+ is_nightly = is_release_or_beta = None
+
+ if "a1" in milestone:
+ is_nightly = True
+ elif "a" not in milestone:
+ is_release_or_beta = True
+
+ major_version = milestone.split(".")[0]
+ m = re.search(r"([ab]\d+)", milestone)
+ ab_patch = m.group(1) if m else ""
+
+ if not as_milestone:
+ defines = os.path.join(build_env.topsrcdir, "build", "defines.sh")
+ with open(defines, "r") as fh:
+ for line in fh.read().splitlines():
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ name, _, value = line.partition("=")
+ name = name.strip()
+ value = value.strip()
+ if name != "EARLY_BETA_OR_EARLIER":
+ die(
+ "Only the EARLY_BETA_OR_EARLIER variable can be set in build/defines.sh"
+ )
+ if value and any(x in app_version_display for x in "ab"):
+ is_early_beta_or_earlier = True
+
+ # Only expose the major version milestone in the UA string and hide the
+ # patch leve (bugs 572659 and 870868).
+ #
+ # Only expose major milestone and alpha version in the symbolversion
+ # string; as the name suggests, we use it for symbol versioning on Linux.
+ return namespace(
+ version=milestone,
+ uaversion="%s.0" % major_version,
+ symbolversion="%s%s" % (major_version, ab_patch),
+ is_nightly=is_nightly,
+ is_release_or_beta=is_release_or_beta,
+ is_early_beta_or_earlier=is_early_beta_or_earlier,
+ is_esr=app_version_display.endswith("esr") or None,
+ app_version=app_version,
+ app_version_display=app_version_display,
+ )
+
+
+set_config("GRE_MILESTONE", milestone.version)
+set_config("NIGHTLY_BUILD", milestone.is_nightly)
+set_define("NIGHTLY_BUILD", milestone.is_nightly)
+set_config("RELEASE_OR_BETA", milestone.is_release_or_beta)
+set_define("RELEASE_OR_BETA", milestone.is_release_or_beta)
+set_config("MOZ_ESR", milestone.is_esr)
+set_define("MOZ_ESR", milestone.is_esr)
+set_config("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
+set_define("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
+set_define("MOZILLA_VERSION", depends(milestone)(lambda m: '"%s"' % m.version))
+set_config("MOZILLA_VERSION", milestone.version)
+set_define("MOZILLA_VERSION_U", milestone.version)
+set_define("MOZILLA_UAVERSION", depends(milestone)(lambda m: '"%s"' % m.uaversion))
+set_config("MOZILLA_SYMBOLVERSION", milestone.symbolversion)
+# JS configure still want to look at this one.
+add_old_configure_assignment("MOZILLA_SYMBOLVERSION", milestone.symbolversion)
+
+set_config("MOZ_APP_VERSION", milestone.app_version)
+set_config("MOZ_APP_VERSION_DISPLAY", milestone.app_version_display)
+add_old_configure_assignment("MOZ_APP_VERSION", milestone.app_version)
+
+
+# The app update channel is 'default' when not supplied. The value is used in
+# the application's confvars.sh (and is made available to a project specific
+# moz.configure).
+option(
+ "--enable-update-channel",
+ nargs=1,
+ help="Select application update channel",
+ default="default",
+)
+
+
+@depends("--enable-update-channel")
+def update_channel(channel):
+ if not channel or channel[0] == "":
+ return "default"
+ return channel[0].lower()
+
+
+set_config("MOZ_UPDATE_CHANNEL", update_channel)
+set_define("MOZ_UPDATE_CHANNEL", update_channel)
+add_old_configure_assignment("MOZ_UPDATE_CHANNEL", update_channel)
+
+
+option(
+ env="MOZBUILD_STATE_PATH",
+ nargs=1,
+ help="Path to a persistent state directory for the build system "
+ "and related tools",
+)
+
+
+@depends("MOZBUILD_STATE_PATH", "--help")
+@imports("os")
+def mozbuild_state_path(path, _):
+ if path:
+ return normalize_path(path[0])
+ return normalize_path(os.path.expanduser(os.path.join("~", ".mozbuild")))
+
+
+@depends("MOZILLABUILD", shell, host_is_windows)
+@imports(_from="pathlib", _import="Path")
+def mozillabuild_bin_paths(mozillabuild, shell, host_is_windows):
+ paths = []
+ if not mozillabuild or not host_is_windows:
+ return paths
+ paths.append(os.path.dirname(shell))
+ paths.append(str(Path(mozillabuild[0]) / "bin"))
+ return paths
+
+
+@depends(mozillabuild_bin_paths)
+@imports("os")
+def prefer_mozillabuild_path(mozillabuild_bin_paths):
+ return mozillabuild_bin_paths + os.environ["PATH"].split(os.pathsep)
+
+
+# A template providing a shorthand for setting a variable. The created
+# option will only be settable with imply_option.
+# It is expected that a project-specific moz.configure will call imply_option
+# to set a value other than the default.
+# If required, the set_as_define and set_for_old_configure arguments
+# will additionally cause the variable to be set using set_define and
+# add_old_configure_assignment. util.configure would be an appropriate place for
+# this, but it uses add_old_configure_assignment, which is defined in this file.
+@template
+def project_flag(env=None, set_for_old_configure=False, set_as_define=False, **kwargs):
+
+ if not env:
+ configure_error("A project_flag must be passed a variable name to set.")
+
+ opt = option(env=env, possible_origins=("implied",), **kwargs)
+
+ @depends(opt.option)
+ def option_implementation(value):
+ if value:
+ if len(value):
+ return value
+ return bool(value)
+
+ set_config(env, option_implementation)
+ if set_as_define:
+ set_define(env, option_implementation)
+ if set_for_old_configure:
+ add_old_configure_assignment(env, option_implementation)
+
+
+# milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set.
+
+
+@depends(milestone)
+def enabled_in_nightly(milestone):
+ return milestone.is_nightly
+
+
+# Branding
+# ==============================================================
+option(
+ "--with-app-basename",
+ env="MOZ_APP_BASENAME",
+ nargs=1,
+ help="Typically stays consistent for multiple branded versions of a "
+ 'given application (e.g. Aurora and Firefox both use "Firefox"), but '
+ "may vary for full rebrandings (e.g. Iceweasel). Used for "
+ 'application.ini\'s "Name" field, which controls profile location in '
+ 'the absence of a "Profile" field (see below), and various system '
+ "integration hooks (Unix remoting, Windows MessageWindow name, etc.",
+)
+
+
+@depends("--with-app-basename", target_is_android)
+def moz_app_basename(value, target_is_android):
+ if value:
+ return value[0]
+ if target_is_android:
+ return "Fennec"
+ return "Firefox"
+
+
+set_config(
+ "MOZ_APP_BASENAME",
+ moz_app_basename,
+ when=depends(build_project)(lambda p: p != "js"),
+)
diff --git a/build/moz.configure/java.configure b/build/moz.configure/java.configure
new file mode 100644
index 0000000000..0bb0a22c71
--- /dev/null
+++ b/build/moz.configure/java.configure
@@ -0,0 +1,76 @@
+# -*- 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/.
+
+
+# Java detection
+# ========================================================
+option(
+ "--with-java-bin-path",
+ nargs=1,
+ help="Location of Java binaries",
+)
+
+
+@depends("--with-java-bin-path", host, toolchains_base_dir)
+@imports(_from="mozboot.android", _import="JavaLocationFailedException")
+@imports(_from="mozboot.android", _import="locate_java_bin_path")
+@imports(_from="os", _import="environ")
+@imports(_from="os.path", _import="dirname")
+def java_search_paths(path, host, toolchains_base_dir):
+ if path:
+ # Look for javac and jar in the specified path.
+ return path
+
+ try:
+ path = locate_java_bin_path(host.kernel, toolchains_base_dir)
+
+ java_home = environ.get("JAVA_HOME")
+ if java_home and java_home != dirname(path):
+ log.info(
+ "Ignoring JAVA_HOME value. Use --with-java-bin-path "
+ "to override the default Java location."
+ )
+ return [path]
+ except JavaLocationFailedException as e:
+ die(str(e))
+
+
+# Finds the given java tool, failing with a custom error message if we can't
+# find it.
+
+
+@template
+def check_java_tool(tool):
+ check = check_prog(
+ tool.upper(), (tool,), paths=java_search_paths, allow_missing=True
+ )
+
+ @depends(check)
+ def require_tool(result):
+ if result is None:
+ die(
+ "The program %s was not found. Use '--with-java-bin-path={java-bin-dir}'"
+ % tool
+ )
+ return result
+
+ return require_tool
+
+
+check_java_tool("java")
+
+
+# Java Code Coverage
+# ========================================================
+option(
+ "--enable-java-coverage",
+ env="MOZ_JAVA_CODE_COVERAGE",
+ help="Enable Java code coverage",
+)
+
+set_config(
+ "MOZ_JAVA_CODE_COVERAGE", depends("--enable-java-coverage")(lambda v: bool(v))
+)
diff --git a/build/moz.configure/keyfiles.configure b/build/moz.configure/keyfiles.configure
new file mode 100644
index 0000000000..242a773aac
--- /dev/null
+++ b/build/moz.configure/keyfiles.configure
@@ -0,0 +1,68 @@
+# -*- 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/.
+
+
+@template
+def keyfile(desc, default=None, help=None, callback=lambda x: x):
+ help = help or (
+ "Use the secret key contained in the given keyfile " "for %s requests" % desc
+ )
+ name = desc.lower().replace(" ", "-")
+ no_key = callback("no-%s-key" % name)
+
+ option("--with-%s-keyfile" % name, nargs=1, default=default, help=help)
+
+ @depends("--with-%s-keyfile" % name)
+ @checking("for the %s key" % desc, lambda x: x and x is not no_key)
+ @imports(_from="__builtin__", _import="open")
+ @imports(_from="__builtin__", _import="IOError")
+ def keyfile(value):
+ if value:
+ try:
+ with open(value[0]) as fh:
+ result = fh.read().strip()
+ if result:
+ return callback(result)
+ raise FatalCheckError("'%s' is empty." % value[0])
+ except IOError as e:
+ raise FatalCheckError("'%s': %s." % (value[0], e.strerror))
+ return no_key
+
+ return keyfile
+
+
+@template
+def simple_keyfile(desc, default=None):
+ value = keyfile(desc, default=default)
+ set_config("MOZ_%s_KEY" % desc.upper().replace(" ", "_"), value)
+
+
+@template
+def id_and_secret_keyfile(desc, default=None):
+ def id_and_secret(value):
+ if value.startswith("no-") and value.endswith("-key"):
+ id = value[:-3] + "clientid"
+ secret = value
+ elif " " in value:
+ id, secret = value.split(" ", 1)
+ else:
+ raise FatalCheckError("%s key file has an invalid format." % desc)
+ return namespace(
+ id=id,
+ secret=secret,
+ )
+
+ content = keyfile(
+ desc,
+ help="Use the client id and secret key contained "
+ "in the given keyfile for %s requests" % desc,
+ default=default,
+ callback=id_and_secret,
+ )
+
+ name = desc.upper().replace(" ", "_")
+ set_config("MOZ_%s_CLIENTID" % name, content.id)
+ set_config("MOZ_%s_KEY" % name, content.secret)
diff --git a/build/moz.configure/lto-pgo.configure b/build/moz.configure/lto-pgo.configure
new file mode 100644
index 0000000000..28f4114780
--- /dev/null
+++ b/build/moz.configure/lto-pgo.configure
@@ -0,0 +1,355 @@
+# -*- 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/.
+
+# PGO
+# ==============================================================
+llvm_profdata = check_prog(
+ "LLVM_PROFDATA", ["llvm-profdata"], allow_missing=True, paths=clang_search_path
+)
+
+option(
+ "--enable-profile-generate",
+ env="MOZ_PROFILE_GENERATE",
+ nargs="?",
+ choices=("cross",),
+ help="Build a PGO instrumented binary",
+)
+
+imply_option("MOZ_PGO", depends_if("--enable-profile-generate")(lambda _: True))
+
+set_config(
+ "MOZ_PROFILE_GENERATE", depends_if("--enable-profile-generate")(lambda _: True)
+)
+
+set_define(
+ "MOZ_PROFILE_GENERATE", depends_if("--enable-profile-generate")(lambda _: True)
+)
+
+add_old_configure_assignment(
+ "MOZ_PROFILE_GENERATE", 1, when="--enable-profile-generate"
+)
+
+option(
+ "--enable-profile-use",
+ env="MOZ_PROFILE_USE",
+ nargs="?",
+ choices=("cross",),
+ help="Use a generated profile during the build",
+)
+
+option(
+ "--with-pgo-profile-path",
+ help="Path to the directory with unmerged profile data to use during the build",
+ nargs=1,
+)
+
+imply_option("MOZ_PGO", depends_if("--enable-profile-use")(lambda _: True))
+
+set_config("MOZ_PROFILE_USE", depends_if("--enable-profile-use")(lambda _: True))
+
+
+@depends(
+ "--with-pgo-profile-path",
+ "--enable-profile-use",
+ llvm_profdata,
+ build_environment,
+)
+@imports("os")
+def pgo_profile_path(path, pgo_use, profdata, build_env):
+ topobjdir = build_env.topobjdir
+ if topobjdir.endswith("/js/src"):
+ topobjdir = topobjdir[:-7]
+
+ if not path:
+ return os.path.join(topobjdir, "instrumented", "merged.profdata")
+ if path and not pgo_use:
+ die("Pass --enable-profile-use to use --with-pgo-profile-path.")
+ if path and not profdata:
+ die("LLVM_PROFDATA must be set to process the pgo profile.")
+ if not os.path.isfile(path[0]):
+ die("Argument to --with-pgo-profile-path must be a file.")
+ if not os.path.isabs(path[0]):
+ die("Argument to --with-pgo-profile-path must be an absolute path.")
+ return path[0]
+
+
+set_config("PGO_PROFILE_PATH", pgo_profile_path)
+
+
+@depends(c_compiler, pgo_profile_path, target_is_windows)
+@imports("multiprocessing")
+def pgo_flags(compiler, profdata, target_is_windows):
+ if compiler.type == "gcc":
+ return namespace(
+ gen_cflags=["-fprofile-generate"],
+ gen_ldflags=["-fprofile-generate"],
+ use_cflags=["-fprofile-use", "-fprofile-correction", "-Wcoverage-mismatch"],
+ use_ldflags=["-fprofile-use"],
+ )
+
+ if compiler.type in ("clang-cl", "clang"):
+ prefix = ""
+ if compiler.type == "clang-cl":
+ prefix = "/clang:"
+ gen_ldflags = None
+ else:
+ gen_ldflags = ["-fprofile-generate"]
+
+ gen_cflags = [prefix + "-fprofile-generate"]
+ if target_is_windows:
+ # native llvm-profdata.exe on Windows can't read profile data
+ # if name compression is enabled (which cross-compiling enables
+ # by default)
+ gen_cflags += ["-mllvm", "-enable-name-compression=false"]
+
+ return namespace(
+ gen_cflags=gen_cflags,
+ gen_ldflags=gen_ldflags,
+ use_cflags=[
+ prefix + "-fprofile-use=%s" % profdata,
+ # Some error messages about mismatched profile data
+ # come in via -Wbackend-plugin, so disable those too.
+ "-Wno-error=backend-plugin",
+ ],
+ use_ldflags=[],
+ )
+
+
+set_config("PROFILE_GEN_CFLAGS", pgo_flags.gen_cflags)
+set_config("PROFILE_GEN_LDFLAGS", pgo_flags.gen_ldflags)
+set_config("PROFILE_USE_CFLAGS", pgo_flags.use_cflags)
+set_config("PROFILE_USE_LDFLAGS", pgo_flags.use_ldflags)
+
+option(
+ "--with-pgo-jarlog",
+ help="Use the provided jarlog file when packaging during a profile-use " "build",
+ nargs=1,
+)
+
+set_config("PGO_JARLOG_PATH", depends_if("--with-pgo-jarlog")(lambda p: p))
+
+
+@depends("MOZ_PGO", "--enable-profile-use", "--enable-profile-generate", c_compiler)
+def moz_pgo_rust(pgo, profile_use, profile_generate, c_compiler):
+ if not pgo:
+ return
+
+ # Enabling PGO through MOZ_PGO only and not --enable* flags.
+ if not profile_use and not profile_generate:
+ return
+
+ if profile_use and profile_generate:
+ die("Cannot build with --enable-profile-use and --enable-profile-generate.")
+
+ want_cross = (len(profile_use) and profile_use[0] == "cross") or (
+ len(profile_generate) and profile_generate[0] == "cross"
+ )
+
+ if not want_cross:
+ return
+
+ if c_compiler.type == "gcc":
+ die("Cannot use cross-language PGO with GCC.")
+
+ return True
+
+
+set_config("MOZ_PGO_RUST", moz_pgo_rust)
+
+# LTO
+# ==============================================================
+
+option(
+ "--enable-lto",
+ env="MOZ_LTO",
+ nargs="*",
+ choices=("full", "thin", "cross"),
+ help="Enable LTO",
+)
+
+option(
+ env="MOZ_LD64_KNOWN_GOOD",
+ nargs=1,
+ help="Indicate that ld64 is free of symbol aliasing bugs.",
+)
+
+imply_option("MOZ_LD64_KNOWN_GOOD", depends_if("MOZ_AUTOMATION")(lambda _: True))
+
+
+@depends(
+ "--enable-lto",
+ c_compiler,
+ select_linker,
+ "MOZ_LD64_KNOWN_GOOD",
+ target,
+ "--enable-profile-generate",
+ pass_manager.enabled,
+)
+@imports("multiprocessing")
+def lto(
+ values,
+ c_compiler,
+ select_linker,
+ ld64_known_good,
+ target,
+ instrumented_build,
+ pass_manager,
+):
+ cflags = []
+ ldflags = []
+ enabled = None
+ rust_lto = False
+
+ if not values:
+ return
+
+ # Sanitize LTO modes.
+ if "full" in values and "thin" in values:
+ die("incompatible --enable-lto choices 'full' and 'thin'")
+
+ # If a value was given to --enable-lto, use that.
+ # Otherwise, make the lto mode explicit, using
+ # thin with clang/clang-cl and full with gcc.
+ if values == () or values == ("cross",):
+ if c_compiler.type == "gcc":
+ values += ("full",)
+ else:
+ values += ("thin",)
+
+ if instrumented_build:
+ log.warning("Disabling LTO because --enable-profile-generate is specified")
+ return
+
+ if c_compiler.type == "gcc":
+ if "cross" in values:
+ die("Cross-language LTO is not supported with GCC.")
+ if "thin" in values:
+ die(
+ "gcc does not support thin LTO. Use `--enable-lto` "
+ "to enable full LTO for gcc."
+ )
+
+ if (
+ target.kernel == "Darwin"
+ and target.os == "OSX"
+ and "cross" in values
+ and select_linker.KIND == "ld64"
+ and not ld64_known_good
+ ):
+ die(
+ "The Mac linker is known to have a bug that affects cross-language "
+ "LTO. If you know that your linker is free from this bug, please "
+ "set the environment variable `MOZ_LD64_KNOWN_GOOD=1` and re-run "
+ "configure."
+ )
+
+ if c_compiler.type == "clang":
+ if "full" in values:
+ cflags.append("-flto")
+ ldflags.append("-flto")
+ else:
+ cflags.append("-flto=thin")
+ ldflags.append("-flto=thin")
+
+ if target.os == "Android" and "cross" in values:
+ # Work around https://github.com/rust-lang/rust/issues/90088
+ # by enabling the highest level of SSE the rust targets default
+ # to.
+ # https://github.com/rust-lang/rust/blob/bdfcb88e8b6203ccb46a2fb6649979b773efc8ac/compiler/rustc_target/src/spec/i686_linux_android.rs#L13
+ # https://github.com/rust-lang/rust/blob/8d1083e319841624f64400e1524805a40d725439/compiler/rustc_target/src/spec/x86_64_linux_android.rs#L7
+ if target.cpu == "x86":
+ ldflags.append("-Wl,-plugin-opt=-mattr=+ssse3")
+ elif target.cpu == "x86_64":
+ ldflags.append("-Wl,-plugin-opt=-mattr=+sse4.2")
+ elif c_compiler.type == "clang-cl":
+ if "full" in values:
+ cflags.append("-flto")
+ else:
+ cflags.append("-flto=thin")
+ # With clang-cl, -flto can only be used with -c or -fuse-ld=lld.
+ # AC_TRY_LINKs during configure don't have -c, so pass -fuse-ld=lld.
+ cflags.append("-fuse-ld=lld")
+
+ # Explicitly set the CPU to optimize for so the linker doesn't
+ # choose a poor default. Rust compilation by default uses the
+ # pentium4 CPU on x86:
+ #
+ # https://github.com/rust-lang/rust/blob/049a49b91151a88c95fa0d62a53fd0a0ac2c3af9/compiler/rustc_target/src/spec/i686_pc_windows_msvc.rs#L5
+ #
+ # which specifically supports "long" (multi-byte) nops. See
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1568450#c8 for details.
+ #
+ # The pentium4 seems like kind of a weird CPU to optimize for, but
+ # it seems to have worked out OK thus far. LLVM does not seem to
+ # specifically schedule code for the pentium4's deep pipeline, so
+ # that probably contributes to it being an OK default for our
+ # purposes.
+ if target.cpu == "x86":
+ ldflags.append("-mllvm:-mcpu=pentium4")
+ # This is also the CPU that Rust uses. The LLVM source code
+ # recommends this as the "generic 64-bit specific x86 processor model":
+ #
+ # https://github.com/llvm/llvm-project/blob/e7694f34ab6a12b8bb480cbfcb396d0a64fe965f/llvm/lib/Target/X86/X86.td#L1165-L1187
+ if target.cpu == "x86_64":
+ ldflags.append("-mllvm:-mcpu=x86-64")
+ # We do not need special flags for arm64. Hooray for fixed-length
+ # instruction sets.
+ else:
+ num_cores = multiprocessing.cpu_count()
+ cflags.append("-flto")
+ cflags.append("-flifetime-dse=1")
+
+ ldflags.append("-flto=%s" % num_cores)
+ ldflags.append("-flifetime-dse=1")
+
+ # Tell LTO not to inline functions above a certain size, to mitigate
+ # binary size growth while still getting good performance.
+ # (For hot functions, PGO will put a multiplier on this limit.)
+ if target.os == "WINNT":
+ ldflags.append("-mllvm:-import-instr-limit=10")
+ elif target.os == "OSX":
+ ldflags.append("-Wl,-mllvm,-import-instr-limit=10")
+ elif c_compiler.type == "clang":
+ ldflags.append("-Wl,-plugin-opt=-import-instr-limit=10")
+
+ # If we're using the new pass manager, we can also enable the new PM
+ # during LTO. Further we can use the resulting size savings to increase
+ # the import limit in hot functions.
+ if pass_manager:
+ if target.os == "WINNT":
+ if c_compiler.version >= "12.0.0" and c_compiler.version < "13.0.0":
+ ldflags.append("-opt:ltonewpassmanager")
+ if c_compiler.version >= "12.0.0":
+ ldflags.append("-mllvm:-import-hot-multiplier=30")
+ elif target.os == "OSX":
+ ldflags.append("-Wl,-mllvm,-import-hot-multiplier=30")
+ else:
+ if c_compiler.version < "13.0.0":
+ ldflags.append("-Wl,-plugin-opt=new-pass-manager")
+ ldflags.append("-Wl,-plugin-opt=-import-hot-multiplier=30")
+
+ # Pick Rust LTO mode in case of cross lTO. Thin is the default.
+ if "cross" in values:
+ rust_lto = "full" if "full" in values else "thin"
+ else:
+ rust_lto = ""
+
+ return namespace(
+ enabled=True,
+ cflags=cflags,
+ ldflags=ldflags,
+ rust_lto=rust_lto,
+ )
+
+
+add_old_configure_assignment("MOZ_LTO", lto.enabled)
+set_config("MOZ_LTO", lto.enabled)
+set_define("MOZ_LTO", lto.enabled)
+set_config("MOZ_LTO_CFLAGS", lto.cflags)
+set_config("MOZ_LTO_LDFLAGS", lto.ldflags)
+set_config("MOZ_LTO_RUST_CROSS", lto.rust_lto)
+add_old_configure_assignment("MOZ_LTO_CFLAGS", lto.cflags)
+add_old_configure_assignment("MOZ_LTO_LDFLAGS", lto.ldflags)
diff --git a/build/moz.configure/memory.configure b/build/moz.configure/memory.configure
new file mode 100644
index 0000000000..3a321bfb95
--- /dev/null
+++ b/build/moz.configure/memory.configure
@@ -0,0 +1,105 @@
+# -*- 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/.
+
+
+@depends(target, js_package)
+def jemalloc_default(target, js_package):
+ if js_package:
+ return False
+ return target.kernel in ("Darwin", "Linux", "WINNT")
+
+
+option(
+ "--enable-jemalloc",
+ env="MOZ_MEMORY",
+ default=jemalloc_default,
+ help="{Replace|Do not replace} memory allocator with jemalloc",
+)
+
+
+set_config("MOZ_MEMORY", True, when="--enable-jemalloc")
+set_define("MOZ_MEMORY", True, when="--enable-jemalloc")
+add_old_configure_assignment("MOZ_MEMORY", True, when="--enable-jemalloc")
+
+
+@depends(milestone, build_project)
+def replace_malloc_default(milestone, build_project):
+ if build_project == "memory":
+ return True
+ if milestone.is_early_beta_or_earlier and build_project != "js":
+ return True
+
+
+option(
+ "--enable-replace-malloc",
+ default=replace_malloc_default,
+ when="--enable-jemalloc",
+ help="{Enable|Disable} ability to dynamically replace the malloc implementation",
+)
+
+
+set_config("MOZ_REPLACE_MALLOC", True, when="--enable-replace-malloc")
+set_define("MOZ_REPLACE_MALLOC", True, when="--enable-replace-malloc")
+
+
+@depends(build_project, when="--enable-replace-malloc")
+def replace_malloc_static(build_project):
+ # Default to statically linking replace-malloc libraries that can be
+ # statically linked, except when building with --enable-project=memory.
+ if build_project != "memory":
+ return True
+
+
+set_config("MOZ_REPLACE_MALLOC_STATIC", replace_malloc_static)
+
+# PHC (Probabilistic Heap Checker)
+# ==============================================================
+
+# In general, it only makes sense for PHC to run on the platforms that have a
+# crash reporter.
+@depends(
+ milestone,
+ target,
+ replace_malloc_default,
+ "--enable-replace-malloc",
+ when="--enable-jemalloc",
+)
+def phc_default(milestone, target, replace_malloc_default, replace_malloc):
+ if not replace_malloc_default or (
+ replace_malloc.origin != "default" and not replace_malloc
+ ):
+ return False
+ # Nightly or early beta only because PHC has a non-negligible performance cost.
+ if not milestone.is_early_beta_or_earlier:
+ return False
+ # Both Linux32 and Win32 have frequent crashes when stack tracing (for
+ # unclear reasons), so PHC is enabled only on 64-bit only in both cases.
+ return (
+ (target.os == "GNU" and target.kernel == "Linux" and target.bitness == 64)
+ or (target.kernel == "WINNT" and target.bitness == 64)
+ or (target.os == "OSX")
+ )
+
+
+option(
+ "--enable-phc",
+ env="MOZ_PHC",
+ default=phc_default,
+ when="--enable-jemalloc",
+ help="{Enable|Disable} PHC (Probabilistic Memory Checker). "
+ "Also enables replace-malloc and frame pointers",
+)
+imply_option("--enable-replace-malloc", True, when="--enable-phc")
+imply_option("--enable-frame-pointers", True, when="--enable-phc")
+
+
+set_config("MOZ_PHC", True, when="--enable-phc")
+
+with only_when(depends(target.os)(lambda os: os != "WINNT")):
+ set_define("HAVE_STRNDUP", check_symbol("strndup"))
+ set_define("HAVE_POSIX_MEMALIGN", check_symbol("posix_memalign"))
+ set_define("HAVE_MEMALIGN", check_symbol("memalign"))
+ set_define("HAVE_MALLOC_USABLE_SIZE", check_symbol("malloc_usable_size"))
diff --git a/build/moz.configure/node.configure b/build/moz.configure/node.configure
new file mode 100644
index 0000000000..66668e7861
--- /dev/null
+++ b/build/moz.configure/node.configure
@@ -0,0 +1,90 @@
+# -*- 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/.
+
+option("--disable-nodejs", help="Require Node.js to build")
+option(env="NODEJS", nargs=1, help="Path to nodejs")
+
+
+@depends(
+ "--enable-nodejs",
+ "NODEJS",
+ bootstrap_search_path("node", when=depends("NODEJS")(lambda x: not x)),
+ host,
+)
+@checking(
+ "for nodejs", callback=lambda x: "%s (%s)" % (x.path, x.str_version) if x else "no"
+)
+@imports(_from="mozbuild.nodeutil", _import="find_node_executable")
+@imports(_from="mozbuild.nodeutil", _import="NODE_MIN_VERSION")
+@imports(_from="__builtin__", _import="OSError")
+@imports("errno")
+def nodejs(require, env_node, search_path, host):
+ # We don't use the dependency directly, but having it ensures the
+ # auto-upgrade code in bootstrap_search_path is triggered, while
+ # find_node_executable will use more or less the same search path.
+ # We do however need to use the variable for the configure lint
+ # not to fail.
+ search_path
+
+ node_exe = env_node[0] if env_node else None
+
+ try:
+ nodejs, version = find_node_executable(node_exe)
+ except OSError as e:
+ if host.cpu == "aarch64" and host.os == "OSX" and e.errno == errno.EBADARCH:
+ # Ideally we'd do it when --enable-bootstrap is set, but when we're wrapped in
+ # mach build or mach configure, running the command doesn't print anything and
+ # waits on input (for license agreement) that it can't actually get.
+ # mach bootstrap should have taken care of it anyways, but in case it hasn't,
+ # it's simpler to ask to run the rosetta install than the whole mach bootstrap.
+ die(
+ "Rosetta is needed to run node. Please run `softwareupdate --install-rosetta`"
+ )
+ raise
+
+ MAYBE_FILE_A_BUG = """
+
+ Executing `mach bootstrap --no-system-changes` should
+ install a compatible version in ~/.mozbuild on most platforms.
+ If you believe this is a bug, <https://mzl.la/2vLbXAv> is a good way
+ to file. More details: <https://bit.ly/2BbyD1E>
+ """
+
+ if not nodejs:
+ msg = (
+ "could not find Node.js executable later than %s; ensure "
+ "`node` or `nodejs` is in PATH or set NODEJS in environment "
+ "to point to an executable.%s" % (NODE_MIN_VERSION, MAYBE_FILE_A_BUG)
+ )
+
+ if require:
+ raise FatalCheckError(msg)
+ else:
+ log.warning(msg)
+ log.warning("(This will become an error in the near future.)")
+ return
+
+ if not version:
+ msg = "NODEJS must point to node %s or newer; found node location: %s. %s" % (
+ NODE_MIN_VERSION,
+ nodejs,
+ MAYBE_FILE_A_BUG,
+ )
+
+ if require:
+ raise FatalCheckError(msg)
+ else:
+ log.warning(msg)
+ return
+
+ return namespace(
+ path=nodejs,
+ version=version,
+ str_version=".".join(str(v) for v in version),
+ )
+
+
+set_config("NODEJS", depends_if(nodejs)(lambda p: p.path))
diff --git a/build/moz.configure/nspr.configure b/build/moz.configure/nspr.configure
new file mode 100644
index 0000000000..5c7358ca50
--- /dev/null
+++ b/build/moz.configure/nspr.configure
@@ -0,0 +1,115 @@
+# -*- 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/.
+
+# Top-level configure defaults to building NSPR from source. Standalone JS
+# doesn't.
+option(
+ "--enable-nspr-build",
+ when=js_standalone,
+ help="{Build|Do not build} NSPR from source tree",
+)
+
+
+@depends("--enable-nspr-build", when=js_standalone)
+def enable_nspr_build(enable):
+ if enable:
+ return enable
+
+
+system_lib_option("--with-system-nspr", help="Use system NSPR")
+
+
+@depends(enable_nspr_build, "--with-system-nspr", js_standalone)
+def build_nspr(nspr_build, system_nspr, js_standalone):
+ if nspr_build is not None and nspr_build.origin != "default":
+ if nspr_build and system_nspr:
+ die("Cannot use both --enable-nspr-build and --with-system-nspr")
+ if js_standalone:
+ return nspr_build
+ return not system_nspr
+
+
+set_config("MOZ_BUILD_NSPR", True, when=build_nspr)
+set_config("MOZ_SYSTEM_NSPR", True, when="--with-system-nspr")
+
+
+@depends(build_nspr, "--with-system-nspr", js_standalone)
+def js_without_nspr(build_nspr, system_nspr, js_standalone):
+ if js_standalone:
+ return not build_nspr and not system_nspr
+
+
+set_config("JS_WITHOUT_NSPR", True, when=js_without_nspr)
+set_define("JS_WITHOUT_NSPR", True, when=js_without_nspr)
+
+
+@depends(js_standalone)
+def nspr_minver(js_standalone):
+ if js_standalone:
+ return "nspr >= 4.10"
+ return "nspr >= 4.32"
+
+
+nspr_pkg = pkg_check_modules("NSPR", nspr_minver, when="--with-system-nspr")
+
+
+@depends_if(nspr_pkg)
+def nspr_pkg(nspr_pkg):
+ def extract(prefix, list):
+ for item in list:
+ if item.startswith(prefix):
+ return item[len(prefix) :]
+ return ""
+
+ include_dir = extract("-I", nspr_pkg.cflags)
+ lib_dir = extract("-L", nspr_pkg.libs)
+ return namespace(
+ cflags=nspr_pkg.cflags,
+ include_dir=include_dir,
+ libs=nspr_pkg.libs,
+ lib_dir=lib_dir,
+ )
+
+
+@depends("--with-system-nspr", nspr_minver)
+def pkgconf_requires_private(system_nspr, nspr_minver):
+ if not system_nspr:
+ return ""
+ return "Requires.private: %s" % nspr_minver
+
+
+set_config("PKGCONF_REQUIRES_PRIVATE", pkgconf_requires_private)
+
+# pkg_check_modules takes care of NSPR_CFLAGS and NSPR_LIBS when using --with-system-nspr.
+@depends(build_environment, c_compiler, fold_libs, when=build_nspr)
+def nspr_config(build_env, c_compiler, fold_libs):
+ libs = ["nspr4", "plc4", "plds4"]
+ if c_compiler.type == "clang-cl":
+ lib_dir = os.path.join(build_env.dist, "lib")
+ libs = [os.path.join(lib_dir, "%s.lib" % lib) for lib in libs]
+ else:
+ lib_dir = os.path.join(build_env.dist, "lib" if fold_libs else "bin")
+ libs = ["-L%s" % lib_dir] + ["-l%s" % lib for lib in libs]
+
+ include_dir = os.path.join(build_env.dist, "include", "nspr")
+ return namespace(
+ cflags=["-I%s" % include_dir],
+ include_dir=include_dir,
+ libs=libs,
+ lib_dir=lib_dir,
+ )
+
+
+set_config("NSPR_CFLAGS", nspr_config.cflags, when=nspr_config)
+set_config("NSPR_LIBS", nspr_config.libs, when=nspr_config)
+
+set_config("NSPR_INCLUDE_DIR", nspr_config.include_dir, when=nspr_config)
+set_config("NSPR_LIB_DIR", nspr_config.lib_dir, when=nspr_config)
+set_config("NSPR_INCLUDE_DIR", nspr_pkg.include_dir, when=nspr_pkg)
+set_config("NSPR_LIB_DIR", nspr_pkg.lib_dir, when=nspr_pkg)
+
+add_old_configure_assignment("NSPR_LIBS", nspr_config.libs, when=nspr_config)
+add_old_configure_assignment("NSPR_LIBS", nspr_pkg.libs, when=nspr_pkg)
diff --git a/build/moz.configure/nss.configure b/build/moz.configure/nss.configure
new file mode 100644
index 0000000000..f4fd994447
--- /dev/null
+++ b/build/moz.configure/nss.configure
@@ -0,0 +1,29 @@
+# -*- 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/.
+
+system_lib_option("--with-system-nss", help="Use system NSS")
+
+imply_option("--with-system-nspr", True, when="--with-system-nss")
+
+nss_pkg = pkg_check_modules(
+ "NSS", "nss >= 3.90", when="--with-system-nss", config=False
+)
+
+set_config("MOZ_SYSTEM_NSS", True, when="--with-system-nss")
+
+
+@depends(nss_pkg, build_environment)
+def nss_config(nss_pkg, build_env):
+ cflags = ["-I%s" % os.path.join(build_env.dist, "include", "nss")]
+ libs = None
+ if nss_pkg:
+ cflags = list(nss_pkg.cflags) + cflags
+ libs = nss_pkg.libs
+ return namespace(cflags=cflags, libs=libs)
+
+
+set_config("NSS_CFLAGS", nss_config.cflags)
+set_config("NSS_LIBS", nss_config.libs)
diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure
new file mode 100644
index 0000000000..ba213b33b4
--- /dev/null
+++ b/build/moz.configure/old.configure
@@ -0,0 +1,354 @@
+# -*- 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/.
+
+
+m4 = check_prog(
+ "M4",
+ (
+ "gm4",
+ "m4",
+ ),
+ paths=prefer_mozillabuild_path,
+)
+
+
+@depends(mozconfig)
+def prepare_mozconfig(mozconfig):
+ if mozconfig["path"]:
+ items = {}
+ for key, value in mozconfig["vars"]["added"].items():
+ items[key] = (value, "added")
+ for key, (old, value) in mozconfig["vars"]["modified"].items():
+ items[key] = (value, "modified")
+ for t in ("env", "vars"):
+ for key in mozconfig[t]["removed"].keys():
+ items[key] = (None, "removed " + t)
+ return items
+
+
+@depends("OLD_CONFIGURE", build_project)
+def old_configure(old_configure, build_project):
+ if not old_configure:
+ die("The OLD_CONFIGURE environment variable must be set")
+
+ # os.path.abspath in the sandbox will ensure forward slashes on Windows,
+ # which is actually necessary because this path actually ends up literally
+ # as $0, and backslashes there breaks autoconf's detection of the source
+ # directory.
+ old_configure = os.path.abspath(old_configure[0])
+ if build_project == "js":
+ old_configure_dir = os.path.dirname(old_configure)
+ if not old_configure_dir.endswith("/js/src"):
+ old_configure = os.path.join(
+ old_configure_dir, "js", "src", os.path.basename(old_configure)
+ )
+ return old_configure
+
+
+@depends(prepare_mozconfig, old_configure_assignments)
+@imports(_from="__builtin__", _import="open")
+@imports(_from="__builtin__", _import="print")
+@imports(_from="mozbuild.shellutil", _import="quote")
+def prepare_configure(mozconfig, old_configure_assignments):
+ with open("old-configure.vars", "w") as out:
+ log.debug("Injecting the following to old-configure:")
+
+ def inject(command):
+ print(command, file=out) # noqa Python 2vs3
+ log.debug("| %s", command)
+
+ if mozconfig:
+ inject("# start of mozconfig values")
+ for key, (value, action) in sorted(mozconfig.items()):
+ if action.startswith("removed "):
+ inject("unset %s # from %s" % (key, action[len("removed ") :]))
+ else:
+ inject("%s=%s # %s" % (key, quote(value), action))
+
+ inject("# end of mozconfig values")
+
+ for k, v in old_configure_assignments:
+ inject("%s=%s" % (k, quote(v)))
+
+
+@template
+def old_configure_options(*options):
+ for opt in options:
+ option(opt, nargs="*", help="Help missing for old configure options")
+
+ @dependable
+ def all_options():
+ return list(options)
+
+ return depends(
+ host_for_sub_configure, target_for_sub_configure, all_options, *options
+ )
+
+
+@old_configure_options(
+ "--cache-file",
+ "--datadir",
+ "--enable-official-branding",
+ "--includedir",
+ "--libdir",
+ "--prefix",
+ "--with-branding",
+ "--with-distribution-id",
+ "--with-macbundlename-prefix",
+ "--x-includes",
+ "--x-libraries",
+)
+def prepare_configure_options(host, target, all_options, *options):
+ # old-configure only supports the options listed in @old_configure_options
+ # so we don't need to pass it every single option we've been passed. Only
+ # the ones that are not supported by python configure need to.
+ options = [
+ value.format(name)
+ for name, value in zip(all_options, options)
+ if value.origin != "default"
+ ] + [host, target]
+
+ return namespace(options=options, all_options=all_options)
+
+
+@template
+def old_configure_for(old_configure_path, extra_env=None):
+ if extra_env is None:
+ extra_env = dependable(None)
+
+ @depends(
+ prepare_configure,
+ prepare_configure_options,
+ prefer_mozillabuild_path,
+ altered_path,
+ extra_env,
+ build_environment,
+ old_configure_path,
+ awk,
+ m4,
+ shell,
+ )
+ @imports(_from="__builtin__", _import="compile")
+ @imports(_from="__builtin__", _import="open")
+ @imports(_from="__builtin__", _import="OSError")
+ @imports("glob")
+ @imports("itertools")
+ @imports("logging")
+ @imports("os")
+ @imports("subprocess")
+ @imports("sys")
+ @imports(_from="mozbuild.shellutil", _import="quote")
+ @imports(_from="mozbuild.shellutil", _import="split")
+ @imports(_from="tempfile", _import="NamedTemporaryFile")
+ @imports(_from="subprocess", _import="CalledProcessError")
+ @imports(_from="__builtin__", _import="exec")
+ def old_configure(
+ prepare_configure,
+ prepare_configure_options,
+ prefer_mozillabuild_path,
+ altered_path,
+ extra_env,
+ build_env,
+ old_configure,
+ awk,
+ m4,
+ shell,
+ ):
+ # Use prepare_configure to make lint happy
+ prepare_configure
+
+ if altered_path:
+ path = altered_path
+ else:
+ path = os.pathsep.join(prefer_mozillabuild_path)
+
+ refresh = True
+ if os.path.exists(old_configure):
+ mtime = os.path.getmtime(old_configure)
+ aclocal = os.path.join(build_env.topsrcdir, "build", "autoconf", "*.m4")
+ for input in itertools.chain(
+ (
+ old_configure + ".in",
+ os.path.join(os.path.dirname(old_configure), "aclocal.m4"),
+ ),
+ glob.iglob(aclocal),
+ ):
+ if os.path.getmtime(input) > mtime:
+ break
+ else:
+ refresh = False
+
+ if refresh:
+ autoconf = os.path.join(
+ build_env.topsrcdir, "build", "autoconf", "autoconf.sh"
+ )
+ log.info("Refreshing %s with %s", old_configure, autoconf)
+ env = dict(os.environ)
+ env["M4"] = m4
+ env["AWK"] = awk
+ env["AC_MACRODIR"] = os.path.join(build_env.topsrcdir, "build", "autoconf")
+ env["PATH"] = path
+
+ try:
+ script = subprocess.check_output(
+ [
+ shell,
+ autoconf,
+ "--localdir=%s" % os.path.dirname(old_configure),
+ old_configure + ".in",
+ ],
+ # Fix the working directory, so that when m4 is called, that
+ # includes of relative paths are deterministically resolved
+ # relative to the directory containing old-configure.
+ cwd=os.path.dirname(old_configure),
+ env=env,
+ )
+ except CalledProcessError as exc:
+ die("autoconf exited with return code {}".format(exc.returncode))
+
+ if not script:
+ die(
+ "Generated old-configure is empty! Check that your autoconf 2.13 program works!"
+ )
+
+ # Make old-configure append to config.log, where we put our own log.
+ # This could be done with a m4 macro, but it's way easier this way
+ script = script.replace(b">./config.log", b">>${CONFIG_LOG=./config.log}")
+
+ with NamedTemporaryFile(
+ mode="wb",
+ prefix=os.path.basename(old_configure),
+ dir=os.path.dirname(old_configure),
+ delete=False,
+ ) as fh:
+ fh.write(script)
+
+ try:
+ os.rename(fh.name, old_configure)
+ except OSError:
+ try:
+ # Likely the file already existed (on Windows). Retry after removing it.
+ os.remove(old_configure)
+ os.rename(fh.name, old_configure)
+ except OSError as e:
+ die("Failed re-creating old-configure: %s" % e.message)
+
+ cmd = [shell, old_configure] + prepare_configure_options.options
+
+ env = dict(os.environ)
+
+ # For debugging purpose, in case it's not what we'd expect.
+ log.debug("Running %s", quote(*cmd))
+
+ # Our logging goes to config.log, the same file old.configure uses.
+ # We can't share the handle on the file, so close it.
+ logger = logging.getLogger("moz.configure")
+ config_log = None
+ for handler in logger.handlers:
+ if isinstance(handler, logging.FileHandler):
+ config_log = handler
+ config_log.close()
+ logger.removeHandler(config_log)
+ env["CONFIG_LOG"] = config_log.baseFilename
+ log_size = os.path.getsize(config_log.baseFilename)
+ break
+
+ env["PATH"] = path
+
+ if extra_env:
+ env.update(extra_env)
+
+ env["OLD_CONFIGURE_VARS"] = os.path.join(
+ build_env.topobjdir, "old-configure.vars"
+ )
+ proc = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env
+ )
+ while True:
+ line = proc.stdout.readline()
+ if not line:
+ break
+ log.info(line.rstrip())
+
+ ret = proc.wait()
+ if ret:
+ with log.queue_debug():
+ if config_log:
+ with open(config_log.baseFilename, "r") as fh:
+ fh.seek(log_size)
+ for line in fh:
+ log.debug(line.rstrip())
+ log.error("old-configure failed")
+ sys.exit(ret)
+
+ if config_log:
+ # Create a new handler in append mode
+ handler = logging.FileHandler(config_log.baseFilename, mode="a", delay=True)
+ handler.setFormatter(config_log.formatter)
+ logger.addHandler(handler)
+
+ raw_config = {
+ "split": split,
+ "unique_list": unique_list,
+ }
+ with open("config.data", "r") as fh:
+ code = compile(fh.read(), "config.data", "exec")
+ exec(code, raw_config)
+
+ # Ensure all the flags known to old-configure appear in the
+ # @old_configure_options above.
+ all_options = set(prepare_configure_options.all_options)
+ for flag in raw_config["flags"]:
+ if flag not in all_options:
+ die(
+ "Missing option in `@old_configure_options` in %s: %s",
+ __file__,
+ flag,
+ )
+
+ # If the code execution above fails, we want to keep the file around for
+ # debugging.
+ os.remove("config.data")
+
+ return namespace(
+ **{
+ c: [
+ (k[1:-1], v[1:-1] if isinstance(v, str) else v)
+ for k, v in raw_config[c]
+ ]
+ for c in ("substs", "defines")
+ }
+ )
+
+ return old_configure
+
+
+old_configure = old_configure_for(old_configure)
+set_config("OLD_CONFIGURE_SUBSTS", old_configure.substs)
+set_config("OLD_CONFIGURE_DEFINES", old_configure.defines)
+
+
+# Assuming no other option is declared after this function, handle the
+# env options that were injected by mozconfig_options by creating dummy
+# Option instances and having the sandbox's CommandLineHelper handle
+# them. We only do so for options that haven't been declared so far,
+# which should be a proxy for the options that old-configure handles
+# and that we don't know anything about.
+@depends("--help")
+@imports("__sandbox__")
+@imports(_from="mozbuild.configure.options", _import="Option")
+def remaining_mozconfig_options(_):
+ helper = __sandbox__._helper
+ for arg in list(helper):
+ if helper._origins[arg] != "mozconfig":
+ continue
+ name = arg.split("=", 1)[0]
+ if name.isupper() and name not in __sandbox__._options:
+ option = Option(env=name, nargs="*", help=name)
+ helper.handle(option)
+
+
+# Please do not add anything after remaining_mozconfig_options()
diff --git a/build/moz.configure/pkg.configure b/build/moz.configure/pkg.configure
new file mode 100644
index 0000000000..6b460ae174
--- /dev/null
+++ b/build/moz.configure/pkg.configure
@@ -0,0 +1,208 @@
+# -*- 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/.
+
+
+@depends(toolchain_prefix, when=compile_environment)
+def pkg_config(prefixes):
+ return tuple("{}pkg-config".format(p) for p in (prefixes or ()) + ("",))
+
+
+pkg_config = check_prog(
+ "PKG_CONFIG",
+ pkg_config,
+ bootstrap=depends(when=target_sysroot.bootstrapped)(lambda: "pkgconf"),
+ allow_missing=True,
+ when=compile_environment
+ & depends(target.os)(lambda os: os not in ("WINNT", "OSX", "Android")),
+)
+
+
+@depends_if(pkg_config)
+@checking("for pkg-config version")
+def pkg_config_version(pkg_config):
+ return Version(check_cmd_output(pkg_config, "--version").rstrip())
+
+
+@depends_if(pkg_config)
+@checking("whether pkg-config is pkgconf")
+def is_pkgconf(pkg_config):
+ return "pkgconf " in check_cmd_output(pkg_config, "--about", onerror=lambda: "")
+
+
+@depends(is_pkgconf, pkg_config_version, target_sysroot.bootstrapped, when=pkg_config)
+def pkg_config_base_flags(is_pkgconf, pkg_config_version, target_sysroot_bootstrapped):
+ # pkgconf 1.7.4 changed the default on Windows to use --static, but
+ # that doesn't work for us.
+ # Note: the --shared flag is not available before pkgconf 1.7
+ flags = []
+ if is_pkgconf and pkg_config_version >= "1.7.4":
+ flags.append("--shared")
+ # When pkg-config is in /usr things work fine by default, but when
+ # it is not, it defines prefix to be something else than /usr, which
+ # won't match what the .pc files actually say, and won't work in
+ # sysroots.
+ if target_sysroot_bootstrapped and (
+ (is_pkgconf and pkg_config_version >= "1.2.0")
+ or (not is_pkgconf and pkg_config_version >= "0.29.0")
+ ):
+ flags.append("--dont-define-prefix")
+ return tuple(flags)
+
+
+@depends(target, target_sysroot.path, target_multiarch_dir, when=pkg_config)
+@imports(_from="os", _import="environ")
+@imports(_from="os", _import="pathsep")
+def pkg_config_vars(target, sysroot_path, multiarch_dir):
+ if sysroot_path and target.kernel != "Darwin":
+ return namespace(
+ PKG_CONFIG_PATH="",
+ PKG_CONFIG_SYSROOT_DIR=sysroot_path,
+ PKG_CONFIG_LIBDIR=pathsep.join(
+ os.path.join(sysroot_path, d)
+ for d in (
+ "usr/lib/pkgconfig",
+ "usr/lib/{}/pkgconfig".format(multiarch_dir),
+ "usr/share/pkgconfig",
+ )
+ ),
+ )
+
+
+@depends(pkg_config_vars)
+@imports(_from="os", _import="environ")
+def pkg_config_env(vars):
+ if vars:
+ env = dict(environ)
+ env["PKG_CONFIG_PATH"] = vars.PKG_CONFIG_PATH
+ env["PKG_CONFIG_SYSROOT_DIR"] = vars.PKG_CONFIG_SYSROOT_DIR
+ env["PKG_CONFIG_LIBDIR"] = vars.PKG_CONFIG_LIBDIR
+ return env
+
+
+set_config("PKG_CONFIG_PATH", pkg_config_vars.PKG_CONFIG_PATH)
+set_config("PKG_CONFIG_SYSROOT_DIR", pkg_config_vars.PKG_CONFIG_SYSROOT_DIR)
+set_config("PKG_CONFIG_LIBDIR", pkg_config_vars.PKG_CONFIG_LIBDIR)
+
+
+# Locates the given module using pkg-config.
+# - `var` determines the name of variables to set when the package is found.
+# <var>_CFLAGS and <var>_LIBS are set with corresponding values.
+# - `package_desc` package name and version requirement string, list of
+# strings describing packages to locate, or depends function that will
+# resolve to such a string or list of strings.
+# - `when` a depends function that will determine whether to perform
+# any checks (default is to always perform checks).
+# - `allow_missing` If set, failure to fulfill the package description
+# will not result in an error or logged message, and any error message
+# will be returned to the caller.
+# Returns `True` when the package description is fulfilled.
+@template
+def pkg_check_modules(
+ var, package_desc, when=always, allow_missing=False, config=True, cflags_only=False
+):
+ @depends(dependable(package_desc), when=when)
+ def package_desc(desc):
+ if isinstance(desc, str):
+ desc = [desc]
+ if not isinstance(desc, (tuple, list)):
+ configure_error(
+ "package_desc must be a string or a tuple or list of strings"
+ )
+
+ return " ".join(desc)
+
+ allow_missing = dependable(allow_missing)
+
+ @depends(when, "--enable-compile-environment")
+ def when_and_compile_environment(when, compile_environment):
+ return when and compile_environment
+
+ @depends(pkg_config, pkg_config_version, when=when_and_compile_environment)
+ def check_pkg_config(pkg_config, version):
+ min_version = "0.9.0"
+ if pkg_config is None:
+ die(
+ "*** The pkg-config script could not be found. Make sure it is\n"
+ "*** in your path, or set the PKG_CONFIG environment variable\n"
+ "*** to the full path to pkg-config."
+ )
+ if version < min_version:
+ die(
+ "*** Your version of pkg-config is too old. You need version %s or newer.",
+ min_version,
+ )
+
+ @depends(
+ pkg_config,
+ pkg_config_env,
+ package_desc,
+ allow_missing,
+ when=when_and_compile_environment,
+ )
+ @imports("sys")
+ @imports(_from="mozbuild.configure.util", _import="LineIO")
+ def package(pkg_config, env, package_desc, allow_missing):
+ # package_desc may start as a depends function, so we can't use
+ # @checking here.
+ log.info("checking for %s... " % package_desc)
+ retcode, stdout, stderr = get_cmd_output(
+ pkg_config,
+ "--errors-to-stdout",
+ "--print-errors",
+ package_desc,
+ env=env,
+ )
+ if retcode == 0:
+ log.info("yes")
+ return True
+ log.info("no")
+ log_writer = log.warning if allow_missing else log.error
+ with LineIO(lambda l: log_writer(l)) as o:
+ o.write(stdout)
+ if not allow_missing:
+ sys.exit(1)
+
+ @depends(
+ pkg_config, pkg_config_env, package_desc, pkg_config_base_flags, when=package
+ )
+ @checking("%s_CFLAGS" % var, callback=lambda t: " ".join(t))
+ def pkg_cflags(pkg_config, env, package_desc, base_flags):
+ args = list(base_flags) + ["--cflags", package_desc]
+ flags = check_cmd_output(pkg_config, *args, env=env)
+ return tuple(flags.split())
+
+ if cflags_only:
+
+ @depends(pkg_cflags, when=package)
+ def pkg_info(cflags):
+ return namespace(cflags=cflags)
+
+ else:
+
+ @depends(
+ pkg_config,
+ pkg_config_env,
+ package_desc,
+ pkg_config_base_flags,
+ when=package,
+ )
+ @checking("%s_LIBS" % var, callback=lambda t: " ".join(t))
+ def pkg_libs(pkg_config, env, package_desc, base_flags):
+ args = list(base_flags) + ["--libs", package_desc]
+ libs = check_cmd_output(pkg_config, *args, env=env)
+ # Remove evil flags like -Wl,--export-dynamic
+ return tuple(libs.replace("-Wl,--export-dynamic", "").split())
+
+ @depends(pkg_cflags, pkg_libs, when=package)
+ def pkg_info(cflags, libs):
+ return namespace(cflags=cflags, libs=libs)
+
+ if config:
+ set_config("%s_CFLAGS" % var, pkg_cflags)
+ if not cflags_only:
+ set_config("%s_LIBS" % var, pkg_libs)
+
+ return pkg_info
diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure
new file mode 100644
index 0000000000..7a2fd1ae70
--- /dev/null
+++ b/build/moz.configure/rust.configure
@@ -0,0 +1,786 @@
+# -*- 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/.
+
+
+# Rust is required by `rust_compiler` below. We allow_missing here
+# to propagate failures to the better error message there.
+option(env="RUSTC", nargs=1, help="Path to the rust compiler")
+option(env="CARGO", nargs=1, help="Path to the Cargo package manager")
+
+rustc = check_prog(
+ "_RUSTC",
+ ["rustc"],
+ what="rustc",
+ paths=rust_search_path,
+ input="RUSTC",
+ allow_missing=True,
+)
+cargo = check_prog(
+ "_CARGO",
+ ["cargo"],
+ what="cargo",
+ paths=rust_search_path,
+ input="CARGO",
+ allow_missing=True,
+)
+
+
+@template
+def unwrap_rustup(prog, name):
+ # rustc and cargo can either be rustup wrappers, or they can be the actual,
+ # plain executables. For cargo, on OSX, rustup sets DYLD_LIBRARY_PATH (at
+ # least until https://github.com/rust-lang/rustup.rs/pull/1752 is merged
+ # and shipped) and that can wreak havoc (see bug 1536486). Similarly, for
+ # rustc, rustup silently honors toolchain overrides set by vendored crates
+ # (see bug 1547196).
+ #
+ # In either case, we need to find the plain executables.
+ #
+ # To achieve that, try to run `PROG +stable`. When the rustup wrapper is in
+ # use, it either prints PROG's help and exits with status 0, or prints
+ # an error message (error: toolchain 'stable' is not installed) and exits
+ # with status 1. In the cargo case, when plain cargo is in use, it exits
+ # with a different error message (e.g. "error: no such subcommand:
+ # `+stable`"), and exits with status 101.
+ #
+ # Unfortunately, in the rustc case, when plain rustc is in use,
+ # `rustc +stable` will exit with status 1, complaining about a missing
+ # "+stable" file. We'll examine the error output to try and distinguish
+ # between failing rustup and failing rustc.
+ @depends(prog, dependable(name))
+ @imports(_from="__builtin__", _import="open")
+ @imports("os")
+ def unwrap(prog, name):
+ if not prog:
+ return
+
+ def from_rustup_which():
+ out = check_cmd_output("rustup", "which", name, executable=prog).rstrip()
+ # If for some reason the above failed to return something, keep the
+ # PROG we found originally.
+ if out:
+ log.info("Actually using '%s'", out)
+ return out
+
+ log.info("No `rustup which` output, using '%s'", prog)
+ return prog
+
+ (retcode, stdout, stderr) = get_cmd_output(prog, "+stable")
+
+ if name == "cargo" and retcode != 101:
+ prog = from_rustup_which()
+ elif name == "rustc":
+ if retcode == 0:
+ prog = from_rustup_which()
+ elif "+stable" in stderr:
+ # PROG looks like plain `rustc`.
+ pass
+ else:
+ # Assume PROG looks like `rustup`. This case is a little weird,
+ # insofar as the user doesn't have the "stable" toolchain
+ # installed, but go ahead and unwrap anyway: the user might
+ # have only certain versions, beta, or nightly installed, and
+ # we'll catch invalid versions later.
+ prog = from_rustup_which()
+
+ return normalize_path(prog)
+
+ return unwrap
+
+
+rustc = unwrap_rustup(rustc, "rustc")
+cargo = unwrap_rustup(cargo, "cargo")
+
+
+set_config("CARGO", cargo)
+set_config("RUSTC", rustc)
+
+
+@depends_if(rustc)
+@checking("rustc version", lambda info: info.version)
+def rustc_info(rustc):
+ if not rustc:
+ return
+ out = check_cmd_output(rustc, "--version", "--verbose").splitlines()
+ info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:])
+ return namespace(
+ version=Version(info.get("release", "0")),
+ commit=info.get("commit-hash", "unknown"),
+ host=info["host"],
+ llvm_version=Version(info.get("LLVM version", "0")),
+ )
+
+
+set_config(
+ "RUSTC_VERSION",
+ depends(rustc_info)(lambda info: str(info.version) if info else None),
+)
+
+
+set_config(
+ "RUSTC_LLVM_VERSION",
+ depends(rustc_info)(lambda info: str(info.llvm_version) if info else None),
+)
+
+set_config(
+ "MOZ_CLANG_NEWER_THAN_RUSTC_LLVM",
+ depends(c_compiler, rustc_info)(
+ lambda c_compiler, rustc_info: rustc_info
+ and c_compiler.type == "clang"
+ and c_compiler.version.major > rustc_info.llvm_version.major
+ ),
+)
+
+
+@depends_if(cargo)
+@checking("cargo version", lambda info: info.version)
+@imports("re")
+def cargo_info(cargo):
+ if not cargo:
+ return
+ out = check_cmd_output(cargo, "--version", "--verbose").splitlines()
+ info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:])
+ version = info.get("release")
+ # Older versions of cargo didn't support --verbose, in which case, they
+ # only output a not-really-pleasant-to-parse output. Fortunately, they
+ # don't error out, so we can just try some regexp matching on the output
+ # we already got.
+ if version is None:
+ VERSION_FORMAT = r"^cargo (\d\.\d+\.\d+).*"
+
+ m = re.search(VERSION_FORMAT, out[0])
+ # Fail fast if cargo changes its output on us.
+ if not m:
+ die("Could not determine cargo version from output: %s", out)
+ version = m.group(1)
+
+ return namespace(
+ version=Version(version),
+ )
+
+
+@depends(rustc_info, cargo_info, target)
+@imports(_from="mozboot.util", _import="MINIMUM_RUST_VERSION")
+@imports(_from="textwrap", _import="dedent")
+def rust_compiler(rustc_info, cargo_info, target):
+ if not rustc_info:
+ die(
+ dedent(
+ """\
+ Rust compiler not found.
+ To compile rust language sources, you must have 'rustc' in your path.
+ See https://www.rust-lang.org/ for more information.
+
+ You can install rust by running './mach bootstrap'
+ or by directly running the installer from https://rustup.rs/
+ """
+ )
+ )
+ rustc_min_version = Version(MINIMUM_RUST_VERSION)
+ cargo_min_version = rustc_min_version
+
+ version = rustc_info.version
+ is_nightly = "nightly" in version.version
+ is_version_number_match = (
+ version.major == rustc_min_version.major
+ and version.minor == rustc_min_version.minor
+ and version.patch == rustc_min_version.patch
+ )
+
+ if version < rustc_min_version or (is_version_number_match and is_nightly):
+ die(
+ dedent(
+ """\
+ Rust compiler {} is too old.
+
+ To compile Rust language sources please install at least
+ version {} of the 'rustc' toolchain (or, if using nightly,
+ at least one version newer than {}) and make sure it is
+ first in your path.
+
+ You can verify this by typing 'rustc --version'.
+
+ If you have the 'rustup' tool installed you can upgrade
+ to the latest release by typing 'rustup update'. The
+ installer is available from https://rustup.rs/
+ """.format(
+ version, rustc_min_version, rustc_min_version
+ )
+ )
+ )
+
+ if target.kernel == "WINNT" and (version.major, version.minor) == (1, 56):
+ die(
+ dedent(
+ """\
+ Rust compiler 1.56.* is not supported for Windows builds.
+
+ Use a newer or an older version.
+
+ See https://github.com/rust-lang/rust/issues/88576.
+ """
+ )
+ )
+
+ if not cargo_info:
+ die(
+ dedent(
+ """\
+ Cargo package manager not found.
+ To compile Rust language sources, you must have 'cargo' in your path.
+ See https://www.rust-lang.org/ for more information.
+
+ You can install cargo by running './mach bootstrap'
+ or by directly running the installer from https://rustup.rs/
+ """
+ )
+ )
+
+ version = cargo_info.version
+ if version < cargo_min_version:
+ die(
+ dedent(
+ """\
+ Cargo package manager {} is too old.
+
+ To compile Rust language sources please install at least
+ version {} of 'cargo' and make sure it is first in your path.
+
+ You can verify this by typing 'cargo --version'.
+ """
+ ).format(version, cargo_min_version)
+ )
+
+ return True
+
+
+@depends(rustc, when=rust_compiler)
+@imports(_from="__builtin__", _import="ValueError")
+def rust_supported_targets(rustc):
+ out = check_cmd_output(rustc, "--print", "target-list").splitlines()
+ data = {}
+ for t in out:
+ try:
+ info = split_triplet(t, allow_wasi=True)
+ except ValueError:
+ if t.startswith("thumb"):
+ cpu, rest = t.split("-", 1)
+ retry = "-".join(("arm", rest))
+ else:
+ continue
+ try:
+ info = split_triplet(retry, allow_wasi=True)
+ except ValueError:
+ continue
+ key = (info.cpu, info.endianness, info.os)
+ data.setdefault(key, []).append(namespace(rust_target=t, target=info))
+ return data
+
+
+def detect_rustc_target(
+ host_or_target, compiler_info, arm_target, rust_supported_targets
+):
+ # Rust's --target options are similar to, but not exactly the same
+ # as, the autoconf-derived targets we use. An example would be that
+ # Rust uses distinct target triples for targetting the GNU C++ ABI
+ # and the MSVC C++ ABI on Win32, whereas autoconf has a single
+ # triple and relies on the user to ensure that everything is
+ # compiled for the appropriate ABI. We need to perform appropriate
+ # munging to get the correct option to rustc.
+ # We correlate the autoconf-derived targets with the list of targets
+ # rustc gives us with --print target-list.
+ candidates = rust_supported_targets.get(
+ (host_or_target.cpu, host_or_target.endianness, host_or_target.os), []
+ )
+
+ def find_candidate(candidates):
+ if len(candidates) == 1:
+ return candidates[0].rust_target
+ elif not candidates:
+ return None
+
+ # We have multiple candidates. There are two cases where we can try to
+ # narrow further down using extra information from the build system.
+ # - For windows targets, correlate with the C compiler type
+ if host_or_target.kernel == "WINNT":
+ if host_or_target.abi:
+ if host_or_target.abi == "msvc":
+ suffix = "windows-msvc"
+ elif host_or_target.abi == "mingw":
+ suffix = "windows-gnu"
+ elif compiler_info.type in ("gcc", "clang"):
+ suffix = "windows-gnu"
+ else:
+ suffix = "windows-msvc"
+ narrowed = [
+ c for c in candidates if c.rust_target.endswith("-{}".format(suffix))
+ ]
+ if len(narrowed) == 1:
+ return narrowed[0].rust_target
+ elif narrowed:
+ candidates = narrowed
+
+ vendor_aliases = {"pc": ("w64", "windows")}
+ narrowed = [
+ c
+ for c in candidates
+ if host_or_target.vendor in vendor_aliases.get(c.target.vendor, ())
+ ]
+
+ if len(narrowed) == 1:
+ return narrowed[0].rust_target
+
+ # - For arm targets, correlate with arm_target
+ # we could be more thorough with the supported rust targets, but they
+ # don't support OSes that are supported to build Gecko anyways.
+ # Also, sadly, the only interface to check the rust target cpu features
+ # is --print target-spec-json, and it's unstable, so we have to rely on
+ # our own knowledge of what each arm target means.
+ if host_or_target.cpu == "arm" and host_or_target.endianness == "little":
+ prefixes = []
+ if arm_target.arm_arch >= 7:
+ if arm_target.thumb2 and arm_target.fpu == "neon":
+ prefixes.append("thumbv7neon")
+ if arm_target.thumb2:
+ prefixes.append("thumbv7a")
+ prefixes.append("armv7")
+ if arm_target.arm_arch >= 6:
+ prefixes.append("armv6")
+ if host_or_target.os != "Android":
+ # arm-* rust targets are armv6... except arm-linux-androideabi
+ prefixes.append("arm")
+ if arm_target.arm_arch >= 5:
+ prefixes.append("armv5te")
+ if host_or_target.os == "Android":
+ # arm-* rust targets are armv6... except arm-linux-androideabi
+ prefixes.append("arm")
+ if arm_target.arm_arch >= 4:
+ prefixes.append("armv4t")
+ # rust freebsd targets are the only ones that don't have a 'hf' suffix
+ # for hard-float. Technically, that means if the float abi ever is not
+ # hard-float, this will pick a wrong target, but since rust only
+ # supports hard-float, let's assume that means freebsd only support
+ # hard-float.
+ if arm_target.float_abi == "hard" and host_or_target.os != "FreeBSD":
+ suffix = "hf"
+ else:
+ suffix = ""
+ for p in prefixes:
+ for c in candidates:
+ if c.rust_target.startswith(
+ "{}-".format(p)
+ ) and c.rust_target.endswith(suffix):
+ return c.rust_target
+
+ # See if we can narrow down on the exact alias.
+ # We use the sub_configure_alias to keep support mingw32 triplets as input.
+ narrowed = [
+ c
+ for c in candidates
+ if c.target.sub_configure_alias == host_or_target.sub_configure_alias
+ ]
+ if len(narrowed) == 1:
+ return narrowed[0].rust_target
+ elif narrowed:
+ candidates = narrowed
+
+ # See if we can narrow down with the raw OS
+ narrowed = [c for c in candidates if c.target.raw_os == host_or_target.raw_os]
+ if len(narrowed) == 1:
+ return narrowed[0].rust_target
+ elif narrowed:
+ candidates = narrowed
+
+ # See if we can narrow down with the raw OS and raw CPU
+ narrowed = [
+ c
+ for c in candidates
+ if c.target.raw_os == host_or_target.raw_os
+ and c.target.raw_cpu == host_or_target.raw_cpu
+ ]
+ if len(narrowed) == 1:
+ return narrowed[0].rust_target
+
+ # Finally, see if the vendor can be used to disambiguate.
+ narrowed = [c for c in candidates if c.target.vendor == host_or_target.vendor]
+ if len(narrowed) == 1:
+ return narrowed[0].rust_target
+
+ return None
+
+ rustc_target = find_candidate(candidates)
+
+ if rustc_target is None:
+ die("Don't know how to translate {} for rustc".format(host_or_target.alias))
+
+ return rustc_target
+
+
+@imports("os")
+@imports(_from="tempfile", _import="mkstemp")
+@imports(_from="textwrap", _import="dedent")
+@imports(_from="mozbuild.configure.util", _import="LineIO")
+def assert_rust_compile(host_or_target, rustc_target, rustc):
+ # Check to see whether our rustc has a reasonably functional stdlib
+ # for our chosen target.
+ target_arg = "--target=" + rustc_target
+ in_fd, in_path = mkstemp(prefix="conftest", suffix=".rs", text=True)
+ out_fd, out_path = mkstemp(prefix="conftest", suffix=".rlib")
+ os.close(out_fd)
+ try:
+ source = b'pub extern fn hello() { println!("Hello world"); }'
+ log.debug("Creating `%s` with content:", in_path)
+ with LineIO(lambda l: log.debug("| %s", l)) as out:
+ out.write(source)
+
+ os.write(in_fd, source)
+ os.close(in_fd)
+
+ cmd = [
+ rustc,
+ "--crate-type",
+ "staticlib",
+ target_arg,
+ "-o",
+ out_path,
+ in_path,
+ ]
+
+ def failed():
+ die(
+ dedent(
+ """\
+ Cannot compile for {} with {}
+ The target may be unsupported, or you may not have
+ a rust std library for that target installed. Try:
+
+ rustup target add {}
+ """.format(
+ host_or_target.alias, rustc, rustc_target
+ )
+ )
+ )
+
+ check_cmd_output(*cmd, onerror=failed)
+ if not os.path.exists(out_path) or os.path.getsize(out_path) == 0:
+ failed()
+ finally:
+ os.remove(in_path)
+ os.remove(out_path)
+
+
+@depends(
+ rustc,
+ host,
+ host_c_compiler,
+ rustc_info.host,
+ rust_supported_targets,
+ arm_target,
+ when=rust_compiler,
+)
+@checking("for rust host triplet")
+@imports(_from="textwrap", _import="dedent")
+def rust_host_triple(
+ rustc, host, compiler_info, rustc_host, rust_supported_targets, arm_target
+):
+ rustc_target = detect_rustc_target(
+ host, compiler_info, arm_target, rust_supported_targets
+ )
+ if rustc_target != rustc_host:
+ if host.alias == rustc_target:
+ configure_host = host.alias
+ else:
+ configure_host = "{}/{}".format(host.alias, rustc_target)
+ die(
+ dedent(
+ """\
+ The rust compiler host ({rustc}) is not suitable for the configure host ({configure}).
+
+ You can solve this by:
+ * Set your configure host to match the rust compiler host by editing your
+ mozconfig and adding "ac_add_options --host={rustc}".
+ * Or, install the rust toolchain for {configure}, if supported, by running
+ "rustup default stable-{rustc_target}"
+ """.format(
+ rustc=rustc_host,
+ configure=configure_host,
+ rustc_target=rustc_target,
+ )
+ )
+ )
+ assert_rust_compile(host, rustc_target, rustc)
+ return rustc_target
+
+
+@depends(
+ rustc, target, c_compiler, rust_supported_targets, arm_target, when=rust_compiler
+)
+@checking("for rust target triplet")
+def rust_target_triple(
+ rustc, target, compiler_info, rust_supported_targets, arm_target
+):
+ rustc_target = detect_rustc_target(
+ target, compiler_info, arm_target, rust_supported_targets
+ )
+ assert_rust_compile(target, rustc_target, rustc)
+ return rustc_target
+
+
+set_config("RUST_TARGET", rust_target_triple)
+set_config("RUST_HOST_TARGET", rust_host_triple)
+
+
+# This is used for putting source info into symbol files.
+set_config("RUSTC_COMMIT", depends(rustc_info)(lambda i: i.commit))
+
+# Rustdoc is required by Rust tests below.
+option(env="RUSTDOC", nargs=1, help="Path to the rustdoc program")
+
+rustdoc = check_prog(
+ "RUSTDOC",
+ ["rustdoc"],
+ paths=rust_search_path,
+ input="RUSTDOC",
+ allow_missing=True,
+)
+
+# This option is separate from --enable-tests because Rust tests are particularly
+# expensive in terms of compile time (especially for code in libxul).
+option(
+ "--enable-rust-tests",
+ help="Enable building and running of Rust tests during `make check`",
+)
+
+
+@depends("--enable-rust-tests", rustdoc)
+def rust_tests(enable_rust_tests, rustdoc):
+ if enable_rust_tests and not rustdoc:
+ die("--enable-rust-tests requires rustdoc")
+ return bool(enable_rust_tests)
+
+
+set_config("MOZ_RUST_TESTS", rust_tests)
+
+
+@depends(target, c_compiler, rustc)
+@imports("os")
+def rustc_natvis_ldflags(target, compiler_info, rustc):
+ if target.kernel == "WINNT" and compiler_info.type == "clang-cl":
+ sysroot = check_cmd_output(rustc, "--print", "sysroot").strip()
+ etc = os.path.join(sysroot, "lib/rustlib/etc")
+ ldflags = []
+ if os.path.isdir(etc):
+ for f in os.listdir(etc):
+ if f.endswith(".natvis"):
+ ldflags.append("-NATVIS:" + normsep(os.path.join(etc, f)))
+ return ldflags
+
+
+set_config("RUSTC_NATVIS_LDFLAGS", rustc_natvis_ldflags)
+
+
+option(
+ "--enable-rust-debug",
+ default=depends(when="--enable-debug")(lambda: True),
+ help="{Build|Do not build} Rust code with debug assertions turned " "on.",
+)
+
+
+@depends(when="--enable-rust-debug")
+def debug_rust():
+ return True
+
+
+set_config("MOZ_DEBUG_RUST", debug_rust)
+set_define("MOZ_DEBUG_RUST", debug_rust)
+
+# ==============================================================
+
+option(env="RUSTFLAGS", nargs=1, help="Rust compiler flags")
+set_config("RUSTFLAGS", depends("RUSTFLAGS")(lambda flags: flags))
+
+
+# Rust compiler flags
+# ==============================================================
+
+
+@depends(moz_optimize)
+def rustc_opt_level_default(moz_optimize):
+ return "2" if moz_optimize.optimize else "0"
+
+
+option(
+ env="RUSTC_OPT_LEVEL",
+ default=rustc_opt_level_default,
+ nargs=1,
+ help="Rust compiler optimization level (-C opt-level=%s)",
+)
+
+
+@depends("RUSTC_OPT_LEVEL")
+def rustc_opt_level(opt_level_option):
+ return opt_level_option[0]
+
+
+set_config("CARGO_PROFILE_RELEASE_OPT_LEVEL", rustc_opt_level)
+set_config("CARGO_PROFILE_DEV_OPT_LEVEL", rustc_opt_level)
+
+
+@depends(
+ rustc_opt_level,
+ debug_rust,
+ target,
+ "--enable-debug-symbols",
+ "--enable-frame-pointers",
+ path_remapping,
+ path_remappings,
+)
+def rust_compile_flags(
+ opt_level,
+ debug_rust,
+ target,
+ debug_symbols,
+ frame_pointers,
+ path_remapping,
+ path_remappings,
+):
+ # Cargo currently supports only two interesting profiles for building:
+ # development and release. Those map (roughly) to --enable-debug and
+ # --disable-debug in Gecko, respectively.
+ #
+ # But we'd also like to support an additional axis of control for
+ # optimization level. Since Cargo only supports 2 profiles, we're in
+ # a bit of a bind.
+ #
+ # Code here derives various compiler options given other configure options.
+ # The options defined here effectively override defaults specified in
+ # Cargo.toml files.
+
+ debug_assertions = None
+ debug_info = None
+
+ # opt-level=0 implies -C debug-assertions, which may not be desired
+ # unless Rust debugging is enabled.
+ if opt_level == "0" and not debug_rust:
+ debug_assertions = False
+
+ if debug_symbols:
+ debug_info = "2"
+
+ opts = []
+
+ if debug_assertions is not None:
+ opts.append("debug-assertions=%s" % ("yes" if debug_assertions else "no"))
+ if debug_info is not None:
+ opts.append("debuginfo=%s" % debug_info)
+ if frame_pointers:
+ opts.append("force-frame-pointers=yes")
+ # CFG for arm64 is crashy, see `def security_hardening_cflags`.
+ if target.kernel == "WINNT" and target.cpu != "aarch64":
+ opts.append("control-flow-guard=yes")
+
+ flags = []
+ for opt in opts:
+ flags.extend(["-C", opt])
+
+ if "rust" in path_remapping:
+ # rustc has supported --remap-path-prefix since version 1.26, well
+ # before our required minimum Rust version, so there's no need to
+ # feature-detect or gate on versions.
+ for old, new in path_remappings:
+ flags.append(f"--remap-path-prefix={old}={new}")
+
+ return flags
+
+
+# Rust incremental compilation
+# ==============================================================
+
+
+option("--disable-cargo-incremental", help="Disable incremental rust compilation.")
+
+
+@depends(
+ developer_options,
+ debug_rust,
+ "MOZ_AUTOMATION",
+ code_coverage,
+ "--disable-cargo-incremental",
+ using_sccache,
+ "RUSTC_WRAPPER",
+)
+@imports("os")
+def cargo_incremental(
+ developer_options,
+ debug_rust,
+ automation,
+ code_coverage,
+ enabled,
+ using_sccache,
+ rustc_wrapper,
+):
+ """Return a value for the CARGO_INCREMENTAL environment variable."""
+
+ if not enabled:
+ return "0"
+ elif enabled.origin != "default":
+ return "1"
+
+ # We never want to use incremental compilation in automation. sccache
+ # handles our automation use case much better than incremental compilation
+ # would.
+ if automation:
+ return "0"
+
+ # Coverage instrumentation doesn't play well with incremental compilation
+ # https://github.com/rust-lang/rust/issues/50203.
+ if code_coverage:
+ return "0"
+
+ # Incremental compilation doesn't work as well as it should, and if we're
+ # using sccache, it's better to use sccache than incremental compilation.
+ if not using_sccache and rustc_wrapper:
+ rustc_wrapper = os.path.basename(rustc_wrapper[0])
+ if os.path.splitext(rustc_wrapper)[0].lower() == "sccache":
+ using_sccache = True
+ if using_sccache:
+ return "0"
+
+ # Incremental compilation is automatically turned on for debug builds, so
+ # we don't need to do anything special here.
+ if debug_rust:
+ return
+
+ # Don't enable on --enable-release builds, because of the runtime
+ # performance cost.
+ if not developer_options:
+ return
+
+ # We're clear to use incremental compilation!
+ return "1"
+
+
+set_config("CARGO_INCREMENTAL", cargo_incremental)
+
+
+@depends(rust_compile_flags, "--enable-warnings-as-errors", rustc_info)
+def rust_flags(compile_flags, warnings_as_errors, rustc_info):
+ warning_flags = []
+
+ # Note that cargo passes --cap-lints warn to rustc for third-party code, so
+ # we don't need a very complicated setup.
+ if warnings_as_errors:
+ warning_flags.append("-Dwarnings")
+ # Work around https://github.com/rust-lang/rust/issues/84428
+ if rustc_info.version >= "1.52":
+ warning_flags.append("-Aproc-macro-back-compat")
+ else:
+ warning_flags.extend(("--cap-lints", "warn"))
+
+ return compile_flags + warning_flags
+
+
+set_config("MOZ_RUST_DEFAULT_FLAGS", rust_flags)
diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure
new file mode 100644
index 0000000000..3f91d71537
--- /dev/null
+++ b/build/moz.configure/toolchain.configure
@@ -0,0 +1,3194 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Code optimization
+# ==============================================================
+
+option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags")
+
+
+@depends("--enable-optimize")
+def moz_optimize(option):
+ flags = None
+
+ if len(option):
+ val = "2"
+ flags = option[0]
+ elif option:
+ val = "1"
+ else:
+ val = None
+
+ return namespace(
+ optimize=val,
+ flags=flags,
+ )
+
+
+set_config("MOZ_OPTIMIZE", moz_optimize.optimize)
+add_old_configure_assignment("MOZ_OPTIMIZE", moz_optimize.optimize)
+add_old_configure_assignment("MOZ_CONFIGURE_OPTIMIZE_FLAGS", moz_optimize.flags)
+
+# Android NDK
+# ==============================================================
+
+
+@depends("--disable-compile-environment", target)
+def compiling_android(compile_env, target):
+ return compile_env and target.os == "Android"
+
+
+include("android-ndk.configure", when=compiling_android)
+
+with only_when(target_is_osx):
+ # MacOS deployment target version
+ # ==============================================================
+ # This needs to happen before any compilation test is done.
+
+ option(
+ "--enable-macos-target",
+ env="MACOSX_DEPLOYMENT_TARGET",
+ nargs=1,
+ default=depends(target, developer_options)
+ # We continue to target 10.12 on Intel, but can target 11.0 for
+ # aarch64 since the earliest hardware was released alongside 11.0.
+ # For local builds, we want to target 10.12 regardless of the
+ # underlying platform to catch any errors or warnings that wouldn't
+ # show up when targeting 11.0, since these would later show up on
+ # CI for Intel builds.
+ (lambda t, d: "11.0" if (t.cpu == "aarch64" and not d) else "10.12"),
+ help="Set the minimum MacOS version needed at runtime{|}",
+ )
+
+ @depends_if("--enable-macos-target", developer_options)
+ def macos_target(value, _):
+ return value[0]
+
+
+with only_when(host_is_osx | target_is_osx):
+ # MacOS SDK
+ # =========
+ option(
+ "--with-macos-sdk",
+ env="MACOS_SDK_DIR",
+ nargs=1,
+ help="Location of platform SDK to use",
+ )
+
+ @imports("plistlib")
+ @imports(_from="__builtin__", _import="open")
+ @imports(_from="__builtin__", _import="Exception")
+ def get_sdk_version(sdk):
+ with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist:
+ obj = plistlib.load(plist)
+ if not obj:
+ raise Exception(
+ "Error parsing SDKSettings.plist in the SDK directory: %s" % sdk
+ )
+ if "Version" not in obj:
+ raise Exception(
+ "Error finding Version information in SDKSettings.plist from the SDK: %s"
+ % sdk
+ )
+ return Version(obj["Version"])
+
+ def sdk_min_version():
+ return "13.3"
+
+ @depends(
+ "--with-macos-sdk",
+ host,
+ bootstrap_path(
+ "MacOSX{}.sdk".format(sdk_min_version()),
+ when=depends("--with-macos-sdk")(lambda x: not x),
+ ),
+ )
+ @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(sdk_min_version()):
+ die(
+ 'SDK version "%s" is too old. Please upgrade to at least %s. Try '
+ "updating your system Xcode." % (version, 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)
+
+
+# GC rooting and hazard analysis.
+# ==============================================================
+option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis")
+
+
+@depends("MOZ_HAZARD")
+def hazard_analysis(value):
+ if value:
+ return True
+
+
+set_config("MOZ_HAZARD", hazard_analysis)
+
+
+# Cross-compilation related things.
+# ==============================================================
+option(
+ "--with-toolchain-prefix",
+ env="TOOLCHAIN_PREFIX",
+ nargs=1,
+ help="Prefix for the target toolchain",
+)
+
+
+@depends("--with-toolchain-prefix", host, target, cross_compiling)
+def toolchain_prefix(value, host, target, cross_compiling):
+ if value:
+ return tuple(value)
+ # We don't want a toolchain prefix by default when building on mac for mac.
+ if cross_compiling and not (target.os == "OSX" and host.os == "OSX"):
+ return ("%s-" % target.toolchain, "%s-" % target.alias)
+
+
+@depends(toolchain_prefix, target)
+def first_toolchain_prefix(toolchain_prefix, target):
+ # Pass TOOLCHAIN_PREFIX down to the build system if it was given from the
+ # command line/environment (in which case there's only one value in the tuple),
+ # or when cross-compiling for Android or OSX.
+ if toolchain_prefix and (
+ target.os in ("Android", "OSX") or len(toolchain_prefix) == 1
+ ):
+ return toolchain_prefix[0]
+
+
+set_config("TOOLCHAIN_PREFIX", first_toolchain_prefix)
+add_old_configure_assignment("TOOLCHAIN_PREFIX", first_toolchain_prefix)
+
+
+# Compilers
+# ==============================================================
+include("compilers-util.configure")
+
+
+def try_preprocess(
+ configure_cache, compiler, language, source, onerror=None, wrapper=[]
+):
+ return try_invoke_compiler(
+ configure_cache, compiler, language, source, ["-E"], onerror, wrapper
+ )
+
+
+@imports(_from="mozbuild.configure.constants", _import="CompilerType")
+@imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks")
+@imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks")
+@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
+@imports(_from="textwrap", _import="dedent")
+@imports(_from="__builtin__", _import="Exception")
+def get_compiler_info(configure_cache, compiler, language):
+ """Returns information about the given `compiler` (command line in the
+ form of a list or tuple), in the given `language`.
+
+ The returned information includes:
+ - the compiler type (clang-cl, clang or gcc)
+ - the compiler version
+ - the compiler supported language
+ - the compiler supported language version
+ """
+ # Xcode clang versions are different from the underlying llvm version (they
+ # instead are aligned with the Xcode version). Fortunately, we can tell
+ # apart plain clang from Xcode clang, and convert the Xcode clang version
+ # into the more or less corresponding plain clang version.
+ check = dedent(
+ """\
+ #if defined(_MSC_VER) && defined(__clang__) && defined(_MT)
+ %COMPILER "clang-cl"
+ %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
+ #elif defined(__clang__)
+ %COMPILER "clang"
+ %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
+ # ifdef __apple_build_version__
+ %XCODE 1
+ # endif
+ #elif defined(__GNUC__) && !defined(__MINGW32__)
+ %COMPILER "gcc"
+ %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
+ #endif
+
+ #if __cplusplus
+ %cplusplus __cplusplus
+ #elif __STDC_VERSION__
+ %STDC_VERSION __STDC_VERSION__
+ #endif
+ """
+ )
+
+ # While we're doing some preprocessing, we might as well do some more
+ # preprocessor-based tests at the same time, to check the toolchain
+ # matches what we want.
+ for name, preprocessor_checks in (
+ ("CPU", CPU_preprocessor_checks),
+ ("KERNEL", kernel_preprocessor_checks),
+ ("OS", OS_preprocessor_checks),
+ ):
+ for n, (value, condition) in enumerate(preprocessor_checks.items()):
+ check += dedent(
+ """\
+ #%(if)s %(condition)s
+ %%%(name)s "%(value)s"
+ """
+ % {
+ "if": "elif" if n else "if",
+ "condition": condition,
+ "name": name,
+ "value": value,
+ }
+ )
+ check += "#endif\n"
+
+ # Also check for endianness. The advantage of living in modern times is
+ # that all the modern compilers we support now have __BYTE_ORDER__ defined
+ # by the preprocessor.
+ check += dedent(
+ """\
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ %ENDIANNESS "little"
+ #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ %ENDIANNESS "big"
+ #endif
+ """
+ )
+
+ result = try_preprocess(configure_cache, compiler, language, check)
+
+ if not result:
+ raise FatalCheckError("Unknown compiler or compiler not supported.")
+
+ # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
+ # have non-ASCII characters. Treat the output as bytearray.
+ data = {}
+ for line in result.splitlines():
+ if line.startswith("%"):
+ k, _, v = line.partition(" ")
+ k = k.lstrip("%")
+ data[k] = v.replace(" ", "").lstrip('"').rstrip('"')
+ log.debug("%s = %s", k, data[k])
+
+ try:
+ type = CompilerType(data["COMPILER"])
+ except Exception:
+ raise FatalCheckError("Unknown compiler or compiler not supported.")
+
+ cplusplus = int(data.get("cplusplus", "0L").rstrip("L"))
+ stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L"))
+
+ version = data.get("VERSION")
+ if version:
+ version = Version(version)
+ if data.get("XCODE"):
+ # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
+ # with enough granularity for major.minor version checks further
+ # down the line
+ if version < "9.1":
+ version = Version("4.0.0.or.less")
+ elif version < "10.0":
+ version = Version("5.0.2")
+ elif version < "10.0.1":
+ version = Version("6.0.1")
+ elif version < "11.0":
+ version = Version("7.0.0")
+ elif version < "11.0.3":
+ version = Version("8.0.0")
+ elif version < "12.0":
+ version = Version("9.0.0")
+ elif version < "12.0.5":
+ version = Version("10.0.0")
+ elif version < "13.0":
+ version = Version("11.1.0")
+ elif version < "13.0.1":
+ version = Version("12.0.0")
+ elif version < "14.0":
+ version = Version("13.0.0")
+ elif version < "15.0":
+ version = Version("14.0.0")
+ else:
+ version = Version("14.0.0.or.more")
+
+ return namespace(
+ type=type,
+ version=version,
+ cpu=data.get("CPU"),
+ kernel=data.get("KERNEL"),
+ endianness=data.get("ENDIANNESS"),
+ os=data.get("OS"),
+ language="C++" if cplusplus else "C",
+ language_version=cplusplus if cplusplus else stdc_version,
+ xcode=bool(data.get("XCODE")),
+ )
+
+
+def same_arch_different_bits():
+ return (
+ ("x86", "x86_64"),
+ ("ppc", "ppc64"),
+ ("sparc", "sparc64"),
+ )
+
+
+@imports(_from="mozbuild.shellutil", _import="quote")
+@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
+def check_compiler(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("-Xclang")
+ flags.append("-std=c++17")
+ else:
+ flags.append("-std=gnu++17")
+
+ # Check compiler target
+ # --------------------------------------------------------------------
+ has_target = False
+ if target.os == "Android" and android_version:
+ # This makes clang define __ANDROID_API__ and use versioned library
+ # directories from the NDK.
+ toolchain = "%s%d" % (target.toolchain, android_version)
+ else:
+ toolchain = target.toolchain
+
+ if info.type == "clang":
+ # Add the target explicitly when the target is aarch64 macosx, because
+ # the Xcode clang target is named differently, and we need to work around
+ # https://github.com/rust-lang/rust-bindgen/issues/1871 and
+ # https://github.com/alexcrichton/cc-rs/issues/542 so we always want
+ # the target on the command line, even if the compiler would default to
+ # that.
+ if info.xcode and target.os == "OSX" and target.cpu == "aarch64":
+ if "--target=arm64-apple-darwin" not in compiler:
+ flags.append("--target=arm64-apple-darwin")
+ has_target = True
+
+ elif (
+ 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(topsrcdir):
+ def vswhere(args):
+ program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get(
+ "PROGRAMFILES"
+ )
+ if not program_files:
+ return []
+ vswhere = os.path.join(
+ program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe"
+ )
+ if not os.path.exists(vswhere):
+ return []
+ return json.loads(check_cmd_output(vswhere, "-format", "json", *args))
+
+ for install in vswhere(
+ [
+ "-products",
+ "*",
+ "-requires",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ ]
+ ):
+ path = install["installationPath"]
+ tools_version = (
+ open(
+ os.path.join(
+ path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
+ ),
+ "r",
+ )
+ .read()
+ .strip()
+ )
+ tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version)
+ yield (Version(install["installationVersion"]), tools_path)
+
+
+@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(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",
+ }.get(host.cpu)
+ if host_dir:
+ path = os.path.join(path, "bin", host_dir)
+ return {
+ "x64": [os.path.join(path, "x64")],
+ # The cross toolchains require DLLs from the native x64 toolchain.
+ "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")],
+ "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")],
+ }
+
+
+@depends(target, vc_compiler_paths_for_version, when=is_windows)
+def vc_compiler_path(target, paths):
+ vc_target = {
+ "x86": "x86",
+ "x86_64": "x64",
+ "arm": "arm",
+ "aarch64": "arm64",
+ }.get(target.cpu)
+ if not paths:
+ return
+ return paths.get(vc_target)
+
+
+@depends(vc_compiler_path, original_path)
+@imports("os")
+@imports(_from="os", _import="environ")
+def vc_toolchain_search_path(vc_compiler_path, original_path):
+ result = list(original_path)
+
+ if vc_compiler_path:
+ # The second item, if there is one, is necessary to have in $PATH for
+ # Windows to load the required DLLs from there.
+ if len(vc_compiler_path) > 1:
+ environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:])
+
+ # The first item is where the programs are going to be
+ result.append(vc_compiler_path[0])
+
+ return result
+
+
+@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 is_vs2019_or_more(vc_compiler_version):
+ return vc_compiler_version >= Version("19.20")
+
+
+add_old_configure_assignment("IS_VS2019_OR_MORE", is_vs2019_or_more)
+
+
+@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"
+ if vc_compiler_version >= Version("19.20"):
+ return "2019"
+ if vc_compiler_version >= Version("19.10"):
+ return "2017"
+
+ return ""
+
+
+set_config("MSVS_VERSION", msvs_version)
+
+
+clang_search_path = bootstrap_search_path("clang/bin")
+
+
+@depends(
+ bootstrap_search_path("rustc/bin", when="MOZ_AUTOMATION"),
+ bootstrap_search_path_order,
+ original_path,
+)
+@imports("os")
+@imports(_from="os", _import="environ")
+def rust_search_path(rust_path, search_order, original_path):
+ result = list(rust_path or original_path)
+ # Also add the rustup install directory for cargo/rustc.
+ cargo_home = environ.get("CARGO_HOME", "")
+ if cargo_home:
+ cargo_home = os.path.abspath(cargo_home)
+ else:
+ cargo_home = os.path.expanduser(os.path.join("~", ".cargo"))
+ rustup_path = os.path.join(cargo_home, "bin")
+ if search_order == "prepend":
+ result.insert(0, rustup_path)
+ else:
+ result.append(rustup_path)
+ return result
+
+
+# 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, target, original_path)
+@imports("os")
+def altered_path(mozillabuild_bin_paths, clang_search_path, target, original_path):
+ altered_path = mozillabuild_bin_paths
+ if target.kernel == "Darwin":
+ # The rust compiler wants to execute dsymutil, but it does so in a
+ # non-configurable way (https://github.com/rust-lang/rust/issues/52728)
+ # so we add the clang path.
+ path = clang_search_path
+ else:
+ path = original_path
+ 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)
+
+
+@depends_if(compiler_wrapper)
+def using_compiler_wrapper(compiler_wrapper):
+ return True
+
+
+set_config("MOZ_USING_COMPILER_WRAPPER", using_compiler_wrapper)
+
+
+@dependable
+def wasm():
+ return split_triplet("wasm32-wasi", allow_wasi=True)
+
+
+@template
+def default_c_compilers(host_or_target, other_c_compiler=None):
+ """Template defining the set of default C compilers for the host and
+ target platforms.
+ `host_or_target` is either `host` or `target` (the @depends functions
+ from init.configure.
+ `other_c_compiler` is the `target` C compiler when `host_or_target` is `host`.
+ """
+ assert host_or_target in {host, target, wasm}
+
+ other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)
+
+ @depends(host_or_target, target, toolchain_prefix, *other_c_compiler)
+ def default_c_compilers(
+ host_or_target, target, toolchain_prefix, *other_c_compiler
+ ):
+ if host_or_target.kernel == "WINNT":
+ 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,
+ bootstrap_path(
+ depends(host_or_target)(lambda t: "sysroot-{}".format(t.toolchain)),
+ when=bootstrap_sysroot,
+ ),
+ )
+ @imports("os")
+ def sysroot(sysroot_input, host_or_target, macos_sdk, path):
+ version = None
+ if sysroot_input:
+ path = sysroot_input[0]
+ elif host_or_target.kernel == "Darwin" and macos_sdk:
+ path = macos_sdk
+ if path:
+ # Find the version of libstdc++ headears in the sysroot
+ include = os.path.join(path, "usr/include/c++")
+ if os.path.isdir(include):
+ with os.scandir(include) as d:
+ version = max(Version(e.name) for e in d if e.is_dir())
+ log.info("Using %s sysroot in %s", host_or_target_str, path)
+ return namespace(
+ path=path,
+ bootstrapped=bool(path and not sysroot_input),
+ stdcxx_version=version,
+ )
+
+ return sysroot
+
+
+target_sysroot = sysroot(target)
+
+
+# Use `system_lib_option` instead of `option` for options that enable building
+# with a system library for which the development headers are not available in
+# the bootstrapped sysroots.
+@template
+def system_lib_option(name, *args, **kwargs):
+ option(name, *args, **kwargs)
+
+ @depends(name, target_sysroot.bootstrapped)
+ def no_system_lib_in_sysroot(value, bootstrapped):
+ if bootstrapped and value:
+ die(
+ "%s is not supported with bootstrapped sysroot. "
+ "Drop the option, or use --without-sysroot or --disable-bootstrap",
+ value.format(name),
+ )
+
+
+host_sysroot = sysroot(host, target_sysroot)
+
+
+@template
+def multiarch_dir(host_or_target):
+ sysroot = {
+ host: host_sysroot,
+ target: target_sysroot,
+ }[host_or_target]
+
+ @depends(host_or_target, when=sysroot.path)
+ def multiarch_dir(target):
+ if target.cpu == "x86":
+ # Turn e.g. i686-linux-gnu into i386-linux-gnu
+ return target.toolchain.replace(target.raw_cpu, "i386")
+ return target.toolchain
+
+ return multiarch_dir
+
+
+target_multiarch_dir = multiarch_dir(target)
+host_multiarch_dir = multiarch_dir(host)
+
+
+def minimum_gcc_version():
+ return Version("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,
+ 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,
+ 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 host_or_target.os == "OSX" and macos_target:
+ flags.append("-mmacosx-version-min=%s" % macos_target)
+ if provided_compiler:
+ wrapper.extend(provided_compiler.wrapper)
+ flags.extend(provided_compiler.flags)
+
+ info = check_compiler(
+ configure_cache,
+ wrapper + [compiler] + flags,
+ language,
+ host_or_target,
+ android_version,
+ )
+
+ # 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 < "7.0":
+ raise FatalCheckError(
+ "Only clang/llvm 7.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 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
+
+ # Set CC/CXX/HOST_CC/HOST_CXX for old-configure, which needs the wrapper
+ # and the flags that were part of the user input for those variables to
+ # be provided.
+ add_old_configure_assignment(
+ var,
+ depends_if(valid_compiler)(
+ lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)
+ ),
+ )
+
+ if host_or_target is target:
+ add_old_configure_assignment(
+ "ac_cv_prog_%s" % var,
+ depends_if(valid_compiler)(
+ lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)
+ ),
+ )
+ # We check that it works in python configure already.
+ add_old_configure_assignment("ac_cv_prog_%s_works" % var.lower(), "yes")
+ add_old_configure_assignment(
+ "ac_cv_prog_%s_cross" % var.lower(),
+ depends(cross_compiling)(lambda x: "yes" if x else "no"),
+ )
+ gcc_like = depends(valid_compiler.type)(
+ lambda x: "yes" if x in ("gcc", "clang") else "no"
+ )
+ add_old_configure_assignment("ac_cv_prog_%s_g" % var.lower(), gcc_like)
+ if language == "C":
+ add_old_configure_assignment("ac_cv_prog_gcc", gcc_like)
+ if language == "C++":
+ add_old_configure_assignment("ac_cv_prog_gxx", gcc_like)
+
+ # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
+ # old-configure to do some of its still existing checks.
+ if language == "C":
+ set_config("%s_TYPE" % var, valid_compiler.type)
+ add_old_configure_assignment("%s_TYPE" % var, valid_compiler.type)
+ set_config(
+ "%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v))
+ )
+
+ valid_compiler = compiler_class(valid_compiler, host_or_target)
+
+ def compiler_error():
+ raise FatalCheckError(
+ "Failed compiling a simple %s source with %s" % (language, what)
+ )
+
+ valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error)
+
+ set_config("%s_BASE_FLAGS" % var, valid_compiler.flags)
+
+ # Set CPP/CXXCPP for both the build system and old-configure. We don't
+ # need to check this works for preprocessing, because we already relied
+ # on $CC -E/$CXX -E doing preprocessing work to validate the compiler
+ # in the first place.
+ if host_or_target is target:
+ pp_var = {
+ "C": "CPP",
+ "C++": "CXXCPP",
+ }[language]
+
+ preprocessor = depends_if(valid_compiler)(
+ lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags)
+ )
+
+ set_config(pp_var, preprocessor)
+ add_old_configure_assignment(pp_var, preprocessor)
+
+ if language == "C":
+ linker_var = {
+ target: "LD",
+ host: "HOST_LD",
+ }.get(host_or_target)
+
+ if linker_var:
+
+ @deprecated_option(env=linker_var, nargs=1)
+ def linker(value):
+ if value:
+ return value[0]
+
+ @depends(linker)
+ def unused_linker(linker):
+ if linker:
+ log.warning(
+ "The value of %s is not used by this build system." % linker_var
+ )
+
+ return valid_compiler
+
+
+c_compiler = compiler("C", target)
+cxx_compiler = compiler("C++", target, c_compiler=c_compiler)
+host_c_compiler = compiler("C", host, other_compiler=c_compiler)
+host_cxx_compiler = compiler(
+ "C++",
+ host,
+ c_compiler=host_c_compiler,
+ other_compiler=cxx_compiler,
+ other_c_compiler=c_compiler,
+)
+
+
+@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")
+
+
+@depends(cxx_compiler, ccache_prefix)
+@imports("os")
+def cxx_is_icecream(info, ccache_prefix):
+ if (
+ os.path.islink(info.compiler)
+ and os.path.basename(os.readlink(info.compiler)) == "icecc"
+ ):
+ return True
+ if ccache_prefix and os.path.basename(ccache_prefix) == "icecc":
+ return True
+
+
+set_config("CXX_IS_ICECREAM", cxx_is_icecream)
+
+
+# Linker detection
+# ==============================================================
+# The policy is as follows:
+# For Windows:
+# - the linker is picked via the LINKER environment variable per windows.configure,
+# but ought to be llvm-lld in any case.
+# For macOS:
+# - the linker is lld if the clang used is >= 15 (per LLVM version, not Xcode version).
+# - the linker is also lld on local developer builds if the clang used is >= 13 (per LLVM
+# version, not Xcode version)
+# - otherwise the linker is ld64, either from XCode on macOS, or from cctools-ports when
+# cross-compiling.
+# For other OSes:
+# - on local developer builds: lld is used if present. Otherwise gold is used if present
+# otherwise, BFD ld is used.
+# - on release/official builds: whatever the compiler uses by default, except on Android
+# (see enable_linker_default below). Usually what the compiler uses by default is BFD
+# ld, except with the Android NDK compiler, where the default varies depending on the
+# NDK version. The default also varies by platform and clang version.
+# lld is not used by default on Linux and Android because it introduces layout changes
+# that prevent elfhack from working. See e.g.
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1563654#c2.
+@template
+def is_not_winnt_or_sunos(host_or_target):
+ @depends(host_or_target)
+ def is_not_winnt_or_sunos(host_or_target):
+ if host_or_target.kernel not in ("WINNT", "SunOS"):
+ return True
+
+ return is_not_winnt_or_sunos
+
+
+is_linker_option_enabled = is_not_winnt_or_sunos(target)
+
+
+@deprecated_option("--enable-gold", env="MOZ_FORCE_GOLD", when=is_linker_option_enabled)
+def enable_gold(value):
+ if value:
+ die("--enable-gold is deprecated, use --enable-linker=gold instead")
+ else:
+ die("--disable-gold is deprecated, use --enable-linker=something_else instead")
+
+
+@depends(target, developer_options)
+def enable_linker_default(target, developer_options):
+ # Recent versions of clang default to lld when targetting Android, but we don't
+ # want that as the default for non developer builds (see above).
+ # So we want to force the default to whatever it was with older versions of clang,
+ # but with caveats/workarounds:
+ # - x86-64 gold has bugs in how it lays out .note.* sections. See bug 1573820.
+ # - x86-32 gold has a bug when assembly files are built. See bug 1651699.
+ # That leaves us with aarch64 and armv7, which respectively defaulted to
+ # bfd and gold.
+ # On developer builds, select_linker will pick lld if it's not the default.
+ if target.os == "Android" and not developer_options:
+ return "bfd" if target.cpu in ("x86", "x86_64", "aarch64") else "gold"
+
+
+option(
+ "--enable-linker",
+ nargs=1,
+ help="Select the linker {bfd, gold, ld64, lld, lld-*, mold}{|}",
+ default=enable_linker_default,
+ when=is_linker_option_enabled,
+)
+
+
+# No-op to enable depending on --enable-linker from default_elfhack in
+# toolkit/moz.configure.
+@depends("--enable-linker", when=is_linker_option_enabled)
+def enable_linker(linker):
+ return linker
+
+
+@template
+def select_linker_tmpl(host_or_target):
+ if host_or_target is target:
+ deps = depends(
+ "--enable-linker",
+ c_compiler,
+ developer_options,
+ extra_toolchain_flags,
+ target,
+ when=is_linker_option_enabled,
+ )
+ host_or_target_str = "target"
+ else:
+ deps = depends(
+ dependable(None),
+ host_c_compiler,
+ developer_options,
+ dependable(None),
+ host,
+ when=is_not_winnt_or_sunos(host_or_target),
+ )
+ host_or_target_str = "host"
+
+ @deps
+ @checking(f"for {host_or_target_str} linker", lambda x: x.KIND)
+ @imports("os")
+ @imports("shutil")
+ def select_linker(linker, c_compiler, developer_options, toolchain_flags, target):
+
+ if linker:
+ linker = linker[0]
+ else:
+ linker = None
+
+ def is_valid_linker(linker):
+ if target.kernel == "Darwin":
+ valid_linkers = ("ld64", "lld")
+ else:
+ valid_linkers = ("bfd", "gold", "lld", "mold")
+ if linker in valid_linkers:
+ return True
+ if "lld" in valid_linkers and linker.startswith("lld-"):
+ return True
+ return False
+
+ if linker and not is_valid_linker(linker):
+ # Check that we are trying to use a supported linker
+ die("Unsupported linker " + linker)
+
+ # Check the kind of linker
+ version_check = ["-Wl,--version"]
+ cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags
+
+ def try_linker(linker):
+ # Generate the compiler flag
+ if linker == "ld64":
+ linker_flag = ["-fuse-ld=ld"]
+ elif linker:
+ linker_flag = ["-fuse-ld=" + linker]
+ else:
+ linker_flag = []
+ cmd = cmd_base + linker_flag + version_check
+ if toolchain_flags:
+ cmd += toolchain_flags
+
+ # ld64 doesn't have anything to print out a version. It does print out
+ # "ld64: For information on command line options please use 'man ld'."
+ # but that would require doing two attempts, one with --version, that
+ # would fail, and another with --help.
+ # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message
+ # specific to it on stderr when it fails to process --version.
+ env = dict(os.environ)
+ env["LD_PRINT_OPTIONS"] = "1"
+ # Some locales might not print out the strings we are looking for, so
+ # ensure consistent output.
+ env["LC_ALL"] = "C"
+ retcode, stdout, stderr = get_cmd_output(*cmd, env=env)
+ if retcode == 1 and "Logging ld64 options" in stderr:
+ kind = "ld64"
+
+ elif retcode != 0:
+ return None
+
+ elif "mold" in stdout:
+ kind = "mold"
+
+ elif "GNU ld" in stdout:
+ # We are using the normal linker
+ kind = "bfd"
+
+ elif "GNU gold" in stdout:
+ kind = "gold"
+
+ elif "LLD" in stdout:
+ kind = "lld"
+
+ else:
+ kind = "unknown"
+
+ if kind == "unknown" or is_valid_linker(kind):
+ return namespace(
+ KIND=kind,
+ LINKER_FLAG=linker_flag,
+ )
+
+ result = try_linker(linker)
+ if result is None and linker:
+ die("Could not use {} as linker".format(linker))
+
+ if (
+ linker is None
+ and target.kernel == "Darwin"
+ and c_compiler.type == "clang"
+ and (
+ (developer_options and c_compiler.version >= "13.0")
+ or c_compiler.version >= "15.0"
+ )
+ ):
+ result = try_linker("lld")
+ elif (
+ linker is None
+ and (
+ developer_options
+ or (host_or_target_str == "host" and c_compiler.type == "clang")
+ )
+ and (result is None or result.KIND in ("bfd", "gold"))
+ ):
+ # try and use lld if available.
+ tried = try_linker("lld")
+ if (result is None or result.KIND != "gold") and (
+ tried is None or tried.KIND != "lld"
+ ):
+ tried = try_linker("gold")
+ if tried is None or tried.KIND != "gold":
+ tried = None
+ if tried:
+ result = tried
+
+ if result is None:
+ die("Failed to find an adequate linker")
+
+ # If an explicit linker was given, error out if what we found is different.
+ if linker and not linker.startswith(result.KIND):
+ die("Could not use {} as linker".format(linker))
+
+ return result
+
+ return select_linker
+
+
+select_linker = select_linker_tmpl(target)
+set_config("LINKER_KIND", select_linker.KIND)
+
+
+@template
+def linker_ldflags_tmpl(host_or_target):
+ if host_or_target is target:
+ deps = depends_if(
+ select_linker,
+ target,
+ target_sysroot,
+ target_multiarch_dir,
+ android_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)
+add_old_configure_assignment("LINKER_LDFLAGS", linker_ldflags)
+
+add_old_configure_assignment("HOST_LINKER_LDFLAGS", linker_ldflags_tmpl(host))
+
+
+# There's a wrinkle with MinGW: linker configuration is not enabled, so
+# `select_linker` is never invoked. Hard-code around it.
+@depends(select_linker, target, c_compiler)
+def gcc_use_gnu_ld(select_linker, target, c_compiler):
+ if select_linker is not None and target.kernel != "Darwin":
+ return select_linker.KIND in ("bfd", "gold", "lld", "mold")
+ if target.kernel == "WINNT" and c_compiler.type == "clang":
+ return True
+ return None
+
+
+# GCC_USE_GNU_LD=1 means the linker is command line compatible with GNU ld.
+set_config("GCC_USE_GNU_LD", gcc_use_gnu_ld)
+add_old_configure_assignment("GCC_USE_GNU_LD", gcc_use_gnu_ld)
+
+
+include("compile-checks.configure")
+include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm"))
+
+
+@depends(
+ have_64_bit,
+ try_compile(
+ body='static_assert(sizeof(void *) == 8, "")', check_msg="for 64-bit OS"
+ ),
+)
+def check_have_64_bit(have_64_bit, compiler_have_64_bit):
+ if have_64_bit != compiler_have_64_bit:
+ configure_error(
+ "The target compiler does not agree with configure "
+ "about the target bitness."
+ )
+
+
+@depends(cxx_compiler, target)
+def needs_libstdcxx_newness_check(cxx_compiler, target):
+ # We only have to care about this on Linux and MinGW.
+ if cxx_compiler.type == "clang-cl":
+ return
+
+ if target.kernel not in ("Linux", "WINNT"):
+ return
+
+ if target.os == "Android":
+ return
+
+ return True
+
+
+def die_on_old_libstdcxx():
+ die(
+ "The libstdc++ in use is not new enough. Please run "
+ "./mach bootstrap to update your compiler, or update your system "
+ "libstdc++ installation."
+ )
+
+
+try_compile(
+ includes=["cstddef"],
+ body="\n".join(
+ [
+ # _GLIBCXX_RELEASE showed up in libstdc++ 7.
+ "#if defined(__GLIBCXX__) && !defined(_GLIBCXX_RELEASE)",
+ "# error libstdc++ not new enough",
+ "#endif",
+ "#if defined(_GLIBCXX_RELEASE)",
+ "# if _GLIBCXX_RELEASE < %d" % minimum_gcc_version().major,
+ "# error libstdc++ not new enough",
+ "# else",
+ " (void) 0",
+ "# endif",
+ "#endif",
+ ]
+ ),
+ check_msg="for new enough STL headers from libstdc++",
+ when=needs_libstdcxx_newness_check,
+ onerror=die_on_old_libstdcxx,
+)
+
+
+@depends(c_compiler, target)
+def default_debug_flags(compiler_info, target):
+ # Debug info is ON by default.
+ if compiler_info.type == "clang-cl":
+ return "-Z7"
+ elif target.kernel == "WINNT" and compiler_info.type == "clang":
+ return "-g -gcodeview"
+ # The oldest versions of supported compilers default to DWARF-4, but
+ # newer versions may default to DWARF-5 or newer (e.g. clang 14), which
+ # Valgrind doesn't support. Force-use DWARF-4.
+ return "-gdwarf-4"
+
+
+option(env="MOZ_DEBUG_FLAGS", nargs=1, help="Debug compiler flags")
+
+imply_option("--enable-debug-symbols", depends_if("--enable-debug")(lambda v: v))
+
+option(
+ "--disable-debug-symbols",
+ nargs="?",
+ help="Disable debug symbols using the given compiler flags",
+)
+
+set_config("MOZ_DEBUG_SYMBOLS", depends_if("--enable-debug-symbols")(lambda _: True))
+
+
+@depends("MOZ_DEBUG_FLAGS", "--enable-debug-symbols", default_debug_flags)
+def debug_flags(env_debug_flags, enable_debug_flags, default_debug_flags):
+ # If MOZ_DEBUG_FLAGS is set, and --enable-debug-symbols is set to a value,
+ # --enable-debug-symbols takes precedence. Note, the value of
+ # --enable-debug-symbols may be implied by --enable-debug.
+ if len(enable_debug_flags):
+ return enable_debug_flags[0]
+ if env_debug_flags:
+ return env_debug_flags[0]
+ return default_debug_flags
+
+
+set_config("MOZ_DEBUG_FLAGS", debug_flags)
+add_old_configure_assignment("MOZ_DEBUG_FLAGS", debug_flags)
+
+
+@depends(c_compiler, host)
+@imports(
+ _from="mach.logging", _import="enable_blessed", _as="_enable_ansi_escape_codes"
+)
+def color_cflags(info, host):
+ # We could test compiling with flags. By why incur the overhead when
+ # color support should always be present in a specific toolchain
+ # version?
+
+ # Code for auto-adding this flag to compiler invocations needs to
+ # determine if an existing flag isn't already present. That is likely
+ # using exact string matching on the returned value. So if the return
+ # value changes to e.g. "<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)
+
+
+@depends(target, build_environment)
+def visibility_flags(target, env):
+ if target.os != "WINNT":
+ if target.kernel == "Darwin":
+ return ("-fvisibility=hidden", "-fvisibility-inlines-hidden")
+ return (
+ "-I%s/system_wrappers" % os.path.join(env.dist),
+ "-include",
+ "%s/config/gcc_hidden.h" % env.topsrcdir,
+ )
+
+
+@depends(target, visibility_flags)
+def wrap_system_includes(target, visibility_flags):
+ if visibility_flags and target.kernel != "Darwin":
+ return True
+
+
+set_define(
+ "HAVE_VISIBILITY_HIDDEN_ATTRIBUTE",
+ depends(visibility_flags)(lambda v: bool(v) or None),
+)
+set_define(
+ "HAVE_VISIBILITY_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None)
+)
+set_config("WRAP_SYSTEM_INCLUDES", wrap_system_includes)
+set_config("VISIBILITY_FLAGS", visibility_flags)
+
+
+@template
+def depend_cflags(host_or_target_c_compiler):
+ @depends(host_or_target_c_compiler)
+ def depend_cflags(host_or_target_c_compiler):
+ if host_or_target_c_compiler.type != "clang-cl":
+ return ["-MD", "-MP", "-MF $(MDDEPDIR)/$(@F).pp"]
+ else:
+ # clang-cl doesn't accept the normal -MD -MP -MF options that clang
+ # does, but the underlying cc1 binary understands how to generate
+ # dependency files. These options are based on analyzing what the
+ # normal clang driver sends to cc1 when given the "correct"
+ # dependency options.
+ return [
+ "-Xclang",
+ "-MP",
+ "-Xclang",
+ "-dependency-file",
+ "-Xclang",
+ "$(MDDEPDIR)/$(@F).pp",
+ "-Xclang",
+ "-MT",
+ "-Xclang",
+ "$@",
+ ]
+
+ return depend_cflags
+
+
+set_config("_DEPEND_CFLAGS", depend_cflags(c_compiler))
+set_config("_HOST_DEPEND_CFLAGS", depend_cflags(host_c_compiler))
+
+
+@depends(c_compiler)
+def preprocess_option(compiler):
+ # The uses of PREPROCESS_OPTION depend on the spacing for -o/-Fi.
+ if compiler.type in ("gcc", "clang"):
+ return "-E -o "
+ else:
+ return "-P -Fi"
+
+
+set_config("PREPROCESS_OPTION", preprocess_option)
+
+
+# We only want to include windows.configure when we are compiling on
+# Windows, or for Windows.
+include("windows.configure", when=is_windows)
+
+
+# On Power ISA, determine compiler flags for VMX, VSX and VSX-3.
+
+set_config(
+ "PPC_VMX_FLAGS",
+ ["-maltivec"],
+ when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")),
+)
+
+set_config(
+ "PPC_VSX_FLAGS",
+ ["-mvsx"],
+ when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")),
+)
+
+set_config(
+ "PPC_VSX3_FLAGS",
+ ["-mvsx", "-mcpu=power9"],
+ when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")),
+)
+
+# ASAN
+# ==============================================================
+
+option("--enable-address-sanitizer", help="Enable Address Sanitizer")
+
+
+@depends(when="--enable-address-sanitizer")
+def asan():
+ return True
+
+
+add_old_configure_assignment("MOZ_ASAN", asan)
+
+# MSAN
+# ==============================================================
+
+option("--enable-memory-sanitizer", help="Enable Memory Sanitizer")
+
+
+@depends(when="--enable-memory-sanitizer")
+def msan():
+ return True
+
+
+add_old_configure_assignment("MOZ_MSAN", msan)
+
+# TSAN
+# ==============================================================
+
+option("--enable-thread-sanitizer", help="Enable Thread Sanitizer")
+
+
+@depends(when="--enable-thread-sanitizer")
+def tsan():
+ return True
+
+
+add_old_configure_assignment("MOZ_TSAN", tsan)
+
+# UBSAN
+# ==============================================================
+
+option(
+ "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer"
+)
+
+
+@depends_if("--enable-undefined-sanitizer")
+def ubsan(options):
+ default_checks = [
+ "bool",
+ "bounds",
+ "enum",
+ "function",
+ "integer-divide-by-zero",
+ "object-size",
+ "pointer-overflow",
+ "return",
+ "vla-bound",
+ ]
+
+ checks = options if len(options) else default_checks
+
+ return ",".join(checks)
+
+
+add_old_configure_assignment("MOZ_UBSAN_CHECKS", ubsan)
+
+
+option(
+ "--enable-signed-overflow-sanitizer",
+ help="Enable UndefinedBehavior Sanitizer (Signed Integer Overflow Parts)",
+)
+
+
+@depends(when="--enable-signed-overflow-sanitizer")
+def ub_signed_overflow_san():
+ return True
+
+
+add_old_configure_assignment("MOZ_SIGNED_OVERFLOW_SANITIZE", ub_signed_overflow_san)
+
+
+option(
+ "--enable-unsigned-overflow-sanitizer",
+ help="Enable UndefinedBehavior Sanitizer (Unsigned Integer Overflow Parts)",
+)
+
+
+@depends(when="--enable-unsigned-overflow-sanitizer")
+def ub_unsigned_overflow_san():
+ return True
+
+
+add_old_configure_assignment("MOZ_UNSIGNED_OVERFLOW_SANITIZE", ub_unsigned_overflow_san)
+
+# Security Hardening
+# ==============================================================
+
+option(
+ "--enable-hardening",
+ env="MOZ_SECURITY_HARDENING",
+ help="Enables security hardening compiler options",
+)
+
+
+# This function is a bit confusing. It adds or removes hardening flags in
+# three stuations: if --enable-hardening is passed; if --disable-hardening
+# is passed, and if no flag is passed.
+#
+# At time of this comment writing, all flags are actually added in the
+# default no-flag case; making --enable-hardening the same as omitting the
+# flag. --disable-hardening will omit the security flags. (However, not all
+# possible security flags will be omitted by --disable-hardening, as many are
+# compiler-default options we do not explicitly enable.)
+@depends(
+ "--enable-hardening",
+ "--enable-address-sanitizer",
+ "--enable-debug",
+ "--enable-optimize",
+ c_compiler,
+ target,
+)
+def security_hardening_cflags(
+ hardening_flag, asan, debug, optimize, c_compiler, target
+):
+ compiler_is_gccish = c_compiler.type in ("gcc", "clang")
+ mingw_clang = c_compiler.type == "clang" and target.os == "WINNT"
+
+ flags = []
+ ldflags = []
+ trivial_auto_var_init = []
+
+ # WASI compiler doesn't support security hardening cflags
+ if target.os == "WASI":
+ return
+
+ # ----------------------------------------------------------
+ # If hardening is explicitly enabled, or not explicitly disabled
+ if hardening_flag.origin == "default" or hardening_flag:
+ # FORTIFY_SOURCE ------------------------------------
+ # Require optimization for FORTIFY_SOURCE. See Bug 1417452
+ # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398
+ if compiler_is_gccish and optimize and not asan:
+ flags.append("-U_FORTIFY_SOURCE")
+ flags.append("-D_FORTIFY_SOURCE=2")
+ if mingw_clang:
+ # mingw-clang needs to link in ssp which is not done by default
+ ldflags.append("-lssp")
+
+ # fstack-protector ------------------------------------
+ # Enable only if hardening is not disabled and ASAN is
+ # not on as ASAN will catch the crashes for us
+ if compiler_is_gccish and not asan:
+ flags.append("-fstack-protector-strong")
+ ldflags.append("-fstack-protector-strong")
+
+ if (
+ c_compiler.type == "clang"
+ and c_compiler.version >= "11.0.1"
+ and target.os not in ("WINNT", "OSX")
+ and target.cpu in ("x86", "x86_64", "ppc64", "s390x")
+ ):
+ flags.append("-fstack-clash-protection")
+ ldflags.append("-fstack-clash-protection")
+
+ # ftrivial-auto-var-init ------------------------------
+ # Initialize local variables with a 0xAA pattern in clang builds.
+ # Linux32 fails some xpcshell tests with -ftrivial-auto-var-init
+ linux32 = target.kernel == "Linux" and target.cpu == "x86"
+ if (
+ (c_compiler.type == "clang" or c_compiler.type == "clang-cl")
+ and c_compiler.version >= "8"
+ and not linux32
+ ):
+ if c_compiler.type == "clang-cl":
+ trivial_auto_var_init.append("-Xclang")
+ trivial_auto_var_init.append("-ftrivial-auto-var-init=pattern")
+ # Always enable on debug builds.
+ if debug:
+ flags.extend(trivial_auto_var_init)
+
+ # ASLR ------------------------------------------------
+ # ASLR (dynamicbase) is enabled by default in clang-cl; but the
+ # mingw-clang build requires it to be explicitly enabled
+ if mingw_clang:
+ ldflags.append("-Wl,--dynamicbase")
+
+ # Control Flow Guard (CFG) ----------------------------
+ if (
+ c_compiler.type == "clang-cl"
+ and c_compiler.version >= "8"
+ and (target.cpu != "aarch64" or c_compiler.version >= "8.0.1")
+ ):
+ if target.cpu == "aarch64" and c_compiler.version >= "10.0.0":
+ # The added checks in clang 10 make arm64 builds crash. (Bug 1639318)
+ flags.append("-guard:cf,nochecks")
+ else:
+ flags.append("-guard:cf")
+ # nolongjmp is needed because clang doesn't emit the CFG tables of
+ # setjmp return addresses https://bugs.llvm.org/show_bug.cgi?id=40057
+ ldflags.append("-guard:cf,nolongjmp")
+
+ # ----------------------------------------------------------
+ # If ASAN _is_ on, 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,
+)
+
+
+# Intel Control-flow Enforcement Technology
+# ==============================================================
+# We keep this separate from the hardening flags above, because we want to be
+# able to easily remove the flags in the build files for certain executables.
+@depends(c_compiler, target)
+def cet_ldflags(c_compiler, target):
+ ldflags = []
+ if (
+ c_compiler.type == "clang-cl"
+ and c_compiler.version >= "11"
+ and target.cpu == "x86_64"
+ ):
+ ldflags.append("-CETCOMPAT")
+ return ldflags
+
+
+set_config("MOZ_CETCOMPAT_LDFLAGS", cet_ldflags)
+
+# Frame pointers
+# ==============================================================
+@depends(c_compiler)
+def frame_pointer_flags(compiler):
+ if compiler.type == "clang-cl":
+ return namespace(
+ enable=["-Oy-"],
+ disable=["-Oy"],
+ )
+ return namespace(
+ enable=["-fno-omit-frame-pointer", "-funwind-tables"],
+ disable=["-fomit-frame-pointer", "-funwind-tables"],
+ )
+
+
+@depends(
+ moz_optimize.optimize,
+ moz_debug,
+ target,
+ "--enable-memory-sanitizer",
+ "--enable-address-sanitizer",
+ "--enable-undefined-sanitizer",
+)
+def frame_pointer_default(optimize, debug, target, msan, asan, ubsan):
+ return bool(
+ not optimize
+ or debug
+ or msan
+ or asan
+ or ubsan
+ or (target.os == "WINNT" and target.cpu in ("x86", "aarch64"))
+ or target.os == "OSX"
+ )
+
+
+option(
+ "--enable-frame-pointers",
+ default=frame_pointer_default,
+ help="{Enable|Disable} frame pointers",
+)
+
+
+@depends("--enable-frame-pointers", frame_pointer_flags)
+def frame_pointer_flags(enable, flags):
+ if enable:
+ return flags.enable
+ return flags.disable
+
+
+set_config("MOZ_FRAMEPTR_FLAGS", frame_pointer_flags)
+
+
+# Code Coverage
+# ==============================================================
+
+option("--enable-coverage", env="MOZ_CODE_COVERAGE", help="Enable code coverage")
+
+
+@depends("--enable-coverage")
+def code_coverage(value):
+ if value:
+ return True
+
+
+set_config("MOZ_CODE_COVERAGE", code_coverage)
+set_define("MOZ_CODE_COVERAGE", code_coverage)
+
+
+@depends(target, c_compiler, build_environment, when=code_coverage)
+@imports("os")
+@imports("re")
+@imports(_from="__builtin__", _import="open")
+def coverage_cflags(target, c_compiler, build_env):
+ cflags = ["--coverage"]
+
+ # clang 11 no longer accepts this flag (its behavior became the default)
+ if c_compiler.type in ("clang", "clang-cl") and c_compiler.version < "11.0.0":
+ cflags += [
+ "-Xclang",
+ "-coverage-no-function-names-in-data",
+ ]
+
+ exclude = []
+ if target.os == "WINNT" and c_compiler.type == "clang-cl":
+ # VS files
+ exclude.append("^.*[vV][sS]20[0-9]{2}.*$")
+ # Files in fetches directory.
+ exclude.append("^.*[\\\\/]fetches[\\\\/].*$")
+ elif target.os == "OSX":
+ # Files in fetches directory.
+ exclude.append("^.*/fetches/.*$")
+ elif target.os == "GNU":
+ # Files in fetches directory.
+ exclude.append("^.*/fetches/.*$")
+ # Files in /usr/
+ exclude.append("^/usr/.*$")
+
+ if exclude:
+ exclude = ";".join(exclude)
+ cflags += [
+ f"-fprofile-exclude-files={exclude}",
+ ]
+
+ response_file_path = os.path.join(build_env.topobjdir, "code_coverage_cflags")
+
+ with open(response_file_path, "w") as f:
+ f.write(" ".join(cflags))
+
+ return ["@{}".format(response_file_path)]
+
+
+set_config("COVERAGE_CFLAGS", coverage_cflags)
+
+# Assembler detection
+# ==============================================================
+
+option(env="AS", nargs=1, help="Path to the assembler")
+
+
+@depends(target, c_compiler)
+def as_info(target, c_compiler):
+ if c_compiler.type == "clang-cl":
+ ml = {
+ "x86": "ml.exe",
+ "x86_64": "ml64.exe",
+ "aarch64": "armasm64.exe",
+ }.get(target.cpu)
+ return namespace(type="masm", names=(ml,))
+ # When building with anything but clang-cl, we just use the C compiler as the assembler.
+ return namespace(type="gcc", names=(c_compiler.compiler,))
+
+
+# One would expect the assembler to be specified merely as a program. But in
+# cases where the assembler is passed down into js/, it can be specified in
+# the same way as CC: a program + a list of argument flags. We might as well
+# permit the same behavior in general, even though it seems somewhat unusual.
+# So we have to do the same sort of dance as we did above with
+# `provided_compiler`.
+provided_assembler = provided_program("AS")
+assembler = check_prog(
+ "_AS",
+ input=provided_assembler.program,
+ what="the assembler",
+ progs=as_info.names,
+ paths=vc_toolchain_search_path,
+)
+
+
+@depends(as_info, assembler, provided_assembler, c_compiler)
+def as_with_flags(as_info, assembler, provided_assembler, c_compiler):
+ if provided_assembler:
+ return provided_assembler.wrapper + [assembler] + provided_assembler.flags
+
+ if as_info.type == "masm":
+ return assembler
+
+ assert as_info.type == "gcc"
+
+ # Need to add compiler wrappers and flags as appropriate.
+ return c_compiler.wrapper + [assembler] + c_compiler.flags
+
+
+set_config("AS", as_with_flags)
+
+
+@depends(assembler, c_compiler, extra_toolchain_flags)
+@imports("subprocess")
+@imports(_from="os", _import="devnull")
+def gnu_as(assembler, c_compiler, toolchain_flags):
+ # clang uses a compatible GNU assembler.
+ if c_compiler.type == "clang":
+ return True
+
+ if c_compiler.type == "gcc":
+ cmd = [assembler] + c_compiler.flags
+ if toolchain_flags:
+ cmd += toolchain_flags
+ cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"]
+ # We don't actually have to provide any input on stdin, `Popen.communicate` will
+ # close the stdin pipe.
+ # clang will error if it uses its integrated assembler for this target,
+ # so handle failures gracefully.
+ if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""):
+ return True
+
+
+set_config("GNU_AS", gnu_as)
+
+
+@depends(as_info, target)
+def as_dash_c_flag(as_info, target):
+ # armasm64 doesn't understand -c.
+ if as_info.type == "masm" and target.cpu == "aarch64":
+ return ""
+ else:
+ return "-c"
+
+
+set_config("AS_DASH_C_FLAG", as_dash_c_flag)
+
+
+@depends(as_info, target)
+def as_outoption(as_info, target):
+ # The uses of ASOUTOPTION depend on the spacing for -o/-Fo.
+ if as_info.type == "masm" and target.cpu != "aarch64":
+ return "-Fo"
+
+ return "-o "
+
+
+set_config("ASOUTOPTION", as_outoption)
+
+# clang plugin handling
+# ==============================================================
+
+option(
+ "--enable-clang-plugin",
+ env="ENABLE_CLANG_PLUGIN",
+ help="Enable building with the Clang plugin (gecko specific static analyzers)",
+)
+
+add_old_configure_assignment(
+ "ENABLE_CLANG_PLUGIN", depends_if("--enable-clang-plugin")(lambda _: True)
+)
+
+
+@depends(host_c_compiler, c_compiler, when="--enable-clang-plugin")
+def llvm_config(host_c_compiler, c_compiler):
+ clang = None
+ for compiler in (host_c_compiler, c_compiler):
+ if compiler and compiler.type == "clang":
+ clang = compiler.compiler
+ break
+ elif compiler and compiler.type == "clang-cl":
+ clang = os.path.join(os.path.dirname(compiler.compiler), "clang")
+ break
+
+ if not clang:
+ die("Cannot --enable-clang-plugin when not building with clang")
+ llvm_config = "llvm-config"
+ out = check_cmd_output(clang, "--print-prog-name=llvm-config", onerror=lambda: None)
+ if out:
+ llvm_config = out.rstrip()
+ return (llvm_config,)
+
+
+llvm_config = check_prog(
+ "LLVM_CONFIG",
+ llvm_config,
+ what="llvm-config",
+ when="--enable-clang-plugin",
+ paths=clang_search_path,
+)
+
+add_old_configure_assignment("LLVM_CONFIG", llvm_config)
+
+
+option(
+ "--enable-clang-plugin-alpha",
+ env="ENABLE_CLANG_PLUGIN_ALPHA",
+ help="Enable static analysis with clang-plugin alpha checks.",
+)
+
+
+@depends("--enable-clang-plugin", "--enable-clang-plugin-alpha")
+def check_clang_plugin_alpha(enable_clang_plugin, enable_clang_plugin_alpha):
+ if enable_clang_plugin_alpha:
+ if enable_clang_plugin:
+ return True
+ die("Cannot enable clang-plugin alpha checkers without --enable-clang-plugin.")
+
+
+add_old_configure_assignment("ENABLE_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha)
+set_define("MOZ_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha)
+
+option(
+ "--enable-mozsearch-plugin",
+ env="ENABLE_MOZSEARCH_PLUGIN",
+ help="Enable building with the mozsearch indexer plugin",
+)
+
+add_old_configure_assignment(
+ "ENABLE_MOZSEARCH_PLUGIN", depends_if("--enable-mozsearch-plugin")(lambda _: True)
+)
+
+# Libstdc++ compatibility hacks
+# ==============================================================
+#
+@depends(target, host)
+def target_or_host_is_linux(target, host):
+ return any(t.os == "GNU" and t.kernel == "Linux" for t in (target, host))
+
+
+option(
+ "--enable-stdcxx-compat",
+ env="MOZ_STDCXX_COMPAT",
+ help="Enable compatibility with older libstdc++",
+ when=target_or_host_is_linux,
+)
+
+
+@depends("--enable-stdcxx-compat", when=target_or_host_is_linux)
+def stdcxx_compat(value):
+ if value:
+ return True
+
+
+set_config("MOZ_STDCXX_COMPAT", True, when=stdcxx_compat)
+add_flag(
+ "-D_GLIBCXX_USE_CXX11_ABI=0",
+ cxx_compiler,
+ when=stdcxx_compat,
+)
+add_flag(
+ "-D_GLIBCXX_USE_CXX11_ABI=0",
+ host_cxx_compiler,
+ when=stdcxx_compat,
+)
+
+
+# Support various fuzzing options
+# ==============================================================
+option("--enable-fuzzing", help="Enable fuzzing support")
+
+
+@depends(build_project)
+def js_build(build_project):
+ return build_project == "js"
+
+
+option(
+ "--enable-js-fuzzilli",
+ when=js_build,
+ help="Enable fuzzilli support for the JS engine",
+)
+
+
+option(
+ "--enable-snapshot-fuzzing",
+ help="Enable experimental snapshot fuzzing support",
+)
+
+
+imply_option("--enable-fuzzing", True, when="--enable-snapshot-fuzzing")
+
+
+@depends("--enable-snapshot-fuzzing")
+def enable_snapshot_fuzzing(value):
+ if value:
+ return True
+
+
+@depends("--enable-fuzzing")
+def enable_fuzzing(value):
+ if value:
+ return True
+
+
+@depends("--enable-js-fuzzilli", when=js_build)
+def enable_js_fuzzilli(value):
+ if value:
+ return True
+
+
+@depends(enable_fuzzing, enable_snapshot_fuzzing)
+def check_aflfuzzer(fuzzing, snapshot_fuzzing):
+ if fuzzing and not snapshot_fuzzing:
+ return True
+
+
+@depends(
+ try_compile(
+ body="__AFL_COMPILER;", check_msg="for AFL compiler", when=check_aflfuzzer
+ )
+)
+def enable_aflfuzzer(afl):
+ if afl:
+ return True
+
+
+@depends(enable_fuzzing, enable_aflfuzzer, enable_snapshot_fuzzing, c_compiler, target)
+def enable_libfuzzer(fuzzing, afl, snapshot_fuzzing, c_compiler, target):
+ if (
+ fuzzing
+ and not afl
+ and not snapshot_fuzzing
+ and c_compiler.type == "clang"
+ and target.os != "Android"
+ ):
+ return True
+
+
+@depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer, enable_js_fuzzilli)
+def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer, enable_js_fuzzilli):
+ if fuzzing and (afl or libfuzzer) and not enable_js_fuzzilli:
+ return True
+
+
+set_config("FUZZING", enable_fuzzing)
+set_define("FUZZING", enable_fuzzing)
+
+set_config("LIBFUZZER", enable_libfuzzer)
+set_define("LIBFUZZER", enable_libfuzzer)
+add_old_configure_assignment("LIBFUZZER", enable_libfuzzer)
+
+set_config("AFLFUZZ", enable_aflfuzzer)
+set_define("AFLFUZZ", enable_aflfuzzer)
+
+set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces)
+set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces)
+add_old_configure_assignment("FUZZING_INTERFACES", enable_fuzzing_interfaces)
+
+set_config("FUZZING_JS_FUZZILLI", enable_js_fuzzilli)
+set_define("FUZZING_JS_FUZZILLI", enable_js_fuzzilli)
+
+set_config("FUZZING_SNAPSHOT", enable_snapshot_fuzzing)
+set_define("FUZZING_SNAPSHOT", enable_snapshot_fuzzing)
+
+
+@depends(
+ c_compiler.try_compile(
+ flags=["-fsanitize=fuzzer-no-link"],
+ when=enable_fuzzing,
+ check_msg="whether the C compiler supports -fsanitize=fuzzer-no-link",
+ ),
+ tsan,
+ enable_js_fuzzilli,
+)
+def libfuzzer_flags(value, tsan, enable_js_fuzzilli):
+ if tsan:
+ # With ThreadSanitizer, we should not use any libFuzzer instrumentation because
+ # it is incompatible (e.g. there are races on global sanitizer coverage counters).
+ # Instead we use an empty set of flags here but still build the fuzzing targets.
+ # With this setup, we can still run files through these targets in TSan builds,
+ # e.g. those obtained from regular fuzzing.
+ # This code can be removed once libFuzzer has been made compatible with TSan.
+ #
+ # Also, this code needs to be kept in sync with certain gyp files, currently:
+ # - dom/media/webrtc/transport/third_party/nICEr/nicer.gyp
+ return namespace(no_link_flag_supported=False, use_flags=[])
+
+ if enable_js_fuzzilli:
+ # Fuzzilli comes with its own trace-pc interceptors and flag requirements.
+ no_link_flag_supported = False
+ use_flags = ["-fsanitize-coverage=trace-pc-guard", "-g"]
+ elif value:
+ no_link_flag_supported = True
+ # recommended for (and only supported by) clang >= 6
+ use_flags = ["-fsanitize=fuzzer-no-link"]
+ else:
+ no_link_flag_supported = False
+ use_flags = ["-fsanitize-coverage=trace-pc-guard,trace-cmp"]
+
+ return namespace(
+ no_link_flag_supported=no_link_flag_supported,
+ use_flags=use_flags,
+ )
+
+
+set_config("HAVE_LIBFUZZER_FLAG_FUZZER_NO_LINK", libfuzzer_flags.no_link_flag_supported)
+set_config("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags)
+add_old_configure_assignment("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags)
+
+# Shared library building
+# ==============================================================
+
+# XXX: The use of makefile constructs in these variables is awful.
+@depends(target, c_compiler)
+def make_shared_library(target, compiler):
+ if target.os == "WINNT":
+ if compiler.type == "gcc":
+ return namespace(
+ mkshlib=["$(CXX)", "$(DSO_LDOPTS)", "-o", "$@"],
+ mkcshlib=["$(CC)", "$(DSO_LDOPTS)", "-o", "$@"],
+ )
+ elif compiler.type == "clang":
+ return namespace(
+ mkshlib=[
+ "$(CXX)",
+ "$(DSO_LDOPTS)",
+ "-Wl,-pdb,$(LINK_PDBFILE)",
+ "-o",
+ "$@",
+ ],
+ mkcshlib=[
+ "$(CC)",
+ "$(DSO_LDOPTS)",
+ "-Wl,-pdb,$(LINK_PDBFILE)",
+ "-o",
+ "$@",
+ ],
+ )
+ else:
+ linker = [
+ "$(LINKER)",
+ "-NOLOGO",
+ "-DLL",
+ "-OUT:$@",
+ "-PDB:$(LINK_PDBFILE)",
+ "$(DSO_LDOPTS)",
+ ]
+ return namespace(
+ mkshlib=linker,
+ mkcshlib=linker,
+ )
+
+ cc = ["$(CC)", "$(COMPUTED_C_LDFLAGS)"]
+ cxx = ["$(CXX)", "$(COMPUTED_CXX_LDFLAGS)"]
+ flags = ["$(PGO_CFLAGS)", "$(DSO_LDOPTS)"]
+ output = ["-o", "$@"]
+
+ if target.kernel == "Darwin":
+ soname = []
+ elif target.os == "NetBSD":
+ soname = ["-Wl,-soname,$(DSO_SONAME)"]
+ else:
+ assert compiler.type in ("gcc", "clang")
+
+ soname = ["-Wl,-h,$(DSO_SONAME)"]
+
+ return namespace(
+ mkshlib=cxx + flags + soname + output,
+ mkcshlib=cc + flags + soname + output,
+ )
+
+
+set_config("MKSHLIB", make_shared_library.mkshlib)
+set_config("MKCSHLIB", make_shared_library.mkcshlib)
+
+
+@depends(c_compiler, toolchain_prefix, when=target_is_windows)
+def rc_names(c_compiler, toolchain_prefix):
+ if c_compiler.type in ("gcc", "clang"):
+ return tuple("%s%s" % (p, "windres") for p in ("",) + (toolchain_prefix or ()))
+ return ("llvm-rc",)
+
+
+check_prog("RC", rc_names, paths=clang_search_path, when=target_is_windows)
+
+
+@template
+def ar_config(c_compiler, toolchain_prefix=None):
+ if not toolchain_prefix:
+ toolchain_prefix = dependable(None)
+
+ @depends(toolchain_prefix, c_compiler)
+ def ar_config(toolchain_prefix, c_compiler):
+ if c_compiler.type == "clang-cl":
+ return namespace(
+ names=("llvm-lib",),
+ flags=("-llvmlibthin", "-out:$@"),
+ )
+
+ names = tuple("%s%s" % (p, "ar") for p in (toolchain_prefix or ()) + ("",))
+ if c_compiler.type == "clang":
+ # Get the llvm-ar path as per the output from clang --print-prog-name=llvm-ar
+ # so that we directly get the one under the clang directory, rather than one
+ # that might be in /usr/bin and that might point to one from a different version
+ # of clang.
+ out = check_cmd_output(
+ c_compiler.compiler, "--print-prog-name=llvm-ar", onerror=lambda: None
+ )
+ llvm_ar = out.rstrip() if out else "llvm-ar"
+ names = (llvm_ar,) + names
+
+ return namespace(
+ names=names,
+ flags=("crs", "$@"),
+ )
+
+ return ar_config
+
+
+target_ar_config = ar_config(c_compiler, toolchain_prefix)
+
+check_prog("AR", target_ar_config.names, paths=clang_search_path)
+
+set_config("AR_FLAGS", target_ar_config.flags)
+
+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)
+
+
+option("--enable-cpp-rtti", help="Enable C++ RTTI")
+
+add_old_configure_assignment("_MOZ_USE_RTTI", "1", when="--enable-cpp-rtti")
+
+
+option(
+ "--enable-path-remapping",
+ nargs="*",
+ choices=("c", "rust"),
+ help="Enable remapping source and object paths in compiled outputs.",
+)
+
+
+@depends("--enable-path-remapping")
+def path_remapping(value):
+ if len(value):
+ return value
+ if bool(value):
+ return ["c", "rust"]
+ return []
+
+
+@depends(
+ target,
+ build_environment,
+ target_sysroot.path,
+ valid_windows_sdk_dir,
+ vc_path,
+ when="--enable-path-remapping",
+)
+def path_remappings(target, build_env, sysroot_path, windows_sdk_dir, vc_path):
+ win = target.kernel == "WINNT"
+
+ # The prefix maps are processed in the order they're specified on the
+ # command line. Therefore, to accommodate object directories in the source
+ # directory, it's important that we map the topobjdir before the topsrcdir,
+ # 'cuz we might have /src/obj/=/o/ and /src/=/s/. The various other
+ # directories might be subdirectories of topsrcdir as well, so they come
+ # earlier still.
+
+ path_remappings = []
+
+ # We will have only one sysroot or SDK, so all can have the same mnemonic: K
+ # for "kit" (since S is taken for "source"). See
+ # https://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html
+ # for how to use the Windows `subst` command to map these in debuggers and
+ # IDEs.
+ if sysroot_path:
+ path_remappings.append((sysroot_path, "k:/" if win else "/sysroot/"))
+ if windows_sdk_dir:
+ path_remappings.append(
+ (windows_sdk_dir.path, "k:/" if win else "/windows_sdk/")
+ )
+ if vc_path:
+ path_remappings.append((vc_path, "v:/" if win else "/vc/"))
+
+ path_remappings += [
+ (build_env.topobjdir, "o:/" if win else "/topobjdir/"),
+ (build_env.topsrcdir, "s:/" if win else "/topsrcdir/"),
+ ]
+
+ path_remappings = [
+ (normsep(old).rstrip("/") + "/", new) for old, new in path_remappings
+ ]
+
+ # It is tempting to sort these, but we want the order to be the same across
+ # machines so that we can share cache hits. Therefore we reject bad
+ # configurations rather than trying to make the configuration good.
+ for i in range(len(path_remappings) - 1):
+ p = path_remappings[i][0]
+ for q, _ in path_remappings[i + 1 :]:
+ if q.startswith(p):
+ die(f"Cannot remap paths because {p} is an ancestor of {q}")
+
+ return path_remappings
+
+
+set_config("MMX_FLAGS", ["-mmmx"])
+set_config("SSE_FLAGS", ["-msse"])
+set_config("SSE2_FLAGS", ["-msse2"])
+set_config("SSSE3_FLAGS", ["-mssse3"])
+set_config("SSE4_2_FLAGS", ["-msse4.2"])
+set_config("FMA_FLAGS", ["-mfma"])
+set_config("AVX2_FLAGS", ["-mavx2"])
diff --git a/build/moz.configure/update-programs.configure b/build/moz.configure/update-programs.configure
new file mode 100644
index 0000000000..07a07f41b5
--- /dev/null
+++ b/build/moz.configure/update-programs.configure
@@ -0,0 +1,171 @@
+# -*- 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/.
+
+# Updater
+# ==============================================================
+@depends(build_project)
+def updater_default(build_project):
+ return build_project != "mobile/android"
+
+
+option(
+ "--enable-updater",
+ default=updater_default,
+ help="{Enable|Disable} building the updater",
+)
+
+set_config("MOZ_UPDATER", True, when="--enable-updater")
+set_define("MOZ_UPDATER", True, when="--enable-updater")
+
+# Updates that do not verify signatures
+# ==============================================================
+
+option(
+ "--enable-unverified-updates",
+ default=False,
+ help="Enable application update without verifying MAR or updater binary signatures",
+)
+
+
+@depends("--enable-unverified-updates", "--enable-compile-environment")
+def disable_unverified_updates(unverified_updates, compile_environment):
+ if unverified_updates:
+ if not compile_environment:
+ die("--enable-unverified-updates requires --enable-compile-environment")
+ return not unverified_updates
+
+
+set_define(
+ "MOZ_VERIFY_MAR_SIGNATURE",
+ depends_if(disable_unverified_updates)(lambda _: True),
+)
+set_config(
+ "MOZ_VERIFY_MAR_SIGNATURE",
+ True,
+ depends_if(disable_unverified_updates)(lambda _: True),
+)
+
+set_config(
+ "DISABLE_UPDATER_AUTHENTICODE_CHECK",
+ True,
+ depends_if("--enable-unverified-updates")(lambda _: True),
+)
+
+# Maintenance service (Windows only)
+# ==============================================================
+
+
+@depends("--enable-updater")
+def maintenance_service_default(updater):
+ return bool(updater)
+
+
+option(
+ "--enable-maintenance-service",
+ when=target_is_windows,
+ default=maintenance_service_default,
+ help="{Enable|Disable} building of maintenance service",
+)
+
+set_define(
+ "MOZ_MAINTENANCE_SERVICE",
+ depends_if("--enable-maintenance-service", when=target_is_windows)(lambda _: True),
+)
+set_config(
+ "MOZ_MAINTENANCE_SERVICE",
+ depends_if("--enable-maintenance-service", when=target_is_windows)(lambda _: True),
+)
+
+
+@depends("--enable-maintenance-service", "--enable-updater", when=target_is_windows)
+def check_maintenance_service(mainteance_service, updater):
+ if mainteance_service and not updater:
+ die("--enable-updater is required to --enable-maintenance-service")
+ return mainteance_service
+
+
+# Update agent (currently Windows and macOS only)
+# This is an independent task that runs on a schedule to
+# check for, download, and install updates.
+# ==============================================================
+
+
+@depends("--enable-backgroundtasks", "--enable-updater", build_project)
+def update_agent_default(backgroundtasks, updater, build_project):
+ return bool(backgroundtasks) and bool(updater) and build_project == "browser"
+
+
+option(
+ "--disable-update-agent",
+ when=target_is_windows | target_is_osx,
+ default=update_agent_default,
+ help="{Enable|Disable} building update agent",
+)
+
+set_config(
+ "MOZ_UPDATE_AGENT",
+ depends_if("--enable-update-agent", when=target_is_windows | target_is_osx)(
+ lambda _: True
+ ),
+)
+
+
+@depends(
+ "--enable-update-agent",
+ "--enable-backgroundtasks",
+ "--enable-updater",
+ when=target_is_windows | target_is_osx,
+)
+def check_update_agent(update_agent, backgroundtasks, updater):
+ if update_agent and not backgroundtasks:
+ die("--enable-backgroundtasks is required to --enable-update-agent")
+ if update_agent and not updater:
+ die("--enable-updater is required to --enable-update-agent")
+ return update_agent
+
+
+# Enable or disable the default browser agent, which monitors the user's default
+# browser setting on Windows.
+# ==============================================================================
+
+
+@depends(target, build_project)
+def default_browser_agent_default(target, build_project):
+ return target.os == "WINNT" and build_project == "browser"
+
+
+option(
+ "--enable-default-browser-agent",
+ default=default_browser_agent_default,
+ help="{Enable|Disable} building the default browser agent",
+)
+
+
+@depends("--enable-default-browser-agent", when=target_is_windows)
+def default_agent_flag(enabled):
+ if enabled:
+ return True
+
+
+set_config("MOZ_DEFAULT_BROWSER_AGENT", default_agent_flag)
+
+
+# Enable or disable the notification server, which allows Windows native
+# notifications to persist when the application is not running and relaunch as
+# necessary.
+# ==============================================================================
+@depends(target, build_project)
+def notification_server_default(target, build_project):
+ return target.os == "WINNT" and build_project == "browser"
+
+
+option(
+ "--disable-notification-server",
+ when=notification_server_default,
+ help="Disable building the notification server",
+)
+
+set_config("MOZ_NOTIFICATION_SERVER", True, when="--enable-notification-server")
diff --git a/build/moz.configure/util.configure b/build/moz.configure/util.configure
new file mode 100644
index 0000000000..f5ff3acd79
--- /dev/null
+++ b/build/moz.configure/util.configure
@@ -0,0 +1,517 @@
+# -*- 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/.
+
+
+@imports("sys")
+def die(*args):
+ "Print an error and terminate configure."
+ log.error(*args)
+ sys.exit(1)
+
+
+@imports(_from="mozbuild.configure", _import="ConfigureError")
+def configure_error(message):
+ """Raise a programming error and terminate configure.
+ Primarily for use in moz.configure templates to sanity check
+ their inputs from moz.configure usage."""
+ raise ConfigureError(message)
+
+
+# A wrapper to obtain a process' output and return code.
+# Returns a tuple (retcode, stdout, stderr).
+@imports("os")
+@imports("subprocess")
+@imports(_from="mozbuild.shellutil", _import="quote")
+@imports(_from="mozbuild.util", _import="system_encoding")
+def get_cmd_output(*args, **kwargs):
+ log.debug("Executing: `%s`", quote(*args))
+ proc = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ # On Python 2 on Windows, close_fds prevents the process from inheriting
+ # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra
+ # file descriptors, which is what we want.
+ close_fds=os.name != "nt",
+ encoding=system_encoding,
+ errors="replace",
+ **kwargs
+ )
+ stdout, stderr = proc.communicate()
+ return proc.wait(), stdout, stderr
+
+
+# A wrapper to obtain a process' output that returns the output generated
+# by running the given command if it exits normally, and streams that
+# output to log.debug and calls die or the given error callback if it
+# does not.
+@imports(_from="mozbuild.configure.util", _import="LineIO")
+@imports(_from="mozbuild.shellutil", _import="quote")
+def check_cmd_output(*args, **kwargs):
+ onerror = kwargs.pop("onerror", None)
+
+ with log.queue_debug():
+ retcode, stdout, stderr = get_cmd_output(*args, **kwargs)
+ if retcode == 0:
+ return stdout
+
+ log.debug("The command returned non-zero exit status %d.", retcode)
+ for out, desc in ((stdout, "output"), (stderr, "error output")):
+ if out:
+ log.debug("Its %s was:", desc)
+ with LineIO(lambda l: log.debug("| %s", l)) as o:
+ o.write(out)
+ if onerror:
+ return onerror()
+ die("Command `%s` failed with exit status %d." % (quote(*args), retcode))
+
+
+@imports("os")
+def is_absolute_or_relative(path):
+ if os.altsep and os.altsep in path:
+ return True
+ return os.sep in path
+
+
+@imports(_import="mozpack.path", _as="mozpath")
+def normsep(path):
+ return mozpath.normsep(path)
+
+
+@imports("ctypes")
+@imports(_from="ctypes", _import="wintypes")
+@imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType")
+def windows_binary_type(path):
+ """Obtain the type of a binary on Windows.
+
+ Returns WindowsBinaryType constant.
+ """
+ GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW
+ GetBinaryTypeW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)]
+ GetBinaryTypeW.restype = wintypes.BOOL
+
+ bin_type = wintypes.DWORD()
+ res = GetBinaryTypeW(path, ctypes.byref(bin_type))
+ if not res:
+ die("could not obtain binary type of %s" % path)
+
+ if bin_type.value == 0:
+ return WindowsBinaryType("win32")
+ elif bin_type.value == 6:
+ return WindowsBinaryType("win64")
+ # If we see another binary type, something is likely horribly wrong.
+ else:
+ die("unsupported binary type on %s: %s" % (path, bin_type))
+
+
+@imports("ctypes")
+@imports(_from="ctypes", _import="wintypes")
+def get_GetShortPathNameW():
+ GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
+ GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
+ GetShortPathNameW.restype = wintypes.DWORD
+ return GetShortPathNameW
+
+
+@template
+@imports("ctypes")
+@imports("platform")
+@imports(_from="mozbuild.shellutil", _import="quote")
+def normalize_path():
+ # Until the build system can properly handle programs that need quoting,
+ # transform those paths into their short version on Windows (e.g.
+ # c:\PROGRA~1...).
+ if platform.system() == "Windows":
+ GetShortPathNameW = get_GetShortPathNameW()
+
+ def normalize_path(path):
+ path = normsep(path)
+ if quote(path) == path:
+ return path
+ size = 0
+ while True:
+ out = ctypes.create_unicode_buffer(size)
+ needed = GetShortPathNameW(path, out, size)
+ if size >= needed:
+ if " " in out.value:
+ die(
+ "GetShortPathName returned a long path name: `%s`. "
+ "Use `fsutil file setshortname' "
+ "to create a short name "
+ "for any components of this path "
+ "that have spaces.",
+ out.value,
+ )
+ return normsep(out.value)
+ size = needed
+
+ else:
+
+ def normalize_path(path):
+ return normsep(path)
+
+ return normalize_path
+
+
+normalize_path = normalize_path()
+
+
+# Locates the given program using which, or returns the given path if it
+# exists.
+# The `paths` parameter may be passed to search the given paths instead of
+# $PATH.
+@imports("sys")
+@imports(_from="os", _import="pathsep")
+@imports(_from="os", _import="environ")
+@imports(_from="mozfile", _import="which")
+def find_program(file, paths=None, allow_spaces=False):
+ def which_normalize(file, path, exts):
+ path = which(file, path=path, exts=exts)
+ if not path:
+ return None
+ if not allow_spaces:
+ return normalize_path(path)
+ return normsep(path)
+
+ # The following snippet comes from `which` itself, with a slight
+ # modification to use lowercase extensions, because it's confusing rustup
+ # (on top of making results not really appealing to the eye).
+
+ # Windows has the concept of a list of extensions (PATHEXT env var).
+ if sys.platform.startswith("win"):
+ exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)]
+ # If '.exe' is not in exts then obviously this is Win9x and
+ # or a bogus PATHEXT, then use a reasonable default.
+ if ".exe" not in exts:
+ exts = [".com", ".exe", ".bat"]
+ else:
+ exts = None
+
+ if is_absolute_or_relative(file):
+ return which_normalize(
+ os.path.basename(file), path=os.path.dirname(file), exts=exts
+ )
+
+ if paths:
+ if not isinstance(paths, (list, tuple)):
+ die(
+ "Paths provided to find_program must be a list of strings, " "not %r",
+ paths,
+ )
+ paths = pathsep.join(paths)
+
+ return which_normalize(file, path=paths, exts=exts)
+
+
+@imports("os")
+@imports(_from="mozbuild.configure.util", _import="LineIO")
+@imports(_from="tempfile", _import="mkstemp")
+@imports(_import="subprocess")
+def try_invoke_compiler(
+ configure_cache, compiler, language, source, flags=None, onerror=None, wrapper=[]
+):
+ compiler_path = compiler[0]
+ compiler = wrapper + compiler
+ use_cache = True
+
+ if compiler_path not in configure_cache.version_checked_compilers:
+ try:
+ version_info = subprocess.check_output(
+ [compiler_path, "--version"],
+ encoding="UTF-8",
+ ).strip()
+ except subprocess.CalledProcessError:
+ # There's no sane way to use the cache without the version details, so
+ # we need to avoid both reads from and writes to the cache.
+ use_cache = False
+ pass
+
+ if use_cache:
+ if version_info != configure_cache.setdefault(compiler_path, {}).get(
+ "version"
+ ):
+ configure_cache[compiler_path].clear()
+
+ configure_cache[compiler_path]["version"] = version_info
+ configure_cache.version_checked_compilers.add(compiler_path)
+
+ flags = flags or []
+
+ if use_cache:
+ key = " ".join(compiler) + language + source + (" ".join(flags) or "")
+
+ if key in configure_cache[compiler_path]:
+ return configure_cache[compiler_path][key]
+
+ if not isinstance(flags, (list, tuple)):
+ die("Flags provided to try_compile must be a list of strings, " "not %r", flags)
+
+ suffix = {
+ "C": ".c",
+ "C++": ".cpp",
+ }[language]
+
+ fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True)
+ try:
+ source = source.encode("ascii", "replace")
+
+ log.debug("Creating `%s` with content:", path)
+ with LineIO(lambda l: log.debug("| %s", l)) as out:
+ out.write(source)
+
+ os.write(fd, source)
+ os.close(fd)
+ cmd = compiler + [path] + list(flags)
+ kwargs = {"onerror": onerror}
+ val = check_cmd_output(*cmd, **kwargs)
+ if use_cache:
+ configure_cache[compiler_path][key] = val
+ return val
+ finally:
+ os.remove(path)
+
+
+def unique_list(l):
+ result = []
+ for i in l:
+ if l not in result:
+ result.append(i)
+ return result
+
+
+# Get values out of the Windows registry. This function can only be called on
+# Windows.
+# The `pattern` argument is a string starting with HKEY_ and giving the full
+# "path" of the registry key to get the value for, with backslash separators.
+# The string can contains wildcards ('*').
+# The result of this functions is an enumerator yielding tuples for each
+# match. Each of these tuples contains the key name matching wildcards
+# followed by the value.
+#
+# The `get_32_and_64_bit` argument is a boolean, if True then it will return the
+# values from the 32-bit and 64-bit registry views. This defaults to False,
+# which will return the view depending on the bitness of python.
+#
+# Examples:
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\Installed Roots\KitsRoot*')
+# yields e.g.:
+# ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\')
+# ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\')
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\Installed Roots\KitsRoot8.1')
+# yields e.g.:
+# (r'C:\Program Files (x86)\Windows Kits\8.1\',)
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\Installed Roots\KitsRoot8.1',
+# get_32_and_64_bit=True)
+# yields e.g.:
+# (r'C:\Program Files (x86)\Windows Kits\8.1\',)
+# (r'C:\Program Files\Windows Kits\8.1\',)
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'Windows Kits\*\KitsRoot*')
+# yields e.g.:
+# ('Installed Roots', 'KitsRoot81',
+# r'C:\Program Files (x86)\Windows Kits\8.1\')
+# ('Installed Roots', 'KitsRoot10',
+# r'C:\Program Files (x86)\Windows Kits\10\')
+#
+# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\'
+# r'VisualStudio\VC\*\x86\*\Compiler')
+# yields e.g.:
+# ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe')
+# ('19.0', 'x64', r'C:\...\amd64\cl.exe')
+# ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe')
+@imports(_import="winreg")
+@imports(_from="__builtin__", _import="WindowsError")
+@imports(_from="fnmatch", _import="fnmatch")
+def get_registry_values(pattern, get_32_and_64_bit=False):
+ def enum_helper(func, key):
+ i = 0
+ while True:
+ try:
+ yield func(key, i)
+ except WindowsError:
+ break
+ i += 1
+
+ def get_keys(key, pattern, access_mask):
+ try:
+ s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
+ except WindowsError:
+ return
+ for k in enum_helper(winreg.EnumKey, s):
+ if fnmatch(k, pattern[-1]):
+ try:
+ yield k, winreg.OpenKey(s, k, 0, access_mask)
+ except WindowsError:
+ pass
+
+ def get_values(key, pattern, access_mask):
+ try:
+ s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask)
+ except WindowsError:
+ return
+ for k, v, t in enum_helper(winreg.EnumValue, s):
+ if fnmatch(k, pattern[-1]):
+ yield k, v
+
+ def split_pattern(pattern):
+ subpattern = []
+ for p in pattern:
+ subpattern.append(p)
+ if "*" in p:
+ yield subpattern
+ subpattern = []
+ if subpattern:
+ yield subpattern
+
+ def get_all_values(keys, pattern, access_mask):
+ for i, p in enumerate(pattern):
+ next_keys = []
+ for base_key in keys:
+ matches = base_key[:-1]
+ base_key = base_key[-1]
+ if i == len(pattern) - 1:
+ want_name = "*" in p[-1]
+ for name, value in get_values(base_key, p, access_mask):
+ yield matches + ((name, value) if want_name else (value,))
+ else:
+ for name, k in get_keys(base_key, p, access_mask):
+ next_keys.append(matches + (name, k))
+ keys = next_keys
+
+ pattern = pattern.split("\\")
+ assert pattern[0].startswith("HKEY_")
+ keys = [(getattr(winreg, pattern[0]),)]
+ pattern = list(split_pattern(pattern[1:]))
+ if get_32_and_64_bit:
+ for match in get_all_values(
+ keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY
+ ):
+ yield match
+ for match in get_all_values(
+ keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY
+ ):
+ yield match
+ else:
+ for match in get_all_values(keys, pattern, winreg.KEY_READ):
+ yield match
+
+
+@imports(_from="mozbuild.configure.util", _import="Version", _as="_Version")
+def Version(v):
+ "A version number that can be compared usefully."
+ return _Version(v)
+
+
+# Denotes a deprecated option. Combines option() and @depends:
+# @deprecated_option('--option')
+# def option(value):
+# ...
+# @deprecated_option() takes the same arguments as option(), except `help`.
+# The function may handle the option like a typical @depends function would,
+# but it is recommended it emits a deprecation error message suggesting an
+# alternative option to use if there is one.
+@template
+def deprecated_option(*args, **kwargs):
+ assert "help" not in kwargs
+ kwargs["help"] = "Deprecated"
+ opt = option(*args, **kwargs)
+ kwargs = {k: v for k, v in kwargs.items() if k == "when"}
+
+ def decorator(func):
+ @depends(opt.option, **kwargs)
+ def deprecated(value):
+ if value.origin != "default":
+ return func(value)
+
+ return deprecated
+
+ return decorator
+
+
+# Turn an object into an object that can be used as an argument to @depends.
+# The given object can be a literal value, a function that takes no argument,
+# or, for convenience, a @depends function.
+@template
+@imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
+def dependable(obj):
+ if isinstance(obj, SandboxDependsFunction):
+ return obj
+ return depends(when=True)(obj)
+
+
+always = dependable(True)
+never = dependable(False)
+
+
+# Create a decorator that will only execute the body of a function
+# if the passed function returns True when passed all positional
+# arguments.
+@template
+def depends_tmpl(eval_args_fn, *args, **kwargs):
+ if kwargs:
+ assert len(kwargs) == 1
+ when = kwargs["when"]
+ else:
+ when = None
+
+ def decorator(func):
+ @depends(*args, when=when)
+ def wrapper(*args):
+ if eval_args_fn(args):
+ return func(*args)
+
+ return wrapper
+
+ return decorator
+
+
+# Like @depends, but the decorated function is only called if one of the
+# arguments it would be called with has a positive value (bool(value) is True)
+@template
+def depends_if(*args, **kwargs):
+ return depends_tmpl(any, *args, **kwargs)
+
+
+# Like @depends, but the decorated function is only called if all of the
+# arguments it would be called with have a positive value.
+@template
+def depends_all(*args, **kwargs):
+ return depends_tmpl(all, *args, **kwargs)
+
+
+# Hacks related to old-configure
+# ==============================
+
+
+@dependable
+def old_configure_assignments():
+ return []
+
+
+@template
+def add_old_configure_assignment(var, value, when=None):
+ var = dependable(var)
+ value = dependable(value)
+
+ @depends(old_configure_assignments, var, value, when=when)
+ @imports(_from="mozbuild.shellutil", _import="quote")
+ def add_assignment(assignments, var, value):
+ if var is None or value is None:
+ return
+ if value is True:
+ assignments.append((var, "1"))
+ elif value is False:
+ assignments.append((var, ""))
+ else:
+ if isinstance(value, (list, tuple)):
+ value = quote(*value)
+ assignments.append((var, str(value)))
diff --git a/build/moz.configure/warnings.configure b/build/moz.configure/warnings.configure
new file mode 100644
index 0000000000..f4c195732f
--- /dev/null
+++ b/build/moz.configure/warnings.configure
@@ -0,0 +1,320 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option(
+ "--enable-warnings-as-errors",
+ env="MOZ_ENABLE_WARNINGS_AS_ERRORS",
+ default=depends("MOZ_AUTOMATION")(lambda x: bool(x)),
+ help="{Enable|Disable} treating warnings as errors",
+)
+
+
+@depends("--enable-warnings-as-errors")
+def warnings_as_errors(warnings_as_errors):
+ if not warnings_as_errors:
+ return ""
+
+ return "-Werror"
+
+
+set_config("WARNINGS_AS_ERRORS", warnings_as_errors)
+
+not_clang_cl = depends(c_compiler)(lambda c: c.type != "clang-cl")
+
+# GCC/Clang warnings:
+# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
+# https://clang.llvm.org/docs/DiagnosticsReference.html
+
+# Lots of useful warnings
+add_warning("-Wall", when=not_clang_cl)
+# In clang-cl, -Wall actually means -Weverything. -W3 does mean -Wall.
+add_warning("-W3", when=depends(c_compiler)(lambda c: c.type == "clang-cl"))
+
+# catch implicit truncation of enum values assigned to smaller bit fields
+check_and_add_warning("-Wbitfield-enum-conversion")
+
+# catches deprecated implicit capture of `this` in lambdas.
+check_and_add_warning("-Wdeprecated-this-capture", cxx_compiler)
+
+# catches bugs, e.g. "if (c); foo();", few false positives
+add_warning("-Wempty-body")
+
+# catches mismatched printf integer sizes.
+check_and_add_warning("-Wformat-type-confusion")
+
+# catches return types with qualifiers like const
+add_warning("-Wignored-qualifiers")
+
+# catches pointer arithmetic using NULL or sizeof(void)
+add_warning("-Wpointer-arith")
+
+# catch modifying constructor parameter that shadows member variable
+check_and_add_warning("-Wshadow-field-in-constructor-modified")
+
+# catches comparing signed/unsigned ints
+add_warning("-Wsign-compare")
+
+# catches comparisons of values and sized types are always true or false
+check_and_add_warning("-Wtautological-constant-in-range-compare")
+
+# catches overflow bugs, few false positives
+add_warning("-Wtype-limits")
+
+# This can be triggered by certain patterns used deliberately in portable code
+check_and_add_warning("-Wno-error=tautological-type-limit-compare")
+
+# catches some dead code
+add_warning("-Wunreachable-code")
+check_and_add_warning("-Wunreachable-code-return")
+
+# catches parameters that are set but not read
+# Only enable on clang because gcc reports false positives.
+check_and_add_warning(
+ "-Wunused-but-set-parameter",
+ when=depends(c_compiler)(lambda c: c.type in ("clang", "clang-cl")),
+)
+
+# turned on by -Wall, but we use offsetof on non-POD types frequently
+add_warning("-Wno-invalid-offsetof", cxx_compiler)
+
+# catches objects passed by value to variadic functions.
+check_and_add_warning("-Wclass-varargs")
+
+# catches empty if/switch/for initialization statements that have no effect
+check_and_add_warning("-Wempty-init-stmt", cxx_compiler)
+
+# catches some implicit conversion of floats to ints
+check_and_add_warning("-Wfloat-overflow-conversion")
+check_and_add_warning("-Wfloat-zero-conversion")
+
+# catches issues around loops
+check_and_add_warning("-Wloop-analysis")
+# But, disable range-loop-analysis because it can raise unhelpful false
+# positives.
+check_and_add_warning("-Wno-range-loop-analysis")
+
+# Enable some C++20 compat warnings. We can remove these flags after we compile
+# as C++20 (bug 1768116), because they will be enabled by default:
+check_and_add_warning("-Wc++2a-compat", cxx_compiler)
+check_and_add_warning("-Wcomma-subscript", cxx_compiler)
+check_and_add_warning("-Wenum-compare-conditional")
+check_and_add_warning("-Wenum-float-conversion")
+check_and_add_warning("-Wvolatile", cxx_compiler)
+
+# Downgrade some C++20 warnings-as-errors to warnings that we can fix after we
+# compile as C++20 (bug 1768116). They don't need to block upgrading to C++20.
+check_and_add_warning("-Wno-error=deprecated", cxx_compiler)
+check_and_add_warning("-Wno-error=deprecated-anon-enum-enum-conversion", cxx_compiler)
+check_and_add_warning("-Wno-error=deprecated-enum-enum-conversion", cxx_compiler)
+check_and_add_warning("-Wno-error=deprecated-pragma", cxx_compiler)
+check_and_add_warning("-Wno-error=deprecated-this-capture", cxx_compiler)
+
+# catches possible misuse of the comma operator
+check_and_add_warning("-Wcomma", cxx_compiler)
+
+# catches duplicated conditions in if-else-if chains
+check_and_add_warning("-Wduplicated-cond")
+
+# catches unintentional switch case fallthroughs
+check_and_add_warning("-Wimplicit-fallthrough", cxx_compiler)
+
+# Warn about suspicious uses of logical operators in expressions.
+check_and_add_warning("-Wlogical-op")
+
+# Enable some ObjC diagnostics that are only relevant when targeting macOS:
+with only_when(depends(target)(lambda t: t.kernel == "Darwin")):
+ # catch redeclaration of ObjC method parameter name
+ check_and_add_warning("-Wduplicate-method-arg")
+
+ # catch multiple declarations of ObjC method found
+ check_and_add_warning("-Wduplicate-method-match")
+
+ # catch ObjC method with no return type specified
+ check_and_add_warning("-Wmissing-method-return-type")
+
+ # catch implicit conversions between ObjC BOOL and int
+ check_and_add_warning("-Wobjc-signed-char-bool")
+
+ # catch semicolon before ObjC method body
+ check_and_add_warning("-Wsemicolon-before-method-body")
+
+ # catch ObjC method parameter type not matching super class method
+ check_and_add_warning("-Wsuper-class-method-mismatch")
+
+# catches string literals used in boolean expressions
+check_and_add_warning("-Wstring-conversion")
+
+# we inline 'new' and 'delete' in mozalloc
+check_and_add_warning("-Wno-inline-new-delete", cxx_compiler)
+
+# Prevent the following GCC warnings from being treated as errors:
+# too many false positives
+check_and_add_warning("-Wno-error=maybe-uninitialized")
+
+# we don't want our builds held hostage when a platform-specific API
+# becomes deprecated.
+check_and_add_warning("-Wno-error=deprecated-declarations")
+
+# false positives depending on optimization
+check_and_add_warning("-Wno-error=array-bounds")
+
+# false positives depending on optimizations
+check_and_add_warning("-Wno-error=free-nonheap-object")
+
+# Would be a pain to fix all occurrences, for very little gain
+check_and_add_warning("-Wno-multistatement-macros")
+
+# Disable the -Werror for -Wclass-memaccess as we have a long
+# tail of issues to fix
+check_and_add_warning("-Wno-error=class-memaccess")
+
+# -Watomic-alignment is a new warning in clang 7 that seems way too broad.
+# https://bugs.llvm.org/show_bug.cgi?id=38593
+check_and_add_warning("-Wno-error=atomic-alignment")
+
+# New warning with clang 15. Catches uses of deprecated builtins in abseil-cpp.
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1779528
+check_and_add_warning("-Wno-error=deprecated-builtins")
+
+# catches format/argument mismatches with printf
+c_format_warning, cxx_format_warning = check_and_add_warning(
+ "-Wformat", when=depends(target)(lambda t: t.kernel != "WINNT")
+)
+
+# Add compile-time warnings for unprotected functions and format functions
+# that represent possible security problems. Enable this only when -Wformat
+# is enabled, otherwise it is an error
+check_and_add_warning("-Wformat-security", when=c_format_warning & cxx_format_warning)
+check_and_add_warning("-Wformat-overflow=2", when=c_format_warning & cxx_format_warning)
+
+# Other Windows specific things
+with only_when(target_is_windows):
+ # When compiling for Windows with gcc, we encounter lots of "#pragma warning"'s
+ # which is an MSVC-only pragma that GCC does not recognize.
+ # With clang-cl, as it claims to be MSVC it would be difficult to add
+ # #if defined(_MSC_VER) && !defined(__clang__) everywhere we use such pragmas,
+ # so just ignore them.
+ check_and_add_warning("-Wno-unknown-pragmas")
+
+ with only_when(depends(c_compiler)(lambda c: c.type == "clang-cl")):
+ # We get errors about various #pragma intrinsic directives from
+ # clang-cl, and we don't need to hear about those.
+ check_and_add_warning("-Wno-ignored-pragmas")
+
+ # clang-cl's Intrin.h marks things like _ReadWriteBarrier as
+ # __attribute((__deprecated__)). This is nice to know, but since we don't
+ # get the equivalent warning from MSVC, let's just ignore it.
+ check_and_add_warning("-Wno-deprecated-declarations")
+
+ # This warns for reasonable things like:
+ # enum { X = 0xffffffffU };
+ # which is annoying for IDL headers.
+ check_and_add_warning("-Wno-microsoft-enum-value", cxx_compiler)
+
+ # This warns for cases that would be reached by the Microsoft
+ # #include rules, but also currently warns on cases that would
+ # *also* be reached by standard C++ include rules. That
+ # behavior doesn't seem useful, so we turn it off.
+ check_and_add_warning("-Wno-microsoft-include", cxx_compiler)
+
+ # We use a function like:
+ # __declspec(noreturn) __inline void f() {}
+ # which -Winvalid-noreturn complains about. Again, MSVC seems
+ # OK with it, so let's silence the warning.
+ check_and_add_warning("-Wno-invalid-noreturn")
+
+ # Missing |override| on virtual function declarations isn't
+ # something that MSVC currently warns about.
+ check_and_add_warning("-Wno-inconsistent-missing-override", cxx_compiler)
+
+ # We use -DHAS_EXCEPTIONS=0, which removes the |throw()|
+ # declaration on |operator delete(void*)|. However, clang-cl
+ # must internally declare |operator delete(void*)| differently,
+ # which causes this warning for virtually every file in the
+ # tree. clang-cl doesn't support -fno-exceptions or equivalent,
+ # so there doesn't seem to be any way to convince clang-cl to
+ # declare |delete| differently. Therefore, suppress this
+ # warning.
+ check_and_add_warning("-Wno-implicit-exception-spec-mismatch", cxx_compiler)
+
+ # Macros like STDMETHOD() and IFACEMETHOD() can declare
+ # __attribute__((nothrow)) on their respective method declarations,
+ # while the definitions are left without the matching attribute.
+ check_and_add_warning("-Wno-microsoft-exception-spec", cxx_compiler)
+
+ # At least one MSVC header and several headers in-tree have
+ # unused typedefs, so turn this on.
+ check_and_add_warning("-Wno-unused-local-typedef", cxx_compiler)
+
+ # jemalloc uses __declspec(allocator) as a profiler hint,
+ # which clang-cl doesn't understand.
+ check_and_add_warning("-Wno-ignored-attributes", cxx_compiler)
+
+ # __attribute__((unused)) really means "might be unused" and
+ # we use it to avoid warnings about things that are unused
+ # in some compilation units, but used in many others. This
+ # warning insists on complaining about the latter case, which
+ # is annoying, and rather noisy.
+ check_and_add_warning("-Wno-used-but-marked-unused", cxx_compiler)
+
+ with only_when(depends(c_compiler)(lambda c: c.type != "clang-cl")):
+ # When compiling for Windows with gcc, gcc throws false positives and true
+ # positives where the callsite is ifdef-ed out
+ check_and_add_warning("-Wno-unused-function")
+
+ # When compiling for Windows with gcc, gcc cannot produce this warning
+ # correctly: it mistakes DWORD_PTR and ULONG_PTR as types you cannot
+ # give NULL to. (You can in fact do that.)
+ check_and_add_warning("-Wno-conversion-null")
+
+ # Throughout the codebase we regularly have switch statements off of enums
+ # without covering every value in the enum. We don't care about these warnings.
+ check_and_add_warning("-Wno-switch")
+
+ # Another code pattern we have is using start and end constants in enums of
+ # different types. We do this for safety, but then when comparing it throws
+ # an error, which we would like to ignore. This seems to only affect the MinGW
+ # build, but we're not sure why.
+ check_and_add_warning("-Wno-enum-compare")
+
+# Make it an error to be missing function declarations for C code.
+check_and_add_warning("-Werror=implicit-function-declaration", c_compiler)
+
+# New in clang 11. We can't really do anything about this warning.
+check_and_add_warning("-Wno-psabi")
+
+# Disable broken missing-braces warning on old clang versions
+check_and_add_warning(
+ "-Wno-missing-braces",
+ when=depends(c_compiler)(lambda c: c.type == "clang" and c.version < "6.0"),
+)
+
+# Turn on clang thread-safety analysis
+# Older clangs don't support AutoUnlock, and have other issues
+check_and_add_warning(
+ "-Wthread-safety",
+ when=depends(c_compiler)(
+ lambda c: c.type in ("clang", "clang-cl") and c.version >= "8.0"
+ ),
+)
+
+# Warn if APIs are used without available() checks on macOS.
+check_and_add_warning("-Werror=unguarded-availability-new", when=target_is_osx)
+
+# clang 17 warns about builtins being redefined and... well, we do that in
+# multiple places, some of which are third-party. Until the situation is
+# fixed, disable the new warning.
+check_and_add_warning("-Wno-error=builtin-macro-redefined")
+
+# Please keep the following last in this file
+
+# Avoid requiring complicated logic for extra warning flags in moz.build files.
+check_and_add_warning("-Wno-unknown-warning-option")
+
+set_config("WARNINGS_CFLAGS", warnings_flags.cflags)
+set_config("WARNINGS_CXXFLAGS", warnings_flags.cxxflags)
+set_config("WARNINGS_HOST_CFLAGS", warnings_flags.host_cflags)
+set_config("WARNINGS_HOST_CXXFLAGS", warnings_flags.host_cxxflags)
diff --git a/build/moz.configure/windows.configure b/build/moz.configure/windows.configure
new file mode 100644
index 0000000000..b5f5eaa169
--- /dev/null
+++ b/build/moz.configure/windows.configure
@@ -0,0 +1,481 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+option(
+ "--with-windows-version",
+ nargs=1,
+ default="603",
+ help="Windows SDK version to target. Win 8.1 (603) is currently"
+ "the minimum supported version.",
+)
+
+
+@depends("--with-windows-version")
+@imports(_from="__builtin__", _import="ValueError")
+def valid_windows_version(value):
+ if not value:
+ die("Cannot build with --without-windows-version")
+ try:
+ version = int(value[0], 16)
+ if version in (0x603,):
+ return version
+ except ValueError:
+ pass
+
+ die("Invalid value for --with-windows-version (%s)", value[0])
+
+
+option(env="WINDOWSSDKDIR", nargs=1, help="Directory containing the Windows SDK")
+
+
+@depends(
+ "WINDOWSSDKDIR", "WINSYSROOT", winsysroot, target_windows_abi, host_windows_abi
+)
+def windows_sdk_dir(
+ value, winsysroot_env, winsysroot, target_windows_abi, host_windows_abi
+):
+ if value:
+ if winsysroot_env:
+ die("WINDOWSSDKDIR and WINSYSROOT cannot be set together.")
+ if winsysroot:
+ die("WINDOWSSDKDIR cannot be set when using the bootstrapped WINSYSROOT")
+ return value
+ if target_windows_abi != "msvc" and host_windows_abi != "msvc":
+ return ()
+
+ if winsysroot:
+ return [os.path.join(winsysroot, "Windows Kits", "10")]
+
+ return set(
+ normalize_path(x[1])
+ for x in get_registry_values(
+ r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots"
+ r"\KitsRoot*",
+ get_32_and_64_bit=True,
+ )
+ )
+
+
+@imports("glob")
+def get_sdk_dirs(sdk, subdir):
+ def get_dirs_containing(sdk, stem, subdir):
+ return [
+ os.path.dirname(p) for p in glob.glob(os.path.join(sdk, stem, "*", subdir))
+ ]
+
+ def categorize(dirs):
+ return {os.path.basename(d): d for d in dirs}
+
+ include_dirs = categorize(get_dirs_containing(sdk, "Include", subdir))
+ lib_dirs = categorize(get_dirs_containing(sdk, "Lib", subdir))
+
+ valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True)
+ return [
+ namespace(
+ path=sdk,
+ lib=lib_dirs[vv],
+ include=include_dirs[vv],
+ )
+ for vv in valid_versions
+ ]
+
+
+@imports(_from="mozbuild.shellutil", _import="quote")
+def valid_windows_sdk_dir_result(value):
+ if value:
+ return "0x%04x in %s" % (value.version, quote(value.path))
+
+
+@depends(
+ target_windows_abi,
+ host_windows_abi,
+ windows_sdk_dir,
+ valid_windows_version,
+ "WINDOWSSDKDIR",
+)
+@checking("for Windows SDK", valid_windows_sdk_dir_result)
+@imports(_from="__builtin__", _import="Exception")
+@imports(_from="__builtin__", _import="open")
+def valid_windows_sdk_dir(
+ target_windows_abi,
+ host_windows_abi,
+ windows_sdk_dir,
+ target_version,
+ windows_sdk_dir_env,
+):
+ if target_windows_abi != "msvc" and host_windows_abi != "msvc":
+ return None
+ if windows_sdk_dir_env:
+ windows_sdk_dir_env = windows_sdk_dir_env[0]
+ sdks = {}
+ for d in windows_sdk_dir:
+ sdklist = get_sdk_dirs(d, "um")
+ for sdk in sdklist:
+ maxver = None
+ winsdkver = os.path.join(sdk.include, "um", "winsdkver.h")
+ with open(winsdkver) as fh:
+ for line in fh.readlines():
+ if line.startswith("#define"):
+ line = line.split(None, 2)
+ assert line.pop(0) == "#define"
+ if line.pop(0) == "WINVER_MAXVER":
+ maxver = line.pop(0)
+ break
+
+ if maxver:
+ try:
+ maxver = int(maxver, 0)
+ except Exception:
+ pass
+ else:
+ sdks[d] = maxver, sdk
+ break
+
+ if d == windows_sdk_dir_env and d not in sdks:
+ raise FatalCheckError(
+ "Error while checking the version of the SDK in "
+ "WINDOWSSDKDIR (%s). Please verify it contains a valid and "
+ "complete SDK installation." % windows_sdk_dir_env
+ )
+
+ valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True)
+ if valid_sdks:
+ biggest_version, sdk = sdks[valid_sdks[0]]
+ if not valid_sdks or biggest_version < target_version:
+ if windows_sdk_dir_env:
+ raise FatalCheckError(
+ "You are targeting Windows version 0x%04x, but your SDK only "
+ "supports up to version 0x%04x. Install and use an updated SDK, "
+ "or target a lower version using --with-windows-version. "
+ "Alternatively, try running the Windows SDK Configuration Tool "
+ "and selecting a newer SDK. See "
+ "https://developer.mozilla.org/En/Windows_SDK_versions for "
+ "details on fixing this." % (target_version, biggest_version)
+ )
+
+ raise FatalCheckError(
+ "Cannot find a Windows SDK for version >= 0x%04x." % target_version
+ )
+
+ return namespace(
+ path=sdk.path,
+ include=sdk.include,
+ lib=sdk.lib,
+ version=biggest_version,
+ )
+
+
+@imports(_from="mozbuild.shellutil", _import="quote")
+def valid_ucrt_sdk_dir_result(value):
+ if value:
+ return "%s in %s" % (value.version, quote(value.path))
+
+
+@depends(windows_sdk_dir, "WINDOWSSDKDIR", target_windows_abi, host_windows_abi)
+@checking("for Universal CRT SDK", valid_ucrt_sdk_dir_result)
+@imports("os")
+@imports(_import="mozpack.path", _as="mozpath")
+def valid_ucrt_sdk_dir(
+ windows_sdk_dir, windows_sdk_dir_env, target_windows_abi, host_windows_abi
+):
+ if target_windows_abi != "msvc" and host_windows_abi != "msvc":
+ return None
+ if windows_sdk_dir_env:
+ windows_sdk_dir_env = windows_sdk_dir_env[0]
+ sdks = {}
+ for d in windows_sdk_dir:
+ sdklist = get_sdk_dirs(d, "ucrt")
+ for sdk in sdklist:
+ version = os.path.basename(sdk.include)
+ # We're supposed to always find a version in the directory, because
+ # the 8.1 SDK, which doesn't have a version in the directory, doesn't
+ # contain the Universal CRT SDK. When the main SDK is 8.1, there
+ # is, however, supposed to be a reduced install of the SDK 10
+ # with the UCRT.
+ if version != "include":
+ sdks[d] = Version(version), sdk
+ break
+
+ if d == windows_sdk_dir_env and d not in sdks:
+ # When WINDOWSSDKDIR is set in the environment and we can't find the
+ # Universal CRT SDK, chances are this is a start-shell-msvc*.bat
+ # setup, where INCLUDE and LIB already contain the UCRT paths.
+ ucrt_includes = [
+ p
+ for p in os.environ.get("INCLUDE", "").split(";")
+ if os.path.basename(p).lower() == "ucrt"
+ ]
+ ucrt_libs = [
+ p
+ for p in os.environ.get("LIB", "").split(";")
+ if os.path.basename(os.path.dirname(p)).lower() == "ucrt"
+ ]
+ if ucrt_includes and ucrt_libs:
+ # Pick the first of each, since they are the ones that the
+ # compiler would look first. Assume they contain the SDK files.
+ include = os.path.dirname(ucrt_includes[0])
+ lib = os.path.dirname(os.path.dirname(ucrt_libs[0]))
+ path = os.path.dirname(os.path.dirname(include))
+ version = os.path.basename(include)
+ if version != "include" and mozpath.basedir(lib, [path]):
+ sdks[d] = (
+ Version(version),
+ namespace(
+ path=path,
+ include=include,
+ lib=lib,
+ ),
+ )
+ continue
+ raise FatalCheckError(
+ "The SDK in WINDOWSSDKDIR (%s) does not contain the Universal "
+ "CRT." % windows_sdk_dir_env
+ )
+
+ valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True)
+ if not valid_sdks:
+ raise FatalCheckError(
+ "Cannot find the Universal CRT SDK. " "Please install it."
+ )
+
+ version, sdk = sdks[valid_sdks[0]]
+ minimum_ucrt_version = Version("10.0.17134.0")
+ if version < minimum_ucrt_version:
+ raise FatalCheckError(
+ "Latest Universal CRT SDK version found %s"
+ " and minimum required is %s. This or a later"
+ " version can be installed using the Visual"
+ " Studio installer." % (version, minimum_ucrt_version)
+ )
+
+ return namespace(
+ path=sdk.path,
+ include=sdk.include,
+ lib=sdk.lib,
+ version=version,
+ )
+
+
+@depends(target_windows_abi, host_windows_abi, vc_toolchain_search_path)
+@imports("os")
+def vc_path(target_windows_abi, host_windows_abi, vc_toolchain_search_path):
+ if target_windows_abi != "msvc" and host_windows_abi != "msvc":
+ return
+
+ # In clang-cl builds, we need the headers and libraries from an MSVC installation.
+ vc_program = find_program("cl.exe", paths=vc_toolchain_search_path)
+ if not vc_program:
+ die("Cannot find a Visual C++ install for e.g. ATL headers.")
+
+ result = os.path.dirname(vc_program)
+ while True:
+ next, p = os.path.split(result)
+ if next == result:
+ die(
+ "Cannot determine the Visual C++ directory the compiler (%s) "
+ "is in" % vc_program
+ )
+ result = next
+ if p.lower() == "bin":
+ break
+ return os.path.normpath(result)
+
+
+@depends(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir)
+@imports("os")
+def include_path(vc_path, windows_sdk_dir, ucrt_sdk_dir):
+ if not vc_path:
+ return
+ atlmfc_dir = os.path.join(vc_path, "atlmfc", "include")
+ if not os.path.isdir(atlmfc_dir):
+ die(
+ "Cannot find the ATL/MFC headers in the Visual C++ directory (%s). "
+ "Please install them." % vc_path
+ )
+
+ winrt_dir = os.path.join(windows_sdk_dir.include, "winrt")
+ if not os.path.isdir(winrt_dir):
+ die(
+ "Cannot find the WinRT headers in the Windows SDK directory (%s). "
+ "Please install them." % windows_sdk_dir.path
+ )
+
+ cppwinrt_dir = os.path.join(windows_sdk_dir.include, "cppwinrt")
+ if not os.path.isdir(cppwinrt_dir):
+ die(
+ "Cannot find the C++/WinRT headers in the Windows SDK directory (%s). "
+ "Please install them." % windows_sdk_dir.path
+ )
+
+ includes = []
+ include_env = os.environ.get("INCLUDE")
+ if include_env:
+ includes.append(include_env)
+ includes.extend(
+ (
+ os.path.join(vc_path, "include"),
+ atlmfc_dir,
+ os.path.join(windows_sdk_dir.include, "shared"),
+ os.path.join(windows_sdk_dir.include, "um"),
+ winrt_dir,
+ cppwinrt_dir,
+ os.path.join(ucrt_sdk_dir.include, "ucrt"),
+ )
+ )
+ # Set in the environment for old-configure
+ includes = ";".join(includes)
+ os.environ["INCLUDE"] = includes
+ return includes
+
+
+set_config("INCLUDE", include_path)
+
+# cppwinrt requires this on clang because of no coroutine support, which is okay
+set_define("_SILENCE_CLANG_COROUTINE_MESSAGE", "")
+
+
+@template
+def lib_path_for(host_or_target):
+ @depends(
+ host_or_target,
+ dependable(host_or_target is host),
+ vc_path,
+ valid_windows_sdk_dir,
+ valid_ucrt_sdk_dir,
+ c_compiler,
+ )
+ @imports("os")
+ def lib_path(target, is_host, vc_path, windows_sdk_dir, ucrt_sdk_dir, compiler):
+ if not vc_path:
+ return
+ sdk_target = {
+ "x86": "x86",
+ "x86_64": "x64",
+ "arm": "arm",
+ "aarch64": "arm64",
+ }.get(target.cpu)
+
+ # MSVC2017 switched to use the same target naming as the sdk.
+ atlmfc_dir = os.path.join(vc_path, "atlmfc", "lib", sdk_target)
+ if not os.path.isdir(atlmfc_dir):
+ die(
+ "Cannot find the ATL/MFC libraries in the Visual C++ directory "
+ "(%s). Please install them." % vc_path
+ )
+
+ libs = []
+ lib_env = os.environ.get("LIB")
+ if lib_env and not is_host:
+ libs.extend(lib_env.split(";"))
+ libs.extend(
+ (
+ os.path.join(vc_path, "lib", sdk_target),
+ atlmfc_dir,
+ os.path.join(windows_sdk_dir.lib, "um", sdk_target),
+ os.path.join(ucrt_sdk_dir.lib, "ucrt", sdk_target),
+ )
+ )
+ if compiler.type == "clang-cl":
+ runtime_dir = check_cmd_output(
+ compiler.compiler,
+ "/clang:--print-runtime-dir",
+ *compiler.flags,
+ onerror=lambda: None
+ ).strip()
+ if runtime_dir and os.path.exists(runtime_dir):
+ # Put the clang runtime directory first, in case there is
+ # a different version in some of the other directories (notably,
+ # some versions of MSVC come with clang runtimes)
+ libs.insert(0, runtime_dir)
+ return libs
+
+ return lib_path
+
+
+@depends_if(lib_path_for(target))
+@imports("os")
+def lib_path(libs):
+ # Set in the environment for old-configure
+ libs = ";".join(libs)
+ os.environ["LIB"] = libs
+ return libs
+
+
+set_config("LIB", lib_path)
+
+
+lib_path_for_host = lib_path_for(host)
+
+
+@depends_if(lib_path_for_host)
+@imports(_from="mozbuild.shellutil", _import="quote")
+def host_linker_libpaths(libs):
+ return ["-LIBPATH:%s" % quote(l) for l in libs]
+
+
+@depends_if(lib_path_for_host)
+@imports(_from="mozbuild.shellutil", _import="quote")
+def host_linker_libpaths_bat(libs):
+ # .bat files need a different style of quoting. Batch quoting is actually
+ # not defined, and up to applications to handle, so it's not really clear
+ # what should be escaped and what not, but most paths should work just
+ # fine without escaping. And we don't care about double-quotes possibly
+ # having to be escaped because they're not allowed in file names on
+ # Windows.
+ return ['"-LIBPATH:%s"' % l for l in libs]
+
+
+set_config("HOST_LINKER_LIBPATHS", host_linker_libpaths)
+set_config("HOST_LINKER_LIBPATHS_BAT", host_linker_libpaths_bat)
+
+
+@depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host)
+@imports(_from="os", _import="environ")
+def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host):
+ if not valid_windows_sdk_dir:
+ return
+
+ vc_host = {
+ "x86": "x86",
+ "x86_64": "x64",
+ }.get(host.cpu)
+
+ # From version 10.0.15063.0 onwards the bin path contains the version number.
+ versioned_bin = (
+ "bin"
+ if valid_ucrt_sdk_dir.version < "10.0.15063.0"
+ else os.path.join("bin", str(valid_ucrt_sdk_dir.version))
+ )
+ result = [
+ environ["PATH"],
+ os.path.join(valid_windows_sdk_dir.path, versioned_bin, vc_host),
+ ]
+ if vc_host == "x64":
+ result.append(os.path.join(valid_windows_sdk_dir.path, versioned_bin, "x86"))
+ return result
+
+
+option(env="LINKER", nargs=1, when=target_is_windows, help="Path to the linker")
+
+link = check_prog(
+ "LINKER",
+ ("lld-link",),
+ input="LINKER",
+ when=target_is_windows,
+ paths=clang_search_path,
+)
+
+option(env="HOST_LINKER", nargs=1, when=host_is_windows, help="Path to the host linker")
+
+host_link = check_prog(
+ "HOST_LINKER",
+ ("lld-link",),
+ input="HOST_LINKER",
+ when=host_is_windows,
+ paths=clang_search_path,
+)
+
+add_old_configure_assignment("LINKER", link)