summaryrefslogtreecommitdiffstats
path: root/build/moz.configure
diff options
context:
space:
mode:
Diffstat (limited to 'build/moz.configure')
-rw-r--r--build/moz.configure/android-ndk.configure407
-rw-r--r--build/moz.configure/android-sdk.configure132
-rw-r--r--build/moz.configure/arm.configure292
-rw-r--r--build/moz.configure/bindgen.configure371
-rw-r--r--build/moz.configure/checks.configure189
-rwxr-xr-xbuild/moz.configure/compile-checks.configure287
-rw-r--r--build/moz.configure/compilers-util.configure135
-rw-r--r--build/moz.configure/flags.configure71
-rw-r--r--build/moz.configure/headers.configure119
-rw-r--r--build/moz.configure/init.configure1408
-rw-r--r--build/moz.configure/java.configure66
-rw-r--r--build/moz.configure/keyfiles.configure68
-rw-r--r--build/moz.configure/lto-pgo.configure331
-rw-r--r--build/moz.configure/memory.configure98
-rw-r--r--build/moz.configure/node.configure71
-rw-r--r--build/moz.configure/nspr.configure117
-rw-r--r--build/moz.configure/nss.configure30
-rw-r--r--build/moz.configure/old.configure381
-rw-r--r--build/moz.configure/pkg.configure103
-rw-r--r--build/moz.configure/rust.configure552
-rwxr-xr-xbuild/moz.configure/toolchain.configure2842
-rw-r--r--build/moz.configure/update-programs.configure83
-rw-r--r--build/moz.configure/util.configure494
-rwxr-xr-xbuild/moz.configure/warnings.configure253
-rw-r--r--build/moz.configure/windows.configure535
25 files changed, 9435 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..51683d8d33
--- /dev/null
+++ b/build/moz.configure/android-ndk.configure
@@ -0,0 +1,407 @@
+# -*- 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-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
+
+
+add_old_configure_assignment("android_version", android_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)
+add_old_configure_assignment("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)
+
+
+@depends(target, android_version, ndk)
+@checking("for android platform directory")
+@imports(_from="os.path", _import="isdir")
+def android_platform(target, android_version, ndk):
+ if target.os != "Android":
+ return
+
+ if "aarch64" == target.cpu:
+ target_dir_name = "arm64"
+ else:
+ target_dir_name = target.cpu
+
+ # Not all Android releases have their own platform release. We use
+ # the next lower platform version in these cases.
+ if android_version in (11, 10):
+ platform_version = 9
+ elif android_version in (20, 22):
+ platform_version = android_version - 1
+ else:
+ platform_version = android_version
+
+ platform_dir = os.path.join(
+ ndk, "platforms", "android-%s" % platform_version, "arch-%s" % target_dir_name
+ )
+
+ if not isdir(platform_dir):
+ die(
+ "Android platform directory not found. With the current "
+ "configuration, it should be in %s" % platform_dir
+ )
+
+ return platform_dir
+
+
+add_old_configure_assignment("android_platform", android_platform)
+set_config("ANDROID_PLATFORM", android_platform)
+
+
+@depends(android_platform, ndk, target)
+@checking("for android sysroot directory")
+@imports(_from="os.path", _import="isdir")
+def android_sysroot(android_platform, ndk, target):
+ if target.os != "Android":
+ return
+
+ # NDK r15 has both unified and non-unified headers, but we only support
+ # non-unified for that NDK, so look for that first.
+ search_dirs = [
+ # (<if this directory exists>, <return this directory>)
+ (os.path.join(android_platform, "usr", "include"), android_platform),
+ (os.path.join(ndk, "sysroot"), os.path.join(ndk, "sysroot")),
+ ]
+
+ for test_dir, sysroot_dir in search_dirs:
+ if isdir(test_dir):
+ return sysroot_dir
+
+ die(
+ "Android sysroot directory not found in %s."
+ % str([sysroot_dir for test_dir, sysroot_dir in search_dirs])
+ )
+
+
+add_old_configure_assignment("android_sysroot", android_sysroot)
+
+
+@depends(android_platform, ndk, target)
+@checking("for android system directory")
+@imports(_from="os.path", _import="isdir")
+def android_system(android_platform, ndk, target):
+ if target.os != "Android":
+ return
+
+ # NDK r15 has both unified and non-unified headers, but we only support
+ # non-unified for that NDK, so look for that first.
+ search_dirs = [
+ os.path.join(android_platform, "usr", "include"),
+ os.path.join(ndk, "sysroot", "usr", "include", target.toolchain),
+ ]
+
+ for system_dir in search_dirs:
+ if isdir(system_dir):
+ return system_dir
+
+ die("Android system directory not found in %s." % str(search_dirs))
+
+
+add_old_configure_assignment("android_system", android_system)
+
+
+@depends(target, host, ndk, "--with-android-toolchain")
+@checking("for the Android toolchain directory", lambda x: x or "not found")
+@imports(_from="os.path", _import="isdir")
+@imports(_from="mozbuild.shellutil", _import="quote")
+def android_toolchain(target, host, ndk, toolchain):
+ if not ndk:
+ return
+ if toolchain:
+ return toolchain[0]
+ else:
+ if target.cpu == "arm" and target.endianness == "little":
+ target_base = "arm-linux-androideabi"
+ elif target.cpu == "x86":
+ target_base = "x86"
+ elif target.cpu == "x86_64":
+ target_base = "x86_64"
+ elif target.cpu == "aarch64" and target.endianness == "little":
+ target_base = "aarch64-linux-android"
+ else:
+ die("Target cpu is not supported.")
+
+ toolchain_format = "%s/toolchains/%s-4.9/prebuilt/%s-%s"
+ host_kernel = "windows" if host.kernel == "WINNT" else host.kernel.lower()
+
+ toolchain = toolchain_format % (ndk, target_base, host_kernel, host.cpu)
+ log.debug("Trying %s" % quote(toolchain))
+ if not isdir(toolchain) and host.cpu == "x86_64":
+ toolchain = toolchain_format % (ndk, target_base, host_kernel, "x86")
+ log.debug("Trying %s" % quote(toolchain))
+ if isdir(toolchain):
+ return toolchain
+ die("You have to specify --with-android-toolchain=" "/path/to/ndk/toolchain.")
+
+
+set_config("ANDROID_TOOLCHAIN", android_toolchain)
+
+
+@depends(target)
+def android_toolchain_prefix_base(target):
+ if target.cpu == "x86":
+ # Ideally, the --target should just have the right x86 variant
+ # in the first place.
+ return "i686-linux-android"
+ return target.toolchain
+
+
+option(
+ env="STLPORT_CPPFLAGS",
+ nargs=1,
+ help="Options compiler should pass for standard C++ library",
+)
+
+
+@depends("STLPORT_CPPFLAGS", ndk)
+@imports(_from="os.path", _import="isdir")
+def stlport_cppflags(value, ndk):
+ if value and len(value):
+ return value.split()
+ if not ndk:
+ return
+
+ ndk_base = os.path.join(ndk, "sources", "cxx-stl")
+ cxx_base = os.path.join(ndk_base, "llvm-libc++")
+ cxx_include = os.path.join(cxx_base, "libcxx", "include")
+ cxxabi_base = os.path.join(ndk_base, "llvm-libc++abi")
+ cxxabi_include = os.path.join(cxxabi_base, "libcxxabi", "include")
+
+ if not isdir(cxx_include):
+ # NDK r13 removes the inner "libcxx" directory.
+ cxx_include = os.path.join(cxx_base, "include")
+ if not isdir(cxx_include):
+ die("Couldn't find path to libc++ includes in the android ndk")
+
+ if not isdir(cxxabi_include):
+ # NDK r13 removes the inner "libcxxabi" directory.
+ cxxabi_include = os.path.join(cxxabi_base, "include")
+ if not isdir(cxxabi_include):
+ die("Couldn't find path to libc++abi includes in the android ndk")
+
+ # Add android/support/include/ for prototyping long double math
+ # functions, locale-specific C library functions, multibyte support,
+ # etc.
+ return [
+ # You'd think we'd want to use -stdlib=libc++, but this doesn't work
+ # (cf. https://bugzilla.mozilla.org/show_bug.cgi?id=1510897#c2)
+ # Using -stdlib=libc++ and removing some of the -I below also doesn't
+ # work because not everything that is in cxx_include comes in the C++
+ # header directory that comes with clang.
+ "-stdlib=libstdc++",
+ "-I%s" % cxx_include,
+ "-I%s" % os.path.join(ndk, "sources", "android", "support", "include"),
+ "-I%s" % cxxabi_include,
+ ]
+
+
+add_old_configure_assignment("stlport_cppflags", stlport_cppflags)
+
+
+@depends(android_system, android_sysroot, android_toolchain, android_version)
+def extra_toolchain_flags(
+ android_system, android_sysroot, toolchain_dir, android_version
+):
+ if not android_sysroot:
+ return []
+ flags = [
+ "-isystem",
+ android_system,
+ "-isystem",
+ os.path.join(android_sysroot, "usr", "include"),
+ "-gcc-toolchain",
+ toolchain_dir,
+ "-D__ANDROID_API__=%d" % android_version,
+ ]
+ return flags
+
+
+@depends(android_toolchain_prefix_base, android_toolchain)
+def android_toolchain_prefix(prefix_base, toolchain):
+ if toolchain:
+ return "%s/bin/%s-" % (toolchain, prefix_base)
+
+
+imply_option(
+ "--with-toolchain-prefix", android_toolchain_prefix, reason="--with-android-ndk"
+)
+
+
+@depends(
+ extra_toolchain_flags,
+ stlport_cppflags,
+ android_toolchain,
+ android_toolchain_prefix_base,
+)
+@imports(_from="os.path", _import="isdir")
+def bindgen_cflags_android(toolchain_flags, stlport_flags, toolchain, toolchain_prefix):
+ if not toolchain_flags:
+ return
+
+ gcc_include = os.path.join(toolchain, "lib", "gcc", toolchain_prefix, "4.9.x")
+ if not isdir(gcc_include):
+ gcc_include = os.path.join(toolchain, "lib", "gcc", toolchain_prefix, "4.9")
+
+ return (
+ toolchain_flags
+ + stlport_flags
+ + [
+ "-I%s" % os.path.join(gcc_include, "include"),
+ "-I%s" % os.path.join(gcc_include, "include-fixed"),
+ ]
+ )
+
+
+@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..129c11525a
--- /dev/null
+++ b/build/moz.configure/android-sdk.configure
@@ -0,0 +1,132 @@
+# -*- 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("--help")
+def android_sdk_version(_):
+ return namespace(build_tools_version="29.0.3", target_sdk_version="29")
+
+
+@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 tools")
+@imports(_from="os.path", _import="isdir")
+def android_tools(sdk_root):
+ tools = os.path.join(sdk_root, "tools")
+ if isdir(tools):
+ return tools
+
+ die("You must install the Android tools. Try |mach bootstrap|")
+
+
+@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_TOOLS", android_tools)
+
+set_config("ANDROID_BUILD_TOOLS_VERSION", android_sdk_version.build_tools_version)
+set_config("ANDROID_TARGET_SDK", android_sdk_version.target_sdk_version)
+add_old_configure_assignment(
+ "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..2082fa640f
--- /dev/null
+++ b/build/moz.configure/arm.configure
@@ -0,0 +1,292 @@
+# -*- 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, "--help")
+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_gcc_flag(
+ "-mno-unaligned-access", when=depends(target.os)(lambda os: os == "Android")
+)
+
+
+@depends(
+ arch_option,
+ thumb_option,
+ thumb_interwork_option,
+ fpu_option,
+ float_abi_option,
+ soft_float_option,
+)
+def all_flags(arch, thumb, interwork, fpu, float_abi, soft_float):
+ return arch + thumb + interwork + fpu + float_abi + soft_float
+
+
+add_old_configure_assignment("_ARM_FLAGS", all_flags)
+add_old_configure_assignment("_THUMB_FLAGS", thumb_option)
+
+
+@depends(c_compiler, all_flags)
+@checking("ARM version support in compiler", lambda x: x.arm_arch)
+@imports(_from="textwrap", _import="dedent")
+def arm_target(compiler, all_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(
+ compiler.wrapper + [compiler.compiler] + compiler.flags,
+ compiler.language,
+ source,
+ ["-E"] + all_flags,
+ )
+ # 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)
+add_old_configure_assignment("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", build_arm_neon)
+set_define("BUILD_ARM_NEON", build_arm_neon)
+
+
+set_config("ARM_ARCH", depends(arm_target.arm_arch)(lambda x: str(x)))
+add_old_configure_assignment("ARM_ARCH", depends(arm_target.arm_arch)(lambda x: str(x)))
+set_config("MOZ_FPU", arm_target.fpu)
+
+
+@depends(arm_target.float_abi)
+def neon_flags(float_abi):
+ # 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 float_abi == "soft":
+ flags.append("-mfloat-abi=softfp")
+ 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..e5e49d8df8
--- /dev/null
+++ b/build/moz.configure/bindgen.configure
@@ -0,0 +1,371 @@
+# -*- 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.16.0")
+
+ # 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
+ )
+ )
+ )
+
+
+@depends_if(
+ "CBINDGEN",
+ bootstrap_search_path("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",
+ c_compiler,
+ cxx_compiler,
+ clang_search_path,
+ target,
+ macos_sdk,
+)
+@checking("for clang for bindgen", lambda x: x.path if x else "not found")
+def bindgen_clang_compiler(
+ clang_path, c_compiler, cxx_compiler, clang_search_path, target, macos_sdk
+):
+ # 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, allow_msvc=True)
+ flags = prepare_flags(target, macos_sdk)
+ info = check_compiler([clang_path] + flags, "C++", target)
+ 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,
+)
+def basic_bindgen_cflags(target, is_unix, compiler_info, android_cflags, clang_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", "-stdlib=libc++"],
+ "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 [])
+
+
+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)
+
+
+add_old_configure_assignment("_BINDGEN_CFLAGS", bindgen_cflags)
diff --git a/build/moz.configure/checks.configure b/build/moz.configure/checks.configure
new file mode 100644
index 0000000000..9269addbb8
--- /dev/null
+++ b/build/moz.configure/checks.configure
@@ -0,0 +1,189 @@
+# -*- 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.
+# - `paths_have_priority` means that any programs found early in the PATH
+# will be prioritized over programs found later in the PATH. The default is
+# False, meaning that any of the programs earlier in the program list will be
+# given priority, no matter where in the PATH they are found.
+#
+# 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,
+ paths_have_priority=False,
+ when=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)
+
+ # 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
+
+ if paths_have_priority:
+ for path in paths:
+ for prog in value or progs:
+ log.debug("%s: Looking for %s", var.lower(), quote(prog))
+ result = find_program(prog, [path])
+ if result:
+ return result
+ else:
+ for prog in value or progs:
+ log.debug("%s: Looking for %s", var.lower(), quote(prog))
+ result = find_program(prog, paths)
+ 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 100755
index 0000000000..25e4f80bf5
--- /dev/null
+++ b/build/moz.configure/compile-checks.configure
@@ -0,0 +1,287 @@
+# -*- 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
+
+
+# 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 = {
+ "C": c_compiler,
+ "C++": cxx_compiler,
+ }[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. */",
+ ]
+
+ return compiler.try_run(
+ header=comment + ["char %s();" % 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]
+
+ @depends(c, when)
+ def result(c, when):
+ if when and c.type in ("clang", "gcc"):
+ return True
+
+ if check:
+
+ @depends(c, dependable(flags))
+ def flags(c, flags):
+ # Don't error out just because clang complains about other things.
+ if c.type == "clang":
+ 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_gcc_warning().
+@template
+def check_and_add_gcc_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_gcc_warning(warning, compiler=None, when=None):
+ check_and_add_gcc_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_gcc_flag().
+@template
+def check_and_add_gcc_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_gcc_flag(warning, compiler=None, when=None):
+ check_and_add_gcc_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..1d8930347f
--- /dev/null
+++ b/build/moz.configure/compilers-util.configure
@@ -0,0 +1,135 @@
+# -*- 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
+
+ @depends(
+ self,
+ dependable(flags),
+ extra_toolchain_flags,
+ stlport_cppflags,
+ dependable(header),
+ when=when,
+ )
+ @checking_fn
+ def func(compiler, flags, extra_flags, stlport_flags, header):
+ flags = list(flags or [])
+ if is_target:
+ flags += extra_flags or []
+ if compiler.language == "C++":
+ flags += stlport_flags or []
+ header = header or ""
+ if isinstance(header, (list, tuple)):
+ header = "\n".join(header)
+ if header:
+ header += "\n"
+
+ if (
+ try_invoke_compiler(
+ compiler.wrapper + [compiler.compiler] + compiler.flags,
+ compiler.language,
+ header + source,
+ flags,
+ onerror=onerror,
+ )
+ 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..3cad466237
--- /dev/null
+++ b/build/moz.configure/flags.configure
@@ -0,0 +1,71 @@
+# -*- 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_gcc_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_gcc_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
+)
+
+
+@depends(rust_compile_flags, rust_warning_flags)
+def rust_flags(compile_flags, warning_flags):
+ return compile_flags + warning_flags
+
+
+set_config("MOZ_RUST_DEFAULT_FLAGS", rust_flags)
+
+
+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 new_pass_manager_flags(enabled, compiler, host, target, pgo, enable_fuzzing, ubsan):
+ 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.
+ 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.
+ return None
+ if enable_fuzzing and compiler.version < "10.0.0":
+ # Clang 9 does not seem to play well with libFuzzer
+ return None
+ if ubsan and compiler.version >= "10.0.0":
+ # Temporary until https://bugs.llvm.org/show_bug.cgi?id=45835 gets a
+ # real fix: clang 10 hangs with some ubsan-inserted code constructs.
+ return None
+ if enabled and compiler.version >= "9.0.0":
+ if compiler.type == "clang":
+ return ["-fexperimental-new-pass-manager"]
+ elif compiler.type == "clang-cl":
+ return ["-Xclang", "-fexperimental-new-pass-manager"]
+
+
+set_config("MOZ_NEW_PASS_MANAGER_FLAGS", new_pass_manager_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..7435bdeaad
--- /dev/null
+++ b/build/moz.configure/init.configure
@@ -0,0 +1,1408 @@
+# -*- 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)
+
+option(env="DIST", nargs=1, help="DIST directory")
+
+
+# Do not allow objdir == srcdir builds.
+# ==============================================================
+@depends("--help", "DIST")
+@imports(_from="__builtin__", _import="open")
+@imports(_from="os.path", _import="exists")
+@imports(_from="six", _import="ensure_text")
+def check_build_environment(help, dist):
+ topobjdir = os.path.realpath(".")
+ topsrcdir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+
+ if dist:
+ dist = normsep(dist[0])
+ else:
+ dist = os.path.join(topobjdir, "dist")
+
+ result = namespace(
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ dist=dist,
+ )
+
+ if help:
+ return result
+
+ # This limitation has mostly to do with GNU make. Since make can't represent
+ # variables with spaces without correct quoting and many paths are used
+ # without proper quoting, using paths with spaces commonly results in
+ # targets or dependencies being treated as multiple paths. This, of course,
+ # undermines the ability for make to perform up-to-date checks and makes
+ # the build system not work very efficiently. In theory, a non-make build
+ # backend will make this limitation go away. But there is likely a long tail
+ # of things that will need fixing due to e.g. lack of proper path quoting.
+ if len(topsrcdir.split()) > 1:
+ die("Source directory cannot be located in a path with spaces: %s" % topsrcdir)
+ if len(topobjdir.split()) > 1:
+ die("Object directory cannot be located in a path with spaces: %s" % topobjdir)
+
+ if topsrcdir == topobjdir:
+ die(
+ " ***\n"
+ " * Building directly in the main source directory is not allowed.\n"
+ " *\n"
+ " * To build, you must run configure from a separate directory\n"
+ " * (referred to as an object directory).\n"
+ " *\n"
+ " * If you are building with a mozconfig, you will need to change your\n"
+ " * mozconfig to point to a different object directory.\n"
+ " ***"
+ )
+
+ # Check for CRLF line endings.
+ with open(os.path.join(topsrcdir, "configure.py"), "r") as fh:
+ data = ensure_text(fh.read())
+ if "\r" in data:
+ die(
+ "\n ***\n"
+ " * The source tree appears to have Windows-style line endings.\n"
+ " *\n"
+ " * If using Git, Git is likely configured to use Windows-style\n"
+ " * line endings.\n"
+ " *\n"
+ " * To convert the working copy to UNIX-style line endings, run\n"
+ " * the following:\n"
+ " *\n"
+ " * $ git config core.autocrlf false\n"
+ " * $ git config core.eof lf\n"
+ " * $ git rm --cached -r .\n"
+ " * $ git reset --hard\n"
+ " *\n"
+ " * If not using Git, the tool you used to obtain the source\n"
+ " * code likely converted files to Windows line endings. See\n"
+ " * usage information for that tool for more.\n"
+ " ***"
+ )
+
+ # Check for a couple representative files in the source tree
+ conflict_files = [
+ "* %s" % f
+ for f in ("Makefile", "config/autoconf.mk")
+ if exists(os.path.join(topsrcdir, f))
+ ]
+ if conflict_files:
+ die(
+ " ***\n"
+ " * Your source tree contains these files:\n"
+ " %s\n"
+ " * This indicates that you previously built in the source tree.\n"
+ " * A source tree build can confuse the separate objdir build.\n"
+ " *\n"
+ " * To clean up the source tree:\n"
+ " * 1. cd %s\n"
+ " * 2. gmake distclean\n"
+ " ***" % ("\n ".join(conflict_files), topsrcdir)
+ )
+
+ return result
+
+
+set_config("TOPSRCDIR", check_build_environment.topsrcdir)
+set_config("TOPOBJDIR", check_build_environment.topobjdir)
+set_config("DIST", check_build_environment.dist)
+
+add_old_configure_assignment("_topsrcdir", check_build_environment.topsrcdir)
+add_old_configure_assignment("_objdir", check_build_environment.topobjdir)
+add_old_configure_assignment("DIST", check_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", check_build_environment, "--help")
+@imports(_from="mozbuild.mozconfig", _import="MozconfigLoader")
+@imports(_from="mozboot.mozconfig", _import="find_mozconfig")
+def mozconfig(mozconfig, old_configure, build_env, help):
+ if not old_configure and not help:
+ die("The OLD_CONFIGURE environment variable must be set")
+
+ # 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"
+ ):
+ 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")
+def shell(value, mozillabuild):
+ if value:
+ return find_program(value[0])
+ shell = "sh"
+ if mozillabuild:
+ 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
+# ========
+
+option(env="PYTHON3", nargs=1, help="Python 3 interpreter (3.6 or later)")
+
+option(
+ env="VIRTUALENV_NAME",
+ nargs=1,
+ default="init_py3",
+ help="Name of the in-objdir virtualenv",
+)
+
+
+@depends("PYTHON3", "VIRTUALENV_NAME", check_build_environment, mozconfig, "--help")
+@imports(_from="__builtin__", _import="Exception")
+@imports("os")
+@imports("sys")
+@imports("subprocess")
+@imports("distutils.sysconfig")
+@imports(_from="mozbuild.configure.util", _import="LineIO")
+@imports(_from="mozbuild.virtualenv", _import="VirtualenvManager")
+@imports(_from="mozbuild.virtualenv", _import="verify_python_version")
+@imports(_from="mozbuild.pythonutil", _import="find_python3_executable")
+@imports(_from="mozbuild.pythonutil", _import="python_executable_version")
+@imports(_from="six", _import="ensure_text")
+def virtualenv_python3(env_python, virtualenv_name, build_env, mozconfig, help):
+ # Avoid re-executing python when running configure --help.
+ if help:
+ return
+
+ # NOTE: We cannot assume the Python we are calling this code with is the
+ # Python we want to set up a virtualenv for.
+ #
+ # We also cannot assume that the Python the caller is configuring meets our
+ # build requirements.
+ #
+ # Because of this the code is written to re-execute itself with the correct
+ # interpreter if required.
+
+ log.debug("python3: running with pid %r" % os.getpid())
+ log.debug("python3: sys.executable: %r" % sys.executable)
+
+ python = env_python[0] if env_python else None
+ virtualenv_name = virtualenv_name[0]
+
+ # Did our python come from mozconfig? Overrides environment setting.
+ # Ideally we'd rely on the mozconfig injection from mozconfig_options,
+ # but we'd rather avoid the verbosity when we need to reexecute with
+ # a different python.
+ if mozconfig["path"]:
+ if "PYTHON3" in mozconfig["env"]["added"]:
+ python = mozconfig["env"]["added"]["PYTHON3"]
+ elif "PYTHON3" in mozconfig["env"]["modified"]:
+ python = mozconfig["env"]["modified"]["PYTHON3"][1]
+ elif "PYTHON3" in mozconfig["vars"]["added"]:
+ python = mozconfig["vars"]["added"]["PYTHON3"]
+ elif "PYTHON3" in mozconfig["vars"]["modified"]:
+ python = mozconfig["vars"]["modified"]["PYTHON3"][1]
+
+ log.debug("python3: executable from configuration: %r" % python)
+
+ # Verify that the Python version we executed this code with is the minimum
+ # required version to handle all project code.
+ with LineIO(lambda l: log.error(l)) as out:
+ verify_python_version(out)
+
+ # If this is a mozilla-central build, we'll find the virtualenv in the top
+ # source directory. If this is a SpiderMonkey build, we assume we're at
+ # js/src and try to find the virtualenv from the mozilla-central root.
+ # See mozilla-central changeset d2cce982a7c809815d86d5daecefe2e7a563ecca
+ # Bug 784841
+ topsrcdir, topobjdir = build_env.topsrcdir, build_env.topobjdir
+ if topobjdir.endswith("/js/src"):
+ topobjdir = topobjdir[:-7]
+
+ virtualenvs_root = os.path.join(topobjdir, "_virtualenvs")
+ with LineIO(lambda l: log.info(l), "replace") as out:
+ manager = VirtualenvManager(
+ topsrcdir,
+ os.path.join(virtualenvs_root, virtualenv_name),
+ out,
+ os.path.join(topsrcdir, "build", "build_virtualenv_packages.txt"),
+ )
+
+ # If we're not in the virtualenv, we need to update the path to include some
+ # necessary modules for find_program.
+ if "MOZBUILD_VIRTUALENV" in os.environ:
+ python = sys.executable
+ else:
+ sys.path.insert(0, os.path.join(topsrcdir, "testing", "mozbase", "mozfile"))
+ sys.path.insert(
+ 0, os.path.join(topsrcdir, "third_party", "python", "backports")
+ )
+
+ # If we know the Python executable the caller is asking for then verify its
+ # version. If the caller did not ask for a specific executable then find
+ # a reasonable default.
+ if python:
+ found_python = find_program(python)
+ if not found_python:
+ die(
+ "The PYTHON3 environment variable does not contain "
+ "a valid path. Cannot find %s",
+ python,
+ )
+ python = found_python
+ try:
+ version = python_executable_version(python).version
+ except Exception as e:
+ raise FatalCheckError(
+ "could not determine version of PYTHON3 " "(%s): %s" % (python, e)
+ )
+ else:
+ # Fall back to the search routine.
+ python, version = find_python3_executable(min_version="3.6.0")
+
+ # The API returns a bytes whereas everything in configure is unicode.
+ if python:
+ python = ensure_text(python)
+
+ if not python:
+ raise FatalCheckError(
+ "Python 3.6 or newer is required to build. "
+ "Ensure a `python3.x` executable is in your "
+ "PATH or define PYTHON3 to point to a Python "
+ "3.6 executable."
+ )
+
+ if version < (3, 6, 0):
+ raise FatalCheckError(
+ "Python 3.6 or newer is required to build; "
+ "%s is Python %d.%d" % (python, version[0], version[1])
+ )
+
+ log.debug("python3: found executable: %r" % python)
+
+ if not manager.up_to_date(python):
+ log.info("Creating Python 3 environment")
+ manager.build(python)
+ else:
+ log.debug("python3: venv is up to date")
+
+ python = normsep(manager.python_path)
+
+ if not normsep(sys.executable).startswith(normsep(virtualenvs_root)):
+ log.debug(
+ "python3: executing as %s, should be running as %s"
+ % (sys.executable, manager.python_path)
+ )
+ log.info("Re-executing in the virtualenv")
+ if env_python:
+ del os.environ["PYTHON3"]
+ # Homebrew on macOS will change Python's sys.executable to a custom
+ # value which messes with mach's virtualenv handling code. Override
+ # Homebrew's changes with the correct sys.executable value.
+ os.environ["PYTHONEXECUTABLE"] = python
+ # Another quirk on macOS, with the system python, the virtualenv is
+ # not fully operational (missing entries in sys.path) if
+ # __PYVENV_LAUNCHER__ is set.
+ os.environ.pop("__PYVENV_LAUNCHER__", None)
+ # One would prefer to use os.execl, but that's completely borked on
+ # Windows.
+ sys.exit(subprocess.call([python] + sys.argv))
+
+ # We are now in the virtualenv
+ if not distutils.sysconfig.get_python_lib():
+ die("Could not determine python site packages directory")
+
+ # We may have set PYTHONEXECUTABLE above, and that affects python
+ # subprocesses we may invoke as part of configure (e.g. hg), so
+ # unset it.
+ os.environ.pop("PYTHONEXECUTABLE", None)
+
+ str_version = ".".join(str(v) for v in version)
+
+ return namespace(
+ path=python,
+ version=version,
+ str_version=str_version,
+ )
+
+
+@depends(virtualenv_python3)
+@checking("for Python 3", callback=lambda x: "%s (%s)" % (x.path, x.str_version))
+def virtualenv_python3(venv):
+ return venv
+
+
+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__")
+ @imports(_from="six", _import="itervalues")
+ def early_options(_):
+ return set(
+ option.env for option in itervalues(__sandbox__._options) 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")
+@imports("six")
+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 six.iteritems(mozconfig["env"]["added"]):
+ add(key, value)
+ os.environ[key] = value
+ for key, (_, value) in six.iteritems(mozconfig["env"]["modified"]):
+ add(key, value)
+ os.environ[key] = value
+ for key, value in six.iteritems(mozconfig["vars"]["added"]):
+ add(key, value)
+ for key, (_, value) in six.iteritems(mozconfig["vars"]["modified"]):
+ add(key, value)
+
+
+# Source checkout and version control integration.
+# ================================================
+
+
+@depends(check_build_environment, "MOZ_AUTOMATION", "--help")
+@checking("for vcs source checkout")
+@imports("os")
+def vcs_checkout_type(build_env, automation, 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 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(check_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(check_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(check_build_environment)
+def default_project(build_env):
+ if build_env.topobjdir.endswith("/js/src"):
+ return "js"
+ return "browser"
+
+
+option("--enable-project", nargs=1, default=default_project, help="Project to build")
+
+
+# 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="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_msvc=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.
+ os = os.replace("/", "_")
+ 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 (allow_msvc and os == "windows-msvc"):
+ # windows-msvc is only opt-in for the caller of this function until
+ # full support in bug 1617793.
+ canonical_os = canonical_kernel = "WINNT"
+ 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"
+ 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 == "sh4":
+ canonical_cpu = "sh4"
+ 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)
+ elif canonical_cpu == "aarch64" and canonical_os == "WINNT":
+ toolchain = "aarch64-windows-msvc"
+ 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),
+ raw_cpu=cpu,
+ raw_os=os,
+ toolchain=toolchain,
+ vendor=vendor,
+ )
+
+
+# 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",
+ 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")
+ return check_cmd_output(shell, config_sub, triplet).strip()
+
+
+@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-mingw32")
+ elif arch == "x86":
+ return split_triplet("i686-pc-mingw32")
+
+ 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))
+ 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)
+add_old_configure_assignment("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)
+
+
+@depends(host)
+def host_os_kernel_major_version(host):
+ versions = host.raw_os.split(".")
+ version = "".join(x for x in versions[0] if x.isdigit())
+ return version
+
+
+set_config("HOST_MAJOR_VERSION", host_os_kernel_major_version)
+
+# Autoconf needs these set
+
+
+@depends(host)
+def host_for_sub_configure(host):
+ return "--host=%s" % host.alias
+
+
+@depends(target)
+def target_for_sub_configure(target):
+ target_alias = target.alias
+ return "--target=%s" % target_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.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)
+
+
+@depends(target)
+def target_is_windows(target):
+ if target.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
+
+
+set_define("XP_MACOSX", target_is_osx)
+
+
+@depends(target)
+def target_is_linux(target):
+ if target.kernel == "Linux":
+ return True
+
+
+set_define("XP_LINUX", target_is_linux)
+
+
+@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("--enable-project", check_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(include_project_configure, check_build_environment)
+def build_project(include_project_configure, build_env):
+ ret = os.path.dirname(
+ os.path.relpath(include_project_configure, build_env.topsrcdir)
+ )
+ return ret
+
+
+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
+
+
+# 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(check_build_environment, build_project, version_path, "--help")
+@imports(_from="__builtin__", _import="open")
+@imports("os")
+@imports("re")
+def milestone(build_env, build_project, version_path, _):
+ 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])
+
+ 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 = is_early_beta_or_earlier = 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 ""
+
+ 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:
+ 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,
+ 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)
+add_old_configure_assignment("RELEASE_OR_BETA", milestone.is_release_or_beta)
+set_config("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
+set_define("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier)
+add_old_configure_assignment(
+ "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 wants to look at these.
+add_old_configure_assignment("MOZILLA_VERSION", milestone.version)
+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)
+
+
+# Dummy function for availability in toolkit/moz.configure. Overridden in
+# mobile/android/moz.configure.
+@depends(milestone.is_nightly)
+def fennec_nightly(is_nightly):
+ return is_nightly
+
+
+# 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 path[0]
+ return os.path.expanduser(os.path.join("~", ".mozbuild"))
+
+
+# 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..1840baf8ec
--- /dev/null
+++ b/build/moz.configure/java.configure
@@ -0,0 +1,66 @@
+# -*- 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")
+@imports(_from="mozboot.util", _import="locate_java_bin_path")
+@imports(_from="mozboot.util", _import="JavaLocationFailedException")
+def java_search_paths(path):
+ if path:
+ # Look for javac and jar in the specified path.
+ return path
+
+ try:
+ return [locate_java_bin_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. Set $JAVA_HOME to your Java "
+ "SDK directory or 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..1fe5a1ab73
--- /dev/null
+++ b/build/moz.configure/lto-pgo.configure
@@ -0,0 +1,331 @@
+# -*- 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,
+ check_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")
+@imports(_from="__builtin__", _import="min")
+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_AUTOMATION",
+ "MOZ_LD64_KNOWN_GOOD",
+ target,
+ "--enable-profile-generate",
+ new_pass_manager_flags,
+)
+@imports("multiprocessing")
+def lto(
+ value,
+ c_compiler,
+ select_linker,
+ automation,
+ ld64_known_good,
+ target,
+ instrumented_build,
+ newpm_flags,
+):
+ cflags = []
+ ldflags = []
+ enabled = None
+ rust_lto = False
+
+ if value:
+ if instrumented_build:
+ log.warning("Disabling LTO because --enable-profile-generate is specified")
+ return
+
+ enabled = True
+ # `cross` implies `thin`, but with Rust code participating in LTO
+ # as well. Make that a little more explicit.
+ if len(value) and value[0].lower() == "cross":
+ if c_compiler.type == "gcc":
+ die("Cross-language LTO is not supported with GCC.")
+
+ rust_lto = True
+ value = ["thin"]
+
+ if (
+ target.kernel == "Darwin"
+ and target.os == "OSX"
+ and len(value)
+ and value[0].lower() == "cross"
+ 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 len(value) and value[0].lower() == "full":
+ cflags.append("-flto")
+ ldflags.append("-flto")
+ else:
+ cflags.append("-flto=thin")
+ ldflags.append("-flto=thin")
+ elif c_compiler.type == "clang-cl":
+ if len(value) and value[0].lower() == "full":
+ 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/master/src/librustc_target/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()
+ if len(value) and value[0].lower() == "thin":
+ die(
+ "gcc does not support thin LTO. Use `--enable-lto` "
+ "to enable full LTO for gcc."
+ )
+ else:
+ 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 newpm_flags:
+ if target.os == "WINNT":
+ # On Windows, this flag requires a change from clang-12, which
+ # is applied as a patch to our automation toolchain.
+ if automation or c_compiler.version >= "12.0.0":
+ ldflags.append("-opt:ltonewpassmanager")
+ ldflags.append("-mllvm:-import-hot-multiplier=30")
+ elif select_linker.KIND != "ld64" and c_compiler.type == "clang":
+ ldflags.append("-Wl,-plugin-opt=new-pass-manager")
+ ldflags.append("-Wl,-plugin-opt=-import-hot-multiplier=30")
+
+ return namespace(
+ enabled=enabled,
+ 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..855706b457
--- /dev/null
+++ b/build/moz.configure/memory.configure
@@ -0,0 +1,98 @@
+# -*- 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)
+def jemalloc_default(target):
+ 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.
+ #
+ # XXX: PHC is implemented but not yet enabled on Mac. Bug 1576515 is about
+ # enabling it on Mac, but it is blocked by bug 1035892.
+ return (
+ target.os == "GNU" and target.kernel == "Linux" and target.bitness == 64
+ ) or (target.kernel == "WINNT" and target.bitness == 64)
+
+
+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")
diff --git a/build/moz.configure/node.configure b/build/moz.configure/node.configure
new file mode 100644
index 0000000000..ba2003d197
--- /dev/null
+++ b/build/moz.configure/node.configure
@@ -0,0 +1,71 @@
+# -*- 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"))
+@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")
+def nodejs(require, env_node, search_path):
+ # 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
+
+ nodejs, version = find_node_executable(node_exe)
+
+ 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..2b21a66f03
--- /dev/null
+++ b/build/moz.configure/nspr.configure
@@ -0,0 +1,117 @@
+# -*- 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
+
+
+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.26"
+
+
+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(check_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_CFLAGS", nspr_config.cflags, when=nspr_config)
+add_old_configure_assignment("NSPR_LIBS", nspr_config.libs, when=nspr_config)
+add_old_configure_assignment("NSPR_CFLAGS", nspr_pkg.cflags, when=nspr_pkg)
+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..3cdee33061
--- /dev/null
+++ b/build/moz.configure/nss.configure
@@ -0,0 +1,30 @@
+# -*- 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("--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.61", when="--with-system-nss", config=False
+)
+
+set_config("MOZ_SYSTEM_NSS", True, when="--with-system-nss")
+
+
+@depends(nss_pkg, check_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)
+add_old_configure_assignment("NSS_CFLAGS", nss_config.cflags)
diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure
new file mode 100644
index 0000000000..f1ebd2c35f
--- /dev/null
+++ b/build/moz.configure/old.configure
@@ -0,0 +1,381 @@
+# -*- 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",
+ ),
+)
+
+
+@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):
+ # 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="__builtin__", _import="sorted")
+@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-crashreporter",
+ "--enable-dbus",
+ "--enable-debug-js-modules",
+ "--enable-dump-painting",
+ "--enable-extensions",
+ "--enable-libproxy",
+ "--enable-logrefcnt",
+ "--enable-necko-wifi",
+ "--enable-negotiateauth",
+ "--enable-official-branding",
+ "--enable-parental-controls",
+ "--enable-sandbox",
+ "--enable-system-cairo",
+ "--enable-system-extension-dirs",
+ "--enable-system-pixman",
+ "--enable-universalchardet",
+ "--enable-updater",
+ "--enable-xul",
+ "--enable-zipwriter",
+ "--includedir",
+ "--libdir",
+ "--prefix",
+ "--with-android-max-sdk",
+ "--with-android-min-sdk",
+ "--with-branding",
+ "--with-distribution-id",
+ "--with-macbundlename-prefix",
+ "--with-system-libevent",
+ "--with-system-png",
+ "--with-user-appdir",
+ "--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,
+ altered_path,
+ extra_env,
+ check_build_environment,
+ old_configure_path,
+ "MOZILLABUILD",
+ 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="six", _import="exec_")
+ @imports(_from="six", _import="iteritems")
+ @imports(_from="six", _import="string_types")
+ def old_configure(
+ prepare_configure,
+ prepare_configure_options,
+ altered_path,
+ extra_env,
+ build_env,
+ old_configure,
+ mozillabuild,
+ awk,
+ m4,
+ shell,
+ ):
+ # Use prepare_configure to make lint happy
+ prepare_configure
+ 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")
+
+ 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:
+ # Autoconf on win32 may break due to a bad $PATH. Let the user know
+ # their $PATH is suspect.
+ if mozillabuild:
+ mozillabuild_path = normsep(mozillabuild[0])
+ sh_path = normsep(find_program("sh"))
+ if mozillabuild_path not in sh_path:
+ log.warning(
+ "The '{}msys/bin' directory is not first in $PATH. "
+ "This may cause autoconf to fail. ($PATH is currently "
+ "set to: {})".format(mozillabuild_path, os.environ["PATH"])
+ )
+ 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
+
+ if altered_path:
+ env["PATH"] = altered_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, string_types) 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..20d90f17fd
--- /dev/null
+++ b/build/moz.configure/pkg.configure
@@ -0,0 +1,103 @@
+# -*- 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, allow_missing=True)
+
+
+@depends_if(pkg_config)
+@checking("for pkg-config version")
+def pkg_config_version(pkg_config):
+ return Version(check_cmd_output(pkg_config, "--version").rstrip())
+
+
+# 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):
+ if isinstance(package_desc, (tuple, list)):
+ package_desc = " ".join(package_desc)
+ package_desc = dependable(package_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, package_desc, allow_missing, when=when_and_compile_environment)
+ @imports("sys")
+ @imports(_from="mozbuild.configure.util", _import="LineIO")
+ def package(pkg_config, 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
+ )
+ 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, package_desc, when=package)
+ @checking("%s_CFLAGS" % var, callback=lambda t: " ".join(t))
+ def pkg_cflags(pkg_config, package_desc):
+ flags = check_cmd_output(pkg_config, "--cflags", package_desc)
+ return tuple(flags.split())
+
+ @depends(pkg_config, package_desc, when=package)
+ @checking("%s_LIBS" % var, callback=lambda t: " ".join(t))
+ def pkg_libs(pkg_config, package_desc):
+ libs = check_cmd_output(pkg_config, "--libs", package_desc)
+ # 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)
+ 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..ff3dbe066e
--- /dev/null
+++ b/build/moz.configure/rust.configure
@@ -0,0 +1,552 @@
+# -*- 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 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),
+)
+
+
+@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, build_project)
+@imports(_from="mozboot.util", _import="MINIMUM_RUST_VERSION")
+@imports(_from="textwrap", _import="dedent")
+def rust_compiler(rustc_info, cargo_info, build_project):
+ 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/
+ """
+ )
+ )
+ if build_project == "tools/crashreporter":
+ rustc_min_version = Version("1.47.0")
+ else:
+ 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 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)
+ except ValueError:
+ if t.startswith("thumb"):
+ cpu, rest = t.split("-", 1)
+ retry = "-".join(("arm", rest))
+ elif t.endswith("-windows-msvc"):
+ retry = t[: -len("windows-msvc")] + "mingw32"
+ elif t.endswith("-windows-gnu"):
+ retry = t[: -len("windows-gnu")] + "mingw32"
+ else:
+ continue
+ try:
+ info = split_triplet(retry)
+ 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 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
+ narrowed = [c for c in candidates if c.target.alias == host_or_target.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="six", _import="ensure_binary")
+@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 = '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, ensure_binary(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)
diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure
new file mode 100755
index 0000000000..2ed03d4587
--- /dev/null
+++ b/build/moz.configure/toolchain.configure
@@ -0,0 +1,2842 @@
+# -*- 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", "--help")
+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)
+
+# yasm detection
+# ==============================================================
+yasm = check_prog("YASM", ["yasm"], allow_missing=True)
+
+
+@depends_if(yasm)
+@checking("yasm version")
+def yasm_version(yasm):
+ version = (
+ check_cmd_output(
+ yasm, "--version", onerror=lambda: die("Failed to get yasm version.")
+ )
+ .splitlines()[0]
+ .split()[1]
+ )
+ return Version(version)
+
+
+@depends(yasm, target)
+def yasm_asflags(yasm, target):
+ if yasm:
+ asflags = {
+ ("OSX", "x86"): ["-f", "macho32"],
+ ("OSX", "x86_64"): ["-f", "macho64"],
+ ("WINNT", "x86"): ["-f", "win32"],
+ ("WINNT", "x86_64"): ["-f", "x64"],
+ }.get((target.os, target.cpu), None)
+ if asflags is None:
+ # We're assuming every x86 platform we support that's
+ # not Windows or Mac is ELF.
+ if target.cpu == "x86":
+ asflags = ["-f", "elf32"]
+ elif target.cpu == "x86_64":
+ asflags = ["-f", "elf64"]
+ if asflags:
+ asflags += ["-rnasm", "-pnasm"]
+ return asflags
+
+
+set_config("YASM_ASFLAGS", yasm_asflags)
+
+
+# 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)(lambda t: "11.0" if t.cpu == "aarch64" else "10.12"),
+ help="Set the minimum MacOS version needed at runtime{|}",
+ )
+
+ @depends("--enable-macos-target")
+ @imports(_from="os", _import="environ")
+ def macos_target(value):
+ if value:
+ # Ensure every compiler process we spawn uses this value.
+ environ["MACOSX_DEPLOYMENT_TARGET"] = value[0]
+ return value[0]
+
+ set_config("MACOSX_DEPLOYMENT_TARGET", macos_target)
+ add_old_configure_assignment("MACOSX_DEPLOYMENT_TARGET", macos_target)
+
+
+@depends(host)
+def host_is_osx(host):
+ if host.os == "OSX":
+ return True
+
+
+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",
+ )
+
+ @depends("--with-macos-sdk", host)
+ @imports(_from="__builtin__", _import="open")
+ @imports(_from="os.path", _import="isdir")
+ @imports("plistlib")
+ def macos_sdk(sdk, host):
+ sdk_min_version = Version("10.12")
+ sdk_max_version = Version("11.1")
+
+ if sdk:
+ sdk = sdk[0]
+ 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."
+ )
+ 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
+ )
+ with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist:
+ obj = plistlib.load(plist)
+ if not obj:
+ die("Error parsing SDKSettings.plist in the SDK directory: %s" % sdk)
+ if "Version" not in obj:
+ die(
+ "Error finding Version information in SDKSettings.plist from the SDK: %s"
+ % sdk
+ )
+ version = Version(obj["Version"])
+ if version < sdk_min_version:
+ die(
+ 'SDK version "%s" is too old. Please upgrade to at least %s. '
+ "You may need to point to it using --with-macos-sdk=<path> in your "
+ "mozconfig." % (version, sdk_min_version)
+ )
+ if version > sdk_max_version:
+ die(
+ 'SDK version "%s" is unsupported. Please downgrade to version '
+ "%s. You may need to point to it using --with-macos-sdk=<path> in "
+ "your mozconfig." % (version, sdk_max_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(compiler, language, source, onerror=None):
+ return try_invoke_compiler(compiler, language, source, ["-E"], onerror)
+
+
+@imports(_from="mozbuild.configure.constants", _import="CompilerType")
+@imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks")
+@imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks")
+@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
+@imports(_from="six", _import="iteritems")
+@imports(_from="textwrap", _import="dedent")
+@imports(_from="__builtin__", _import="Exception")
+def get_compiler_info(compiler, language):
+ """Returns information about the given `compiler` (command line in the
+ form of a list or tuple), in the given `language`.
+
+ The returned information includes:
+ - the compiler type (clang-cl, clang or gcc)
+ - the compiler version
+ - the compiler supported language
+ - the compiler supported language version
+ """
+ # Xcode clang versions are different from the underlying llvm version (they
+ # instead are aligned with the Xcode version). Fortunately, we can tell
+ # apart plain clang from Xcode clang, and convert the Xcode clang version
+ # into the more or less corresponding plain clang version.
+ check = dedent(
+ """\
+ #if defined(_MSC_VER) && defined(__clang__) && defined(_MT)
+ %COMPILER "clang-cl"
+ %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
+ #elif defined(__clang__)
+ %COMPILER "clang"
+ %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
+ # ifdef __apple_build_version__
+ %XCODE 1
+ # endif
+ #elif defined(__GNUC__) && !defined(__MINGW32__)
+ %COMPILER "gcc"
+ %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
+ #endif
+
+ #if __cplusplus
+ %cplusplus __cplusplus
+ #elif __STDC_VERSION__
+ %STDC_VERSION __STDC_VERSION__
+ #endif
+ """
+ )
+
+ # While we're doing some preprocessing, we might as well do some more
+ # preprocessor-based tests at the same time, to check the toolchain
+ # matches what we want.
+ for name, preprocessor_checks in (
+ ("CPU", CPU_preprocessor_checks),
+ ("KERNEL", kernel_preprocessor_checks),
+ ("OS", OS_preprocessor_checks),
+ ):
+ for n, (value, condition) in enumerate(iteritems(preprocessor_checks)):
+ check += dedent(
+ """\
+ #%(if)s %(condition)s
+ %%%(name)s "%(value)s"
+ """
+ % {
+ "if": "elif" if n else "if",
+ "condition": condition,
+ "name": name,
+ "value": value,
+ }
+ )
+ check += "#endif\n"
+
+ # Also check for endianness. The advantage of living in modern times is
+ # that all the modern compilers we support now have __BYTE_ORDER__ defined
+ # by the preprocessor.
+ check += dedent(
+ """\
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ %ENDIANNESS "little"
+ #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ %ENDIANNESS "big"
+ #endif
+ """
+ )
+
+ result = try_preprocess(compiler, language, check)
+
+ if not result:
+ raise FatalCheckError("Unknown compiler or compiler not supported.")
+
+ # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
+ # have non-ASCII characters. Treat the output as bytearray.
+ data = {}
+ for line in result.splitlines():
+ if line.startswith("%"):
+ k, _, v = line.partition(" ")
+ k = k.lstrip("%")
+ data[k] = v.replace(" ", "").lstrip('"').rstrip('"')
+ log.debug("%s = %s", k, data[k])
+
+ try:
+ type = CompilerType(data["COMPILER"])
+ except Exception:
+ raise FatalCheckError("Unknown compiler or compiler not supported.")
+
+ cplusplus = int(data.get("cplusplus", "0L").rstrip("L"))
+ stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L"))
+
+ version = data.get("VERSION")
+ if version:
+ version = Version(version)
+ if data.get("XCODE"):
+ # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
+ # with enough granularity for major.minor version checks further
+ # down the line
+ if version < "9.1":
+ version = Version("4.0.0.or.less")
+ elif version < "10.0":
+ version = Version("5.0.2")
+ elif version < "10.0.1":
+ version = Version("6.0.1")
+ elif version < "11.0":
+ version = Version("7.0.0")
+ elif version < "11.0.3":
+ version = Version("8.0.0")
+ elif version < "12.0":
+ version = Version("9.0.0")
+ elif version < "12.0.1":
+ version = Version("10.0.0")
+ else:
+ version = Version("10.0.0.or.more")
+
+ return namespace(
+ type=type,
+ version=version,
+ cpu=data.get("CPU"),
+ kernel=data.get("KERNEL"),
+ endianness=data.get("ENDIANNESS"),
+ os=data.get("OS"),
+ language="C++" if cplusplus else "C",
+ language_version=cplusplus if cplusplus else stdc_version,
+ xcode=bool(data.get("XCODE")),
+ )
+
+
+def same_arch_different_bits():
+ return (
+ ("x86", "x86_64"),
+ ("ppc", "ppc64"),
+ ("sparc", "sparc64"),
+ )
+
+
+@imports(_from="mozbuild.shellutil", _import="quote")
+@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
+def check_compiler(compiler, language, target):
+ info = get_compiler_info(compiler, language)
+
+ flags = []
+
+ # Check language standards
+ # --------------------------------------------------------------------
+ if language != info.language:
+ raise FatalCheckError(
+ "`%s` is not a %s compiler." % (quote(*compiler), language)
+ )
+
+ # Note: We do a strict version check because there sometimes are backwards
+ # incompatible changes in the standard, and not all code that compiles as
+ # C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for
+ # example)
+ if info.language == "C" and info.language_version != 199901:
+ if info.type == "clang-cl":
+ flags.append("-Xclang")
+ flags.append("-std=gnu99")
+
+ cxx17_version = 201703
+ if info.language == "C++":
+ if info.language_version != cxx17_version:
+ # MSVC headers include C++17 features, but don't guard them
+ # with appropriate checks.
+ if info.type == "clang-cl":
+ flags.append("-Xclang")
+ flags.append("-std=c++17")
+ else:
+ flags.append("-std=gnu++17")
+
+ # Check compiler target
+ # --------------------------------------------------------------------
+ has_target = False
+ if info.type == "clang":
+ # Add the target explicitly when the target is aarch64 macosx, because
+ # the Xcode clang target is named differently, and we need to work around
+ # https://github.com/rust-lang/rust-bindgen/issues/1871 and
+ # https://github.com/alexcrichton/cc-rs/issues/542 so we always want
+ # the target on the command line, even if the compiler would default to
+ # that.
+ if info.xcode and target.os == "OSX" and target.cpu == "aarch64":
+ if "--target=arm64-apple-darwin" not in compiler:
+ flags.append("--target=arm64-apple-darwin")
+ has_target = True
+
+ elif (
+ not info.kernel
+ or info.kernel != target.kernel
+ or not info.endianness
+ or info.endianness != target.endianness
+ ):
+ flags.append("--target=%s" % target.toolchain)
+ has_target = True
+
+ # Add target flag when there is an OS mismatch (e.g. building for Android on
+ # Linux). However, only do this if the target OS is in our whitelist, to
+ # keep things the same on other platforms.
+ elif target.os in OS_preprocessor_checks and (
+ not info.os or info.os != target.os
+ ):
+ flags.append("--target=%s" % target.toolchain)
+ has_target = True
+
+ if not has_target and (not info.cpu or info.cpu != target.cpu):
+ same_arch = same_arch_different_bits()
+ if (target.cpu, info.cpu) in same_arch:
+ flags.append("-m32")
+ elif (info.cpu, target.cpu) in same_arch:
+ flags.append("-m64")
+ elif info.type == "clang-cl" and target.cpu == "aarch64":
+ flags.append("--target=%s" % target.toolchain)
+ elif info.type == "clang":
+ flags.append("--target=%s" % target.toolchain)
+
+ return namespace(
+ type=info.type,
+ version=info.version,
+ target_cpu=info.cpu,
+ target_kernel=info.kernel,
+ target_endianness=info.endianness,
+ target_os=info.os,
+ flags=flags,
+ )
+
+
+@imports(_from="__builtin__", _import="open")
+@imports("json")
+@imports("os")
+def get_vc_paths(topsrcdir):
+ def vswhere(args):
+ program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get(
+ "PROGRAMFILES"
+ )
+ if not program_files:
+ return []
+ vswhere = os.path.join(
+ program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe"
+ )
+ if not os.path.exists(vswhere):
+ return []
+ return json.loads(check_cmd_output(vswhere, "-format", "json", *args))
+
+ for install in vswhere(
+ [
+ "-products",
+ "*",
+ "-requires",
+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ ]
+ ):
+ path = install["installationPath"]
+ tools_version = (
+ open(
+ os.path.join(
+ path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
+ ),
+ "r",
+ )
+ .read()
+ .strip()
+ )
+ tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version)
+ yield (Version(install["installationVersion"]), tools_path)
+
+
+@depends(host)
+def host_is_windows(host):
+ if host.kernel == "WINNT":
+ return True
+
+
+option(
+ "--with-visual-studio-version",
+ nargs=1,
+ choices=("2017",),
+ when=host_is_windows,
+ help="Select a specific Visual Studio version to use",
+)
+
+
+@depends("--with-visual-studio-version", when=host_is_windows)
+def vs_major_version(value):
+ if value:
+ return {"2017": 15}[value[0]]
+
+
+option(
+ env="VC_PATH",
+ nargs=1,
+ when=host_is_windows,
+ help="Path to the Microsoft Visual C/C++ compiler",
+)
+
+
+@depends(
+ host,
+ vs_major_version,
+ check_build_environment,
+ "VC_PATH",
+ "--with-visual-studio-version",
+ when=host_is_windows,
+)
+@imports(_from="__builtin__", _import="sorted")
+@imports(_from="operator", _import="itemgetter")
+def vc_compiler_paths_for_version(
+ host, vs_major_version, env, vc_path, vs_release_name
+):
+ if vc_path and vs_release_name:
+ die("VC_PATH and --with-visual-studio-version cannot be used together.")
+ if vc_path:
+ # Use an arbitrary version, it doesn't matter.
+ all_versions = [(Version("15"), vc_path[0])]
+ else:
+ all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0))
+ if not all_versions:
+ return
+ if vs_major_version:
+ versions = [d for (v, d) in all_versions if v.major == vs_major_version]
+ if not versions:
+ die("Visual Studio %s could not be found!" % vs_release_name)
+ path = versions[0]
+ else:
+ # Choose the newest version.
+ path = all_versions[-1][1]
+ host_dir = {
+ "x86_64": "HostX64",
+ "x86": "HostX86",
+ }.get(host.cpu)
+ if host_dir:
+ path = os.path.join(path, "bin", host_dir)
+ return {
+ "x64": [os.path.join(path, "x64")],
+ # The cross toolchains require DLLs from the native x64 toolchain.
+ "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")],
+ "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")],
+ }
+
+
+@depends(target, vc_compiler_paths_for_version, when=host_is_windows)
+def vc_compiler_path(target, paths):
+ vc_target = {
+ "x86": "x86",
+ "x86_64": "x64",
+ "arm": "arm",
+ "aarch64": "arm64",
+ }.get(target.cpu)
+ if not paths:
+ return
+ return paths.get(vc_target)
+
+
+@depends(vc_compiler_path, original_path)
+@imports("os")
+@imports(_from="os", _import="environ")
+def vc_toolchain_search_path(vc_compiler_path, original_path):
+ result = list(original_path)
+
+ if vc_compiler_path:
+ # The second item, if there is one, is necessary to have in $PATH for
+ # Windows to load the required DLLs from there.
+ if len(vc_compiler_path) > 1:
+ environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:])
+
+ # The first item is where the programs are going to be
+ result.append(vc_compiler_path[0])
+
+ return result
+
+
+clang_search_path = bootstrap_search_path("clang", "bin")
+
+
+@depends(bootstrap_search_path("rustc", "bin", when="MOZ_AUTOMATION"), original_path)
+@imports("os")
+@imports(_from="os", _import="environ")
+def rust_search_path(rust_path, original_path):
+ result = list(rust_path or original_path)
+ # Also add the rustup install directory for cargo/rustc.
+ cargo_home = environ.get("CARGO_HOME", "")
+ if cargo_home:
+ cargo_home = os.path.abspath(cargo_home)
+ else:
+ cargo_home = os.path.expanduser(os.path.join("~", ".cargo"))
+ rustup_path = os.path.join(cargo_home, "bin")
+ result.append(rustup_path)
+ return result
+
+
+# As a workaround until bug 1516228 and bug 1516253 are fixed, set the PATH
+# variable for the build to contain the toolchain search path.
+@depends(vc_toolchain_search_path)
+@imports("os")
+@imports(_from="os", _import="environ")
+def altered_path(vc_toolchain_search_path):
+ path = environ["PATH"].split(os.pathsep)
+ altered_path = list(vc_toolchain_search_path)
+ for p in path:
+ if p not in altered_path:
+ altered_path.append(p)
+ return os.pathsep.join(altered_path)
+
+
+set_config("PATH", altered_path)
+
+
+# Compiler wrappers
+# ==============================================================
+option(
+ "--with-compiler-wrapper",
+ env="COMPILER_WRAPPER",
+ nargs=1,
+ help="Enable compiling with wrappers such as distcc and ccache",
+)
+
+option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache")
+
+
+@depends_if("--with-ccache")
+def ccache(value):
+ if len(value):
+ return value
+ # If --with-ccache was given without an explicit value, we default to
+ # 'ccache'.
+ return "ccache"
+
+
+ccache = check_prog(
+ "CCACHE",
+ progs=(),
+ input=ccache,
+ paths=bootstrap_search_path("sccache"),
+ 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)
+
+
+@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}
+
+ other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)
+
+ @depends(host_or_target, target, toolchain_prefix, *other_c_compiler)
+ def default_c_compilers(
+ host_or_target, target, toolchain_prefix, *other_c_compiler
+ ):
+ if host_or_target.kernel == "WINNT":
+ supported = types = ("clang-cl", "clang")
+ elif host_or_target.kernel == "Darwin":
+ types = ("clang",)
+ supported = ("clang", "gcc")
+ 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
+
+ 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
+
+
+def prepare_flags(host_or_target, macos_sdk):
+ if macos_sdk and host_or_target.os == "OSX":
+ return ["-isysroot", macos_sdk]
+ return []
+
+
+def minimum_gcc_version():
+ return Version("7.1.0")
+
+
+@template
+def compiler(
+ language,
+ host_or_target,
+ c_compiler=None,
+ other_compiler=None,
+ other_c_compiler=None,
+):
+ """Template handling the generic base checks for the compiler for the
+ given `language` on the given platform (`host_or_target`).
+ `host_or_target` is either `host` or `target` (the @depends functions
+ from init.configure.
+ When the language is 'C++', `c_compiler` is the result of the `compiler`
+ template for the language 'C' for the same `host_or_target`.
+ When `host_or_target` is `host`, `other_compiler` is the result of the
+ `compiler` template for the same `language` for `target`.
+ When `host_or_target` is `host` and the language is 'C++',
+ `other_c_compiler` is the result of the `compiler` template for the
+ language 'C' for `target`.
+ """
+ assert host_or_target in {host, target}
+ 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",
+ }[host_or_target]
+
+ var = {
+ ("C", target): "CC",
+ ("C++", target): "CXX",
+ ("C", host): "HOST_CC",
+ ("C++", host): "HOST_CXX",
+ }[language, host_or_target]
+
+ default_compilers = {
+ "C": lambda: default_c_compilers(host_or_target, other_compiler),
+ "C++": lambda: default_cxx_compilers(
+ c_compiler, other_c_compiler, other_compiler
+ ),
+ }[language]()
+
+ what = "the %s %s compiler" % (host_or_target_str, language)
+
+ option(env=var, nargs=1, help="Path to %s" % what)
+
+ # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
+ # HOST_CXX variables.
+ provided_compiler = provided_program(var)
+
+ # Normally, we'd use `var` instead of `_var`, but the interaction with
+ # old-configure complicates things, and for now, we a) can't take the plain
+ # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
+ # old-configure AC_SUBST it (because it's autoconf doing it, not us)
+ compiler = check_prog(
+ "_%s" % var,
+ what=what,
+ progs=default_compilers,
+ input=provided_compiler.program,
+ paths=clang_search_path,
+ )
+
+ @depends(compiler, provided_compiler, compiler_wrapper, host_or_target, macos_sdk)
+ @checking("whether %s can be used" % what, lambda x: bool(x))
+ @imports(_from="mozbuild.shellutil", _import="quote")
+ def valid_compiler(
+ compiler, provided_compiler, compiler_wrapper, host_or_target, macos_sdk
+ ):
+ wrapper = list(compiler_wrapper or ())
+ flags = prepare_flags(host_or_target, macos_sdk)
+ if provided_compiler:
+ provided_wrapper = list(provided_compiler.wrapper)
+ # When doing a subconfigure, the compiler is set by old-configure
+ # and it contains the wrappers from --with-compiler-wrapper and
+ # --with-ccache.
+ if provided_wrapper[: len(wrapper)] == wrapper:
+ provided_wrapper = provided_wrapper[len(wrapper) :]
+ wrapper.extend(provided_wrapper)
+ flags.extend(provided_compiler.flags)
+
+ info = check_compiler(wrapper + [compiler] + flags, language, host_or_target)
+
+ # Check that the additional flags we got are enough to not require any
+ # more flags. If we get an exception, just ignore it; it's liable to be
+ # invalid command-line flags, which means the compiler we're checking
+ # doesn't support those command-line flags and will fail one or more of
+ # the checks below.
+ try:
+ if info.flags:
+ flags += info.flags
+ info = check_compiler(
+ wrapper + [compiler] + flags, language, host_or_target
+ )
+ except FatalCheckError:
+ pass
+
+ if not info.target_cpu or info.target_cpu != host_or_target.cpu:
+ raise FatalCheckError(
+ "%s %s compiler target CPU (%s) does not match --%s CPU (%s)"
+ % (
+ host_or_target_str.capitalize(),
+ language,
+ info.target_cpu or "unknown",
+ host_or_target_str,
+ host_or_target.raw_cpu,
+ )
+ )
+
+ if not info.target_kernel or (info.target_kernel != host_or_target.kernel):
+ raise FatalCheckError(
+ "%s %s compiler target kernel (%s) does not match --%s kernel (%s)"
+ % (
+ host_or_target_str.capitalize(),
+ language,
+ info.target_kernel or "unknown",
+ host_or_target_str,
+ host_or_target.kernel,
+ )
+ )
+
+ if not info.target_endianness or (
+ info.target_endianness != host_or_target.endianness
+ ):
+ raise FatalCheckError(
+ "%s %s compiler target endianness (%s) does not match --%s "
+ "endianness (%s)"
+ % (
+ host_or_target_str.capitalize(),
+ language,
+ info.target_endianness or "unknown",
+ host_or_target_str,
+ host_or_target.endianness,
+ )
+ )
+
+ # Compiler version checks
+ # ===================================================
+ # Check the compiler version here instead of in `compiler_version` so
+ # that the `checking` message doesn't pretend the compiler can be used
+ # to then bail out one line later.
+ if info.type == "gcc":
+ if host_or_target.os == "Android":
+ raise FatalCheckError(
+ "GCC is not supported on Android.\n"
+ "Please use clang from the Android NDK instead."
+ )
+ gcc_version = minimum_gcc_version()
+ if info.version < gcc_version:
+ raise FatalCheckError(
+ "Only GCC %d.%d or newer is supported (found version %s)."
+ % (gcc_version.major, gcc_version.minor, info.version)
+ )
+
+ if info.type == "clang-cl":
+ if info.version < "8.0.0":
+ raise FatalCheckError(
+ "Only clang-cl 8.0 or newer is supported (found version %s)"
+ % info.version
+ )
+
+ # If you want to bump the version check here ensure the version
+ # is known for Xcode in get_compiler_info.
+ if info.type == "clang" and info.version < "5.0":
+ raise FatalCheckError(
+ "Only clang/llvm 5.0 or newer is supported (found version %s)."
+ % info.version
+ )
+
+ if 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",
+ }[host_or_target]
+
+ @deprecated_option(env=linker_var, nargs=1)
+ def linker(value):
+ if value:
+ return value[0]
+
+ @depends(linker)
+ def unused_linker(linker):
+ if linker:
+ log.warning(
+ "The value of %s is not used by this build system." % linker_var
+ )
+
+ return valid_compiler
+
+
+c_compiler = compiler("C", target)
+cxx_compiler = compiler("C++", target, c_compiler=c_compiler)
+host_c_compiler = compiler("C", host, other_compiler=c_compiler)
+host_cxx_compiler = compiler(
+ "C++",
+ host,
+ c_compiler=host_c_compiler,
+ other_compiler=cxx_compiler,
+ other_c_compiler=c_compiler,
+)
+
+# Generic compiler-based conditions.
+building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc")
+
+
+@depends(cxx_compiler, ccache_prefix)
+@imports("os")
+def cxx_is_icecream(info, ccache_prefix):
+ if (
+ os.path.islink(info.compiler)
+ and os.path.basename(os.readlink(info.compiler)) == "icecc"
+ ):
+ return True
+ if ccache_prefix and os.path.basename(ccache_prefix) == "icecc":
+ return True
+
+
+set_config("CXX_IS_ICECREAM", cxx_is_icecream)
+
+
+@depends(c_compiler)
+def msvs_version(info):
+ # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to
+ # be set for GYP on Windows.
+ if info.type == "clang-cl":
+ return "2017"
+
+ return ""
+
+
+set_config("MSVS_VERSION", msvs_version)
+
+include("compile-checks.configure")
+include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm"))
+
+
+@depends(host, host_os_kernel_major_version, target)
+def needs_macos_sdk_headers_check(host, version, target):
+ # Only an issue on Mac OS X 10.14 (and probably above).
+ if host.kernel != "Darwin" or target.kernel != "Darwin" or version < "18":
+ return
+
+ return True
+
+
+@depends(
+ cxx_compiler.try_run(
+ header="#include_next <inttypes.h>",
+ check_msg="for macOS SDK headers",
+ when=needs_macos_sdk_headers_check,
+ ),
+ when=needs_macos_sdk_headers_check,
+)
+def check_have_mac_10_14_sdk(value):
+ if value:
+ return
+
+ die(
+ "System inttypes.h not found. Please try running "
+ "`open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg` "
+ "and following the instructions to install the necessary headers"
+ )
+
+
+@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"
+ return "-g"
+
+
+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)
+def color_cflags(info):
+ # 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 == "clang":
+ 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, check_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.
+
+
+@depends(target, host)
+def is_windows(target, host):
+ return host.kernel == "WINNT" or target.kernel == "WINNT"
+
+
+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",
+ "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 = []
+ js_flags = []
+ js_ldflags = []
+
+ # ----------------------------------------------------------
+ # 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:
+ # Don't enable FORTIFY_SOURCE on Android on the top-level, but do enable in js/
+ if target.os != "Android":
+ flags.append("-U_FORTIFY_SOURCE")
+ flags.append("-D_FORTIFY_SOURCE=2")
+ js_flags.append("-U_FORTIFY_SOURCE")
+ js_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")
+ js_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")
+ js_flags.append("-fstack-protector-strong")
+ js_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")
+ js_flags.append("-fstack-clash-protection")
+ js_ldflags.append("-fstack-clash-protection")
+
+ # ftrivial-auto-var-init ------------------------------
+ # Initialize local variables with a 0xAA pattern in clang debug 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 debug
+ and not linux32
+ ):
+ if c_compiler.type == "clang-cl":
+ flags.append("-Xclang")
+ js_flags.append("-Xclang")
+ flags.append("-ftrivial-auto-var-init=pattern")
+ js_flags.append("-ftrivial-auto-var-init=pattern")
+
+ # 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")
+ js_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")
+ js_flags.append("-guard:cf,nochecks")
+ else:
+ flags.append("-guard:cf")
+ js_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")
+ js_ldflags.append("-guard:cf,nolongjmp")
+
+ # ----------------------------------------------------------
+ # If ASAN _is_ on, undefine FORTIFY_SOURCE just to be safe
+ if asan:
+ flags.append("-U_FORTIFY_SOURCE")
+ js_flags.append("-U_FORTIFY_SOURCE")
+
+ # fno-common -----------------------------------------
+ # Do not merge variables for ASAN; can detect some subtle bugs
+ if asan:
+ # clang-cl does not recognize the flag, it must be passed down to clang
+ if c_compiler.type == "clang-cl":
+ flags.append("-Xclang")
+ flags.append("-fno-common")
+
+ return namespace(
+ flags=flags,
+ ldflags=ldflags,
+ js_flags=js_flags,
+ js_ldflags=js_ldflags,
+ )
+
+
+set_config("MOZ_HARDENING_CFLAGS", security_hardening_cflags.flags)
+set_config("MOZ_HARDENING_LDFLAGS", security_hardening_cflags.ldflags)
+set_config("MOZ_HARDENING_CFLAGS_JS", security_hardening_cflags.js_flags)
+set_config("MOZ_HARDENING_LDFLAGS_JS", security_hardening_cflags.js_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"))
+ )
+
+
+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)
+
+
+# nasm detection
+# ==============================================================
+nasm = check_prog(
+ "NASM", ["nasm"], allow_missing=True, paths=bootstrap_search_path("nasm")
+)
+
+
+@depends_if(nasm)
+@checking("nasm version")
+def nasm_version(nasm):
+ (retcode, stdout, _) = get_cmd_output(nasm, "-v")
+ if retcode:
+ # mac stub binary
+ return None
+
+ version = stdout.splitlines()[0].split()[2]
+ return Version(version)
+
+
+@depends_if(nasm_version)
+def nasm_major_version(nasm_version):
+ return str(nasm_version.major)
+
+
+@depends_if(nasm_version)
+def nasm_minor_version(nasm_version):
+ return str(nasm_version.minor)
+
+
+set_config("NASM_MAJOR_VERSION", nasm_major_version)
+set_config("NASM_MINOR_VERSION", nasm_minor_version)
+
+
+@depends(nasm, target)
+def nasm_asflags(nasm, target):
+ if nasm:
+ asflags = {
+ ("OSX", "x86"): ["-f", "macho32"],
+ ("OSX", "x86_64"): ["-f", "macho64"],
+ ("WINNT", "x86"): ["-f", "win32"],
+ ("WINNT", "x86_64"): ["-f", "win64"],
+ }.get((target.os, target.cpu), None)
+ if asflags is None:
+ # We're assuming every x86 platform we support that's
+ # not Windows or Mac is ELF.
+ if target.cpu == "x86":
+ asflags = ["-f", "elf32"]
+ elif target.cpu == "x86_64":
+ asflags = ["-f", "elf64"]
+ return asflags
+
+
+set_config("NASM_ASFLAGS", nasm_asflags)
+
+
+@depends(nasm_asflags)
+def have_nasm(value):
+ if value:
+ return True
+
+
+@depends(yasm_asflags)
+def have_yasm(yasm_asflags):
+ if yasm_asflags:
+ return True
+
+
+set_config("HAVE_NASM", have_nasm)
+
+set_config("HAVE_YASM", have_yasm)
+# Until the YASM variable is not necessary in old-configure.
+add_old_configure_assignment("YASM", have_yasm)
+
+
+# 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, vc_path, check_build_environment, when=code_coverage)
+@imports("os")
+@imports("re")
+@imports(_from="__builtin__", _import="open")
+def coverage_cflags(target, c_compiler, vc_path, 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",
+ ]
+
+ if target.os == "WINNT" and c_compiler.type == "clang-cl":
+ # The Visual Studio directory is the parent of the Visual C++ directory.
+ vs_path = os.path.dirname(vc_path)
+
+ # We need to get the real path of Visual Studio, which can be in a
+ # symlinked directory (for example, on automation).
+ vs_path = os.path.realpath(vs_path)
+
+ cflags += [
+ "-fprofile-exclude-files=^{}.*$".format(re.escape(vs_path)),
+ ]
+
+ 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)
+
+# ==============================================================
+
+option(env="RUSTFLAGS", nargs=1, help="Rust compiler flags")
+set_config("RUSTFLAGS", depends("RUSTFLAGS")(lambda flags: flags))
+
+
+# Rust compiler flags
+# ==============================================================
+
+option(
+ env="RUSTC_OPT_LEVEL",
+ nargs=1,
+ help="Rust compiler optimization level (-C opt-level=%s)",
+)
+
+# --enable-release kicks in full optimizations.
+imply_option("RUSTC_OPT_LEVEL", "2", when="--enable-release")
+
+
+@depends("RUSTC_OPT_LEVEL", moz_optimize)
+def rustc_opt_level(opt_level_option, moz_optimize):
+ if opt_level_option:
+ return opt_level_option[0]
+ else:
+ return "1" if moz_optimize.optimize else "0"
+
+
+@depends(
+ rustc_opt_level,
+ debug_rust,
+ target,
+ "--enable-debug-symbols",
+ "--enable-frame-pointers",
+)
+def rust_compile_flags(opt_level, debug_rust, target, debug_symbols, frame_pointers):
+ # 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 opt_level is not None:
+ opts.append("opt-level=%s" % opt_level)
+ 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])
+
+ return flags
+
+
+# Rust incremental compilation
+# ==============================================================
+
+
+option("--disable-cargo-incremental", help="Disable incremental rust compilation.")
+
+
+@depends(
+ rustc_opt_level,
+ debug_rust,
+ "MOZ_AUTOMATION",
+ code_coverage,
+ "--disable-cargo-incremental",
+ using_sccache,
+ "RUSTC_WRAPPER",
+)
+@imports("os")
+def cargo_incremental(
+ opt_level,
+ debug_rust,
+ automation,
+ code_coverage,
+ enabled,
+ using_sccache,
+ rustc_wrapper,
+):
+ """Return a value for the CARGO_INCREMENTAL environment variable."""
+
+ if not enabled:
+ return "0"
+
+ # 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
+
+ # --enable-release automatically sets -O2 for Rust code, and people can
+ # set RUSTC_OPT_LEVEL to 2 or even 3 if they want to profile Rust code.
+ # Let's assume that if Rust code is using -O2 or higher, we shouldn't
+ # be using incremental compilation, because we'd be imposing a
+ # significant runtime cost.
+ if opt_level not in ("0", "1"):
+ return
+
+ # We're clear to use incremental compilation!
+ return "1"
+
+
+set_config("CARGO_INCREMENTAL", cargo_incremental)
+
+# 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 ld64, either from XCode on macOS, or from cctools-ports when
+# cross-compiling. lld can be enabled manually, but as of writing, mach-o support
+# for lld is incomplete.
+# 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 "ld" resolves to is used, except on Android x86/x86_64
+# where BFD ld is used. Usually, "ld" resolves to BFD ld, except with the Android NDK,
+# where it resolves to gold. 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.
+@depends(target)
+def is_linker_option_enabled(target):
+ if target.kernel not in ("WINNT", "SunOS"):
+ return True
+
+
+option(
+ "--enable-gold",
+ env="MOZ_FORCE_GOLD",
+ help="Enable GNU Gold Linker when it is not already the default",
+ when=is_linker_option_enabled,
+)
+
+imply_option("--enable-linker", "gold", when="--enable-gold")
+
+
+@depends(target, developer_options)
+def enable_linker_default(target, developer_options):
+ # 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.
+ # lld is faster, so prefer that for developer builds.
+ if target.os == "Android" and target.cpu in ("x86", "x86_64"):
+ return "lld" if developer_options else "bfd"
+
+
+option(
+ "--enable-linker",
+ nargs=1,
+ help="Select the linker {bfd, gold, ld64, lld, lld-*}{|}",
+ 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
+
+
+@depends(
+ "--enable-linker",
+ c_compiler,
+ developer_options,
+ "--enable-gold",
+ extra_toolchain_flags,
+ target,
+ when=is_linker_option_enabled,
+)
+@checking("for linker", lambda x: x.KIND)
+@imports("os")
+@imports("shutil")
+def select_linker(
+ linker, c_compiler, developer_options, enable_gold, 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")
+ 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 "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"
+
+ return namespace(
+ KIND=kind,
+ LINKER_FLAG=linker_flag,
+ )
+
+ result = try_linker(linker)
+ if result is None:
+ if linker:
+ die("Could not use {} as linker".format(linker))
+ die("Failed to find a linker")
+
+ if (
+ linker is None
+ and enable_gold.origin == "default"
+ and developer_options
+ and result.KIND in ("bfd", "gold")
+ ):
+ # try and use lld if available.
+ tried = try_linker("lld")
+ if 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 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
+
+
+set_config("LINKER_KIND", select_linker.KIND)
+
+
+@depends_if(select_linker, macos_sdk)
+def linker_ldflags(linker, macos_sdk):
+ flags = list((linker and linker.LINKER_FLAG) or [])
+ if macos_sdk:
+ if linker and linker.KIND == "ld64":
+ flags.append("-Wl,-syslibroot,%s" % macos_sdk)
+ else:
+ flags.append("-Wl,--sysroot=%s" % macos_sdk)
+
+ return flags
+
+
+add_old_configure_assignment("LINKER_LDFLAGS", linker_ldflags)
+
+
+# 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:
+ return select_linker.KIND in ("bfd", "gold", "lld")
+ 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)
+
+# 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
+
+
+add_old_configure_assignment("AS", as_with_flags)
+add_old_configure_assignment("ac_cv_prog_AS", as_with_flags)
+
+
+@depends(assembler, c_compiler, extra_toolchain_flags)
+@imports("subprocess")
+@imports(_from="os", _import="devnull")
+def gnu_as(assembler, c_compiler, toolchain_flags):
+ # clang uses a compatible GNU assembler.
+ if c_compiler.type == "clang":
+ return True
+
+ if c_compiler.type == "gcc":
+ cmd = [assembler] + c_compiler.flags
+ if toolchain_flags:
+ cmd += toolchain_flags
+ cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"]
+ # We don't actually have to provide any input on stdin, `Popen.communicate` will
+ # close the stdin pipe.
+ # clang will error if it uses its integrated assembler for this target,
+ # so handle failures gracefully.
+ if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""):
+ return True
+
+
+set_config("GNU_AS", gnu_as)
+add_old_configure_assignment("GNU_AS", gnu_as)
+
+
+@depends(as_info, target)
+def as_dash_c_flag(as_info, target):
+ # armasm64 doesn't understand -c.
+ if as_info.type == "masm" and target.cpu == "aarch64":
+ return ""
+ else:
+ return "-c"
+
+
+set_config("AS_DASH_C_FLAG", as_dash_c_flag)
+
+
+@depends(as_info, target)
+def as_outoption(as_info, target):
+ # The uses of ASOUTOPTION depend on the spacing for -o/-Fo.
+ if as_info.type == "masm" and target.cpu != "aarch64":
+ return "-Fo"
+
+ return "-o "
+
+
+set_config("ASOUTOPTION", as_outoption)
+
+# clang plugin handling
+# ==============================================================
+
+option(
+ "--enable-clang-plugin",
+ env="ENABLE_CLANG_PLUGIN",
+ help="Enable building with the Clang plugin (gecko specific static analyzers)",
+)
+
+add_old_configure_assignment(
+ "ENABLE_CLANG_PLUGIN", depends_if("--enable-clang-plugin")(lambda _: True)
+)
+
+
+@depends(host_c_compiler, c_compiler, when="--enable-clang-plugin")
+def llvm_config(host_c_compiler, c_compiler):
+ clang = None
+ for compiler in (host_c_compiler, c_compiler):
+ if compiler and compiler.type == "clang":
+ clang = compiler.compiler
+ break
+ elif compiler and compiler.type == "clang-cl":
+ clang = os.path.join(os.path.dirname(compiler.compiler), "clang")
+ break
+
+ if not clang:
+ die("Cannot --enable-clang-plugin when not building with clang")
+ llvm_config = "llvm-config"
+ out = check_cmd_output(clang, "--print-prog-name=llvm-config", onerror=lambda: None)
+ if out:
+ llvm_config = out.rstrip()
+ return (llvm_config,)
+
+
+llvm_config = check_prog(
+ "LLVM_CONFIG",
+ llvm_config,
+ what="llvm-config",
+ when="--enable-clang-plugin",
+ paths=clang_search_path,
+)
+
+add_old_configure_assignment("LLVM_CONFIG", llvm_config)
+
+
+option(
+ "--enable-clang-plugin-alpha",
+ env="ENABLE_CLANG_PLUGIN_ALPHA",
+ help="Enable static analysis with clang-plugin alpha checks.",
+)
+
+
+@depends("--enable-clang-plugin", "--enable-clang-plugin-alpha")
+def check_clang_plugin_alpha(enable_clang_plugin, enable_clang_plugin_alpha):
+ if enable_clang_plugin_alpha:
+ if enable_clang_plugin:
+ return True
+ die("Cannot enable clang-plugin alpha checkers without --enable-clang-plugin.")
+
+
+add_old_configure_assignment("ENABLE_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha)
+set_define("MOZ_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha)
+
+option(
+ "--enable-mozsearch-plugin",
+ env="ENABLE_MOZSEARCH_PLUGIN",
+ help="Enable building with the mozsearch indexer plugin",
+)
+
+add_old_configure_assignment(
+ "ENABLE_MOZSEARCH_PLUGIN", depends_if("--enable-mozsearch-plugin")(lambda _: True)
+)
+
+# Libstdc++ compatibility hacks
+# ==============================================================
+#
+option(
+ "--enable-stdcxx-compat",
+ env="MOZ_STDCXX_COMPAT",
+ help="Enable compatibility with older libstdc++",
+)
+
+
+@template
+def libstdcxx_version(var, compiler):
+ @depends(compiler, when="--enable-stdcxx-compat")
+ @checking(var, lambda v: v and "GLIBCXX_%s" % v.dotted)
+ @imports(_from="mozbuild.configure.libstdcxx", _import="find_version")
+ @imports(_from="__builtin__", _import="Exception")
+ def version(compiler):
+ try:
+ result = find_version(
+ compiler.wrapper + [compiler.compiler] + compiler.flags
+ )
+ except Exception:
+ die("Couldn't determine libstdc++ version")
+ if result:
+ return namespace(
+ dotted=result[0],
+ encoded=str(result[1]),
+ )
+
+ set_config(var, version.encoded)
+ return version
+
+
+add_gcc_flag(
+ "-D_GLIBCXX_USE_CXX11_ABI=0",
+ cxx_compiler,
+ when=libstdcxx_version("MOZ_LIBSTDCXX_TARGET_VERSION", cxx_compiler),
+)
+add_gcc_flag(
+ "-D_GLIBCXX_USE_CXX11_ABI=0",
+ host_cxx_compiler,
+ when=libstdcxx_version("MOZ_LIBSTDCXX_HOST_VERSION", host_cxx_compiler),
+)
+
+
+# Support various fuzzing options
+# ==============================================================
+option("--enable-fuzzing", help="Enable fuzzing support")
+
+
+@depends("--enable-fuzzing")
+def enable_fuzzing(value):
+ if value:
+ return True
+
+
+@depends(
+ try_compile(
+ body="__AFL_COMPILER;", check_msg="for AFL compiler", when="--enable-fuzzing"
+ )
+)
+def enable_aflfuzzer(afl):
+ if afl:
+ return True
+
+
+@depends(enable_fuzzing, enable_aflfuzzer, c_compiler, target)
+def enable_libfuzzer(fuzzing, afl, c_compiler, target):
+ if fuzzing and not afl and c_compiler.type == "clang" and target.os != "Android":
+ return True
+
+
+@depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer)
+def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer):
+ if fuzzing and (afl or libfuzzer):
+ 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("FUZZING_INTERFACES", enable_fuzzing_interfaces)
+set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces)
+add_old_configure_assignment("FUZZING_INTERFACES", enable_fuzzing_interfaces)
+
+
+@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,
+)
+def libfuzzer_flags(value, tsan):
+ 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 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_PIC_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)
+
+
+@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", "$@"),
+ )
+
+
+ar = check_prog("AR", ar_config.names, paths=clang_search_path)
+
+add_old_configure_assignment("AR", ar)
+
+set_config("AR_FLAGS", ar_config.flags)
+
+
+@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_is_linux)
+
+
+option("--enable-cpp-rtti", help="Enable C++ RTTI")
+
+add_old_configure_assignment("_MOZ_USE_RTTI", "1", when="--enable-cpp-rtti")
diff --git a/build/moz.configure/update-programs.configure b/build/moz.configure/update-programs.configure
new file mode 100644
index 0000000000..d5a75b9ac8
--- /dev/null
+++ b/build/moz.configure/update-programs.configure
@@ -0,0 +1,83 @@
+# -*- 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/.
+
+# Verify MAR signatures
+# ==============================================================
+
+option("--disable-verify-mar", help="Disable verifying MAR signatures")
+
+set_define(
+ "MOZ_VERIFY_MAR_SIGNATURE", depends_if("--enable-verify-mar")(lambda _: True)
+)
+set_config(
+ "MOZ_VERIFY_MAR_SIGNATURE", depends_if("--enable-verify-mar")(lambda _: True)
+)
+
+# Maintenance service (Windows only)
+# ==============================================================
+
+option(
+ "--enable-maintenance-service",
+ when=target_is_windows,
+ default=target_is_windows,
+ 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),
+)
+
+# Update agent (currently Windows only)
+# This is an independent task that runs on a schedule to
+# check for, download, and install updates.
+# ==============================================================
+
+option(
+ "--enable-update-agent",
+ when=target_is_windows,
+ default=False,
+ help="{Enable|Disable} building update agent",
+)
+
+set_define(
+ "MOZ_UPDATE_AGENT",
+ depends_if("--enable-update-agent", when=target_is_windows)(lambda _: True),
+)
+
+set_config(
+ "MOZ_UPDATE_AGENT",
+ depends_if("--enable-update-agent", when=target_is_windows)(lambda _: True),
+)
+
+# Enable or disable the default browser agent, which monitors the user's default
+# browser setting on Windows.
+# ==============================================================================
+
+
+@depends(target)
+def default_browser_agent_default(target):
+ return target.os == "WINNT"
+
+
+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)
diff --git a/build/moz.configure/util.configure b/build/moz.configure/util.configure
new file mode 100644
index 0000000000..fe82698c62
--- /dev/null
+++ b/build/moz.configure/util.configure
@@ -0,0 +1,494 @@
+# -*- 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("six")
+@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",
+ **kwargs
+ )
+ stdout, stderr = proc.communicate()
+ # Normally we would set the `encoding` and `errors` arguments in the
+ # constructor to subprocess.Popen, but those arguments were added in 3.6
+ # and we need to support back to 3.5, so instead we need to do this
+ # nonsense.
+ stdout = six.ensure_text(
+ stdout, encoding=system_encoding, errors="replace"
+ ).replace("\r\n", "\n")
+ stderr = six.ensure_text(
+ stderr, encoding=system_encoding, errors="replace"
+ ).replace("\r\n", "\n")
+ 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):
+ # 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):
+ path = which(os.path.basename(file), path=os.path.dirname(file), exts=exts)
+ return normalize_path(path) if path else None
+
+ 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)
+
+ path = which(file, path=paths, exts=exts)
+ return normalize_path(path) if path else None
+
+
+@imports("os")
+@imports(_from="mozbuild.configure.util", _import="LineIO")
+@imports(_from="six", _import="ensure_binary")
+@imports(_from="tempfile", _import="mkstemp")
+def try_invoke_compiler(compiler, language, source, flags=None, onerror=None):
+ flags = flags or []
+
+ 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, ensure_binary(source))
+ os.close(fd)
+ cmd = compiler + [path] + list(flags)
+ kwargs = {"onerror": onerror}
+ return check_cmd_output(*cmd, **kwargs)
+ 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)
+
+ def decorator(func):
+ @depends(opt.option)
+ def deprecated(value):
+ if value.origin != "default":
+ return func(value)
+
+ return deprecated
+
+ return decorator
+
+
+# from mozbuild.util import ReadOnlyNamespace as namespace
+@imports(_from="mozbuild.util", _import="ReadOnlyNamespace")
+def namespace(**kwargs):
+ return ReadOnlyNamespace(**kwargs)
+
+
+# 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="inspect", _import="isfunction")
+@imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
+def dependable(obj):
+ if isinstance(obj, SandboxDependsFunction):
+ return obj
+ if isfunction(obj):
+ return depends(when=True)(obj)
+ # Depend on --help to make lint happy if the dependable is used as an input
+ # to an option().
+ return depends("--help", when=True)(lambda _: 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 100755
index 0000000000..d0db70f6d3
--- /dev/null
+++ b/build/moz.configure/warnings.configure
@@ -0,0 +1,253 @@
+# -*- 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 rust_warning_flags(warnings_as_errors):
+ 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:
+ flags.append("-Dwarnings")
+ else:
+ flags.extend(("--cap-lints", "warn"))
+
+ return flags
+
+
+c_warning_flag = dependable("-Werror")
+
+
+@depends("--enable-warnings-as-errors", c_warning_flag)
+def warnings_as_errors(warnings_as_errors, c_warning_flag):
+ if not warnings_as_errors:
+ return ""
+
+ return c_warning_flag
+
+
+set_config("WARNINGS_AS_ERRORS", warnings_as_errors)
+# We have a peculiar setup in old-configure.in where some compilation tests
+# depend on enabling warnings-as-errors even if it's disabled for Firefox
+# compilation. We therefore need this assignment.
+add_old_configure_assignment("WARNINGS_AS_ERRORS", c_warning_flag)
+
+
+# GCC/Clang warnings:
+# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
+# https://clang.llvm.org/docs/DiagnosticsReference.html
+
+# lots of useful warnings
+add_gcc_warning("-Wall")
+
+# catch implicit truncation of enum values assigned to smaller bit fields
+check_and_add_gcc_warning("-Wbitfield-enum-conversion")
+
+# catches bugs, e.g. "if (c); foo();", few false positives
+add_gcc_warning("-Wempty-body")
+
+# catches return types with qualifiers like const
+add_gcc_warning("-Wignored-qualifiers")
+
+# function declaration hides virtual function from base class.
+# Don't enable for GCC, since it's more strict than clang,
+# and the additional cases it covers are not valuable.
+add_gcc_warning(
+ "-Woverloaded-virtual",
+ cxx_compiler,
+ when=depends(cxx_compiler)(lambda c: c.type != "gcc"),
+)
+
+# catches pointer arithmetic using NULL or sizeof(void)
+add_gcc_warning("-Wpointer-arith")
+
+# catch modifying constructor parameter that shadows member variable
+check_and_add_gcc_warning("-Wshadow-field-in-constructor-modified")
+
+# catches comparing signed/unsigned ints
+add_gcc_warning("-Wsign-compare")
+
+# catches overflow bugs, few false positives
+add_gcc_warning("-Wtype-limits")
+
+# catches some dead code
+add_gcc_warning("-Wunreachable-code")
+check_and_add_gcc_warning("-Wunreachable-code-return")
+
+# catches treating string literals as non-const
+add_gcc_warning("-Wwrite-strings", cxx_compiler)
+
+# turned on by -Wall, but we use offsetof on non-POD types frequently
+add_gcc_warning("-Wno-invalid-offsetof", cxx_compiler)
+
+# catches objects passed by value to variadic functions.
+check_and_add_gcc_warning("-Wclass-varargs")
+
+# catches empty if/switch/for initialization statements that have no effect
+check_and_add_gcc_warning("-Wempty-init-stmt", cxx_compiler)
+
+# catches some implicit conversion of floats to ints
+check_and_add_gcc_warning("-Wfloat-overflow-conversion")
+check_and_add_gcc_warning("-Wfloat-zero-conversion")
+
+# catches issues around loops
+check_and_add_gcc_warning("-Wloop-analysis")
+# But, disable range-loop-analysis because it can raise unhelpful false
+# positives.
+check_and_add_gcc_warning("-Wno-range-loop-analysis")
+
+# catches C++ version forward-compat issues
+check_and_add_gcc_warning("-Wc++2a-compat", cxx_compiler)
+
+# catches possible misuse of the comma operator
+check_and_add_gcc_warning("-Wcomma", cxx_compiler)
+
+# catches duplicated conditions in if-else-if chains
+check_and_add_gcc_warning("-Wduplicated-cond")
+
+# catches unintentional switch case fallthroughs
+check_and_add_gcc_warning("-Wimplicit-fallthrough", cxx_compiler)
+
+# catches unused variable/function declarations
+check_and_add_gcc_warning("-Wunused-function", cxx_compiler)
+check_and_add_gcc_warning("-Wunused-variable", cxx_compiler)
+
+# catches expressions used as a null pointer constant
+# XXX: at the time of writing, the version of clang used on the OS X test
+# machines has a bug that causes it to reject some valid files if both
+# -Wnon-literal-null-conversion and -Wsometimes-uninitialized are
+# specified. We work around this by instead using
+# -Werror=non-literal-null-conversion, but we only do that when
+# --enable-warnings-as-errors is specified so that no unexpected fatal
+# warnings are produced.
+check_and_add_gcc_warning(
+ "-Werror=non-literal-null-conversion", when="--enable-warnings-as-errors"
+)
+
+# catches string literals used in boolean expressions
+check_and_add_gcc_warning("-Wstring-conversion")
+
+# catches comparisons that are always true or false
+check_and_add_gcc_warning("-Wtautological-overlap-compare")
+check_and_add_gcc_warning("-Wtautological-unsigned-enum-zero-compare")
+check_and_add_gcc_warning("-Wtautological-unsigned-zero-compare")
+# This can be triggered by certain patterns used deliberately in portable code
+check_and_add_gcc_warning("-Wno-error=tautological-type-limit-compare")
+
+# we inline 'new' and 'delete' in mozalloc
+check_and_add_gcc_warning("-Wno-inline-new-delete", cxx_compiler)
+
+# Prevent the following GCC warnings from being treated as errors:
+# too many false positives
+check_and_add_gcc_warning("-Wno-error=maybe-uninitialized")
+
+# we don't want our builds held hostage when a platform-specific API
+# becomes deprecated.
+check_and_add_gcc_warning("-Wno-error=deprecated-declarations")
+
+# false positives depending on optimization
+check_and_add_gcc_warning("-Wno-error=array-bounds")
+
+# can't get rid of those PGO warnings
+check_and_add_gcc_warning("-Wno-error=coverage-mismatch")
+
+# -Wbackend-plugin warnings from Android PGO profile-use builds:
+# error: /builds/worker/workspace/build/src/mozglue/misc/AutoProfilerLabel.cpp:
+# Function control flow change detected (hash mismatch)
+# _ZN7mozilla17AutoProfilerLabelD2Ev [-Werror,-Wbackend-plugin]
+check_and_add_gcc_warning("-Wno-error=backend-plugin")
+
+# false positives depending on optimizations
+check_and_add_gcc_warning("-Wno-error=free-nonheap-object")
+
+# Would be a pain to fix all occurrences, for very little gain
+check_and_add_gcc_warning("-Wno-multistatement-macros")
+
+# Disable the -Werror for return-std-move because of a false positive
+# on nsTAutoStringN: https://bugs.llvm.org/show_bug.cgi?id=37249
+check_and_add_gcc_warning("-Wno-error=return-std-move")
+
+# Disable the -Werror for -Wclass-memaccess as we have a long
+# tail of issues to fix
+check_and_add_gcc_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_gcc_warning("-Wno-error=atomic-alignment")
+
+# New warning with gcc 9. Not useful
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1515356
+check_and_add_gcc_warning("-Wno-error=deprecated-copy")
+
+# catches format/argument mismatches with printf
+c_format_warning, cxx_format_warning = check_and_add_gcc_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_gcc_warning(
+ "-Wformat-security", when=c_format_warning & cxx_format_warning
+)
+check_and_add_gcc_warning(
+ "-Wformat-overflow=2", when=c_format_warning & cxx_format_warning
+)
+
+# Other MinGW specific things
+with only_when(depends(target)(lambda t: t.kernel == "WINNT")):
+ # When compiling for Windows with gcc, we encounter lots of "#pragma warning"'s
+ # which is an MSVC-only pragma that GCC does not recognize.
+ check_and_add_gcc_warning("-Wno-unknown-pragmas")
+
+ # When compiling for Windows with gcc, gcc throws false positives and true
+ # positives where the callsite is ifdef-ed out
+ check_and_add_gcc_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_gcc_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_gcc_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_gcc_warning("-Wno-enum-compare")
+
+# We hit this all over the place with the gtest INSTANTIATE_TEST_CASE_P macro
+check_and_add_gcc_warning("-Wno-gnu-zero-variadic-macro-arguments")
+
+# Make it an error to be missing function declarations for C code.
+check_and_add_gcc_warning("-Werror=implicit-function-declaration", c_compiler)
+
+# New in clang 11. We can't really do anything about this warning.
+check_and_add_gcc_warning("-Wno-psabi")
+
+# Disable broken missing-braces warning on old clang versions
+check_and_add_gcc_warning(
+ "-Wno-missing-braces",
+ when=depends(c_compiler)(lambda c: c.type == "clang" and c.version < "6.0"),
+)
+
+
+# Please keep these last in this file
+add_old_configure_assignment("_WARNINGS_CFLAGS", warnings_flags.cflags)
+add_old_configure_assignment("_WARNINGS_CXXFLAGS", warnings_flags.cxxflags)
+add_old_configure_assignment("_WARNINGS_HOST_CFLAGS", warnings_flags.host_cflags)
+add_old_configure_assignment("_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..568046c2e5
--- /dev/null
+++ b/build/moz.configure/windows.configure
@@ -0,0 +1,535 @@
+# -*- 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", host, c_compiler)
+def windows_sdk_dir(value, host, compiler):
+ if value:
+ return value
+ # Ideally, we'd actually check for host/target ABI being MSVC, but
+ # that's waiting for bug 1617793.
+ if host.kernel != "WINNT" or compiler.type != "clang-cl":
+ return ()
+
+ return set(
+ 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,
+ )
+ )
+
+
+# The Windows SDK 8.1 and 10 have different layouts. The former has
+# $SDK/include/$subdir, while the latter has $SDK/include/$version/$subdir.
+# The vcvars* scripts don't actually care about the version, they just take
+# the last alphanumerically.
+# The $SDK/lib directories always have version subdirectories, but while the
+# versions match the one in $SDK/include for SDK 10, it's "winv6.3" for SDK
+# 8.1.
+
+
+@imports("os")
+@imports("re")
+@imports(_from="__builtin__", _import="sorted")
+@imports(_from="__builtin__", _import="Exception")
+def get_sdk_dirs(sdk, subdir):
+ def get_dirs_containing(sdk, stem, subdir):
+ base = os.path.join(sdk, stem)
+ try:
+ subdirs = [
+ d for d in os.listdir(base) if os.path.isdir(os.path.join(base, d))
+ ]
+ except Exception:
+ subdirs = []
+ if not subdirs:
+ return ()
+ if subdir in subdirs:
+ return (base,)
+ # At this point, either we have an incomplete or invalid SDK directory,
+ # or we exclusively have version numbers in subdirs.
+ return tuple(
+ os.path.join(base, s)
+ for s in subdirs
+ if os.path.isdir(os.path.join(base, s, 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))
+
+ if "include" in include_dirs:
+ include_dirs["winv6.3"] = include_dirs["include"]
+ del include_dirs["include"]
+
+ valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True)
+ if valid_versions:
+ return namespace(
+ path=sdk,
+ lib=lib_dirs[valid_versions[0]],
+ include=include_dirs[valid_versions[0]],
+ )
+
+
+@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(c_compiler, windows_sdk_dir, valid_windows_version, "WINDOWSSDKDIR")
+@checking("for Windows SDK", valid_windows_sdk_dir_result)
+@imports(_from="__builtin__", _import="sorted")
+@imports(_from="__builtin__", _import="Exception")
+@imports(_from="textwrap", _import="dedent")
+def valid_windows_sdk_dir(
+ compiler, windows_sdk_dir, target_version, windows_sdk_dir_env
+):
+ # Ideally, we'd actually check for host/target ABI being MSVC, but
+ # that's waiting for bug 1617793.
+ if compiler.type != "clang-cl":
+ return None
+ if windows_sdk_dir_env:
+ windows_sdk_dir_env = windows_sdk_dir_env[0]
+ sdks = {}
+ for d in windows_sdk_dir:
+ sdk = get_sdk_dirs(d, "um")
+ if sdk:
+ check = dedent(
+ """\
+ #include <winsdkver.h>
+ WINVER_MAXVER
+ """
+ )
+ um_dir = os.path.join(sdk.include, "um")
+ shared_dir = os.path.join(sdk.include, "shared")
+ result = try_preprocess(
+ compiler.wrapper
+ + [compiler.compiler]
+ + compiler.flags
+ + ["-X", "-I", um_dir, "-I", shared_dir],
+ "C",
+ check,
+ onerror=lambda: "",
+ )
+ if result:
+ maxver = result.splitlines()[-1]
+ try:
+ maxver = int(maxver, 0)
+ except Exception:
+ pass
+ else:
+ sdks[d] = maxver, sdk
+ continue
+ if d == windows_sdk_dir_env:
+ 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", c_compiler)
+@checking("for Universal CRT SDK", valid_ucrt_sdk_dir_result)
+@imports("os")
+@imports(_from="__builtin__", _import="sorted")
+@imports(_import="mozpack.path", _as="mozpath")
+def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env, compiler):
+ # Ideally, we'd actually check for host/target ABI being MSVC, but
+ # that's waiting for bug 1617793.
+ if compiler.type != "clang-cl":
+ return None
+ if windows_sdk_dir_env:
+ windows_sdk_dir_env = windows_sdk_dir_env[0]
+ sdks = {}
+ for d in windows_sdk_dir:
+ sdk = get_sdk_dirs(d, "ucrt")
+ if sdk:
+ 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
+ continue
+ if d == windows_sdk_dir_env:
+ # 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(c_compiler, host_c_compiler, vc_toolchain_search_path)
+@imports("os")
+def vc_path(c_compiler, host_c_compiler, vc_toolchain_search_path):
+ if c_compiler.type != "clang-cl" and host_c_compiler.type != "clang-cl":
+ 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)
+
+
+option(env="DIA_SDK_PATH", nargs=1, help="Path to the Debug Interface Access SDK")
+
+
+@depends(vc_path, "DIA_SDK_PATH")
+@checking("for the Debug Interface Access SDK", lambda x: x or "not found")
+@imports("os")
+def dia_sdk_dir(vc_path, dia_sdk_path):
+ if dia_sdk_path:
+ path = os.path.normpath(dia_sdk_path[0])
+
+ elif vc_path:
+ # This would be easier if we had the installationPath that
+ # get_vc_paths works with, since 'DIA SDK' is relative to that.
+ path = os.path.normpath(
+ os.path.join(vc_path, "..", "..", "..", "..", "DIA SDK")
+ )
+ else:
+ return
+
+ if os.path.exists(os.path.join(path, "include", "dia2.h")):
+ return path
+
+
+@depends(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
+@imports("os")
+def include_path(vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_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
+ )
+
+ 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,
+ os.path.join(ucrt_sdk_dir.include, "ucrt"),
+ )
+ )
+ if dia_sdk_dir:
+ includes.append(os.path.join(dia_sdk_dir, "include"))
+ # Set in the environment for old-configure
+ includes = ";".join(includes)
+ os.environ["INCLUDE"] = includes
+ return includes
+
+
+set_config("INCLUDE", include_path)
+
+
+@template
+def dia_sdk_subdir(host_or_target, subdir):
+ @depends(dia_sdk_dir, host_or_target, dependable(subdir))
+ def dia_sdk_subdir(dia_sdk_dir, target, subdir):
+ if not dia_sdk_dir:
+ return
+ # For some reason the DIA SDK still uses the old-style targets
+ # even in a newer MSVC.
+ old_target = {
+ "x86": "",
+ "x86_64": "amd64",
+ "arm": "arm",
+ "aarch64": "arm64",
+ }.get(target.cpu)
+ if old_target is None:
+ return
+ # As old_target can be '', and os.path.join will happily use the empty
+ # string, leading to a string ending with a backslash, that Make will
+ # interpret as a "string continues on next line" indicator, use variable
+ # args.
+ old_target = (old_target,) if old_target else ()
+ return os.path.join(dia_sdk_dir, subdir, *old_target)
+
+ return dia_sdk_subdir
+
+
+set_config("WIN_DIA_SDK_BIN_DIR", dia_sdk_subdir(host, "bin"))
+
+
+@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,
+ dia_sdk_subdir(host_or_target, "lib"),
+ )
+ @imports("os")
+ def lib_path(
+ target, is_host, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_lib_dir
+ ):
+ 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 dia_sdk_lib_dir:
+ libs.append(dia_sdk_lib_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)