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