diff options
Diffstat (limited to 'build/moz.configure')
25 files changed, 9435 insertions, 0 deletions
diff --git a/build/moz.configure/android-ndk.configure b/build/moz.configure/android-ndk.configure new file mode 100644 index 0000000000..51683d8d33 --- /dev/null +++ b/build/moz.configure/android-ndk.configure @@ -0,0 +1,407 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@depends(toolchains_base_dir, "--help") +@imports(_from="os.path", _import="isdir") +@imports(_from="mozboot.android", _import="NDK_VERSION") +def default_android_ndk_root(toolchains_base_dir, _): + for ndk in ("android-ndk-%s" % NDK_VERSION, "android-ndk"): + path = os.path.join(toolchains_base_dir, ndk) + if isdir(path): + return path + + +option( + "--with-android-ndk", + nargs=1, + default=default_android_ndk_root, + help="location where the Android NDK can be found{|}", +) + +option("--with-android-toolchain", nargs=1, help="location of the Android toolchain") + +option( + "--with-android-googlevr-sdk", nargs=1, help="location of the Android GoogleVR SDK" +) + + +@depends(target) +def min_android_version(target): + if target.cpu in ["aarch64", "x86_64"]: + # 64-bit support was added in API 21. + return "21" + return "16" + + +option( + "--with-android-version", + nargs=1, + help="android platform version{|}", + default=min_android_version, +) + + +@depends("--with-android-version", min_android_version) +@imports(_from="__builtin__", _import="ValueError") +def android_version(value, min_version): + if not value: + # Someone has passed --without-android-version. + die("--with-android-version cannot be disabled.") + + try: + version = int(value[0]) + except ValueError: + die("--with-android-version expects an integer value") + + if version < int(min_version): + die( + "--with-android-version must be at least %s (got %s)", min_version, value[0] + ) + + return version + + +add_old_configure_assignment("android_version", android_version) + + +@depends("--with-android-ndk") +@imports(_from="os.path", _import="isdir") +def ndk(value): + if value: + if not isdir(value[0]): + die( + "The path you specified with --with-android-ndk (%s) is not " + "a directory" % value[0] + ) + return value[0] + + die( + "You must specify --with-android-ndk=/path/to/ndk when targeting Android, " + "or try |mach bootstrap|." + ) + + +set_config("ANDROID_NDK", ndk) +add_old_configure_assignment("android_ndk", ndk) + + +@depends(ndk) +@checking("for android ndk version") +@imports(_from="__builtin__", _import="open") +@imports(_from="mozboot.android", _import="NDK_VERSION") +@imports(_from="mozboot.android", _import="get_ndk_version") +@imports(_from="mozboot.android", _import="GetNdkVersionError") +def ndk_version(ndk): + if not ndk: + # Building 'js/src' for non-Android. + return + + try: + major, minor, human = get_ndk_version(ndk) + except GetNdkVersionError as e: + die(str(e)) + + if NDK_VERSION != human: + die( + "The only supported version of the NDK is %s (have %s)\n" + "Please run |mach bootstrap| " + "to install the correct NDK." % (NDK_VERSION, human) + ) + return namespace( + major=major, + minor=minor, + ) + + +set_config("ANDROID_NDK_MAJOR_VERSION", ndk_version.major) +set_config("ANDROID_NDK_MINOR_VERSION", ndk_version.minor) + + +@depends(target, android_version, ndk) +@checking("for android platform directory") +@imports(_from="os.path", _import="isdir") +def android_platform(target, android_version, ndk): + if target.os != "Android": + return + + if "aarch64" == target.cpu: + target_dir_name = "arm64" + else: + target_dir_name = target.cpu + + # Not all Android releases have their own platform release. We use + # the next lower platform version in these cases. + if android_version in (11, 10): + platform_version = 9 + elif android_version in (20, 22): + platform_version = android_version - 1 + else: + platform_version = android_version + + platform_dir = os.path.join( + ndk, "platforms", "android-%s" % platform_version, "arch-%s" % target_dir_name + ) + + if not isdir(platform_dir): + die( + "Android platform directory not found. With the current " + "configuration, it should be in %s" % platform_dir + ) + + return platform_dir + + +add_old_configure_assignment("android_platform", android_platform) +set_config("ANDROID_PLATFORM", android_platform) + + +@depends(android_platform, ndk, target) +@checking("for android sysroot directory") +@imports(_from="os.path", _import="isdir") +def android_sysroot(android_platform, ndk, target): + if target.os != "Android": + return + + # NDK r15 has both unified and non-unified headers, but we only support + # non-unified for that NDK, so look for that first. + search_dirs = [ + # (<if this directory exists>, <return this directory>) + (os.path.join(android_platform, "usr", "include"), android_platform), + (os.path.join(ndk, "sysroot"), os.path.join(ndk, "sysroot")), + ] + + for test_dir, sysroot_dir in search_dirs: + if isdir(test_dir): + return sysroot_dir + + die( + "Android sysroot directory not found in %s." + % str([sysroot_dir for test_dir, sysroot_dir in search_dirs]) + ) + + +add_old_configure_assignment("android_sysroot", android_sysroot) + + +@depends(android_platform, ndk, target) +@checking("for android system directory") +@imports(_from="os.path", _import="isdir") +def android_system(android_platform, ndk, target): + if target.os != "Android": + return + + # NDK r15 has both unified and non-unified headers, but we only support + # non-unified for that NDK, so look for that first. + search_dirs = [ + os.path.join(android_platform, "usr", "include"), + os.path.join(ndk, "sysroot", "usr", "include", target.toolchain), + ] + + for system_dir in search_dirs: + if isdir(system_dir): + return system_dir + + die("Android system directory not found in %s." % str(search_dirs)) + + +add_old_configure_assignment("android_system", android_system) + + +@depends(target, host, ndk, "--with-android-toolchain") +@checking("for the Android toolchain directory", lambda x: x or "not found") +@imports(_from="os.path", _import="isdir") +@imports(_from="mozbuild.shellutil", _import="quote") +def android_toolchain(target, host, ndk, toolchain): + if not ndk: + return + if toolchain: + return toolchain[0] + else: + if target.cpu == "arm" and target.endianness == "little": + target_base = "arm-linux-androideabi" + elif target.cpu == "x86": + target_base = "x86" + elif target.cpu == "x86_64": + target_base = "x86_64" + elif target.cpu == "aarch64" and target.endianness == "little": + target_base = "aarch64-linux-android" + else: + die("Target cpu is not supported.") + + toolchain_format = "%s/toolchains/%s-4.9/prebuilt/%s-%s" + host_kernel = "windows" if host.kernel == "WINNT" else host.kernel.lower() + + toolchain = toolchain_format % (ndk, target_base, host_kernel, host.cpu) + log.debug("Trying %s" % quote(toolchain)) + if not isdir(toolchain) and host.cpu == "x86_64": + toolchain = toolchain_format % (ndk, target_base, host_kernel, "x86") + log.debug("Trying %s" % quote(toolchain)) + if isdir(toolchain): + return toolchain + die("You have to specify --with-android-toolchain=" "/path/to/ndk/toolchain.") + + +set_config("ANDROID_TOOLCHAIN", android_toolchain) + + +@depends(target) +def android_toolchain_prefix_base(target): + if target.cpu == "x86": + # Ideally, the --target should just have the right x86 variant + # in the first place. + return "i686-linux-android" + return target.toolchain + + +option( + env="STLPORT_CPPFLAGS", + nargs=1, + help="Options compiler should pass for standard C++ library", +) + + +@depends("STLPORT_CPPFLAGS", ndk) +@imports(_from="os.path", _import="isdir") +def stlport_cppflags(value, ndk): + if value and len(value): + return value.split() + if not ndk: + return + + ndk_base = os.path.join(ndk, "sources", "cxx-stl") + cxx_base = os.path.join(ndk_base, "llvm-libc++") + cxx_include = os.path.join(cxx_base, "libcxx", "include") + cxxabi_base = os.path.join(ndk_base, "llvm-libc++abi") + cxxabi_include = os.path.join(cxxabi_base, "libcxxabi", "include") + + if not isdir(cxx_include): + # NDK r13 removes the inner "libcxx" directory. + cxx_include = os.path.join(cxx_base, "include") + if not isdir(cxx_include): + die("Couldn't find path to libc++ includes in the android ndk") + + if not isdir(cxxabi_include): + # NDK r13 removes the inner "libcxxabi" directory. + cxxabi_include = os.path.join(cxxabi_base, "include") + if not isdir(cxxabi_include): + die("Couldn't find path to libc++abi includes in the android ndk") + + # Add android/support/include/ for prototyping long double math + # functions, locale-specific C library functions, multibyte support, + # etc. + return [ + # You'd think we'd want to use -stdlib=libc++, but this doesn't work + # (cf. https://bugzilla.mozilla.org/show_bug.cgi?id=1510897#c2) + # Using -stdlib=libc++ and removing some of the -I below also doesn't + # work because not everything that is in cxx_include comes in the C++ + # header directory that comes with clang. + "-stdlib=libstdc++", + "-I%s" % cxx_include, + "-I%s" % os.path.join(ndk, "sources", "android", "support", "include"), + "-I%s" % cxxabi_include, + ] + + +add_old_configure_assignment("stlport_cppflags", stlport_cppflags) + + +@depends(android_system, android_sysroot, android_toolchain, android_version) +def extra_toolchain_flags( + android_system, android_sysroot, toolchain_dir, android_version +): + if not android_sysroot: + return [] + flags = [ + "-isystem", + android_system, + "-isystem", + os.path.join(android_sysroot, "usr", "include"), + "-gcc-toolchain", + toolchain_dir, + "-D__ANDROID_API__=%d" % android_version, + ] + return flags + + +@depends(android_toolchain_prefix_base, android_toolchain) +def android_toolchain_prefix(prefix_base, toolchain): + if toolchain: + return "%s/bin/%s-" % (toolchain, prefix_base) + + +imply_option( + "--with-toolchain-prefix", android_toolchain_prefix, reason="--with-android-ndk" +) + + +@depends( + extra_toolchain_flags, + stlport_cppflags, + android_toolchain, + android_toolchain_prefix_base, +) +@imports(_from="os.path", _import="isdir") +def bindgen_cflags_android(toolchain_flags, stlport_flags, toolchain, toolchain_prefix): + if not toolchain_flags: + return + + gcc_include = os.path.join(toolchain, "lib", "gcc", toolchain_prefix, "4.9.x") + if not isdir(gcc_include): + gcc_include = os.path.join(toolchain, "lib", "gcc", toolchain_prefix, "4.9") + + return ( + toolchain_flags + + stlport_flags + + [ + "-I%s" % os.path.join(gcc_include, "include"), + "-I%s" % os.path.join(gcc_include, "include-fixed"), + ] + ) + + +@depends("--with-android-googlevr-sdk", target) +@checking("for GoogleVR SDK", lambda x: x.result) +@imports(_from="os.path", _import="exists") +@imports(_from="os.path", _import="abspath") +def googlevr_sdk(value, target): + if not value: + return namespace(result="Not specified") + path = abspath(value[0]) + if not exists(path): + die("Could not find GoogleVR SDK %s", path) + include = "%s/libraries/headers/" % path + if "arm" == target.cpu: + arch = "armeabi-v7a" + elif "aarch64" == target.cpu: + arch = "arm64-v8a" + elif "x86" == target.cpu: + arch = "x86" + else: + die("Unsupported GoogleVR cpu architecture %s" % target.cpu) + + libs = "{0}/libraries/jni/{1}/".format(path, arch) + + if not exists(libs): + die( + "Could not find GoogleVR NDK at %s. Did you try running " + "'./gradlew :extractNdk' in %s?", + libs, + path, + ) + + return namespace( + result=path, + include=include, + libs=libs, + enabled=True, + ) + + +set_define("MOZ_ANDROID_GOOGLE_VR", googlevr_sdk.enabled) +set_config("MOZ_ANDROID_GOOGLE_VR", googlevr_sdk.enabled) +set_config("MOZ_ANDROID_GOOGLE_VR_INCLUDE", googlevr_sdk.include) +set_config("MOZ_ANDROID_GOOGLE_VR_LIBS", googlevr_sdk.libs) diff --git a/build/moz.configure/android-sdk.configure b/build/moz.configure/android-sdk.configure new file mode 100644 index 0000000000..129c11525a --- /dev/null +++ b/build/moz.configure/android-sdk.configure @@ -0,0 +1,132 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Ensure Android SDK and build-tools versions depending on mobile target. + + +@depends(host, toolchains_base_dir, "--help") +@imports(_from="os.path", _import="isdir") +def default_android_sdk_root(host, toolchains_base_dir, _): + sdk_basename = { + "Darwin": "android-sdk-macosx", + "Linux": "android-sdk-linux", + "WINNT": "android-sdk-windows", + }.get(host.kernel, "android-sdk") + for sdk_basename in (sdk_basename, "android-sdk"): + path = os.path.join(toolchains_base_dir, sdk_basename) + if isdir(path): + return path + + +option( + "--with-android-sdk", + nargs=1, + default=default_android_sdk_root, + help="location where the Android SDK can be found (like ~/.mozbuild/android-sdk-linux){|}", +) + + +@depends("--with-android-sdk") +@imports(_from="os.path", _import="isdir") +def android_sdk_root(value): + if value: + if not isdir(value[0]): + die( + "The path you specified with --with-android-sdk (%s) is not " + "a directory" % value[0] + ) + return value[0] + + die( + "You must specify --with-android-sdk=/path/to/sdk when targeting Android, " + "or try |mach bootstrap|." + ) + + +@depends("--help") +def android_sdk_version(_): + return namespace(build_tools_version="29.0.3", target_sdk_version="29") + + +@depends(android_sdk_root, android_sdk_version) +@checking("for Android build-tools") +@imports(_from="os.path", _import="exists") +@imports(_from="os.path", _import="isdir") +def android_build_tools(sdk_root, sdk_version): + android_build_tools_base = os.path.join(sdk_root, "build-tools") + version = sdk_version.build_tools_version + if isdir(os.path.join(android_build_tools_base, version)): + tools = os.path.join(android_build_tools_base, version) + for zipalign in ("zipalign", "zipalign.exe"): + if exists(os.path.join(tools, zipalign)): + return [tools] + + die( + "You must install the Android build-tools version %s. " + "Try |mach bootstrap|. (Looked for %s/%s)" + % (version, android_build_tools_base, version) + ) + + +@depends(android_sdk_root) +@checking("for Android tools") +@imports(_from="os.path", _import="isdir") +def android_tools(sdk_root): + tools = os.path.join(sdk_root, "tools") + if isdir(tools): + return tools + + die("You must install the Android tools. Try |mach bootstrap|") + + +@depends(android_sdk_root) +@checking("for Android platform-tools") +@imports(_from="os.path", _import="exists") +@imports(_from="os.path", _import="isdir") +def android_platform_tools(sdk_root): + tools = os.path.join(sdk_root, "platform-tools") + for adb in ("adb", "adb.exe"): + if exists(os.path.join(tools, adb)): + return [tools] + + die( + "You must install the Android platform-tools. Try |mach bootstrap|. (Looked for %s)" + % tools + ) + + +@depends(android_sdk_root) +def android_emulator_path(sdk_root): + return [os.path.join(sdk_root, "emulator")] + + +@template +def check_android_tools(tool, tool_dir): + check = check_prog( + tool.upper(), (tool, tool + ".exe"), paths=tool_dir, allow_missing=True + ) + + @depends(check) + def require_tool(result): + if result is None: + die("The program %s was not found. Try |mach bootstrap|" % tool) + return result + + return require_tool + + +check_android_tools("zipalign", android_build_tools) +check_android_tools("adb", android_platform_tools) +check_android_tools("emulator", android_emulator_path) + +set_config("ANDROID_SDK_ROOT", android_sdk_root) +set_config("ANDROID_TOOLS", android_tools) + +set_config("ANDROID_BUILD_TOOLS_VERSION", android_sdk_version.build_tools_version) +set_config("ANDROID_TARGET_SDK", android_sdk_version.target_sdk_version) +add_old_configure_assignment( + "ANDROID_TARGET_SDK", android_sdk_version.target_sdk_version +) diff --git a/build/moz.configure/arm.configure b/build/moz.configure/arm.configure new file mode 100644 index 0000000000..2082fa640f --- /dev/null +++ b/build/moz.configure/arm.configure @@ -0,0 +1,292 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@depends(target.os, "--help") +def arm_option_defaults(os, _): + if os == "Android": + arch = "armv7-a" + thumb = "yes" + fpu = "neon" + float_abi = "softfp" + else: + arch = thumb = fpu = float_abi = "toolchain-default" + return namespace( + arch=arch, + thumb=thumb, + fpu=fpu, + float_abi=float_abi, + ) + + +# Note: '{...|}' in the help of all options with a non-constant default to +# make the lint happy. The first arm is always going to be used, because a +# default is always returned. The lint is fooled by this file being +# conditional. If it weren't conditional, the lint wouldn't ask for '{|}' to +# be there. +option( + "--with-arch", + nargs=1, + default=arm_option_defaults.arch, + help="{Use specific CPU features (-march=type). Resets thumb, fpu, " + "float-abi, etc. defaults when set|}", +) + + +@depends("--with-arch") +def arch_option(value): + if value: + if value[0] != "toolchain-default": + return ["-march={}".format(value[0])] + return [] + + +option( + "--with-thumb", + choices=("yes", "no", "toolchain-default"), + default=arm_option_defaults.thumb, + nargs="?", + help="{Use Thumb instruction set (-mthumb)|}", +) + + +def normalize_arm_option(value): + if value: + if len(value): + if value[0] == "yes": + return True + elif value[0] == "no": + return False + else: + return value[0] + return True + return False + + +@depends("--with-thumb") +def thumb_option(value): + value = normalize_arm_option(value) + if value is True: + return ["-mthumb"] + if value is False: + return ["-marm"] + return [] + + +option( + "--with-thumb-interwork", + choices=("yes", "no", "toolchain-default"), + default="toolchain-default", + nargs="?", + help="Use Thumb/ARM instuctions interwork (-mthumb-interwork)", +) + + +@depends("--with-thumb-interwork") +def thumb_interwork_option(value): + value = normalize_arm_option(value) + if value is True: + return ["-mthumb-interwork"] + if value is False: + return ["-mno-thumb-interwork"] + return [] + + +option( + "--with-fpu", + nargs=1, + default=arm_option_defaults.fpu, + help="{Use specific FPU type (-mfpu=type)|}", +) + + +@depends("--with-fpu") +def fpu_option(value): + if value: + if value[0] != "toolchain-default": + return ["-mfpu={}".format(value[0])] + return [] + + +option( + "--with-float-abi", + nargs=1, + default=arm_option_defaults.float_abi, + help="{Use specific arm float ABI (-mfloat-abi=type)|}", +) + + +@depends("--with-float-abi") +def float_abi_option(value): + if value: + if value[0] != "toolchain-default": + return ["-mfloat-abi={}".format(value[0])] + return [] + + +option( + "--with-soft-float", + choices=("yes", "no", "toolchain-default"), + default="toolchain-default", + nargs="?", + help="Use soft float library (-msoft-float)", +) + + +@depends("--with-soft-float") +def soft_float_option(value): + value = normalize_arm_option(value) + if value is True: + return ["-msoft-float"] + if value is False: + return ["-mno-soft-float"] + return [] + + +check_and_add_gcc_flag( + "-mno-unaligned-access", when=depends(target.os)(lambda os: os == "Android") +) + + +@depends( + arch_option, + thumb_option, + thumb_interwork_option, + fpu_option, + float_abi_option, + soft_float_option, +) +def all_flags(arch, thumb, interwork, fpu, float_abi, soft_float): + return arch + thumb + interwork + fpu + float_abi + soft_float + + +add_old_configure_assignment("_ARM_FLAGS", all_flags) +add_old_configure_assignment("_THUMB_FLAGS", thumb_option) + + +@depends(c_compiler, all_flags) +@checking("ARM version support in compiler", lambda x: x.arm_arch) +@imports(_from="textwrap", _import="dedent") +def arm_target(compiler, all_flags): + # We're going to preprocess the following source to figure out some details + # about the arm target options we have enabled. + source = dedent( + """\ + %ARM_ARCH __ARM_ARCH + #if __thumb2__ + %THUMB2 yes + #else + %THUMB2 no + #endif + // Confusingly, the __SOFTFP__ preprocessor variable indicates the + // "softfloat" ABI, not the "softfp" ABI. + #if __SOFTFP__ + %FLOAT_ABI soft + #elif __ARM_PCS_VFP + %FLOAT_ABI hard + #else + %FLOAT_ABI softfp + #endif + // There is more subtlety to it than this preprocessor test, but MOZ_FPU doesn't + // need to be too fine-grained. + #if __ARM_NEON + %FPU neon + #elif __ARM_VFPV2__ || __ARM_FP == 12 + %FPU vfpv2 + #elif __ARM_VFPV3__ + %FPU vfpv3 + #elif __ARM_VFPV4__ || __ARM_FP == 14 + %FPU vfpv4 + #elif __ARM_FPV5__ + %FPU fp-armv8 + #endif + """ + ) + result = try_invoke_compiler( + compiler.wrapper + [compiler.compiler] + compiler.flags, + compiler.language, + source, + ["-E"] + all_flags, + ) + # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may + # have non-ASCII characters. Treat the output as bytearray. + data = {"fpu": None} # fpu may not get a value from the preprocessor. + for line in result.splitlines(): + if line.startswith("%"): + k, _, v = line.partition(" ") + k = k.lstrip("%").lower() + if k == "arm_arch": + data[k] = int(v) + else: + data[k] = { + "yes": True, + "no": False, + }.get(v, v) + log.debug("%s = %s", k, data[k]) + + return namespace(**data) + + +@depends(arm_target.arm_arch, when=depends(target.os)(lambda os: os == "Android")) +def armv7(arch): + if arch < 7: + die("Android/armv6 and earlier are not supported") + + +set_config("MOZ_THUMB2", True, when=arm_target.thumb2) +set_define("MOZ_THUMB2", True, when=arm_target.thumb2) +add_old_configure_assignment("MOZ_THUMB2", True, when=arm_target.thumb2) + + +have_arm_simd = c_compiler.try_compile( + body='asm("uqadd8 r1, r1, r2");', check_msg="for ARM SIMD support in compiler" +) + +set_config("HAVE_ARM_SIMD", have_arm_simd) +set_define("HAVE_ARM_SIMD", have_arm_simd) + +have_arm_neon = c_compiler.try_compile( + body='asm(".fpu neon\\n vadd.i8 d0, d0, d0");', + check_msg="for ARM NEON support in compiler", +) + +set_config("HAVE_ARM_NEON", have_arm_neon) +set_define("HAVE_ARM_NEON", have_arm_neon) + + +# We don't need to build NEON support if we're targetting a non-NEON device. +# This matches media/webrtc/trunk/webrtc/build/common.gypi. +@depends(arm_target.arm_arch, when=have_arm_neon) +def build_arm_neon(arm_arch): + return arm_arch >= 7 + + +set_config("BUILD_ARM_NEON", build_arm_neon) +set_define("BUILD_ARM_NEON", build_arm_neon) + + +set_config("ARM_ARCH", depends(arm_target.arm_arch)(lambda x: str(x))) +add_old_configure_assignment("ARM_ARCH", depends(arm_target.arm_arch)(lambda x: str(x))) +set_config("MOZ_FPU", arm_target.fpu) + + +@depends(arm_target.float_abi) +def neon_flags(float_abi): + # Building with -mfpu=neon requires either the "softfp" or the + # "hardfp" ABI. Depending on the compiler's default target, and the + # CFLAGS, the default ABI might be neither, in which case it is the + # "softfloat" ABI. + # The "softfloat" ABI is binary-compatible with the "softfp" ABI, so + # we can safely mix code built with both ABIs. So, if we detect + # that compiling uses the "softfloat" ABI, force the use of the + # "softfp" ABI instead. + flags = ["-mfpu=neon"] + if float_abi == "soft": + flags.append("-mfloat-abi=softfp") + return tuple(flags) + + +set_config("NEON_FLAGS", neon_flags) diff --git a/build/moz.configure/bindgen.configure b/build/moz.configure/bindgen.configure new file mode 100644 index 0000000000..e5e49d8df8 --- /dev/null +++ b/build/moz.configure/bindgen.configure @@ -0,0 +1,371 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@depends(build_project, "--enable-smoosh") +def cbindgen_is_needed(build_project, js_enable_smoosh): + if build_project != "js": + # cbindgen is needed by the style system build and webrender. + return True + + # cbindgen is needed by SmooshMonkey. + return js_enable_smoosh + + +option(env="CBINDGEN", nargs=1, when=cbindgen_is_needed, help="Path to cbindgen") + + +@imports(_from="textwrap", _import="dedent") +def check_cbindgen_version(cbindgen, fatal=False): + log.debug("trying cbindgen: %s" % cbindgen) + + cbindgen_min_version = Version("0.16.0") + + # cbindgen x.y.z + version = Version(check_cmd_output(cbindgen, "--version").strip().split(" ")[1]) + log.debug("%s has version %s" % (cbindgen, version)) + if version >= cbindgen_min_version: + return True + if not fatal: + return False + + die( + dedent( + """\ + cbindgen version {} is too old. At least version {} is required. + + Please update using 'cargo install cbindgen --force' or running + './mach bootstrap', after removing the existing executable located at + {}. + """.format( + version, cbindgen_min_version, cbindgen + ) + ) + ) + + +@depends_if( + "CBINDGEN", + bootstrap_search_path("cbindgen"), + rust_search_path, + when=cbindgen_is_needed, +) +@checking("for cbindgen") +@imports(_from="textwrap", _import="dedent") +def cbindgen(cbindgen_override, bootstrap_search_path, rust_search_path): + if cbindgen_override: + check_cbindgen_version(cbindgen_override[0], fatal=True) + return cbindgen_override[0] + + candidates = [] + for path in bootstrap_search_path + rust_search_path: + candidate = find_program("cbindgen", [path]) + if not candidate: + continue + if check_cbindgen_version(candidate): + return candidate + candidates.append(candidate) + + if not candidates: + raise FatalCheckError( + dedent( + """\ + Cannot find cbindgen. Please run `mach bootstrap`, + `cargo install cbindgen`, ensure that `cbindgen` is on your PATH, + or point at an executable with `CBINDGEN`. + """ + ) + ) + check_cbindgen_version(candidates[0], fatal=True) + + +set_config("CBINDGEN", cbindgen) + +# Bindgen can use rustfmt to format Rust file, but it's not required. +option(env="RUSTFMT", nargs=1, help="Path to the rustfmt program") + +rustfmt = check_prog( + "RUSTFMT", + ["rustfmt"], + paths=rust_search_path, + input="RUSTFMT", + allow_missing=True, +) + + +option( + "--with-libclang-path", + nargs=1, + help="Absolute path to a directory containing Clang/LLVM libraries for bindgen (version 3.9.x or above)", +) +option( + "--with-clang-path", + nargs=1, + help="Absolute path to a Clang binary for bindgen (version 3.9.x or above)", +) + + +@depends( + "--with-clang-path", + c_compiler, + cxx_compiler, + clang_search_path, + target, + macos_sdk, +) +@checking("for clang for bindgen", lambda x: x.path if x else "not found") +def bindgen_clang_compiler( + clang_path, c_compiler, cxx_compiler, clang_search_path, target, macos_sdk +): + # When the target compiler is clang, use that, including flags. + if cxx_compiler.type == "clang": + if clang_path and clang_path[0] not in ( + c_compiler.compiler, + cxx_compiler.compiler, + ): + die( + "--with-clang-path is not valid when the target compiler is %s", + cxx_compiler.type, + ) + return namespace( + path=cxx_compiler.compiler, + flags=cxx_compiler.flags, + ) + # When the target compiler is clang-cl, use clang in the same directory, + # and figure the right flags to use. + if cxx_compiler.type == "clang-cl": + if clang_path and os.path.dirname(clang_path[0]) != os.path.dirname( + cxx_compiler.compiler + ): + die( + "--with-clang-path must point to clang in the same directory " + "as the target compiler" + ) + if not clang_path: + clang_path = [os.path.join(os.path.dirname(cxx_compiler.compiler), "clang")] + + clang_path = find_program( + clang_path[0] if clang_path else "clang++", clang_search_path + ) + if not clang_path: + return + # Hack before bug 1617793: if the compiler is clang-cl, hack the target + if cxx_compiler.type == "clang-cl": + target = split_triplet("%s-pc-windows-msvc" % target.raw_cpu, allow_msvc=True) + flags = prepare_flags(target, macos_sdk) + info = check_compiler([clang_path] + flags, "C++", target) + return namespace( + path=clang_path, + flags=flags + info.flags, + ) + + +@depends("--with-libclang-path", bindgen_clang_compiler, host_library_name_info, host) +@checking("for libclang for bindgen", lambda x: x if x else "not found") +@imports("glob") +@imports(_from="os", _import="pathsep") +@imports(_from="os.path", _import="split", _as="pathsplit") +@imports("re") +def bindgen_libclang_path(libclang_path, clang, library_name_info, host): + if not clang: + if libclang_path: + die( + "--with-libclang-path is not valid without a clang compiler " + "for bindgen" + ) + return + + # Try to ensure that the clang shared library that bindgen is going + # to look for is actually present. The files that we search for + # mirror the logic in clang-sys/build.rs. + libclang_choices = [] + if host.os == "WINNT": + libclang_choices.append("libclang.dll") + libclang_choices.append( + "%sclang%s" % (library_name_info.dll.prefix, library_name_info.dll.suffix) + ) + if host.kernel == "Linux": + libclang_choices.append("libclang.so.*") + + if host.os == "OpenBSD": + libclang_choices.append("libclang.so.*.*") + + candidates = [] + if not libclang_path: + # Try to find libclang_path based on clang search dirs. + clang_search_dirs = check_cmd_output(clang.path, "-print-search-dirs") + for line in clang_search_dirs.splitlines(): + name, _, value = line.partition(": =") + if host.os == "WINNT" and name == "programs": + # On Windows, libclang.dll is in bin/ rather than lib/, + # so scan the programs search dirs. + # To make matters complicated, clang before version 9 uses `:` + # separate between paths (and `;` in newer versions) + if pathsep in value: + candidates.extend(value.split(pathsep)) + else: + for part in value.split(":"): + # Assume that if previous "candidate" was of length 1, + # it's a drive letter and the current part is the rest of + # the corresponding full path. + if candidates and len(candidates[-1]) == 1: + candidates[-1] += ":" + part + else: + candidates.append(part) + elif host.os != "WINNT" and name == "libraries": + # On other platforms, use the directories from the libraries + # search dirs that looks like $something/clang/$version. + for dir in value.split(pathsep): + dir, version = pathsplit(dir) + if re.match(r"[0-9.]+", version): + dir, name = pathsplit(dir) + if name == "clang": + candidates.append(dir) + else: + candidates.append(libclang_path[0]) + + for dir in candidates: + for pattern in libclang_choices: + log.debug('Trying "%s" in "%s"', pattern, dir) + libs = glob.glob(os.path.join(dir, pattern)) + if libs: + return libs[0] + + +@depends(bindgen_clang_compiler, bindgen_libclang_path, build_project) +def bindgen_config_paths(clang, libclang, build_project): + # XXX: we want this code to be run for both Gecko and JS, but we don't + # necessarily want to force a bindgen/Rust dependency on JS just yet. + # Actually, we don't want to force an error if we're not building the + # browser generally. We therefore whitelist the projects that require + # bindgen facilities at this point and leave it at that. + if build_project in ("browser", "mobile/android"): + if not clang: + die( + "Could not find clang to generate run bindings for C/C++. " + "Please install the necessary packages, run `mach bootstrap`, " + "or use --with-clang-path to give the location of clang." + ) + + if not libclang: + die( + "Could not find libclang to generate rust bindings for C/C++. " + "Please install the necessary packages, run `mach bootstrap`, " + "or use --with-libclang-path to give the path containing it." + ) + + if clang and libclang: + return namespace( + libclang=libclang, + libclang_path=os.path.dirname(libclang), + clang_path=clang.path, + clang_flags=clang.flags, + ) + + +@depends(bindgen_config_paths.libclang, when=bindgen_config_paths) +@checking("that libclang is new enough", lambda s: "yes" if s else "no") +@imports(_from="ctypes", _import="CDLL") +@imports(_from="textwrap", _import="dedent") +def min_libclang_version(libclang): + try: + lib = CDLL(libclang) + # We want at least 5.0. The API we test below is enough for that. + # Just accessing it should throw if not found. + fun = lib.clang_getAddressSpace + return True + except: + die( + dedent( + """\ + The libclang located at {} is too old (need at least 5.0). + + Please make sure to update it or point to a newer libclang using + --with-libclang-path. + """.format( + libclang + ) + ) + ) + return False + + +set_config("MOZ_LIBCLANG_PATH", bindgen_config_paths.libclang_path) +set_config("MOZ_CLANG_PATH", bindgen_config_paths.clang_path) + + +@depends( + target, + target_is_unix, + cxx_compiler, + bindgen_cflags_android, + bindgen_config_paths.clang_flags, +) +def basic_bindgen_cflags(target, is_unix, compiler_info, android_cflags, clang_flags): + args = [ + "-x", + "c++", + "-fno-sized-deallocation", + "-fno-aligned-new", + "-DTRACING=1", + "-DIMPL_LIBXUL", + "-DMOZILLA_INTERNAL_API", + "-DRUST_BINDGEN", + ] + + if is_unix: + args += ["-DOS_POSIX=1"] + + if target.os == "Android": + args += android_cflags + + args += { + "Android": ["-DOS_ANDROID=1"], + "DragonFly": ["-DOS_BSD=1", "-DOS_DRAGONFLY=1"], + "FreeBSD": ["-DOS_BSD=1", "-DOS_FREEBSD=1"], + "GNU": ["-DOS_LINUX=1"], + "NetBSD": ["-DOS_BSD=1", "-DOS_NETBSD=1"], + "OpenBSD": ["-DOS_BSD=1", "-DOS_OPENBSD=1"], + "OSX": ["-DOS_MACOSX=1", "-stdlib=libc++"], + "SunOS": ["-DOS_SOLARIS=1"], + "WINNT": [ + "-DOS_WIN=1", + "-DWIN32=1", + ], + }.get(target.os, []) + + if compiler_info.type == "clang-cl": + args += [ + # To enable the builtin __builtin_offsetof so that CRT wouldn't + # use reinterpret_cast in offsetof() which is not allowed inside + # static_assert(). + "-D_CRT_USE_BUILTIN_OFFSETOF", + # Enable hidden attribute (which is not supported by MSVC and + # thus not enabled by default with a MSVC-compatibile build) + # to exclude hidden symbols from the generated file. + "-DHAVE_VISIBILITY_HIDDEN_ATTRIBUTE=1", + ] + + return args + (clang_flags or []) + + +option( + env="BINDGEN_CFLAGS", + nargs=1, + help="Options bindgen should pass to the C/C++ parser", +) + + +@depends(basic_bindgen_cflags, "BINDGEN_CFLAGS") +@checking("bindgen cflags", lambda s: s if s else "no") +def bindgen_cflags(base_flags, extra_flags): + flags = base_flags + if extra_flags and len(extra_flags): + flags += extra_flags[0].split() + return " ".join(flags) + + +add_old_configure_assignment("_BINDGEN_CFLAGS", bindgen_cflags) diff --git a/build/moz.configure/checks.configure b/build/moz.configure/checks.configure new file mode 100644 index 0000000000..9269addbb8 --- /dev/null +++ b/build/moz.configure/checks.configure @@ -0,0 +1,189 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Templates implementing some generic checks. +# ============================================================== + +# Declare some exceptions. This is cumbersome, but since we shouldn't need a +# lot of them, let's stack them all here. When adding a new one, put it in the +# _declare_exceptions template, and add it to the return statement. Then +# destructure in the assignment below the function declaration. + + +@template +@imports(_from="__builtin__", _import="Exception") +def _declare_exceptions(): + class FatalCheckError(Exception): + """An exception to throw from a function decorated with @checking. + It will result in calling die() with the given message. + Debugging messages emitted from the decorated function will also be + printed out.""" + + return (FatalCheckError,) + + +(FatalCheckError,) = _declare_exceptions() + +del _declare_exceptions + +# Helper to display "checking" messages +# @checking('for foo') +# def foo(): +# return 'foo' +# is equivalent to: +# def foo(): +# log.info('checking for foo... ') +# ret = foo +# log.info(ret) +# return ret +# This can be combined with e.g. @depends: +# @depends(some_option) +# @checking('for something') +# def check(value): +# ... +# An optional callback can be given, that will be used to format the returned +# value when displaying it. + + +@template +def checking(what, callback=None): + def decorator(func): + def wrapped(*args, **kwargs): + log.info("checking %s... ", what) + with log.queue_debug(): + error, ret = None, None + try: + ret = func(*args, **kwargs) + except FatalCheckError as e: + error = str(e) + display_ret = callback(ret) if callback else ret + if display_ret is True: + log.info("yes") + elif display_ret is False or display_ret is None: + log.info("no") + else: + log.info(display_ret) + if error is not None: + die(error) + return ret + + return wrapped + + return decorator + + +# Template to check for programs in $PATH. +# - `var` is the name of the variable that will be set with `set_config` when +# the program is found. +# - `progs` is a list (or tuple) of program names that will be searched for. +# It can also be a reference to a @depends function that returns such a +# list. If the list is empty and there is no input, the check is skipped. +# - `what` is a human readable description of what is being looked for. It +# defaults to the lowercase version of `var`. +# - `input` is a string reference to an existing option or a reference to a +# @depends function resolving to explicit input for the program check. +# The default is to create an option for the environment variable `var`. +# This argument allows to use a different kind of option (possibly using a +# configure flag), or doing some pre-processing with a @depends function. +# - `allow_missing` indicates whether not finding the program is an error. +# - `paths` is a list of paths or @depends function returning a list of paths +# that will cause the given path(s) to be searched rather than $PATH. Input +# paths may either be individual paths or delimited by os.pathsep, to allow +# passing $PATH (for example) as an element. +# - `paths_have_priority` means that any programs found early in the PATH +# will be prioritized over programs found later in the PATH. The default is +# False, meaning that any of the programs earlier in the program list will be +# given priority, no matter where in the PATH they are found. +# +# The simplest form is: +# check_prog('PROG', ('a', 'b')) +# This will look for 'a' or 'b' in $PATH, and set_config PROG to the one +# it can find. If PROG is already set from the environment or command line, +# use that value instead. +@template +@imports(_from="mozbuild.shellutil", _import="quote") +def check_prog( + var, + progs, + what=None, + input=None, + allow_missing=False, + paths=None, + paths_have_priority=False, + when=None, +): + if input is not None: + # Wrap input with type checking and normalization. + @depends(input, when=when) + def input(value): + if not value: + return + if isinstance(value, str): + return (value,) + if isinstance(value, (tuple, list)) and len(value) == 1: + return value + configure_error( + "input must resolve to a tuple or a list with a " + "single element, or a string" + ) + + else: + option( + env=var, + nargs=1, + when=when, + help="Path to %s" % (what or "the %s program" % var.lower()), + ) + input = var + what = what or var.lower() + + # Trick to make a @depends function out of an immediate value. + progs = dependable(progs) + paths = dependable(paths) + allow_missing = dependable(allow_missing) + + # Avoid displaying the "Checking for" message when the inputs are such + # that we don't actually want anything to be checked. It is a bit + # convoluted because of how `when` works. + # We first wrap all the inputs except allow_missing (which doesn't count + # for whether to display the "Checking for" message). + @depends_if(input, progs, paths, when=when) + def inputs(input, progs, paths): + if progs is None: + progs = () + + if not isinstance(progs, (tuple, list)): + configure_error("progs must resolve to a list or tuple!") + + return namespace(value=input, progs=progs, paths=paths) + + @depends(inputs, allow_missing, when=inputs) + @checking("for %s" % what, lambda x: quote(x) if x else "not found") + def check(inputs, allow_missing): + value = inputs.value + progs = inputs.progs + paths = inputs.paths + + if paths_have_priority: + for path in paths: + for prog in value or progs: + log.debug("%s: Looking for %s", var.lower(), quote(prog)) + result = find_program(prog, [path]) + if result: + return result + else: + for prog in value or progs: + log.debug("%s: Looking for %s", var.lower(), quote(prog)) + result = find_program(prog, paths) + if result: + return result + + if not allow_missing or value: + raise FatalCheckError("Cannot find %s" % what) + + set_config(var, check) + + return check diff --git a/build/moz.configure/compile-checks.configure b/build/moz.configure/compile-checks.configure new file mode 100755 index 0000000000..25e4f80bf5 --- /dev/null +++ b/build/moz.configure/compile-checks.configure @@ -0,0 +1,287 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +# Generates a test program and attempts to compile it. In case of failure, the +# resulting check will return None. If the test program succeeds, it will return +# the output of the test program. +# - `includes` are the includes (as file names) that will appear at the top of +# the generated test program. +# - `body` is the code that will appear in the main function of the generated +# test program. `return 0;` is appended to the function body automatically. +# - `language` is the language selection, so that the appropriate compiler is +# used. +# - `flags` are the flags to be passed to the compiler, in addition to `-c`. +# - `check_msg` is the message to be printed to accompany compiling the test +# program. +@template +def try_compile( + includes=None, + body="", + language="C++", + flags=None, + check_msg=None, + when=None, + onerror=lambda: None, +): + compiler = { + "C": c_compiler, + "C++": cxx_compiler, + }[language] + + return compiler.try_compile( + includes, body, flags, check_msg, when=when, onerror=onerror + ) + + +# Checks for the presence of the given header on the target system by compiling +# a test program including that header. The return value of the template is a +# check function returning True if the header is present, and None if it is not. +# The value of this check function is also used to set a variable (with set_define) +# corresponding to the checked header. For instance, HAVE_MALLOC_H will be set in +# defines if check_header if called with 'malloc.h' as input and malloc.h is +# present on the target. +# - `header` is the header, as a file name, to check for. +# - `language` is the language selection, so that the appropriate compiler is +# used. +# - `flags` are the flags to be passed to the compiler, in addition to `-c`. +# - `includes` are additional includes, as file names, to appear before the +# header checked for. +# - `when` is a depends function that if present will make performing the check +# conditional on the value of that function. +@template +def check_header( + header, language="C++", flags=None, includes=None, when=None, onerror=lambda: None +): + if when is None: + when = always + + if includes: + includes = includes[:] + else: + includes = [] + includes.append(header) + + have_header = try_compile( + includes=includes, + language=language, + flags=flags, + check_msg="for %s" % header, + when=when, + onerror=onerror, + ) + header_var = "HAVE_%s" % ( + header.upper().replace("-", "_").replace("/", "_").replace(".", "_") + ) + set_define(header_var, have_header) + return have_header + + +# A convenience wrapper for check_header for checking multiple headers. +# returns an array of the resulting checks in order corresponding to the +# provided headers. +# - `headers` are the headers to be checked. +# - `kwargs` are keyword arguments passed verbatim to check_header. + + +@template +def check_headers(*headers, **kwargs): + checks = [] + for header in headers: + checks.append(check_header(header, **kwargs)) + return checks + + +# Checks for the presence of the given symbol on the target system by compiling +# a test program. The return value of the template is a check function +# returning True if the symbol can be found, and None if it is not. +@template +def check_symbol(symbol, language="C", flags=None, when=None, onerror=lambda: None): + if when is None: + when = always + + compiler = { + "C": c_compiler, + "C++": cxx_compiler, + }[language] + + # Stolen from autoconf 2.13 ; might be irrelevant now, but it doesn't hurt to + # keep using a char return type. + comment = [ + "/* Override any gcc2 internal prototype to avoid an error. */", + "/* We use char because int might match the return type of a gcc2", + " builtin and then its argument prototype would still apply. */", + ] + + return compiler.try_run( + header=comment + ["char %s();" % symbol], + body="%s();" % symbol, + flags=flags, + check_msg="for %s" % symbol, + when=when, + onerror=onerror, + ) + + +# Determine whether to add a given flag to the given lists of flags for C or +# C++ compilation. +# - `flag` is the flag to test +# - `flags_collection` is a @depends function for a namespace of lists of +# C/C++ compiler flags to add to. +# - `test_flags` is a list of flags to pass to the compiler instead of merely +# passing `flag`. This is especially useful for checking warning flags. If +# this list is empty, `flag` will be passed on its own. +# - `compiler` (optional) is the compiler to test against (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, both compilers +# are tested; the list of flags added to is dependent on the compiler tested. +# - `when` (optional) is a @depends function or option name conditioning +# when the warning flag is wanted. +# - `check`, when not set, skips checking whether the flag is supported and +# adds it to the list of flags unconditionally. +@template +def check_and_add_flags( + flag, flags_collection, test_flags, compiler=None, when=None, check=True +): + if compiler is not None: + compilers = (compiler,) + else: + compilers = (c_compiler, cxx_compiler) + + if when is None: + when = always + + results = [] + + if test_flags: + flags = test_flags + else: + flags = [flag] + + for c in compilers: + assert c in {c_compiler, cxx_compiler, host_c_compiler, host_cxx_compiler} + lang, list_of_flags = { + c_compiler: ("C", flags_collection.cflags), + cxx_compiler: ("C++", flags_collection.cxxflags), + host_c_compiler: ("host C", flags_collection.host_cflags), + host_cxx_compiler: ("host C++", flags_collection.host_cxxflags), + }[c] + + @depends(c, when) + def result(c, when): + if when and c.type in ("clang", "gcc"): + return True + + if check: + + @depends(c, dependable(flags)) + def flags(c, flags): + # Don't error out just because clang complains about other things. + if c.type == "clang": + flags += ["-Wno-error=unused-command-line-argument"] + + return flags + + result = c.try_compile( + flags=flags, + when=result, + check_msg="whether the %s compiler supports %s" % (lang, flag), + ) + + @depends(result, list_of_flags) + def maybe_add_flag(result, list_of_flags): + if result: + list_of_flags.append(flag) + + results.append(result) + + return tuple(results) + + +@dependable +def warnings_flags(): + return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[]) + + +# Tests whether GCC or clang support the given warning flag, and if it is, +# add it to the list of warning flags for the build. +# - `warning` is the warning flag (e.g. -Wfoo) +# - `compiler` (optional) is the compiler to test against (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, both compilers +# are tested. +# - `when` (optional) is a @depends function or option name conditioning +# when the warning flag is wanted. +# - `check`, when not set, skips checking whether the flag is supported and +# adds it to the list of warning flags unconditionally. This is only meant +# for add_gcc_warning(). +@template +def check_and_add_gcc_warning(warning, compiler=None, when=None, check=True): + # GCC and clang will fail if given an unknown warning option like + # -Wfoobar. But later versions won't fail if given an unknown negated + # warning option like -Wno-foobar. So when we are checking for support + # of a negated warning option, we actually test the positive form, but + # add the negated form to the flags variable. + if warning.startswith("-Wno-") and not warning.startswith("-Wno-error="): + flags = ["-Werror", "-W" + warning[5:]] + elif warning.startswith("-Werror="): + flags = [warning] + else: + flags = ["-Werror", warning] + + return check_and_add_flags( + warning, warnings_flags, flags, compiler=compiler, when=when, check=check + ) + + +# Add the given warning to the list of warning flags for the build. +# - `warning` is the warning flag (e.g. -Wfoo) +# - `compiler` (optional) is the compiler to add the flag for (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, the warning flag +# is added for both compilers. +# - `when` (optional) is a @depends function or option name conditioning +# when the warning flag is wanted. + + +@template +def add_gcc_warning(warning, compiler=None, when=None): + check_and_add_gcc_warning(warning, compiler, when, check=False) + + +# Like the warning checks above, but for general compilation flags. +@dependable +def compilation_flags(): + return namespace(cflags=[], cxxflags=[], host_cflags=[], host_cxxflags=[]) + + +# Tests whether GCC or clang support the given compilation flag; if the flag +# is supported, add it to the list of compilation flags for the build. +# - `flag` is the flag to test +# - `compiler` (optional) is the compiler to test against (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, both compilers +# are tested. +# - `when` (optional) is a @depends function or option name conditioning +# when the warning flag is wanted. +# - `check`, when not set, skips checking whether the flag is supported and +# adds it to the list of flags unconditionally. This is only meant for +# add_gcc_flag(). +@template +def check_and_add_gcc_flag(flag, compiler=None, when=None, check=True): + flags = ["-Werror", flag] + + return check_and_add_flags( + flag, compilation_flags, flags, compiler=compiler, when=when, check=check + ) + + +# Add the given flag to the list of flags for the build. +# - `flag` is the flag (e.g. -fno-sized-deallocation) +# - `compiler` (optional) is the compiler to add the flag for (c_compiler or +# cxx_compiler, from toolchain.configure). When omitted, the flag is added +# for both compilers. +# - `when` (optional) is a @depends function or option name conditioning +# when the flag is wanted. +@template +def add_gcc_flag(warning, compiler=None, when=None): + check_and_add_gcc_flag(warning, compiler, when, check=False) diff --git a/build/moz.configure/compilers-util.configure b/build/moz.configure/compilers-util.configure new file mode 100644 index 0000000000..1d8930347f --- /dev/null +++ b/build/moz.configure/compilers-util.configure @@ -0,0 +1,135 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@template +@imports("textwrap") +@imports(_from="mozbuild.configure", _import="SandboxDependsFunction") +def compiler_class(compiler, host_or_target): + is_target = host_or_target is target + + class Compiler(SandboxDependsFunction): + # Generates a test program and attempts to compile it. In case of + # failure, the resulting check will return None. If the test program + # succeeds, it will return the output of the test program. + # - `includes` are the includes (as file names) that will appear at the + # top of the generated test program. + # - `body` is the code that will appear in the main function of the + # generated test program. `return 0;` is appended to the function + # body automatically. + # - `flags` are the flags to be passed to the compiler, in addition to + # `-c`. + # - `check_msg` is the message to be printed to accompany compiling the + # test program. + def try_compile( + self, + includes=None, + body="", + flags=None, + check_msg=None, + when=None, + onerror=lambda: None, + ): + @depends(dependable(flags)) + def flags(flags): + flags = list(flags or []) + flags.append("-c") + return flags + + @depends(dependable(includes)) + def header(includes): + includes = includes or [] + return ["#include <%s>" % f for f in includes] + + return self.try_run( + header=header, + body=body, + flags=flags, + check_msg=check_msg, + when=when, + onerror=onerror, + ) + + # Generates a test program and run the compiler against it. In case of + # failure, the resulting check will return None. + # - `header` is code that will appear at the top of the generated test + # program. + # - `body` is the code that will appear in the main function of the + # generated test program. `return 0;` is appended to the function + # body automatically. + # - `flags` are the flags to be passed to the compiler. + # - `check_msg` is the message to be printed to accompany compiling the + # test program. + # - `onerror` is a function called when the check fails. + def try_run( + self, + header=None, + body="", + flags=None, + check_msg=None, + when=None, + onerror=lambda: None, + ): + source = textwrap.dedent( + """\ + int + main(void) + { + %s + ; + return 0; + } + """ + % body + ) + + if check_msg: + + def checking_fn(fn): + return checking(check_msg)(fn) + + else: + + def checking_fn(fn): + return fn + + @depends( + self, + dependable(flags), + extra_toolchain_flags, + stlport_cppflags, + dependable(header), + when=when, + ) + @checking_fn + def func(compiler, flags, extra_flags, stlport_flags, header): + flags = list(flags or []) + if is_target: + flags += extra_flags or [] + if compiler.language == "C++": + flags += stlport_flags or [] + header = header or "" + if isinstance(header, (list, tuple)): + header = "\n".join(header) + if header: + header += "\n" + + if ( + try_invoke_compiler( + compiler.wrapper + [compiler.compiler] + compiler.flags, + compiler.language, + header + source, + flags, + onerror=onerror, + ) + is not None + ): + return True + + return func + + compiler.__class__ = Compiler + return compiler diff --git a/build/moz.configure/flags.configure b/build/moz.configure/flags.configure new file mode 100644 index 0000000000..3cad466237 --- /dev/null +++ b/build/moz.configure/flags.configure @@ -0,0 +1,71 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# We support C++14, but we don't want to enable the sized deallocation +# facilities in C++14 yet. +check_and_add_gcc_flag("-fno-sized-deallocation", compiler=cxx_compiler) +# Likewise for C++17 and aligned allocation. It's not immediately obvious +# from the clang and GCC documentation, but they both support this. +check_and_add_gcc_flag("-fno-aligned-new", compiler=cxx_compiler) + +# Please keep these last in this file. +add_old_configure_assignment("_COMPILATION_CFLAGS", compilation_flags.cflags) +add_old_configure_assignment("_COMPILATION_CXXFLAGS", compilation_flags.cxxflags) +add_old_configure_assignment("_COMPILATION_HOST_CFLAGS", compilation_flags.host_cflags) +add_old_configure_assignment( + "_COMPILATION_HOST_CXXFLAGS", compilation_flags.host_cxxflags +) + + +@depends(rust_compile_flags, rust_warning_flags) +def rust_flags(compile_flags, warning_flags): + return compile_flags + warning_flags + + +set_config("MOZ_RUST_DEFAULT_FLAGS", rust_flags) + + +option( + "--disable-new-pass-manager", + help="Use the legacy LLVM pass manager in clang builds", +) + + +@depends( + "--enable-new-pass-manager", + c_compiler, + host, + target, + "MOZ_PGO", + enable_fuzzing, + ubsan, +) +def new_pass_manager_flags(enabled, compiler, host, target, pgo, enable_fuzzing, ubsan): + if host.os == "OSX": + # Some native Mac builds hang with the new pass manager. Given the + # inability to test in CI, don't take the risk of further breakage. + return None + if target.os == "OSX" and not pgo: + # Also disable when cross-compiling to Mac, because plain-ish opt + # builds hang. Variants like asan and ccov work fine, but it would be + # too tedious to test them all here. PGO is the only thing that matters + # enough to make an exception for. + return None + if enable_fuzzing and compiler.version < "10.0.0": + # Clang 9 does not seem to play well with libFuzzer + return None + if ubsan and compiler.version >= "10.0.0": + # Temporary until https://bugs.llvm.org/show_bug.cgi?id=45835 gets a + # real fix: clang 10 hangs with some ubsan-inserted code constructs. + return None + if enabled and compiler.version >= "9.0.0": + if compiler.type == "clang": + return ["-fexperimental-new-pass-manager"] + elif compiler.type == "clang-cl": + return ["-Xclang", "-fexperimental-new-pass-manager"] + + +set_config("MOZ_NEW_PASS_MANAGER_FLAGS", new_pass_manager_flags) diff --git a/build/moz.configure/headers.configure b/build/moz.configure/headers.configure new file mode 100644 index 0000000000..5332c7365f --- /dev/null +++ b/build/moz.configure/headers.configure @@ -0,0 +1,119 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Check for headers defining standard int types. +check_header("stdint.h") +have_inttypes = check_header("inttypes.h") + +# Assume we have ansi C header files available. +set_define("STDC_HEADERS", True) + +set_config("HAVE_INTTYPES_H", have_inttypes) + +building_linux = depends(target)(lambda target: target.kernel == "Linux") + +have_malloc = check_header("malloc.h") + +check_header("alloca.h") + +add_old_configure_assignment("HAVE_MALLOC_H", have_malloc) + +check_headers( + "sys/byteorder.h", + "getopt.h", + "unistd.h", + "nl_types.h", + "cpuid.h", + "fts.h", +) + +# These are all the places some variant of statfs can be hiding. +check_headers( + "sys/statvfs.h", + "sys/statfs.h", + "sys/vfs.h", + "sys/mount.h", +) + +# Quota support +# Check for both the header and quotactl() because Android headers can have the +# header but not quotactl(). +set_define( + "HAVE_SYS_QUOTA_H", + try_compile( + includes=["sys/quota.h"], + body="quotactl(0, nullptr, 0, (caddr_t)nullptr);", + check_msg="for sys/quota.h", + ), +) +check_header("linux/quota.h", includes=["sys/socket.h"], when=building_linux) + +# SCTP support - needs various network include headers +check_headers( + "linux/if_addr.h", + "linux/rtnetlink.h", + includes=["sys/socket.h"], + when=building_linux, +) + +check_header("sys/queue.h") + +check_headers( + "sys/types.h", + "netinet/in.h", + "byteswap.h", +) + +# memfd_create(2) -- Note that older versions of the Linux man-pages +# project incorrectly cite <sys/memfd.h>, which doesn't exist; this +# was fixed in the man-pages-5.00 release. +set_define( + "HAVE_MEMFD_CREATE", + try_compile( + includes=["sys/mman.h"], + body='memfd_create("", 0);', + check_msg="for memfd_create in sys/mman.h", + ), +) + +# TODO: Move these checks to file specific to --enable-project=js. +have_perf_event_h = check_header("linux/perf_event.h", when=building_linux) + +option( + "--with-linux-headers", + help="location where the Linux kernel headers can be found", + nargs=1, +) + +passed_linux_header_flags = depends_if("--with-linux-headers")( + lambda v: ["-I%s" % v[0]] +) + + +@depends( + try_compile( + includes=["asm/unistd.h"], + body="return sizeof(__NR_perf_event_open);", + flags=passed_linux_header_flags, + check_msg="for perf_event_open system call", + ), + when=have_perf_event_h, +) +def have_perf_event_open(have_perf_event_open): + if have_perf_event_open: + return True + + +set_config("HAVE_LINUX_PERF_EVENT_H", have_perf_event_open) + + +@depends(passed_linux_header_flags, have_perf_event_open) +def linux_headers_includes(passed_linux_header_flags, have_perf_event_open): + if have_perf_event_open and passed_linux_header_flags: + return passed_linux_header_flags[0] + + +set_config("LINUX_HEADERS_INCLUDES", linux_headers_includes) diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure new file mode 100644 index 0000000000..7435bdeaad --- /dev/null +++ b/build/moz.configure/init.configure @@ -0,0 +1,1408 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +include("util.configure") +include("checks.configure") + +# Make `toolkit` available when toolkit/moz.configure is not included. +toolkit = dependable(None) +# Likewise with `bindgen_config_paths` when +# build/moz.configure/bindgen.configure is not included. +bindgen_config_paths = dependable(None) + +option(env="DIST", nargs=1, help="DIST directory") + + +# Do not allow objdir == srcdir builds. +# ============================================================== +@depends("--help", "DIST") +@imports(_from="__builtin__", _import="open") +@imports(_from="os.path", _import="exists") +@imports(_from="six", _import="ensure_text") +def check_build_environment(help, dist): + topobjdir = os.path.realpath(".") + topsrcdir = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) + + if dist: + dist = normsep(dist[0]) + else: + dist = os.path.join(topobjdir, "dist") + + result = namespace( + topsrcdir=topsrcdir, + topobjdir=topobjdir, + dist=dist, + ) + + if help: + return result + + # This limitation has mostly to do with GNU make. Since make can't represent + # variables with spaces without correct quoting and many paths are used + # without proper quoting, using paths with spaces commonly results in + # targets or dependencies being treated as multiple paths. This, of course, + # undermines the ability for make to perform up-to-date checks and makes + # the build system not work very efficiently. In theory, a non-make build + # backend will make this limitation go away. But there is likely a long tail + # of things that will need fixing due to e.g. lack of proper path quoting. + if len(topsrcdir.split()) > 1: + die("Source directory cannot be located in a path with spaces: %s" % topsrcdir) + if len(topobjdir.split()) > 1: + die("Object directory cannot be located in a path with spaces: %s" % topobjdir) + + if topsrcdir == topobjdir: + die( + " ***\n" + " * Building directly in the main source directory is not allowed.\n" + " *\n" + " * To build, you must run configure from a separate directory\n" + " * (referred to as an object directory).\n" + " *\n" + " * If you are building with a mozconfig, you will need to change your\n" + " * mozconfig to point to a different object directory.\n" + " ***" + ) + + # Check for CRLF line endings. + with open(os.path.join(topsrcdir, "configure.py"), "r") as fh: + data = ensure_text(fh.read()) + if "\r" in data: + die( + "\n ***\n" + " * The source tree appears to have Windows-style line endings.\n" + " *\n" + " * If using Git, Git is likely configured to use Windows-style\n" + " * line endings.\n" + " *\n" + " * To convert the working copy to UNIX-style line endings, run\n" + " * the following:\n" + " *\n" + " * $ git config core.autocrlf false\n" + " * $ git config core.eof lf\n" + " * $ git rm --cached -r .\n" + " * $ git reset --hard\n" + " *\n" + " * If not using Git, the tool you used to obtain the source\n" + " * code likely converted files to Windows line endings. See\n" + " * usage information for that tool for more.\n" + " ***" + ) + + # Check for a couple representative files in the source tree + conflict_files = [ + "* %s" % f + for f in ("Makefile", "config/autoconf.mk") + if exists(os.path.join(topsrcdir, f)) + ] + if conflict_files: + die( + " ***\n" + " * Your source tree contains these files:\n" + " %s\n" + " * This indicates that you previously built in the source tree.\n" + " * A source tree build can confuse the separate objdir build.\n" + " *\n" + " * To clean up the source tree:\n" + " * 1. cd %s\n" + " * 2. gmake distclean\n" + " ***" % ("\n ".join(conflict_files), topsrcdir) + ) + + return result + + +set_config("TOPSRCDIR", check_build_environment.topsrcdir) +set_config("TOPOBJDIR", check_build_environment.topobjdir) +set_config("DIST", check_build_environment.dist) + +add_old_configure_assignment("_topsrcdir", check_build_environment.topsrcdir) +add_old_configure_assignment("_objdir", check_build_environment.topobjdir) +add_old_configure_assignment("DIST", check_build_environment.dist) + +option(env="MOZ_AUTOMATION", help="Enable options for automated builds") +set_config("MOZ_AUTOMATION", depends_if("MOZ_AUTOMATION")(lambda x: True)) + + +option(env="OLD_CONFIGURE", nargs=1, help="Path to the old configure script") + +option(env="MOZCONFIG", nargs=1, help="Mozconfig location") + + +# Read user mozconfig +# ============================================================== +# Note: the dependency on --help is only there to always read the mozconfig, +# even when --help is passed. Without this dependency, the function wouldn't +# be called when --help is passed, and the mozconfig wouldn't be read. + + +@depends("MOZCONFIG", "OLD_CONFIGURE", check_build_environment, "--help") +@imports(_from="mozbuild.mozconfig", _import="MozconfigLoader") +@imports(_from="mozboot.mozconfig", _import="find_mozconfig") +def mozconfig(mozconfig, old_configure, build_env, help): + if not old_configure and not help: + die("The OLD_CONFIGURE environment variable must be set") + + # Don't read the mozconfig for the js configure (yay backwards + # compatibility) + # While the long term goal is that js and top-level use the same configure + # and the same overall setup, including the possibility to use mozconfigs, + # figuring out what we want to do wrt mozconfig vs. command line and + # environment variable is not a clear-cut case, and it's more important to + # fix the immediate problem mozconfig causes to js developers by + # "temporarily" returning to the previous behavior of not loading the + # mozconfig for the js configure. + # Separately to the immediate problem for js developers, there is also the + # need to not load a mozconfig when running js configure as a subconfigure. + # Unfortunately, there is no direct way to tell whether the running + # configure is the js configure. The indirect way is to look at the + # OLD_CONFIGURE path, which points to js/src/old-configure. + # I expect we'll have figured things out for mozconfigs well before + # old-configure dies. + if old_configure and os.path.dirname(os.path.abspath(old_configure[0])).endswith( + "/js/src" + ): + return {"path": None} + + topsrcdir = build_env.topsrcdir + loader = MozconfigLoader(topsrcdir) + mozconfig = mozconfig[0] if mozconfig else None + mozconfig = find_mozconfig(topsrcdir, env={"MOZCONFIG": mozconfig}) + mozconfig = loader.read_mozconfig(mozconfig) + + return mozconfig + + +set_config("MOZCONFIG", depends(mozconfig)(lambda m: m["path"])) + + +# Mozilla-Build +# ============================================================== +option(env="MOZILLABUILD", nargs=1, help="Path to Mozilla Build (Windows-only)") + +option(env="CONFIG_SHELL", nargs=1, help="Path to a POSIX shell") + +# It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py, +# but the end goal being that the configure script would go away... + + +@depends("CONFIG_SHELL", "MOZILLABUILD") +@checking("for a shell") +@imports("sys") +def shell(value, mozillabuild): + if value: + return find_program(value[0]) + shell = "sh" + if mozillabuild: + shell = mozillabuild[0] + "/msys/bin/sh" + if sys.platform == "win32": + shell = shell + ".exe" + return find_program(shell) + + +# This defines a reasonable shell for when running with --help. +# If one was passed in the environment, though, fall back to that. +@depends("--help", "CONFIG_SHELL") +def help_shell(help, shell): + if help and not shell: + return "sh" + + +shell = help_shell | shell + + +# Python 3 +# ======== + +option(env="PYTHON3", nargs=1, help="Python 3 interpreter (3.6 or later)") + +option( + env="VIRTUALENV_NAME", + nargs=1, + default="init_py3", + help="Name of the in-objdir virtualenv", +) + + +@depends("PYTHON3", "VIRTUALENV_NAME", check_build_environment, mozconfig, "--help") +@imports(_from="__builtin__", _import="Exception") +@imports("os") +@imports("sys") +@imports("subprocess") +@imports("distutils.sysconfig") +@imports(_from="mozbuild.configure.util", _import="LineIO") +@imports(_from="mozbuild.virtualenv", _import="VirtualenvManager") +@imports(_from="mozbuild.virtualenv", _import="verify_python_version") +@imports(_from="mozbuild.pythonutil", _import="find_python3_executable") +@imports(_from="mozbuild.pythonutil", _import="python_executable_version") +@imports(_from="six", _import="ensure_text") +def virtualenv_python3(env_python, virtualenv_name, build_env, mozconfig, help): + # Avoid re-executing python when running configure --help. + if help: + return + + # NOTE: We cannot assume the Python we are calling this code with is the + # Python we want to set up a virtualenv for. + # + # We also cannot assume that the Python the caller is configuring meets our + # build requirements. + # + # Because of this the code is written to re-execute itself with the correct + # interpreter if required. + + log.debug("python3: running with pid %r" % os.getpid()) + log.debug("python3: sys.executable: %r" % sys.executable) + + python = env_python[0] if env_python else None + virtualenv_name = virtualenv_name[0] + + # Did our python come from mozconfig? Overrides environment setting. + # Ideally we'd rely on the mozconfig injection from mozconfig_options, + # but we'd rather avoid the verbosity when we need to reexecute with + # a different python. + if mozconfig["path"]: + if "PYTHON3" in mozconfig["env"]["added"]: + python = mozconfig["env"]["added"]["PYTHON3"] + elif "PYTHON3" in mozconfig["env"]["modified"]: + python = mozconfig["env"]["modified"]["PYTHON3"][1] + elif "PYTHON3" in mozconfig["vars"]["added"]: + python = mozconfig["vars"]["added"]["PYTHON3"] + elif "PYTHON3" in mozconfig["vars"]["modified"]: + python = mozconfig["vars"]["modified"]["PYTHON3"][1] + + log.debug("python3: executable from configuration: %r" % python) + + # Verify that the Python version we executed this code with is the minimum + # required version to handle all project code. + with LineIO(lambda l: log.error(l)) as out: + verify_python_version(out) + + # If this is a mozilla-central build, we'll find the virtualenv in the top + # source directory. If this is a SpiderMonkey build, we assume we're at + # js/src and try to find the virtualenv from the mozilla-central root. + # See mozilla-central changeset d2cce982a7c809815d86d5daecefe2e7a563ecca + # Bug 784841 + topsrcdir, topobjdir = build_env.topsrcdir, build_env.topobjdir + if topobjdir.endswith("/js/src"): + topobjdir = topobjdir[:-7] + + virtualenvs_root = os.path.join(topobjdir, "_virtualenvs") + with LineIO(lambda l: log.info(l), "replace") as out: + manager = VirtualenvManager( + topsrcdir, + os.path.join(virtualenvs_root, virtualenv_name), + out, + os.path.join(topsrcdir, "build", "build_virtualenv_packages.txt"), + ) + + # If we're not in the virtualenv, we need to update the path to include some + # necessary modules for find_program. + if "MOZBUILD_VIRTUALENV" in os.environ: + python = sys.executable + else: + sys.path.insert(0, os.path.join(topsrcdir, "testing", "mozbase", "mozfile")) + sys.path.insert( + 0, os.path.join(topsrcdir, "third_party", "python", "backports") + ) + + # If we know the Python executable the caller is asking for then verify its + # version. If the caller did not ask for a specific executable then find + # a reasonable default. + if python: + found_python = find_program(python) + if not found_python: + die( + "The PYTHON3 environment variable does not contain " + "a valid path. Cannot find %s", + python, + ) + python = found_python + try: + version = python_executable_version(python).version + except Exception as e: + raise FatalCheckError( + "could not determine version of PYTHON3 " "(%s): %s" % (python, e) + ) + else: + # Fall back to the search routine. + python, version = find_python3_executable(min_version="3.6.0") + + # The API returns a bytes whereas everything in configure is unicode. + if python: + python = ensure_text(python) + + if not python: + raise FatalCheckError( + "Python 3.6 or newer is required to build. " + "Ensure a `python3.x` executable is in your " + "PATH or define PYTHON3 to point to a Python " + "3.6 executable." + ) + + if version < (3, 6, 0): + raise FatalCheckError( + "Python 3.6 or newer is required to build; " + "%s is Python %d.%d" % (python, version[0], version[1]) + ) + + log.debug("python3: found executable: %r" % python) + + if not manager.up_to_date(python): + log.info("Creating Python 3 environment") + manager.build(python) + else: + log.debug("python3: venv is up to date") + + python = normsep(manager.python_path) + + if not normsep(sys.executable).startswith(normsep(virtualenvs_root)): + log.debug( + "python3: executing as %s, should be running as %s" + % (sys.executable, manager.python_path) + ) + log.info("Re-executing in the virtualenv") + if env_python: + del os.environ["PYTHON3"] + # Homebrew on macOS will change Python's sys.executable to a custom + # value which messes with mach's virtualenv handling code. Override + # Homebrew's changes with the correct sys.executable value. + os.environ["PYTHONEXECUTABLE"] = python + # Another quirk on macOS, with the system python, the virtualenv is + # not fully operational (missing entries in sys.path) if + # __PYVENV_LAUNCHER__ is set. + os.environ.pop("__PYVENV_LAUNCHER__", None) + # One would prefer to use os.execl, but that's completely borked on + # Windows. + sys.exit(subprocess.call([python] + sys.argv)) + + # We are now in the virtualenv + if not distutils.sysconfig.get_python_lib(): + die("Could not determine python site packages directory") + + # We may have set PYTHONEXECUTABLE above, and that affects python + # subprocesses we may invoke as part of configure (e.g. hg), so + # unset it. + os.environ.pop("PYTHONEXECUTABLE", None) + + str_version = ".".join(str(v) for v in version) + + return namespace( + path=python, + version=version, + str_version=str_version, + ) + + +@depends(virtualenv_python3) +@checking("for Python 3", callback=lambda x: "%s (%s)" % (x.path, x.str_version)) +def virtualenv_python3(venv): + return venv + + +set_config("PYTHON3", virtualenv_python3.path) +set_config("PYTHON3_VERSION", virtualenv_python3.str_version) +add_old_configure_assignment("PYTHON3", virtualenv_python3.path) + + +# Inject mozconfig options +# ============================================================== +# All options defined above this point can't be injected in mozconfig_options +# below, so collect them. + + +@template +def early_options(): + @depends("--help") + @imports("__sandbox__") + @imports(_from="six", _import="itervalues") + def early_options(_): + return set( + option.env for option in itervalues(__sandbox__._options) if option.env + ) + + return early_options + + +early_options = early_options() + + +@depends(mozconfig, early_options, "MOZ_AUTOMATION", "--help") +# This gives access to the sandbox. Don't copy this blindly. +@imports("__sandbox__") +@imports("os") +@imports("six") +def mozconfig_options(mozconfig, early_options, automation, help): + if mozconfig["path"]: + if "MOZ_AUTOMATION_MOZCONFIG" in mozconfig["env"]["added"]: + if not automation: + log.error( + "%s directly or indirectly includes an in-tree " "mozconfig.", + mozconfig["path"], + ) + log.error( + "In-tree mozconfigs make strong assumptions about " + "and are only meant to be used by Mozilla " + "automation." + ) + die("Please don't use them.") + helper = __sandbox__._helper + log.info("Adding configure options from %s" % mozconfig["path"]) + for arg in mozconfig["configure_args"]: + log.info(" %s" % arg) + # We could be using imply_option() here, but it has other + # contraints that don't really apply to the command-line + # emulation that mozconfig provides. + helper.add(arg, origin="mozconfig", args=helper._args) + + def add(key, value): + if key.isupper(): + arg = "%s=%s" % (key, value) + log.info(" %s" % arg) + if key not in early_options: + helper.add(arg, origin="mozconfig", args=helper._args) + + for key, value in six.iteritems(mozconfig["env"]["added"]): + add(key, value) + os.environ[key] = value + for key, (_, value) in six.iteritems(mozconfig["env"]["modified"]): + add(key, value) + os.environ[key] = value + for key, value in six.iteritems(mozconfig["vars"]["added"]): + add(key, value) + for key, (_, value) in six.iteritems(mozconfig["vars"]["modified"]): + add(key, value) + + +# Source checkout and version control integration. +# ================================================ + + +@depends(check_build_environment, "MOZ_AUTOMATION", "--help") +@checking("for vcs source checkout") +@imports("os") +def vcs_checkout_type(build_env, automation, help): + if os.path.exists(os.path.join(build_env.topsrcdir, ".hg")): + return "hg" + elif os.path.exists(os.path.join(build_env.topsrcdir, ".git")): + return "git" + elif automation and not help: + raise FatalCheckError( + "unable to resolve VCS type; must run " + "from a source checkout when MOZ_AUTOMATION " + "is set" + ) + + +# Resolve VCS binary for detected repository type. + + +# TODO remove hg.exe once bug 1382940 addresses ambiguous executables case. +hg = check_prog( + "HG", + ( + "hg.exe", + "hg", + ), + allow_missing=True, + when=depends(vcs_checkout_type)(lambda x: x == "hg"), +) +git = check_prog( + "GIT", + ("git",), + allow_missing=True, + when=depends(vcs_checkout_type)(lambda x: x == "git"), +) + + +@depends_if(hg) +@checking("for Mercurial version") +@imports("os") +@imports("re") +def hg_version(hg): + # HGPLAIN in Mercurial 1.5+ forces stable output, regardless of set + # locale or encoding. + env = dict(os.environ) + env["HGPLAIN"] = "1" + + out = check_cmd_output(hg, "--version", env=env) + + match = re.search(r"Mercurial Distributed SCM \(version ([^\)]+)", out) + + if not match: + raise FatalCheckError("unable to determine Mercurial version: %s" % out) + + # The version string may be "unknown" for Mercurial run out of its own + # source checkout or for bad builds. But LooseVersion handles it. + + return Version(match.group(1)) + + +# Resolve Mercurial config items so other checks have easy access. +# Do NOT set this in the config because it may contain sensitive data +# like API keys. + + +@depends_all(check_build_environment, hg, hg_version) +@imports("os") +def hg_config(build_env, hg, version): + env = dict(os.environ) + env["HGPLAIN"] = "1" + + # Warnings may get sent to stderr. But check_cmd_output() ignores + # stderr if exit code is 0. And the command should always succeed if + # `hg version` worked. + out = check_cmd_output(hg, "config", env=env, cwd=build_env.topsrcdir) + + config = {} + + for line in out.strip().splitlines(): + key, value = [s.strip() for s in line.split("=", 1)] + config[key] = value + + return config + + +@depends_if(git) +@checking("for Git version") +@imports("re") +def git_version(git): + out = check_cmd_output(git, "--version").rstrip() + + match = re.search("git version (.*)$", out) + + if not match: + raise FatalCheckError("unable to determine Git version: %s" % out) + + return Version(match.group(1)) + + +# Only set VCS_CHECKOUT_TYPE if we resolved the VCS binary. +# Require resolved VCS info when running in automation so automation's +# environment is more well-defined. + + +@depends(vcs_checkout_type, hg_version, git_version, "MOZ_AUTOMATION") +def exposed_vcs_checkout_type(vcs_checkout_type, hg, git, automation): + if vcs_checkout_type == "hg": + if hg: + return "hg" + + if automation: + raise FatalCheckError("could not resolve Mercurial binary info") + + elif vcs_checkout_type == "git": + if git: + return "git" + + if automation: + raise FatalCheckError("could not resolve Git binary info") + elif vcs_checkout_type: + raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type) + + +set_config("VCS_CHECKOUT_TYPE", exposed_vcs_checkout_type) + +# Obtain a Repository interface for the current VCS repository. + + +@depends(check_build_environment, exposed_vcs_checkout_type, hg, git) +@imports(_from="mozversioncontrol", _import="get_repository_object") +def vcs_repository(build_env, vcs_checkout_type, hg, git): + if vcs_checkout_type == "hg": + return get_repository_object(build_env.topsrcdir, hg=hg) + elif vcs_checkout_type == "git": + return get_repository_object(build_env.topsrcdir, git=git) + elif vcs_checkout_type: + raise FatalCheckError("unhandled VCS type: %s" % vcs_checkout_type) + + +@depends_if(vcs_repository) +@checking("for sparse checkout") +def vcs_sparse_checkout(repo): + return repo.sparse_checkout_present() + + +set_config("VCS_SPARSE_CHECKOUT", vcs_sparse_checkout) + +# The application/project to build +# ============================================================== +option( + "--enable-application", + nargs=1, + env="MOZ_BUILD_APP", + help="Application to build. Same as --enable-project.", +) + + +@depends("--enable-application") +def application(app): + if app: + return app + + +imply_option("--enable-project", application) + + +@depends(check_build_environment) +def default_project(build_env): + if build_env.topobjdir.endswith("/js/src"): + return "js" + return "browser" + + +option("--enable-project", nargs=1, default=default_project, help="Project to build") + + +# Host and target systems +# ============================================================== +option("--host", nargs=1, help="Define the system type performing the build") + +option( + "--target", + nargs=1, + help="Define the system type where the resulting executables will be " "used", +) + + +@imports(_from="mozbuild.configure.constants", _import="CPU") +@imports(_from="mozbuild.configure.constants", _import="CPU_bitness") +@imports(_from="mozbuild.configure.constants", _import="Endianness") +@imports(_from="mozbuild.configure.constants", _import="Kernel") +@imports(_from="mozbuild.configure.constants", _import="OS") +@imports(_from="__builtin__", _import="ValueError") +def split_triplet(triplet, allow_msvc=False): + # The standard triplet is defined as + # CPU_TYPE-VENDOR-OPERATING_SYSTEM + # There is also a quartet form: + # CPU_TYPE-VENDOR-KERNEL-OPERATING_SYSTEM + # But we can consider the "KERNEL-OPERATING_SYSTEM" as one. + # Additionally, some may omit "unknown" when the vendor + # is not specified and emit + # CPU_TYPE-OPERATING_SYSTEM + vendor = "unknown" + parts = triplet.split("-", 2) + if len(parts) == 3: + cpu, vendor, os = parts + elif len(parts) == 2: + cpu, os = parts + else: + raise ValueError("Unexpected triplet string: %s" % triplet) + + # Autoconf uses config.sub to validate and canonicalize those triplets, + # but the granularity of its results has never been satisfying to our + # use, so we've had our own, different, canonicalization. We've also + # historically not been very consistent with how we use the canonicalized + # values. Hopefully, this will help us make things better. + # The tests are inherited from our decades-old autoconf-based configure, + # which can probably be improved/cleaned up because they are based on a + # mix of uname and config.guess output, while we now only use the latter, + # which presumably has a cleaner and leaner output. Let's refine later. + os = os.replace("/", "_") + if "android" in os: + canonical_os = "Android" + canonical_kernel = "Linux" + elif os.startswith("linux"): + canonical_os = "GNU" + canonical_kernel = "Linux" + elif os.startswith("kfreebsd") and os.endswith("-gnu"): + canonical_os = "GNU" + canonical_kernel = "kFreeBSD" + elif os.startswith("gnu"): + canonical_os = canonical_kernel = "GNU" + elif os.startswith("mingw") or (allow_msvc and os == "windows-msvc"): + # windows-msvc is only opt-in for the caller of this function until + # full support in bug 1617793. + canonical_os = canonical_kernel = "WINNT" + elif os.startswith("darwin"): + canonical_kernel = "Darwin" + canonical_os = "OSX" + elif os.startswith("dragonfly"): + canonical_os = canonical_kernel = "DragonFly" + elif os.startswith("freebsd"): + canonical_os = canonical_kernel = "FreeBSD" + elif os.startswith("netbsd"): + canonical_os = canonical_kernel = "NetBSD" + elif os.startswith("openbsd"): + canonical_os = canonical_kernel = "OpenBSD" + elif os.startswith("solaris"): + canonical_os = canonical_kernel = "SunOS" + else: + raise ValueError("Unknown OS: %s" % os) + + # The CPU granularity is probably not enough. Moving more things from + # old-configure will tell us if we need more + if cpu.endswith("86") or (cpu.startswith("i") and "86" in cpu): + canonical_cpu = "x86" + endianness = "little" + elif cpu in ("x86_64", "ia64"): + canonical_cpu = cpu + endianness = "little" + elif cpu in ("s390", "s390x"): + canonical_cpu = cpu + endianness = "big" + elif cpu in ("powerpc64", "ppc64", "powerpc64le", "ppc64le"): + canonical_cpu = "ppc64" + endianness = "little" if "le" in cpu else "big" + elif cpu in ("powerpc", "ppc", "rs6000") or cpu.startswith("powerpc"): + canonical_cpu = "ppc" + endianness = "big" + elif cpu in ("Alpha", "alpha", "ALPHA"): + canonical_cpu = "Alpha" + endianness = "little" + elif cpu.startswith("hppa") or cpu == "parisc": + canonical_cpu = "hppa" + endianness = "big" + elif cpu.startswith("sparc64") or cpu.startswith("sparcv9"): + canonical_cpu = "sparc64" + endianness = "big" + elif cpu.startswith("sparc") or cpu == "sun4u": + canonical_cpu = "sparc" + endianness = "big" + elif cpu.startswith("arm"): + canonical_cpu = "arm" + endianness = "big" if cpu.startswith(("armeb", "armbe")) else "little" + elif cpu in ("m68k"): + canonical_cpu = "m68k" + endianness = "big" + elif cpu in ("mips", "mipsel"): + canonical_cpu = "mips32" + endianness = "little" if "el" in cpu else "big" + elif cpu in ("mips64", "mips64el"): + canonical_cpu = "mips64" + endianness = "little" if "el" in cpu else "big" + elif cpu.startswith("aarch64"): + canonical_cpu = "aarch64" + endianness = "little" + elif cpu in ("riscv64", "riscv64gc"): + canonical_cpu = "riscv64" + endianness = "little" + elif cpu == "sh4": + canonical_cpu = "sh4" + endianness = "little" + else: + raise ValueError("Unknown CPU type: %s" % cpu) + + # Toolchains, most notably for cross compilation may use cpu-os + # prefixes. We need to be more specific about the LLVM target on Mac + # so cross-language LTO will work correctly. + + if os.startswith("darwin"): + toolchain = "%s-apple-%s" % (cpu, os) + elif canonical_cpu == "aarch64" and canonical_os == "WINNT": + toolchain = "aarch64-windows-msvc" + else: + toolchain = "%s-%s" % (cpu, os) + + return namespace( + alias=triplet, + cpu=CPU(canonical_cpu), + bitness=CPU_bitness[canonical_cpu], + kernel=Kernel(canonical_kernel), + os=OS(canonical_os), + endianness=Endianness(endianness), + raw_cpu=cpu, + raw_os=os, + toolchain=toolchain, + vendor=vendor, + ) + + +# This defines a fake target/host namespace for when running with --help +# If either --host or --target is passed on the command line, then fall +# back to the real deal. +@depends("--help", "--host", "--target") +def help_host_target(help, host, target): + if help and not host and not target: + return namespace( + alias="unknown-unknown-unknown", + cpu="unknown", + bitness="unknown", + kernel="unknown", + os="unknown", + endianness="unknown", + raw_cpu="unknown", + raw_os="unknown", + toolchain="unknown-unknown", + ) + + +def config_sub(shell, triplet): + config_sub = os.path.join(os.path.dirname(__file__), "..", "autoconf", "config.sub") + return check_cmd_output(shell, config_sub, triplet).strip() + + +@depends("--host", shell) +@checking("for host system type", lambda h: h.alias) +@imports("os") +@imports("sys") +@imports(_from="__builtin__", _import="ValueError") +def real_host(value, shell): + if not value and sys.platform == "win32": + arch = os.environ.get("PROCESSOR_ARCHITEW6432") or os.environ.get( + "PROCESSOR_ARCHITECTURE" + ) + if arch == "AMD64": + return split_triplet("x86_64-pc-mingw32") + elif arch == "x86": + return split_triplet("i686-pc-mingw32") + + if not value: + config_guess = os.path.join( + os.path.dirname(__file__), "..", "autoconf", "config.guess" + ) + + # Ensure that config.guess is determining the host triplet, not the target + # triplet + env = os.environ.copy() + env.pop("CC_FOR_BUILD", None) + env.pop("HOST_CC", None) + env.pop("CC", None) + + host = check_cmd_output(shell, config_guess, env=env).strip() + try: + return split_triplet(host) + except ValueError: + pass + else: + host = value[0] + + host = config_sub(shell, host) + + try: + return split_triplet(host) + except ValueError as e: + die(e) + + +host = help_host_target | real_host + + +@depends("--target", real_host, shell, "--enable-project", "--enable-application") +@checking("for target system type", lambda t: t.alias) +@imports(_from="__builtin__", _import="ValueError") +def real_target(value, host, shell, project, application): + # Because --enable-project is implied by --enable-application, and + # implied options are not currently handled during --help, which is + # used get the build target in mozbuild.base, we manually check + # whether --enable-application was given, and fall back to + # --enable-project if not. Both can't be given contradictory values + # under normal circumstances, so it's fine. + if application: + project = application[0] + elif project: + project = project[0] + if not value: + if project == "mobile/android": + if host.raw_os == "mingw32": + log.warning( + "Building Firefox for Android on Windows is not fully " + "supported. See https://bugzilla.mozilla.org/show_bug.cgi?" + "id=1169873 for details." + ) + return split_triplet("arm-unknown-linux-androideabi") + return host + # If --target was only given a cpu arch, expand it with the + # non-cpu part of the host. For mobile/android, expand it with + # unknown-linux-android. + target = value[0] + if "-" not in target: + if project == "mobile/android": + rest = "unknown-linux-android" + if target.startswith("arm"): + rest += "eabi" + else: + cpu, rest = host.alias.split("-", 1) + target = "-".join((target, rest)) + try: + return split_triplet(target) + except ValueError: + pass + + try: + return split_triplet(config_sub(shell, target)) + except ValueError as e: + die(e) + + +target = help_host_target | real_target + + +@depends(host, target) +@checking("whether cross compiling") +def cross_compiling(host, target): + return host != target + + +set_config("CROSS_COMPILE", cross_compiling) +set_define("CROSS_COMPILE", cross_compiling) +add_old_configure_assignment("CROSS_COMPILE", cross_compiling) + + +@depends(target) +def have_64_bit(target): + if target.bitness == 64: + return True + + +set_config("HAVE_64BIT_BUILD", have_64_bit) +set_define("HAVE_64BIT_BUILD", have_64_bit) +add_old_configure_assignment("HAVE_64BIT_BUILD", have_64_bit) + + +@depends(host) +def host_os_kernel_major_version(host): + versions = host.raw_os.split(".") + version = "".join(x for x in versions[0] if x.isdigit()) + return version + + +set_config("HOST_MAJOR_VERSION", host_os_kernel_major_version) + +# Autoconf needs these set + + +@depends(host) +def host_for_sub_configure(host): + return "--host=%s" % host.alias + + +@depends(target) +def target_for_sub_configure(target): + target_alias = target.alias + return "--target=%s" % target_alias + + +# These variables are for compatibility with the current moz.builds and +# old-configure. Eventually, we'll want to canonicalize better. +@depends(target) +def target_variables(target): + if target.kernel == "kFreeBSD": + os_target = "GNU/kFreeBSD" + os_arch = "GNU_kFreeBSD" + elif target.kernel == "Darwin" or (target.kernel == "Linux" and target.os == "GNU"): + os_target = target.kernel + os_arch = target.kernel + else: + os_target = target.os + os_arch = target.kernel + + return namespace( + OS_TARGET=os_target, + OS_ARCH=os_arch, + INTEL_ARCHITECTURE=target.cpu in ("x86", "x86_64") or None, + ) + + +set_config("OS_TARGET", target_variables.OS_TARGET) +add_old_configure_assignment("OS_TARGET", target_variables.OS_TARGET) +set_config("OS_ARCH", target_variables.OS_ARCH) +add_old_configure_assignment("OS_ARCH", target_variables.OS_ARCH) +set_config("CPU_ARCH", target.cpu) +add_old_configure_assignment("CPU_ARCH", target.cpu) +set_config("INTEL_ARCHITECTURE", target_variables.INTEL_ARCHITECTURE) +set_config("TARGET_CPU", target.raw_cpu) +set_config("TARGET_OS", target.raw_os) +set_config("TARGET_ENDIANNESS", target.endianness) + + +@depends(host) +def host_variables(host): + if host.kernel == "kFreeBSD": + os_arch = "GNU_kFreeBSD" + else: + os_arch = host.kernel + return namespace( + HOST_OS_ARCH=os_arch, + ) + + +set_config("HOST_CPU_ARCH", host.cpu) +set_config("HOST_OS_ARCH", host_variables.HOST_OS_ARCH) +add_old_configure_assignment("HOST_OS_ARCH", host_variables.HOST_OS_ARCH) + + +@depends(target) +def target_is_windows(target): + if target.kernel == "WINNT": + return True + + +set_define("_WINDOWS", target_is_windows) +set_define("WIN32", target_is_windows) +set_define("XP_WIN", target_is_windows) + + +@depends(target) +def target_is_unix(target): + if target.kernel != "WINNT": + return True + + +set_define("XP_UNIX", target_is_unix) + + +@depends(target) +def target_is_darwin(target): + if target.kernel == "Darwin": + return True + + +set_define("XP_DARWIN", target_is_darwin) + + +@depends(target) +def target_is_osx(target): + if target.kernel == "Darwin" and target.os == "OSX": + return True + + +set_define("XP_MACOSX", target_is_osx) + + +@depends(target) +def target_is_linux(target): + if target.kernel == "Linux": + return True + + +set_define("XP_LINUX", target_is_linux) + + +@depends(target) +def target_is_android(target): + if target.os == "Android": + return True + + +set_define("ANDROID", target_is_android) + + +@depends(target) +def target_is_openbsd(target): + if target.kernel == "OpenBSD": + return True + + +set_define("XP_OPENBSD", target_is_openbsd) + + +@depends(target) +def target_is_netbsd(target): + if target.kernel == "NetBSD": + return True + + +set_define("XP_NETBSD", target_is_netbsd) + + +@depends(target) +def target_is_freebsd(target): + if target.kernel == "FreeBSD": + return True + + +set_define("XP_FREEBSD", target_is_freebsd) + + +@depends(target) +def target_is_solaris(target): + if target.kernel == "SunOS": + return True + + +set_define("XP_SOLARIS", target_is_solaris) + + +@depends(target) +def target_is_sparc(target): + if target.cpu == "sparc64": + return True + + +set_define("SPARC64", target_is_sparc) + + +@depends("--enable-project", check_build_environment, "--help") +@imports(_from="os.path", _import="exists") +def include_project_configure(project, build_env, help): + if not project: + die("--enable-project is required.") + + base_dir = build_env.topsrcdir + path = os.path.join(base_dir, project[0], "moz.configure") + if not exists(path): + die("Cannot find project %s", project[0]) + return path + + +@depends(include_project_configure, check_build_environment) +def build_project(include_project_configure, build_env): + ret = os.path.dirname( + os.path.relpath(include_project_configure, build_env.topsrcdir) + ) + return ret + + +set_config("MOZ_BUILD_APP", build_project) +set_define("MOZ_BUILD_APP", build_project) +add_old_configure_assignment("MOZ_BUILD_APP", build_project) + + +option(env="MOZILLA_OFFICIAL", help="Build an official release") + + +@depends("MOZILLA_OFFICIAL") +def mozilla_official(official): + if official: + return True + + +set_config("MOZILLA_OFFICIAL", mozilla_official) +set_define("MOZILLA_OFFICIAL", mozilla_official) +add_old_configure_assignment("MOZILLA_OFFICIAL", mozilla_official) + + +# Allow specifying custom paths to the version files used by the milestone() function below. +option( + "--with-version-file-path", + nargs=1, + help="Specify a custom path to app version files instead of auto-detecting", + default=None, +) + + +@depends("--with-version-file-path") +def version_path(path): + return path + + +# set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in +# The logic works like this: +# - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD) +# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora +# - otherwise, we're building Release/Beta (define RELEASE_OR_BETA) +@depends(check_build_environment, build_project, version_path, "--help") +@imports(_from="__builtin__", _import="open") +@imports("os") +@imports("re") +def milestone(build_env, build_project, version_path, _): + versions = [] + paths = ["config/milestone.txt"] + if build_project == "js": + paths = paths * 3 + else: + paths += [ + "browser/config/version.txt", + "browser/config/version_display.txt", + ] + if version_path: + version_path = version_path[0] + else: + version_path = os.path.join(build_project, "config") + for f in ("version.txt", "version_display.txt"): + f = os.path.join(version_path, f) + if not os.path.exists(os.path.join(build_env.topsrcdir, f)): + break + paths.append(f) + + for p in paths: + with open(os.path.join(build_env.topsrcdir, p), "r") as fh: + content = fh.read().splitlines() + if not content: + die("Could not find a version number in {}".format(p)) + versions.append(content[-1]) + + milestone, firefox_version, firefox_version_display = versions[:3] + + # version.txt content from the project directory if there is one, otherwise + # the firefox version. + app_version = versions[3] if len(versions) > 3 else firefox_version + # version_display.txt content from the project directory if there is one, + # otherwise version.txt content from the project directory, otherwise the + # firefox version for display. + app_version_display = versions[-1] if len(versions) > 3 else firefox_version_display + + is_nightly = is_release_or_beta = is_early_beta_or_earlier = None + + if "a1" in milestone: + is_nightly = True + elif "a" not in milestone: + is_release_or_beta = True + + major_version = milestone.split(".")[0] + m = re.search(r"([ab]\d+)", milestone) + ab_patch = m.group(1) if m else "" + + defines = os.path.join(build_env.topsrcdir, "build", "defines.sh") + with open(defines, "r") as fh: + for line in fh.read().splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + name, _, value = line.partition("=") + name = name.strip() + value = value.strip() + if name != "EARLY_BETA_OR_EARLIER": + die( + "Only the EARLY_BETA_OR_EARLIER variable can be set in build/defines.sh" + ) + if value: + is_early_beta_or_earlier = True + + # Only expose the major version milestone in the UA string and hide the + # patch leve (bugs 572659 and 870868). + # + # Only expose major milestone and alpha version in the symbolversion + # string; as the name suggests, we use it for symbol versioning on Linux. + return namespace( + version=milestone, + uaversion="%s.0" % major_version, + symbolversion="%s%s" % (major_version, ab_patch), + is_nightly=is_nightly, + is_release_or_beta=is_release_or_beta, + is_early_beta_or_earlier=is_early_beta_or_earlier, + app_version=app_version, + app_version_display=app_version_display, + ) + + +set_config("GRE_MILESTONE", milestone.version) +set_config("NIGHTLY_BUILD", milestone.is_nightly) +set_define("NIGHTLY_BUILD", milestone.is_nightly) +set_config("RELEASE_OR_BETA", milestone.is_release_or_beta) +set_define("RELEASE_OR_BETA", milestone.is_release_or_beta) +add_old_configure_assignment("RELEASE_OR_BETA", milestone.is_release_or_beta) +set_config("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier) +set_define("EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier) +add_old_configure_assignment( + "EARLY_BETA_OR_EARLIER", milestone.is_early_beta_or_earlier +) +set_define("MOZILLA_VERSION", depends(milestone)(lambda m: '"%s"' % m.version)) +set_config("MOZILLA_VERSION", milestone.version) +set_define("MOZILLA_VERSION_U", milestone.version) +set_define("MOZILLA_UAVERSION", depends(milestone)(lambda m: '"%s"' % m.uaversion)) +set_config("MOZILLA_SYMBOLVERSION", milestone.symbolversion) +# JS configure still wants to look at these. +add_old_configure_assignment("MOZILLA_VERSION", milestone.version) +add_old_configure_assignment("MOZILLA_SYMBOLVERSION", milestone.symbolversion) + +set_config("MOZ_APP_VERSION", milestone.app_version) +set_config("MOZ_APP_VERSION_DISPLAY", milestone.app_version_display) +add_old_configure_assignment("MOZ_APP_VERSION", milestone.app_version) + + +# Dummy function for availability in toolkit/moz.configure. Overridden in +# mobile/android/moz.configure. +@depends(milestone.is_nightly) +def fennec_nightly(is_nightly): + return is_nightly + + +# The app update channel is 'default' when not supplied. The value is used in +# the application's confvars.sh (and is made available to a project specific +# moz.configure). +option( + "--enable-update-channel", + nargs=1, + help="Select application update channel", + default="default", +) + + +@depends("--enable-update-channel") +def update_channel(channel): + if not channel or channel[0] == "": + return "default" + return channel[0].lower() + + +set_config("MOZ_UPDATE_CHANNEL", update_channel) +set_define("MOZ_UPDATE_CHANNEL", update_channel) +add_old_configure_assignment("MOZ_UPDATE_CHANNEL", update_channel) + + +option( + env="MOZBUILD_STATE_PATH", + nargs=1, + help="Path to a persistent state directory for the build system " + "and related tools", +) + + +@depends("MOZBUILD_STATE_PATH", "--help") +@imports("os") +def mozbuild_state_path(path, _): + if path: + return path[0] + return os.path.expanduser(os.path.join("~", ".mozbuild")) + + +# A template providing a shorthand for setting a variable. The created +# option will only be settable with imply_option. +# It is expected that a project-specific moz.configure will call imply_option +# to set a value other than the default. +# If required, the set_as_define and set_for_old_configure arguments +# will additionally cause the variable to be set using set_define and +# add_old_configure_assignment. util.configure would be an appropriate place for +# this, but it uses add_old_configure_assignment, which is defined in this file. +@template +def project_flag(env=None, set_for_old_configure=False, set_as_define=False, **kwargs): + + if not env: + configure_error("A project_flag must be passed a variable name to set.") + + opt = option(env=env, possible_origins=("implied",), **kwargs) + + @depends(opt.option) + def option_implementation(value): + if value: + if len(value): + return value + return bool(value) + + set_config(env, option_implementation) + if set_as_define: + set_define(env, option_implementation) + if set_for_old_configure: + add_old_configure_assignment(env, option_implementation) + + +# milestone.is_nightly corresponds to cases NIGHTLY_BUILD is set. + + +@depends(milestone) +def enabled_in_nightly(milestone): + return milestone.is_nightly + + +# Branding +# ============================================================== +option( + "--with-app-basename", + env="MOZ_APP_BASENAME", + nargs=1, + help="Typically stays consistent for multiple branded versions of a " + 'given application (e.g. Aurora and Firefox both use "Firefox"), but ' + "may vary for full rebrandings (e.g. Iceweasel). Used for " + 'application.ini\'s "Name" field, which controls profile location in ' + 'the absence of a "Profile" field (see below), and various system ' + "integration hooks (Unix remoting, Windows MessageWindow name, etc.", +) + + +@depends("--with-app-basename", target_is_android) +def moz_app_basename(value, target_is_android): + if value: + return value[0] + if target_is_android: + return "Fennec" + return "Firefox" + + +set_config( + "MOZ_APP_BASENAME", + moz_app_basename, + when=depends(build_project)(lambda p: p != "js"), +) diff --git a/build/moz.configure/java.configure b/build/moz.configure/java.configure new file mode 100644 index 0000000000..1840baf8ec --- /dev/null +++ b/build/moz.configure/java.configure @@ -0,0 +1,66 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +# Java detection +# ======================================================== +option( + "--with-java-bin-path", + nargs=1, + help="Location of Java binaries", +) + + +@depends("--with-java-bin-path") +@imports(_from="mozboot.util", _import="locate_java_bin_path") +@imports(_from="mozboot.util", _import="JavaLocationFailedException") +def java_search_paths(path): + if path: + # Look for javac and jar in the specified path. + return path + + try: + return [locate_java_bin_path()] + except JavaLocationFailedException as e: + die(str(e)) + + +# Finds the given java tool, failing with a custom error message if we can't +# find it. + + +@template +def check_java_tool(tool): + check = check_prog( + tool.upper(), (tool,), paths=java_search_paths, allow_missing=True + ) + + @depends(check) + def require_tool(result): + if result is None: + die( + "The program %s was not found. Set $JAVA_HOME to your Java " + "SDK directory or use '--with-java-bin-path={java-bin-dir}'" % tool + ) + return result + + return require_tool + + +check_java_tool("java") + + +# Java Code Coverage +# ======================================================== +option( + "--enable-java-coverage", + env="MOZ_JAVA_CODE_COVERAGE", + help="Enable Java code coverage", +) + +set_config( + "MOZ_JAVA_CODE_COVERAGE", depends("--enable-java-coverage")(lambda v: bool(v)) +) diff --git a/build/moz.configure/keyfiles.configure b/build/moz.configure/keyfiles.configure new file mode 100644 index 0000000000..242a773aac --- /dev/null +++ b/build/moz.configure/keyfiles.configure @@ -0,0 +1,68 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@template +def keyfile(desc, default=None, help=None, callback=lambda x: x): + help = help or ( + "Use the secret key contained in the given keyfile " "for %s requests" % desc + ) + name = desc.lower().replace(" ", "-") + no_key = callback("no-%s-key" % name) + + option("--with-%s-keyfile" % name, nargs=1, default=default, help=help) + + @depends("--with-%s-keyfile" % name) + @checking("for the %s key" % desc, lambda x: x and x is not no_key) + @imports(_from="__builtin__", _import="open") + @imports(_from="__builtin__", _import="IOError") + def keyfile(value): + if value: + try: + with open(value[0]) as fh: + result = fh.read().strip() + if result: + return callback(result) + raise FatalCheckError("'%s' is empty." % value[0]) + except IOError as e: + raise FatalCheckError("'%s': %s." % (value[0], e.strerror)) + return no_key + + return keyfile + + +@template +def simple_keyfile(desc, default=None): + value = keyfile(desc, default=default) + set_config("MOZ_%s_KEY" % desc.upper().replace(" ", "_"), value) + + +@template +def id_and_secret_keyfile(desc, default=None): + def id_and_secret(value): + if value.startswith("no-") and value.endswith("-key"): + id = value[:-3] + "clientid" + secret = value + elif " " in value: + id, secret = value.split(" ", 1) + else: + raise FatalCheckError("%s key file has an invalid format." % desc) + return namespace( + id=id, + secret=secret, + ) + + content = keyfile( + desc, + help="Use the client id and secret key contained " + "in the given keyfile for %s requests" % desc, + default=default, + callback=id_and_secret, + ) + + name = desc.upper().replace(" ", "_") + set_config("MOZ_%s_CLIENTID" % name, content.id) + set_config("MOZ_%s_KEY" % name, content.secret) diff --git a/build/moz.configure/lto-pgo.configure b/build/moz.configure/lto-pgo.configure new file mode 100644 index 0000000000..1fe5a1ab73 --- /dev/null +++ b/build/moz.configure/lto-pgo.configure @@ -0,0 +1,331 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# PGO +# ============================================================== +llvm_profdata = check_prog( + "LLVM_PROFDATA", ["llvm-profdata"], allow_missing=True, paths=clang_search_path +) + +option( + "--enable-profile-generate", + env="MOZ_PROFILE_GENERATE", + nargs="?", + choices=("cross",), + help="Build a PGO instrumented binary", +) + +imply_option("MOZ_PGO", depends_if("--enable-profile-generate")(lambda _: True)) + +set_config( + "MOZ_PROFILE_GENERATE", depends_if("--enable-profile-generate")(lambda _: True) +) + +set_define( + "MOZ_PROFILE_GENERATE", depends_if("--enable-profile-generate")(lambda _: True) +) + +add_old_configure_assignment( + "MOZ_PROFILE_GENERATE", 1, when="--enable-profile-generate" +) + +option( + "--enable-profile-use", + env="MOZ_PROFILE_USE", + nargs="?", + choices=("cross",), + help="Use a generated profile during the build", +) + +option( + "--with-pgo-profile-path", + help="Path to the directory with unmerged profile data to use during the build", + nargs=1, +) + +imply_option("MOZ_PGO", depends_if("--enable-profile-use")(lambda _: True)) + +set_config("MOZ_PROFILE_USE", depends_if("--enable-profile-use")(lambda _: True)) + + +@depends( + "--with-pgo-profile-path", + "--enable-profile-use", + llvm_profdata, + check_build_environment, +) +@imports("os") +def pgo_profile_path(path, pgo_use, profdata, build_env): + topobjdir = build_env.topobjdir + if topobjdir.endswith("/js/src"): + topobjdir = topobjdir[:-7] + + if not path: + return os.path.join(topobjdir, "instrumented", "merged.profdata") + if path and not pgo_use: + die("Pass --enable-profile-use to use --with-pgo-profile-path.") + if path and not profdata: + die("LLVM_PROFDATA must be set to process the pgo profile.") + if not os.path.isfile(path[0]): + die("Argument to --with-pgo-profile-path must be a file.") + if not os.path.isabs(path[0]): + die("Argument to --with-pgo-profile-path must be an absolute path.") + return path[0] + + +set_config("PGO_PROFILE_PATH", pgo_profile_path) + + +@depends(c_compiler, pgo_profile_path, target_is_windows) +@imports("multiprocessing") +@imports(_from="__builtin__", _import="min") +def pgo_flags(compiler, profdata, target_is_windows): + if compiler.type == "gcc": + return namespace( + gen_cflags=["-fprofile-generate"], + gen_ldflags=["-fprofile-generate"], + use_cflags=["-fprofile-use", "-fprofile-correction", "-Wcoverage-mismatch"], + use_ldflags=["-fprofile-use"], + ) + + if compiler.type in ("clang-cl", "clang"): + prefix = "" + if compiler.type == "clang-cl": + prefix = "/clang:" + gen_ldflags = None + else: + gen_ldflags = ["-fprofile-generate"] + + gen_cflags = [prefix + "-fprofile-generate"] + if target_is_windows: + # native llvm-profdata.exe on Windows can't read profile data + # if name compression is enabled (which cross-compiling enables + # by default) + gen_cflags += ["-mllvm", "-enable-name-compression=false"] + + return namespace( + gen_cflags=gen_cflags, + gen_ldflags=gen_ldflags, + use_cflags=[ + prefix + "-fprofile-use=%s" % profdata, + # Some error messages about mismatched profile data + # come in via -Wbackend-plugin, so disable those too. + "-Wno-error=backend-plugin", + ], + use_ldflags=[], + ) + + +set_config("PROFILE_GEN_CFLAGS", pgo_flags.gen_cflags) +set_config("PROFILE_GEN_LDFLAGS", pgo_flags.gen_ldflags) +set_config("PROFILE_USE_CFLAGS", pgo_flags.use_cflags) +set_config("PROFILE_USE_LDFLAGS", pgo_flags.use_ldflags) + +option( + "--with-pgo-jarlog", + help="Use the provided jarlog file when packaging during a profile-use " "build", + nargs=1, +) + +set_config("PGO_JARLOG_PATH", depends_if("--with-pgo-jarlog")(lambda p: p)) + + +@depends("MOZ_PGO", "--enable-profile-use", "--enable-profile-generate", c_compiler) +def moz_pgo_rust(pgo, profile_use, profile_generate, c_compiler): + if not pgo: + return + + # Enabling PGO through MOZ_PGO only and not --enable* flags. + if not profile_use and not profile_generate: + return + + if profile_use and profile_generate: + die("Cannot build with --enable-profile-use and --enable-profile-generate.") + + want_cross = (len(profile_use) and profile_use[0] == "cross") or ( + len(profile_generate) and profile_generate[0] == "cross" + ) + + if not want_cross: + return + + if c_compiler.type == "gcc": + die("Cannot use cross-language PGO with GCC.") + + return True + + +set_config("MOZ_PGO_RUST", moz_pgo_rust) + +# LTO +# ============================================================== + +option( + "--enable-lto", + env="MOZ_LTO", + nargs="?", + choices=("full", "thin", "cross"), + help="Enable LTO", +) + +option( + env="MOZ_LD64_KNOWN_GOOD", + nargs=1, + help="Indicate that ld64 is free of symbol aliasing bugs.", +) + +imply_option("MOZ_LD64_KNOWN_GOOD", depends_if("MOZ_AUTOMATION")(lambda _: True)) + + +@depends( + "--enable-lto", + c_compiler, + select_linker, + "MOZ_AUTOMATION", + "MOZ_LD64_KNOWN_GOOD", + target, + "--enable-profile-generate", + new_pass_manager_flags, +) +@imports("multiprocessing") +def lto( + value, + c_compiler, + select_linker, + automation, + ld64_known_good, + target, + instrumented_build, + newpm_flags, +): + cflags = [] + ldflags = [] + enabled = None + rust_lto = False + + if value: + if instrumented_build: + log.warning("Disabling LTO because --enable-profile-generate is specified") + return + + enabled = True + # `cross` implies `thin`, but with Rust code participating in LTO + # as well. Make that a little more explicit. + if len(value) and value[0].lower() == "cross": + if c_compiler.type == "gcc": + die("Cross-language LTO is not supported with GCC.") + + rust_lto = True + value = ["thin"] + + if ( + target.kernel == "Darwin" + and target.os == "OSX" + and len(value) + and value[0].lower() == "cross" + and not ld64_known_good + ): + die( + "The Mac linker is known to have a bug that affects cross-language " + "LTO. If you know that your linker is free from this bug, please " + "set the environment variable `MOZ_LD64_KNOWN_GOOD=1` and re-run " + "configure." + ) + + if c_compiler.type == "clang": + if len(value) and value[0].lower() == "full": + cflags.append("-flto") + ldflags.append("-flto") + else: + cflags.append("-flto=thin") + ldflags.append("-flto=thin") + elif c_compiler.type == "clang-cl": + if len(value) and value[0].lower() == "full": + cflags.append("-flto") + else: + cflags.append("-flto=thin") + # With clang-cl, -flto can only be used with -c or -fuse-ld=lld. + # AC_TRY_LINKs during configure don't have -c, so pass -fuse-ld=lld. + cflags.append("-fuse-ld=lld") + + # Explicitly set the CPU to optimize for so the linker doesn't + # choose a poor default. Rust compilation by default uses the + # pentium4 CPU on x86: + # + # https://github.com/rust-lang/rust/blob/master/src/librustc_target/spec/i686_pc_windows_msvc.rs#L5 + # + # which specifically supports "long" (multi-byte) nops. See + # https://bugzilla.mozilla.org/show_bug.cgi?id=1568450#c8 for details. + # + # The pentium4 seems like kind of a weird CPU to optimize for, but + # it seems to have worked out OK thus far. LLVM does not seem to + # specifically schedule code for the pentium4's deep pipeline, so + # that probably contributes to it being an OK default for our + # purposes. + if target.cpu == "x86": + ldflags.append("-mllvm:-mcpu=pentium4") + # This is also the CPU that Rust uses. The LLVM source code + # recommends this as the "generic 64-bit specific x86 processor model": + # + # https://github.com/llvm/llvm-project/blob/e7694f34ab6a12b8bb480cbfcb396d0a64fe965f/llvm/lib/Target/X86/X86.td#L1165-L1187 + if target.cpu == "x86_64": + ldflags.append("-mllvm:-mcpu=x86-64") + # We do not need special flags for arm64. Hooray for fixed-length + # instruction sets. + else: + num_cores = multiprocessing.cpu_count() + if len(value) and value[0].lower() == "thin": + die( + "gcc does not support thin LTO. Use `--enable-lto` " + "to enable full LTO for gcc." + ) + else: + cflags.append("-flto") + cflags.append("-flifetime-dse=1") + + ldflags.append("-flto=%s" % num_cores) + ldflags.append("-flifetime-dse=1") + + # Tell LTO not to inline functions above a certain size, to mitigate + # binary size growth while still getting good performance. + # (For hot functions, PGO will put a multiplier on this limit.) + if target.os == "WINNT": + ldflags.append("-mllvm:-import-instr-limit=10") + elif target.os == "OSX": + ldflags.append("-Wl,-mllvm,-import-instr-limit=10") + elif c_compiler.type == "clang": + ldflags.append("-Wl,-plugin-opt=-import-instr-limit=10") + + # If we're using the new pass manager, we can also enable the new PM + # during LTO. Further we can use the resulting size savings to increase + # the import limit in hot functions. + if newpm_flags: + if target.os == "WINNT": + # On Windows, this flag requires a change from clang-12, which + # is applied as a patch to our automation toolchain. + if automation or c_compiler.version >= "12.0.0": + ldflags.append("-opt:ltonewpassmanager") + ldflags.append("-mllvm:-import-hot-multiplier=30") + elif select_linker.KIND != "ld64" and c_compiler.type == "clang": + ldflags.append("-Wl,-plugin-opt=new-pass-manager") + ldflags.append("-Wl,-plugin-opt=-import-hot-multiplier=30") + + return namespace( + enabled=enabled, + cflags=cflags, + ldflags=ldflags, + rust_lto=rust_lto, + ) + + +add_old_configure_assignment("MOZ_LTO", lto.enabled) +set_config("MOZ_LTO", lto.enabled) +set_define("MOZ_LTO", lto.enabled) +set_config("MOZ_LTO_CFLAGS", lto.cflags) +set_config("MOZ_LTO_LDFLAGS", lto.ldflags) +set_config("MOZ_LTO_RUST_CROSS", lto.rust_lto) +add_old_configure_assignment("MOZ_LTO_CFLAGS", lto.cflags) +add_old_configure_assignment("MOZ_LTO_LDFLAGS", lto.ldflags) diff --git a/build/moz.configure/memory.configure b/build/moz.configure/memory.configure new file mode 100644 index 0000000000..855706b457 --- /dev/null +++ b/build/moz.configure/memory.configure @@ -0,0 +1,98 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@depends(target) +def jemalloc_default(target): + return target.kernel in ("Darwin", "Linux", "WINNT") + + +option( + "--enable-jemalloc", + env="MOZ_MEMORY", + default=jemalloc_default, + help="{Replace|Do not replace} memory allocator with jemalloc", +) + + +set_config("MOZ_MEMORY", True, when="--enable-jemalloc") +set_define("MOZ_MEMORY", True, when="--enable-jemalloc") +add_old_configure_assignment("MOZ_MEMORY", True, when="--enable-jemalloc") + + +@depends(milestone, build_project) +def replace_malloc_default(milestone, build_project): + if build_project == "memory": + return True + if milestone.is_early_beta_or_earlier and build_project != "js": + return True + + +option( + "--enable-replace-malloc", + default=replace_malloc_default, + when="--enable-jemalloc", + help="{Enable|Disable} ability to dynamically replace the malloc implementation", +) + + +set_config("MOZ_REPLACE_MALLOC", True, when="--enable-replace-malloc") +set_define("MOZ_REPLACE_MALLOC", True, when="--enable-replace-malloc") + + +@depends(build_project, when="--enable-replace-malloc") +def replace_malloc_static(build_project): + # Default to statically linking replace-malloc libraries that can be + # statically linked, except when building with --enable-project=memory. + if build_project != "memory": + return True + + +set_config("MOZ_REPLACE_MALLOC_STATIC", replace_malloc_static) + +# PHC (Probabilistic Heap Checker) +# ============================================================== + +# In general, it only makes sense for PHC to run on the platforms that have a +# crash reporter. +@depends( + milestone, + target, + replace_malloc_default, + "--enable-replace-malloc", + when="--enable-jemalloc", +) +def phc_default(milestone, target, replace_malloc_default, replace_malloc): + if not replace_malloc_default or ( + replace_malloc.origin != "default" and not replace_malloc + ): + return False + # Nightly or early beta only because PHC has a non-negligible performance cost. + if not milestone.is_early_beta_or_earlier: + return False + # Both Linux32 and Win32 have frequent crashes when stack tracing (for + # unclear reasons), so PHC is enabled only on 64-bit only in both cases. + # + # XXX: PHC is implemented but not yet enabled on Mac. Bug 1576515 is about + # enabling it on Mac, but it is blocked by bug 1035892. + return ( + target.os == "GNU" and target.kernel == "Linux" and target.bitness == 64 + ) or (target.kernel == "WINNT" and target.bitness == 64) + + +option( + "--enable-phc", + env="MOZ_PHC", + default=phc_default, + when="--enable-jemalloc", + help="{Enable|Disable} PHC (Probabilistic Memory Checker). " + "Also enables replace-malloc and frame pointers", +) +imply_option("--enable-replace-malloc", True, when="--enable-phc") +imply_option("--enable-frame-pointers", True, when="--enable-phc") + + +set_config("MOZ_PHC", True, when="--enable-phc") diff --git a/build/moz.configure/node.configure b/build/moz.configure/node.configure new file mode 100644 index 0000000000..ba2003d197 --- /dev/null +++ b/build/moz.configure/node.configure @@ -0,0 +1,71 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +option("--disable-nodejs", help="Require Node.js to build") +option(env="NODEJS", nargs=1, help="Path to nodejs") + + +@depends("--enable-nodejs", "NODEJS", bootstrap_search_path("node")) +@checking( + "for nodejs", callback=lambda x: "%s (%s)" % (x.path, x.str_version) if x else "no" +) +@imports(_from="mozbuild.nodeutil", _import="find_node_executable") +@imports(_from="mozbuild.nodeutil", _import="NODE_MIN_VERSION") +def nodejs(require, env_node, search_path): + # We don't use the dependency directly, but having it ensures the + # auto-upgrade code in bootstrap_search_path is triggered, while + # find_node_executable will use more or less the same search path. + # We do however need to use the variable for the configure lint + # not to fail. + search_path + + node_exe = env_node[0] if env_node else None + + nodejs, version = find_node_executable(node_exe) + + MAYBE_FILE_A_BUG = """ + + Executing `mach bootstrap --no-system-changes` should + install a compatible version in ~/.mozbuild on most platforms. + If you believe this is a bug, <https://mzl.la/2vLbXAv> is a good way + to file. More details: <https://bit.ly/2BbyD1E> + """ + + if not nodejs: + msg = ( + "could not find Node.js executable later than %s; ensure " + "`node` or `nodejs` is in PATH or set NODEJS in environment " + "to point to an executable.%s" % (NODE_MIN_VERSION, MAYBE_FILE_A_BUG) + ) + + if require: + raise FatalCheckError(msg) + else: + log.warning(msg) + log.warning("(This will become an error in the near future.)") + return + + if not version: + msg = "NODEJS must point to node %s or newer; found node location: %s. %s" % ( + NODE_MIN_VERSION, + nodejs, + MAYBE_FILE_A_BUG, + ) + + if require: + raise FatalCheckError(msg) + else: + log.warning(msg) + return + + return namespace( + path=nodejs, + version=version, + str_version=".".join(str(v) for v in version), + ) + + +set_config("NODEJS", depends_if(nodejs)(lambda p: p.path)) diff --git a/build/moz.configure/nspr.configure b/build/moz.configure/nspr.configure new file mode 100644 index 0000000000..2b21a66f03 --- /dev/null +++ b/build/moz.configure/nspr.configure @@ -0,0 +1,117 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Top-level configure defaults to building NSPR from source. Standalone JS +# doesn't. +option( + "--enable-nspr-build", + when=js_standalone, + help="{Build|Do not build} NSPR from source tree", +) + + +@depends("--enable-nspr-build", when=js_standalone) +def enable_nspr_build(enable): + if enable: + return enable + + +option("--with-system-nspr", help="Use system NSPR") + + +@depends(enable_nspr_build, "--with-system-nspr", js_standalone) +def build_nspr(nspr_build, system_nspr, js_standalone): + if nspr_build is not None and nspr_build.origin != "default": + if nspr_build and system_nspr: + die("Cannot use both --enable-nspr-build and --with-system-nspr") + if js_standalone: + return nspr_build + return not system_nspr + + +set_config("MOZ_BUILD_NSPR", True, when=build_nspr) +set_config("MOZ_SYSTEM_NSPR", True, when="--with-system-nspr") + + +@depends(build_nspr, "--with-system-nspr", js_standalone) +def js_without_nspr(build_nspr, system_nspr, js_standalone): + if js_standalone: + return not build_nspr and not system_nspr + + +set_config("JS_WITHOUT_NSPR", True, when=js_without_nspr) +set_define("JS_WITHOUT_NSPR", True, when=js_without_nspr) + + +@depends(js_standalone) +def nspr_minver(js_standalone): + if js_standalone: + return "nspr >= 4.10" + return "nspr >= 4.26" + + +nspr_pkg = pkg_check_modules("NSPR", nspr_minver, when="--with-system-nspr") + + +@depends_if(nspr_pkg) +def nspr_pkg(nspr_pkg): + def extract(prefix, list): + for item in list: + if item.startswith(prefix): + return item[len(prefix) :] + return "" + + include_dir = extract("-I", nspr_pkg.cflags) + lib_dir = extract("-L", nspr_pkg.libs) + return namespace( + cflags=nspr_pkg.cflags, + include_dir=include_dir, + libs=nspr_pkg.libs, + lib_dir=lib_dir, + ) + + +@depends("--with-system-nspr", nspr_minver) +def pkgconf_requires_private(system_nspr, nspr_minver): + if not system_nspr: + return "" + return "Requires.private: %s" % nspr_minver + + +set_config("PKGCONF_REQUIRES_PRIVATE", pkgconf_requires_private) + +# pkg_check_modules takes care of NSPR_CFLAGS and NSPR_LIBS when using --with-system-nspr. +@depends(check_build_environment, c_compiler, fold_libs, when=build_nspr) +def nspr_config(build_env, c_compiler, fold_libs): + libs = ["nspr4", "plc4", "plds4"] + if c_compiler.type == "clang-cl": + lib_dir = os.path.join(build_env.dist, "lib") + libs = [os.path.join(lib_dir, "%s.lib" % lib) for lib in libs] + else: + lib_dir = os.path.join(build_env.dist, "lib" if fold_libs else "bin") + libs = ["-L%s" % lib_dir] + ["-l%s" % lib for lib in libs] + + include_dir = os.path.join(build_env.dist, "include", "nspr") + return namespace( + cflags=["-I%s" % include_dir], + include_dir=include_dir, + libs=libs, + lib_dir=lib_dir, + ) + + +set_config("NSPR_CFLAGS", nspr_config.cflags, when=nspr_config) +set_config("NSPR_LIBS", nspr_config.libs, when=nspr_config) + +set_config("NSPR_INCLUDE_DIR", nspr_config.include_dir, when=nspr_config) +set_config("NSPR_LIB_DIR", nspr_config.lib_dir, when=nspr_config) +set_config("NSPR_INCLUDE_DIR", nspr_pkg.include_dir, when=nspr_pkg) +set_config("NSPR_LIB_DIR", nspr_pkg.lib_dir, when=nspr_pkg) + +add_old_configure_assignment("NSPR_CFLAGS", nspr_config.cflags, when=nspr_config) +add_old_configure_assignment("NSPR_LIBS", nspr_config.libs, when=nspr_config) +add_old_configure_assignment("NSPR_CFLAGS", nspr_pkg.cflags, when=nspr_pkg) +add_old_configure_assignment("NSPR_LIBS", nspr_pkg.libs, when=nspr_pkg) diff --git a/build/moz.configure/nss.configure b/build/moz.configure/nss.configure new file mode 100644 index 0000000000..3cdee33061 --- /dev/null +++ b/build/moz.configure/nss.configure @@ -0,0 +1,30 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +option("--with-system-nss", help="Use system NSS") + +imply_option("--with-system-nspr", True, when="--with-system-nss") + +nss_pkg = pkg_check_modules( + "NSS", "nss >= 3.61", when="--with-system-nss", config=False +) + +set_config("MOZ_SYSTEM_NSS", True, when="--with-system-nss") + + +@depends(nss_pkg, check_build_environment) +def nss_config(nss_pkg, build_env): + cflags = ["-I%s" % os.path.join(build_env.dist, "include", "nss")] + libs = None + if nss_pkg: + cflags = list(nss_pkg.cflags) + cflags + libs = nss_pkg.libs + return namespace(cflags=cflags, libs=libs) + + +set_config("NSS_CFLAGS", nss_config.cflags) +set_config("NSS_LIBS", nss_config.libs) +add_old_configure_assignment("NSS_CFLAGS", nss_config.cflags) diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure new file mode 100644 index 0000000000..f1ebd2c35f --- /dev/null +++ b/build/moz.configure/old.configure @@ -0,0 +1,381 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +m4 = check_prog( + "M4", + ( + "gm4", + "m4", + ), +) + + +@depends(mozconfig) +def prepare_mozconfig(mozconfig): + if mozconfig["path"]: + items = {} + for key, value in mozconfig["vars"]["added"].items(): + items[key] = (value, "added") + for key, (old, value) in mozconfig["vars"]["modified"].items(): + items[key] = (value, "modified") + for t in ("env", "vars"): + for key in mozconfig[t]["removed"].keys(): + items[key] = (None, "removed " + t) + return items + + +@depends("OLD_CONFIGURE", build_project) +def old_configure(old_configure, build_project): + # os.path.abspath in the sandbox will ensure forward slashes on Windows, + # which is actually necessary because this path actually ends up literally + # as $0, and backslashes there breaks autoconf's detection of the source + # directory. + old_configure = os.path.abspath(old_configure[0]) + if build_project == "js": + old_configure_dir = os.path.dirname(old_configure) + if not old_configure_dir.endswith("/js/src"): + old_configure = os.path.join( + old_configure_dir, "js", "src", os.path.basename(old_configure) + ) + return old_configure + + +@depends(prepare_mozconfig, old_configure_assignments) +@imports(_from="__builtin__", _import="open") +@imports(_from="__builtin__", _import="print") +@imports(_from="__builtin__", _import="sorted") +@imports(_from="mozbuild.shellutil", _import="quote") +def prepare_configure(mozconfig, old_configure_assignments): + with open("old-configure.vars", "w") as out: + log.debug("Injecting the following to old-configure:") + + def inject(command): + print(command, file=out) # noqa Python 2vs3 + log.debug("| %s", command) + + if mozconfig: + inject("# start of mozconfig values") + for key, (value, action) in sorted(mozconfig.items()): + if action.startswith("removed "): + inject("unset %s # from %s" % (key, action[len("removed ") :])) + else: + inject("%s=%s # %s" % (key, quote(value), action)) + + inject("# end of mozconfig values") + + for k, v in old_configure_assignments: + inject("%s=%s" % (k, quote(v))) + + +@template +def old_configure_options(*options): + for opt in options: + option(opt, nargs="*", help="Help missing for old configure options") + + @dependable + def all_options(): + return list(options) + + return depends( + host_for_sub_configure, target_for_sub_configure, all_options, *options + ) + + +@old_configure_options( + "--cache-file", + "--datadir", + "--enable-crashreporter", + "--enable-dbus", + "--enable-debug-js-modules", + "--enable-dump-painting", + "--enable-extensions", + "--enable-libproxy", + "--enable-logrefcnt", + "--enable-necko-wifi", + "--enable-negotiateauth", + "--enable-official-branding", + "--enable-parental-controls", + "--enable-sandbox", + "--enable-system-cairo", + "--enable-system-extension-dirs", + "--enable-system-pixman", + "--enable-universalchardet", + "--enable-updater", + "--enable-xul", + "--enable-zipwriter", + "--includedir", + "--libdir", + "--prefix", + "--with-android-max-sdk", + "--with-android-min-sdk", + "--with-branding", + "--with-distribution-id", + "--with-macbundlename-prefix", + "--with-system-libevent", + "--with-system-png", + "--with-user-appdir", + "--x-includes", + "--x-libraries", +) +def prepare_configure_options(host, target, all_options, *options): + # old-configure only supports the options listed in @old_configure_options + # so we don't need to pass it every single option we've been passed. Only + # the ones that are not supported by python configure need to. + options = [ + value.format(name) + for name, value in zip(all_options, options) + if value.origin != "default" + ] + [host, target] + + return namespace(options=options, all_options=all_options) + + +@template +def old_configure_for(old_configure_path, extra_env=None): + if extra_env is None: + extra_env = dependable(None) + + @depends( + prepare_configure, + prepare_configure_options, + altered_path, + extra_env, + check_build_environment, + old_configure_path, + "MOZILLABUILD", + awk, + m4, + shell, + ) + @imports(_from="__builtin__", _import="compile") + @imports(_from="__builtin__", _import="open") + @imports(_from="__builtin__", _import="OSError") + @imports("glob") + @imports("itertools") + @imports("logging") + @imports("os") + @imports("subprocess") + @imports("sys") + @imports(_from="mozbuild.shellutil", _import="quote") + @imports(_from="mozbuild.shellutil", _import="split") + @imports(_from="tempfile", _import="NamedTemporaryFile") + @imports(_from="subprocess", _import="CalledProcessError") + @imports(_from="six", _import="exec_") + @imports(_from="six", _import="iteritems") + @imports(_from="six", _import="string_types") + def old_configure( + prepare_configure, + prepare_configure_options, + altered_path, + extra_env, + build_env, + old_configure, + mozillabuild, + awk, + m4, + shell, + ): + # Use prepare_configure to make lint happy + prepare_configure + refresh = True + if os.path.exists(old_configure): + mtime = os.path.getmtime(old_configure) + aclocal = os.path.join(build_env.topsrcdir, "build", "autoconf", "*.m4") + for input in itertools.chain( + ( + old_configure + ".in", + os.path.join(os.path.dirname(old_configure), "aclocal.m4"), + ), + glob.iglob(aclocal), + ): + if os.path.getmtime(input) > mtime: + break + else: + refresh = False + + if refresh: + autoconf = os.path.join( + build_env.topsrcdir, "build", "autoconf", "autoconf.sh" + ) + log.info("Refreshing %s with %s", old_configure, autoconf) + env = dict(os.environ) + env["M4"] = m4 + env["AWK"] = awk + env["AC_MACRODIR"] = os.path.join(build_env.topsrcdir, "build", "autoconf") + + try: + script = subprocess.check_output( + [ + shell, + autoconf, + "--localdir=%s" % os.path.dirname(old_configure), + old_configure + ".in", + ], + # Fix the working directory, so that when m4 is called, that + # includes of relative paths are deterministically resolved + # relative to the directory containing old-configure. + cwd=os.path.dirname(old_configure), + env=env, + ) + except CalledProcessError as exc: + # Autoconf on win32 may break due to a bad $PATH. Let the user know + # their $PATH is suspect. + if mozillabuild: + mozillabuild_path = normsep(mozillabuild[0]) + sh_path = normsep(find_program("sh")) + if mozillabuild_path not in sh_path: + log.warning( + "The '{}msys/bin' directory is not first in $PATH. " + "This may cause autoconf to fail. ($PATH is currently " + "set to: {})".format(mozillabuild_path, os.environ["PATH"]) + ) + die("autoconf exited with return code {}".format(exc.returncode)) + + if not script: + die( + "Generated old-configure is empty! Check that your autoconf 2.13 program works!" + ) + + # Make old-configure append to config.log, where we put our own log. + # This could be done with a m4 macro, but it's way easier this way + script = script.replace(b">./config.log", b">>${CONFIG_LOG=./config.log}") + + with NamedTemporaryFile( + mode="wb", + prefix=os.path.basename(old_configure), + dir=os.path.dirname(old_configure), + delete=False, + ) as fh: + fh.write(script) + + try: + os.rename(fh.name, old_configure) + except OSError: + try: + # Likely the file already existed (on Windows). Retry after removing it. + os.remove(old_configure) + os.rename(fh.name, old_configure) + except OSError as e: + die("Failed re-creating old-configure: %s" % e.message) + + cmd = [shell, old_configure] + prepare_configure_options.options + + env = dict(os.environ) + + # For debugging purpose, in case it's not what we'd expect. + log.debug("Running %s", quote(*cmd)) + + # Our logging goes to config.log, the same file old.configure uses. + # We can't share the handle on the file, so close it. + logger = logging.getLogger("moz.configure") + config_log = None + for handler in logger.handlers: + if isinstance(handler, logging.FileHandler): + config_log = handler + config_log.close() + logger.removeHandler(config_log) + env["CONFIG_LOG"] = config_log.baseFilename + log_size = os.path.getsize(config_log.baseFilename) + break + + if altered_path: + env["PATH"] = altered_path + + if extra_env: + env.update(extra_env) + + env["OLD_CONFIGURE_VARS"] = os.path.join( + build_env.topobjdir, "old-configure.vars" + ) + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env + ) + while True: + line = proc.stdout.readline() + if not line: + break + log.info(line.rstrip()) + + ret = proc.wait() + if ret: + with log.queue_debug(): + if config_log: + with open(config_log.baseFilename, "r") as fh: + fh.seek(log_size) + for line in fh: + log.debug(line.rstrip()) + log.error("old-configure failed") + sys.exit(ret) + + if config_log: + # Create a new handler in append mode + handler = logging.FileHandler(config_log.baseFilename, mode="a", delay=True) + handler.setFormatter(config_log.formatter) + logger.addHandler(handler) + + raw_config = { + "split": split, + "unique_list": unique_list, + } + with open("config.data", "r") as fh: + code = compile(fh.read(), "config.data", "exec") + exec_(code, raw_config) + + # Ensure all the flags known to old-configure appear in the + # @old_configure_options above. + all_options = set(prepare_configure_options.all_options) + for flag in raw_config["flags"]: + if flag not in all_options: + die( + "Missing option in `@old_configure_options` in %s: %s", + __file__, + flag, + ) + + # If the code execution above fails, we want to keep the file around for + # debugging. + os.remove("config.data") + + return namespace( + **{ + c: [ + (k[1:-1], v[1:-1] if isinstance(v, string_types) else v) + for k, v in raw_config[c] + ] + for c in ("substs", "defines") + } + ) + + return old_configure + + +old_configure = old_configure_for(old_configure) +set_config("OLD_CONFIGURE_SUBSTS", old_configure.substs) +set_config("OLD_CONFIGURE_DEFINES", old_configure.defines) + + +# Assuming no other option is declared after this function, handle the +# env options that were injected by mozconfig_options by creating dummy +# Option instances and having the sandbox's CommandLineHelper handle +# them. We only do so for options that haven't been declared so far, +# which should be a proxy for the options that old-configure handles +# and that we don't know anything about. +@depends("--help") +@imports("__sandbox__") +@imports(_from="mozbuild.configure.options", _import="Option") +def remaining_mozconfig_options(_): + helper = __sandbox__._helper + for arg in list(helper): + if helper._origins[arg] != "mozconfig": + continue + name = arg.split("=", 1)[0] + if name.isupper() and name not in __sandbox__._options: + option = Option(env=name, nargs="*", help=name) + helper.handle(option) + + +# Please do not add anything after remaining_mozconfig_options() diff --git a/build/moz.configure/pkg.configure b/build/moz.configure/pkg.configure new file mode 100644 index 0000000000..20d90f17fd --- /dev/null +++ b/build/moz.configure/pkg.configure @@ -0,0 +1,103 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@depends(toolchain_prefix, when=compile_environment) +def pkg_config(prefixes): + return tuple("{}pkg-config".format(p) for p in (prefixes or ()) + ("",)) + + +pkg_config = check_prog("PKG_CONFIG", pkg_config, allow_missing=True) + + +@depends_if(pkg_config) +@checking("for pkg-config version") +def pkg_config_version(pkg_config): + return Version(check_cmd_output(pkg_config, "--version").rstrip()) + + +# Locates the given module using pkg-config. +# - `var` determines the name of variables to set when the package is found. +# <var>_CFLAGS and <var>_LIBS are set with corresponding values. +# - `package_desc` package name and version requirement string, list of +# strings describing packages to locate, or depends function that will +# resolve to such a string or list of strings. +# - `when` a depends function that will determine whether to perform +# any checks (default is to always perform checks). +# - `allow_missing` If set, failure to fulfill the package description +# will not result in an error or logged message, and any error message +# will be returned to the caller. +# Returns `True` when the package description is fulfilled. + + +@template +def pkg_check_modules(var, package_desc, when=always, allow_missing=False, config=True): + if isinstance(package_desc, (tuple, list)): + package_desc = " ".join(package_desc) + package_desc = dependable(package_desc) + allow_missing = dependable(allow_missing) + + @depends(when, "--enable-compile-environment") + def when_and_compile_environment(when, compile_environment): + return when and compile_environment + + @depends(pkg_config, pkg_config_version, when=when_and_compile_environment) + def check_pkg_config(pkg_config, version): + min_version = "0.9.0" + if pkg_config is None: + die( + "*** The pkg-config script could not be found. Make sure it is\n" + "*** in your path, or set the PKG_CONFIG environment variable\n" + "*** to the full path to pkg-config." + ) + if version < min_version: + die( + "*** Your version of pkg-config is too old. You need version %s or newer.", + min_version, + ) + + @depends(pkg_config, package_desc, allow_missing, when=when_and_compile_environment) + @imports("sys") + @imports(_from="mozbuild.configure.util", _import="LineIO") + def package(pkg_config, package_desc, allow_missing): + # package_desc may start as a depends function, so we can't use + # @checking here. + log.info("checking for %s... " % package_desc) + retcode, stdout, stderr = get_cmd_output( + pkg_config, "--errors-to-stdout", "--print-errors", package_desc + ) + if retcode == 0: + log.info("yes") + return True + log.info("no") + log_writer = log.warning if allow_missing else log.error + with LineIO(lambda l: log_writer(l)) as o: + o.write(stdout) + if not allow_missing: + sys.exit(1) + + @depends(pkg_config, package_desc, when=package) + @checking("%s_CFLAGS" % var, callback=lambda t: " ".join(t)) + def pkg_cflags(pkg_config, package_desc): + flags = check_cmd_output(pkg_config, "--cflags", package_desc) + return tuple(flags.split()) + + @depends(pkg_config, package_desc, when=package) + @checking("%s_LIBS" % var, callback=lambda t: " ".join(t)) + def pkg_libs(pkg_config, package_desc): + libs = check_cmd_output(pkg_config, "--libs", package_desc) + # Remove evil flags like -Wl,--export-dynamic + return tuple(libs.replace("-Wl,--export-dynamic", "").split()) + + @depends(pkg_cflags, pkg_libs, when=package) + def pkg_info(cflags, libs): + return namespace(cflags=cflags, libs=libs) + + if config: + set_config("%s_CFLAGS" % var, pkg_cflags) + set_config("%s_LIBS" % var, pkg_libs) + + return pkg_info diff --git a/build/moz.configure/rust.configure b/build/moz.configure/rust.configure new file mode 100644 index 0000000000..ff3dbe066e --- /dev/null +++ b/build/moz.configure/rust.configure @@ -0,0 +1,552 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +# Rust is required by `rust_compiler` below. We allow_missing here +# to propagate failures to the better error message there. +option(env="RUSTC", nargs=1, help="Path to the rust compiler") +option(env="CARGO", nargs=1, help="Path to the Cargo package manager") + +rustc = check_prog( + "_RUSTC", + ["rustc"], + what="rustc", + paths=rust_search_path, + input="RUSTC", + allow_missing=True, +) +cargo = check_prog( + "_CARGO", + ["cargo"], + what="cargo", + paths=rust_search_path, + input="CARGO", + allow_missing=True, +) + + +@template +def unwrap_rustup(prog, name): + # rustc and cargo can either be rustup wrappers, or they can be the actual, + # plain executables. For cargo, on OSX, rustup sets DYLD_LIBRARY_PATH (at + # least until https://github.com/rust-lang/rustup.rs/pull/1752 is merged + # and shipped) and that can wreak havoc (see bug 1536486). Similarly, for + # rustc, rustup silently honors toolchain overrides set by vendored crates + # (see bug 1547196). + # + # In either case, we need to find the plain executables. + # + # To achieve that, try to run `PROG +stable`. When the rustup wrapper is in + # use, it either prints PROG's help and exits with status 0, or prints + # an error message (error: toolchain 'stable' is not installed) and exits + # with status 1. In the cargo case, when plain cargo is in use, it exits + # with a different error message (e.g. "error: no such subcommand: + # `+stable`"), and exits with status 101. + # + # Unfortunately, in the rustc case, when plain rustc is in use, + # `rustc +stable` will exit with status 1, complaining about a missing + # "+stable" file. We'll examine the error output to try and distinguish + # between failing rustup and failing rustc. + @depends(prog, dependable(name)) + @imports(_from="__builtin__", _import="open") + @imports("os") + def unwrap(prog, name): + if not prog: + return + + def from_rustup_which(): + out = check_cmd_output("rustup", "which", name, executable=prog).rstrip() + # If for some reason the above failed to return something, keep the + # PROG we found originally. + if out: + log.info("Actually using '%s'", out) + return out + + log.info("No `rustup which` output, using '%s'", prog) + return prog + + (retcode, stdout, stderr) = get_cmd_output(prog, "+stable") + + if name == "cargo" and retcode != 101: + prog = from_rustup_which() + elif name == "rustc": + if retcode == 0: + prog = from_rustup_which() + elif "+stable" in stderr: + # PROG looks like plain `rustc`. + pass + else: + # Assume PROG looks like `rustup`. This case is a little weird, + # insofar as the user doesn't have the "stable" toolchain + # installed, but go ahead and unwrap anyway: the user might + # have only certain versions, beta, or nightly installed, and + # we'll catch invalid versions later. + prog = from_rustup_which() + + return prog + + return unwrap + + +rustc = unwrap_rustup(rustc, "rustc") +cargo = unwrap_rustup(cargo, "cargo") + + +set_config("CARGO", cargo) +set_config("RUSTC", rustc) + + +@depends_if(rustc) +@checking("rustc version", lambda info: info.version) +def rustc_info(rustc): + if not rustc: + return + out = check_cmd_output(rustc, "--version", "--verbose").splitlines() + info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:]) + return namespace( + version=Version(info.get("release", "0")), + commit=info.get("commit-hash", "unknown"), + host=info["host"], + llvm_version=Version(info.get("LLVM version", "0")), + ) + + +set_config( + "RUSTC_VERSION", + depends(rustc_info)(lambda info: str(info.version) if info else None), +) + + +@depends_if(cargo) +@checking("cargo version", lambda info: info.version) +@imports("re") +def cargo_info(cargo): + if not cargo: + return + out = check_cmd_output(cargo, "--version", "--verbose").splitlines() + info = dict((s.strip() for s in line.split(":", 1)) for line in out[1:]) + version = info.get("release") + # Older versions of cargo didn't support --verbose, in which case, they + # only output a not-really-pleasant-to-parse output. Fortunately, they + # don't error out, so we can just try some regexp matching on the output + # we already got. + if version is None: + VERSION_FORMAT = r"^cargo (\d\.\d+\.\d+).*" + + m = re.search(VERSION_FORMAT, out[0]) + # Fail fast if cargo changes its output on us. + if not m: + die("Could not determine cargo version from output: %s", out) + version = m.group(1) + + return namespace( + version=Version(version), + ) + + +@depends(rustc_info, cargo_info, build_project) +@imports(_from="mozboot.util", _import="MINIMUM_RUST_VERSION") +@imports(_from="textwrap", _import="dedent") +def rust_compiler(rustc_info, cargo_info, build_project): + if not rustc_info: + die( + dedent( + """\ + Rust compiler not found. + To compile rust language sources, you must have 'rustc' in your path. + See https://www.rust-lang.org/ for more information. + + You can install rust by running './mach bootstrap' + or by directly running the installer from https://rustup.rs/ + """ + ) + ) + if build_project == "tools/crashreporter": + rustc_min_version = Version("1.47.0") + else: + rustc_min_version = Version(MINIMUM_RUST_VERSION) + cargo_min_version = rustc_min_version + + version = rustc_info.version + is_nightly = "nightly" in version.version + is_version_number_match = ( + version.major == rustc_min_version.major + and version.minor == rustc_min_version.minor + and version.patch == rustc_min_version.patch + ) + + if version < rustc_min_version or (is_version_number_match and is_nightly): + die( + dedent( + """\ + Rust compiler {} is too old. + + To compile Rust language sources please install at least + version {} of the 'rustc' toolchain (or, if using nightly, + at least one version newer than {}) and make sure it is + first in your path. + + You can verify this by typing 'rustc --version'. + + If you have the 'rustup' tool installed you can upgrade + to the latest release by typing 'rustup update'. The + installer is available from https://rustup.rs/ + """.format( + version, rustc_min_version, rustc_min_version + ) + ) + ) + + if not cargo_info: + die( + dedent( + """\ + Cargo package manager not found. + To compile Rust language sources, you must have 'cargo' in your path. + See https://www.rust-lang.org/ for more information. + + You can install cargo by running './mach bootstrap' + or by directly running the installer from https://rustup.rs/ + """ + ) + ) + + version = cargo_info.version + if version < cargo_min_version: + die( + dedent( + """\ + Cargo package manager {} is too old. + + To compile Rust language sources please install at least + version {} of 'cargo' and make sure it is first in your path. + + You can verify this by typing 'cargo --version'. + """ + ).format(version, cargo_min_version) + ) + + return True + + +@depends(rustc, when=rust_compiler) +@imports(_from="__builtin__", _import="ValueError") +def rust_supported_targets(rustc): + out = check_cmd_output(rustc, "--print", "target-list").splitlines() + data = {} + for t in out: + try: + info = split_triplet(t) + except ValueError: + if t.startswith("thumb"): + cpu, rest = t.split("-", 1) + retry = "-".join(("arm", rest)) + elif t.endswith("-windows-msvc"): + retry = t[: -len("windows-msvc")] + "mingw32" + elif t.endswith("-windows-gnu"): + retry = t[: -len("windows-gnu")] + "mingw32" + else: + continue + try: + info = split_triplet(retry) + except ValueError: + continue + key = (info.cpu, info.endianness, info.os) + data.setdefault(key, []).append(namespace(rust_target=t, target=info)) + return data + + +def detect_rustc_target( + host_or_target, compiler_info, arm_target, rust_supported_targets +): + # Rust's --target options are similar to, but not exactly the same + # as, the autoconf-derived targets we use. An example would be that + # Rust uses distinct target triples for targetting the GNU C++ ABI + # and the MSVC C++ ABI on Win32, whereas autoconf has a single + # triple and relies on the user to ensure that everything is + # compiled for the appropriate ABI. We need to perform appropriate + # munging to get the correct option to rustc. + # We correlate the autoconf-derived targets with the list of targets + # rustc gives us with --print target-list. + candidates = rust_supported_targets.get( + (host_or_target.cpu, host_or_target.endianness, host_or_target.os), [] + ) + + def find_candidate(candidates): + if len(candidates) == 1: + return candidates[0].rust_target + elif not candidates: + return None + + # We have multiple candidates. There are two cases where we can try to + # narrow further down using extra information from the build system. + # - For windows targets, correlate with the C compiler type + if host_or_target.kernel == "WINNT": + if compiler_info.type in ("gcc", "clang"): + suffix = "windows-gnu" + else: + suffix = "windows-msvc" + narrowed = [ + c for c in candidates if c.rust_target.endswith("-{}".format(suffix)) + ] + if len(narrowed) == 1: + return narrowed[0].rust_target + elif narrowed: + candidates = narrowed + + vendor_aliases = {"pc": ("w64", "windows")} + narrowed = [ + c + for c in candidates + if host_or_target.vendor in vendor_aliases.get(c.target.vendor, ()) + ] + + if len(narrowed) == 1: + return narrowed[0].rust_target + + # - For arm targets, correlate with arm_target + # we could be more thorough with the supported rust targets, but they + # don't support OSes that are supported to build Gecko anyways. + # Also, sadly, the only interface to check the rust target cpu features + # is --print target-spec-json, and it's unstable, so we have to rely on + # our own knowledge of what each arm target means. + if host_or_target.cpu == "arm" and host_or_target.endianness == "little": + prefixes = [] + if arm_target.arm_arch >= 7: + if arm_target.thumb2 and arm_target.fpu == "neon": + prefixes.append("thumbv7neon") + if arm_target.thumb2: + prefixes.append("thumbv7a") + prefixes.append("armv7") + if arm_target.arm_arch >= 6: + prefixes.append("armv6") + if host_or_target.os != "Android": + # arm-* rust targets are armv6... except arm-linux-androideabi + prefixes.append("arm") + if arm_target.arm_arch >= 5: + prefixes.append("armv5te") + if host_or_target.os == "Android": + # arm-* rust targets are armv6... except arm-linux-androideabi + prefixes.append("arm") + if arm_target.arm_arch >= 4: + prefixes.append("armv4t") + # rust freebsd targets are the only ones that don't have a 'hf' suffix + # for hard-float. Technically, that means if the float abi ever is not + # hard-float, this will pick a wrong target, but since rust only + # supports hard-float, let's assume that means freebsd only support + # hard-float. + if arm_target.float_abi == "hard" and host_or_target.os != "FreeBSD": + suffix = "hf" + else: + suffix = "" + for p in prefixes: + for c in candidates: + if c.rust_target.startswith( + "{}-".format(p) + ) and c.rust_target.endswith(suffix): + return c.rust_target + + # See if we can narrow down on the exact alias + narrowed = [c for c in candidates if c.target.alias == host_or_target.alias] + if len(narrowed) == 1: + return narrowed[0].rust_target + elif narrowed: + candidates = narrowed + + # See if we can narrow down with the raw OS + narrowed = [c for c in candidates if c.target.raw_os == host_or_target.raw_os] + if len(narrowed) == 1: + return narrowed[0].rust_target + elif narrowed: + candidates = narrowed + + # See if we can narrow down with the raw OS and raw CPU + narrowed = [ + c + for c in candidates + if c.target.raw_os == host_or_target.raw_os + and c.target.raw_cpu == host_or_target.raw_cpu + ] + if len(narrowed) == 1: + return narrowed[0].rust_target + + # Finally, see if the vendor can be used to disambiguate. + narrowed = [c for c in candidates if c.target.vendor == host_or_target.vendor] + if len(narrowed) == 1: + return narrowed[0].rust_target + + return None + + rustc_target = find_candidate(candidates) + + if rustc_target is None: + die("Don't know how to translate {} for rustc".format(host_or_target.alias)) + + return rustc_target + + +@imports("os") +@imports(_from="six", _import="ensure_binary") +@imports(_from="tempfile", _import="mkstemp") +@imports(_from="textwrap", _import="dedent") +@imports(_from="mozbuild.configure.util", _import="LineIO") +def assert_rust_compile(host_or_target, rustc_target, rustc): + # Check to see whether our rustc has a reasonably functional stdlib + # for our chosen target. + target_arg = "--target=" + rustc_target + in_fd, in_path = mkstemp(prefix="conftest", suffix=".rs", text=True) + out_fd, out_path = mkstemp(prefix="conftest", suffix=".rlib") + os.close(out_fd) + try: + source = 'pub extern fn hello() { println!("Hello world"); }' + log.debug("Creating `%s` with content:", in_path) + with LineIO(lambda l: log.debug("| %s", l)) as out: + out.write(source) + + os.write(in_fd, ensure_binary(source)) + os.close(in_fd) + + cmd = [ + rustc, + "--crate-type", + "staticlib", + target_arg, + "-o", + out_path, + in_path, + ] + + def failed(): + die( + dedent( + """\ + Cannot compile for {} with {} + The target may be unsupported, or you may not have + a rust std library for that target installed. Try: + + rustup target add {} + """.format( + host_or_target.alias, rustc, rustc_target + ) + ) + ) + + check_cmd_output(*cmd, onerror=failed) + if not os.path.exists(out_path) or os.path.getsize(out_path) == 0: + failed() + finally: + os.remove(in_path) + os.remove(out_path) + + +@depends( + rustc, + host, + host_c_compiler, + rustc_info.host, + rust_supported_targets, + arm_target, + when=rust_compiler, +) +@checking("for rust host triplet") +@imports(_from="textwrap", _import="dedent") +def rust_host_triple( + rustc, host, compiler_info, rustc_host, rust_supported_targets, arm_target +): + rustc_target = detect_rustc_target( + host, compiler_info, arm_target, rust_supported_targets + ) + if rustc_target != rustc_host: + if host.alias == rustc_target: + configure_host = host.alias + else: + configure_host = "{}/{}".format(host.alias, rustc_target) + die( + dedent( + """\ + The rust compiler host ({rustc}) is not suitable for the configure host ({configure}). + + You can solve this by: + * Set your configure host to match the rust compiler host by editing your + mozconfig and adding "ac_add_options --host={rustc}". + * Or, install the rust toolchain for {configure}, if supported, by running + "rustup default stable-{rustc_target}" + """.format( + rustc=rustc_host, + configure=configure_host, + rustc_target=rustc_target, + ) + ) + ) + assert_rust_compile(host, rustc_target, rustc) + return rustc_target + + +@depends( + rustc, target, c_compiler, rust_supported_targets, arm_target, when=rust_compiler +) +@checking("for rust target triplet") +def rust_target_triple( + rustc, target, compiler_info, rust_supported_targets, arm_target +): + rustc_target = detect_rustc_target( + target, compiler_info, arm_target, rust_supported_targets + ) + assert_rust_compile(target, rustc_target, rustc) + return rustc_target + + +set_config("RUST_TARGET", rust_target_triple) +set_config("RUST_HOST_TARGET", rust_host_triple) + + +# This is used for putting source info into symbol files. +set_config("RUSTC_COMMIT", depends(rustc_info)(lambda i: i.commit)) + +# Rustdoc is required by Rust tests below. +option(env="RUSTDOC", nargs=1, help="Path to the rustdoc program") + +rustdoc = check_prog( + "RUSTDOC", + ["rustdoc"], + paths=rust_search_path, + input="RUSTDOC", + allow_missing=True, +) + +# This option is separate from --enable-tests because Rust tests are particularly +# expensive in terms of compile time (especially for code in libxul). +option( + "--enable-rust-tests", + help="Enable building and running of Rust tests during `make check`", +) + + +@depends("--enable-rust-tests", rustdoc) +def rust_tests(enable_rust_tests, rustdoc): + if enable_rust_tests and not rustdoc: + die("--enable-rust-tests requires rustdoc") + return bool(enable_rust_tests) + + +set_config("MOZ_RUST_TESTS", rust_tests) + + +@depends(target, c_compiler, rustc) +@imports("os") +def rustc_natvis_ldflags(target, compiler_info, rustc): + if target.kernel == "WINNT" and compiler_info.type == "clang-cl": + sysroot = check_cmd_output(rustc, "--print", "sysroot").strip() + etc = os.path.join(sysroot, "lib/rustlib/etc") + ldflags = [] + if os.path.isdir(etc): + for f in os.listdir(etc): + if f.endswith(".natvis"): + ldflags.append("-NATVIS:" + normsep(os.path.join(etc, f))) + return ldflags + + +set_config("RUSTC_NATVIS_LDFLAGS", rustc_natvis_ldflags) diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure new file mode 100755 index 0000000000..2ed03d4587 --- /dev/null +++ b/build/moz.configure/toolchain.configure @@ -0,0 +1,2842 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Code optimization +# ============================================================== + +option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags") + + +@depends("--enable-optimize", "--help") +def moz_optimize(option, _): + flags = None + + if len(option): + val = "2" + flags = option[0] + elif option: + val = "1" + else: + val = None + + return namespace( + optimize=val, + flags=flags, + ) + + +set_config("MOZ_OPTIMIZE", moz_optimize.optimize) +add_old_configure_assignment("MOZ_OPTIMIZE", moz_optimize.optimize) +add_old_configure_assignment("MOZ_CONFIGURE_OPTIMIZE_FLAGS", moz_optimize.flags) + +# yasm detection +# ============================================================== +yasm = check_prog("YASM", ["yasm"], allow_missing=True) + + +@depends_if(yasm) +@checking("yasm version") +def yasm_version(yasm): + version = ( + check_cmd_output( + yasm, "--version", onerror=lambda: die("Failed to get yasm version.") + ) + .splitlines()[0] + .split()[1] + ) + return Version(version) + + +@depends(yasm, target) +def yasm_asflags(yasm, target): + if yasm: + asflags = { + ("OSX", "x86"): ["-f", "macho32"], + ("OSX", "x86_64"): ["-f", "macho64"], + ("WINNT", "x86"): ["-f", "win32"], + ("WINNT", "x86_64"): ["-f", "x64"], + }.get((target.os, target.cpu), None) + if asflags is None: + # We're assuming every x86 platform we support that's + # not Windows or Mac is ELF. + if target.cpu == "x86": + asflags = ["-f", "elf32"] + elif target.cpu == "x86_64": + asflags = ["-f", "elf64"] + if asflags: + asflags += ["-rnasm", "-pnasm"] + return asflags + + +set_config("YASM_ASFLAGS", yasm_asflags) + + +# Android NDK +# ============================================================== + + +@depends("--disable-compile-environment", target) +def compiling_android(compile_env, target): + return compile_env and target.os == "Android" + + +include("android-ndk.configure", when=compiling_android) + +with only_when(target_is_osx): + # MacOS deployment target version + # ============================================================== + # This needs to happen before any compilation test is done. + + option( + "--enable-macos-target", + env="MACOSX_DEPLOYMENT_TARGET", + nargs=1, + default=depends(target)(lambda t: "11.0" if t.cpu == "aarch64" else "10.12"), + help="Set the minimum MacOS version needed at runtime{|}", + ) + + @depends("--enable-macos-target") + @imports(_from="os", _import="environ") + def macos_target(value): + if value: + # Ensure every compiler process we spawn uses this value. + environ["MACOSX_DEPLOYMENT_TARGET"] = value[0] + return value[0] + + set_config("MACOSX_DEPLOYMENT_TARGET", macos_target) + add_old_configure_assignment("MACOSX_DEPLOYMENT_TARGET", macos_target) + + +@depends(host) +def host_is_osx(host): + if host.os == "OSX": + return True + + +with only_when(host_is_osx | target_is_osx): + # MacOS SDK + # ========= + option( + "--with-macos-sdk", + env="MACOS_SDK_DIR", + nargs=1, + help="Location of platform SDK to use", + ) + + @depends("--with-macos-sdk", host) + @imports(_from="__builtin__", _import="open") + @imports(_from="os.path", _import="isdir") + @imports("plistlib") + def macos_sdk(sdk, host): + sdk_min_version = Version("10.12") + sdk_max_version = Version("11.1") + + if sdk: + sdk = sdk[0] + elif host.os == "OSX": + sdk = check_cmd_output( + "xcrun", "--show-sdk-path", onerror=lambda: "" + ).rstrip() + if not sdk: + die( + "Could not find the macOS SDK. Please use --with-macos-sdk to give " + "the path to a macOS SDK." + ) + else: + die( + "Need a macOS SDK when targeting macOS. Please use --with-macos-sdk " + "to give the path to a macOS SDK." + ) + + if not isdir(sdk): + die( + "SDK not found in %s. When using --with-macos-sdk, you must specify a " + "valid SDK. SDKs are installed when the optional cross-development " + "tools are selected during the Xcode/Developer Tools installation." + % sdk + ) + with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist: + obj = plistlib.load(plist) + if not obj: + die("Error parsing SDKSettings.plist in the SDK directory: %s" % sdk) + if "Version" not in obj: + die( + "Error finding Version information in SDKSettings.plist from the SDK: %s" + % sdk + ) + version = Version(obj["Version"]) + if version < sdk_min_version: + die( + 'SDK version "%s" is too old. Please upgrade to at least %s. ' + "You may need to point to it using --with-macos-sdk=<path> in your " + "mozconfig." % (version, sdk_min_version) + ) + if version > sdk_max_version: + die( + 'SDK version "%s" is unsupported. Please downgrade to version ' + "%s. You may need to point to it using --with-macos-sdk=<path> in " + "your mozconfig." % (version, sdk_max_version) + ) + return sdk + + set_config("MACOS_SDK_DIR", macos_sdk) + + +with only_when(target_is_osx): + with only_when(cross_compiling): + option( + "--with-macos-private-frameworks", + env="MACOS_PRIVATE_FRAMEWORKS_DIR", + nargs=1, + help="Location of private frameworks to use", + ) + + @depends_if("--with-macos-private-frameworks") + @imports(_from="os.path", _import="isdir") + def macos_private_frameworks(value): + if value and not isdir(value[0]): + die( + "PrivateFrameworks not found not found in %s. When using " + "--with-macos-private-frameworks, you must specify a valid " + "directory", + value[0], + ) + return value[0] + + @depends(macos_private_frameworks, macos_sdk) + def macos_private_frameworks(value, sdk): + if value: + return value + return os.path.join(sdk or "/", "System/Library/PrivateFrameworks") + + set_config("MACOS_PRIVATE_FRAMEWORKS_DIR", macos_private_frameworks) + + +# GC rooting and hazard analysis. +# ============================================================== +option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis") + + +@depends("MOZ_HAZARD") +def hazard_analysis(value): + if value: + return True + + +set_config("MOZ_HAZARD", hazard_analysis) + + +# Cross-compilation related things. +# ============================================================== +option( + "--with-toolchain-prefix", + env="TOOLCHAIN_PREFIX", + nargs=1, + help="Prefix for the target toolchain", +) + + +@depends("--with-toolchain-prefix", host, target, cross_compiling) +def toolchain_prefix(value, host, target, cross_compiling): + if value: + return tuple(value) + # We don't want a toolchain prefix by default when building on mac for mac. + if cross_compiling and not (target.os == "OSX" and host.os == "OSX"): + return ("%s-" % target.toolchain, "%s-" % target.alias) + + +@depends(toolchain_prefix, target) +def first_toolchain_prefix(toolchain_prefix, target): + # Pass TOOLCHAIN_PREFIX down to the build system if it was given from the + # command line/environment (in which case there's only one value in the tuple), + # or when cross-compiling for Android or OSX. + if toolchain_prefix and ( + target.os in ("Android", "OSX") or len(toolchain_prefix) == 1 + ): + return toolchain_prefix[0] + + +set_config("TOOLCHAIN_PREFIX", first_toolchain_prefix) +add_old_configure_assignment("TOOLCHAIN_PREFIX", first_toolchain_prefix) + + +# Compilers +# ============================================================== +include("compilers-util.configure") + + +def try_preprocess(compiler, language, source, onerror=None): + return try_invoke_compiler(compiler, language, source, ["-E"], onerror) + + +@imports(_from="mozbuild.configure.constants", _import="CompilerType") +@imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks") +@imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks") +@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks") +@imports(_from="six", _import="iteritems") +@imports(_from="textwrap", _import="dedent") +@imports(_from="__builtin__", _import="Exception") +def get_compiler_info(compiler, language): + """Returns information about the given `compiler` (command line in the + form of a list or tuple), in the given `language`. + + The returned information includes: + - the compiler type (clang-cl, clang or gcc) + - the compiler version + - the compiler supported language + - the compiler supported language version + """ + # Xcode clang versions are different from the underlying llvm version (they + # instead are aligned with the Xcode version). Fortunately, we can tell + # apart plain clang from Xcode clang, and convert the Xcode clang version + # into the more or less corresponding plain clang version. + check = dedent( + """\ + #if defined(_MSC_VER) && defined(__clang__) && defined(_MT) + %COMPILER "clang-cl" + %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__ + #elif defined(__clang__) + %COMPILER "clang" + %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__ + # ifdef __apple_build_version__ + %XCODE 1 + # endif + #elif defined(__GNUC__) && !defined(__MINGW32__) + %COMPILER "gcc" + %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__ + #endif + + #if __cplusplus + %cplusplus __cplusplus + #elif __STDC_VERSION__ + %STDC_VERSION __STDC_VERSION__ + #endif + """ + ) + + # While we're doing some preprocessing, we might as well do some more + # preprocessor-based tests at the same time, to check the toolchain + # matches what we want. + for name, preprocessor_checks in ( + ("CPU", CPU_preprocessor_checks), + ("KERNEL", kernel_preprocessor_checks), + ("OS", OS_preprocessor_checks), + ): + for n, (value, condition) in enumerate(iteritems(preprocessor_checks)): + check += dedent( + """\ + #%(if)s %(condition)s + %%%(name)s "%(value)s" + """ + % { + "if": "elif" if n else "if", + "condition": condition, + "name": name, + "value": value, + } + ) + check += "#endif\n" + + # Also check for endianness. The advantage of living in modern times is + # that all the modern compilers we support now have __BYTE_ORDER__ defined + # by the preprocessor. + check += dedent( + """\ + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + %ENDIANNESS "little" + #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + %ENDIANNESS "big" + #endif + """ + ) + + result = try_preprocess(compiler, language, check) + + if not result: + raise FatalCheckError("Unknown compiler or compiler not supported.") + + # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may + # have non-ASCII characters. Treat the output as bytearray. + data = {} + for line in result.splitlines(): + if line.startswith("%"): + k, _, v = line.partition(" ") + k = k.lstrip("%") + data[k] = v.replace(" ", "").lstrip('"').rstrip('"') + log.debug("%s = %s", k, data[k]) + + try: + type = CompilerType(data["COMPILER"]) + except Exception: + raise FatalCheckError("Unknown compiler or compiler not supported.") + + cplusplus = int(data.get("cplusplus", "0L").rstrip("L")) + stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L")) + + version = data.get("VERSION") + if version: + version = Version(version) + if data.get("XCODE"): + # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions + # with enough granularity for major.minor version checks further + # down the line + if version < "9.1": + version = Version("4.0.0.or.less") + elif version < "10.0": + version = Version("5.0.2") + elif version < "10.0.1": + version = Version("6.0.1") + elif version < "11.0": + version = Version("7.0.0") + elif version < "11.0.3": + version = Version("8.0.0") + elif version < "12.0": + version = Version("9.0.0") + elif version < "12.0.1": + version = Version("10.0.0") + else: + version = Version("10.0.0.or.more") + + return namespace( + type=type, + version=version, + cpu=data.get("CPU"), + kernel=data.get("KERNEL"), + endianness=data.get("ENDIANNESS"), + os=data.get("OS"), + language="C++" if cplusplus else "C", + language_version=cplusplus if cplusplus else stdc_version, + xcode=bool(data.get("XCODE")), + ) + + +def same_arch_different_bits(): + return ( + ("x86", "x86_64"), + ("ppc", "ppc64"), + ("sparc", "sparc64"), + ) + + +@imports(_from="mozbuild.shellutil", _import="quote") +@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks") +def check_compiler(compiler, language, target): + info = get_compiler_info(compiler, language) + + flags = [] + + # Check language standards + # -------------------------------------------------------------------- + if language != info.language: + raise FatalCheckError( + "`%s` is not a %s compiler." % (quote(*compiler), language) + ) + + # Note: We do a strict version check because there sometimes are backwards + # incompatible changes in the standard, and not all code that compiles as + # C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for + # example) + if info.language == "C" and info.language_version != 199901: + if info.type == "clang-cl": + flags.append("-Xclang") + flags.append("-std=gnu99") + + cxx17_version = 201703 + if info.language == "C++": + if info.language_version != cxx17_version: + # MSVC headers include C++17 features, but don't guard them + # with appropriate checks. + if info.type == "clang-cl": + flags.append("-Xclang") + flags.append("-std=c++17") + else: + flags.append("-std=gnu++17") + + # Check compiler target + # -------------------------------------------------------------------- + has_target = False + if info.type == "clang": + # Add the target explicitly when the target is aarch64 macosx, because + # the Xcode clang target is named differently, and we need to work around + # https://github.com/rust-lang/rust-bindgen/issues/1871 and + # https://github.com/alexcrichton/cc-rs/issues/542 so we always want + # the target on the command line, even if the compiler would default to + # that. + if info.xcode and target.os == "OSX" and target.cpu == "aarch64": + if "--target=arm64-apple-darwin" not in compiler: + flags.append("--target=arm64-apple-darwin") + has_target = True + + elif ( + not info.kernel + or info.kernel != target.kernel + or not info.endianness + or info.endianness != target.endianness + ): + flags.append("--target=%s" % target.toolchain) + has_target = True + + # Add target flag when there is an OS mismatch (e.g. building for Android on + # Linux). However, only do this if the target OS is in our whitelist, to + # keep things the same on other platforms. + elif target.os in OS_preprocessor_checks and ( + not info.os or info.os != target.os + ): + flags.append("--target=%s" % target.toolchain) + has_target = True + + if not has_target and (not info.cpu or info.cpu != target.cpu): + same_arch = same_arch_different_bits() + if (target.cpu, info.cpu) in same_arch: + flags.append("-m32") + elif (info.cpu, target.cpu) in same_arch: + flags.append("-m64") + elif info.type == "clang-cl" and target.cpu == "aarch64": + flags.append("--target=%s" % target.toolchain) + elif info.type == "clang": + flags.append("--target=%s" % target.toolchain) + + return namespace( + type=info.type, + version=info.version, + target_cpu=info.cpu, + target_kernel=info.kernel, + target_endianness=info.endianness, + target_os=info.os, + flags=flags, + ) + + +@imports(_from="__builtin__", _import="open") +@imports("json") +@imports("os") +def get_vc_paths(topsrcdir): + def vswhere(args): + program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get( + "PROGRAMFILES" + ) + if not program_files: + return [] + vswhere = os.path.join( + program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe" + ) + if not os.path.exists(vswhere): + return [] + return json.loads(check_cmd_output(vswhere, "-format", "json", *args)) + + for install in vswhere( + [ + "-products", + "*", + "-requires", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + ] + ): + path = install["installationPath"] + tools_version = ( + open( + os.path.join( + path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" + ), + "r", + ) + .read() + .strip() + ) + tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version) + yield (Version(install["installationVersion"]), tools_path) + + +@depends(host) +def host_is_windows(host): + if host.kernel == "WINNT": + return True + + +option( + "--with-visual-studio-version", + nargs=1, + choices=("2017",), + when=host_is_windows, + help="Select a specific Visual Studio version to use", +) + + +@depends("--with-visual-studio-version", when=host_is_windows) +def vs_major_version(value): + if value: + return {"2017": 15}[value[0]] + + +option( + env="VC_PATH", + nargs=1, + when=host_is_windows, + help="Path to the Microsoft Visual C/C++ compiler", +) + + +@depends( + host, + vs_major_version, + check_build_environment, + "VC_PATH", + "--with-visual-studio-version", + when=host_is_windows, +) +@imports(_from="__builtin__", _import="sorted") +@imports(_from="operator", _import="itemgetter") +def vc_compiler_paths_for_version( + host, vs_major_version, env, vc_path, vs_release_name +): + if vc_path and vs_release_name: + die("VC_PATH and --with-visual-studio-version cannot be used together.") + if vc_path: + # Use an arbitrary version, it doesn't matter. + all_versions = [(Version("15"), vc_path[0])] + else: + all_versions = sorted(get_vc_paths(env.topsrcdir), key=itemgetter(0)) + if not all_versions: + return + if vs_major_version: + versions = [d for (v, d) in all_versions if v.major == vs_major_version] + if not versions: + die("Visual Studio %s could not be found!" % vs_release_name) + path = versions[0] + else: + # Choose the newest version. + path = all_versions[-1][1] + host_dir = { + "x86_64": "HostX64", + "x86": "HostX86", + }.get(host.cpu) + if host_dir: + path = os.path.join(path, "bin", host_dir) + return { + "x64": [os.path.join(path, "x64")], + # The cross toolchains require DLLs from the native x64 toolchain. + "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")], + "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")], + } + + +@depends(target, vc_compiler_paths_for_version, when=host_is_windows) +def vc_compiler_path(target, paths): + vc_target = { + "x86": "x86", + "x86_64": "x64", + "arm": "arm", + "aarch64": "arm64", + }.get(target.cpu) + if not paths: + return + return paths.get(vc_target) + + +@depends(vc_compiler_path, original_path) +@imports("os") +@imports(_from="os", _import="environ") +def vc_toolchain_search_path(vc_compiler_path, original_path): + result = list(original_path) + + if vc_compiler_path: + # The second item, if there is one, is necessary to have in $PATH for + # Windows to load the required DLLs from there. + if len(vc_compiler_path) > 1: + environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:]) + + # The first item is where the programs are going to be + result.append(vc_compiler_path[0]) + + return result + + +clang_search_path = bootstrap_search_path("clang", "bin") + + +@depends(bootstrap_search_path("rustc", "bin", when="MOZ_AUTOMATION"), original_path) +@imports("os") +@imports(_from="os", _import="environ") +def rust_search_path(rust_path, original_path): + result = list(rust_path or original_path) + # Also add the rustup install directory for cargo/rustc. + cargo_home = environ.get("CARGO_HOME", "") + if cargo_home: + cargo_home = os.path.abspath(cargo_home) + else: + cargo_home = os.path.expanduser(os.path.join("~", ".cargo")) + rustup_path = os.path.join(cargo_home, "bin") + result.append(rustup_path) + return result + + +# As a workaround until bug 1516228 and bug 1516253 are fixed, set the PATH +# variable for the build to contain the toolchain search path. +@depends(vc_toolchain_search_path) +@imports("os") +@imports(_from="os", _import="environ") +def altered_path(vc_toolchain_search_path): + path = environ["PATH"].split(os.pathsep) + altered_path = list(vc_toolchain_search_path) + for p in path: + if p not in altered_path: + altered_path.append(p) + return os.pathsep.join(altered_path) + + +set_config("PATH", altered_path) + + +# Compiler wrappers +# ============================================================== +option( + "--with-compiler-wrapper", + env="COMPILER_WRAPPER", + nargs=1, + help="Enable compiling with wrappers such as distcc and ccache", +) + +option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache") + + +@depends_if("--with-ccache") +def ccache(value): + if len(value): + return value + # If --with-ccache was given without an explicit value, we default to + # 'ccache'. + return "ccache" + + +ccache = check_prog( + "CCACHE", + progs=(), + input=ccache, + paths=bootstrap_search_path("sccache"), + allow_missing=True, +) + +option(env="CCACHE_PREFIX", nargs=1, help="Compiler prefix to use when using ccache") + +ccache_prefix = depends_if("CCACHE_PREFIX")(lambda prefix: prefix[0]) +set_config("CCACHE_PREFIX", ccache_prefix) + +# Distinguish ccache from sccache. + + +@depends_if(ccache) +def ccache_is_sccache(ccache): + return check_cmd_output(ccache, "--version").startswith("sccache") + + +@depends(ccache, ccache_is_sccache) +def using_ccache(ccache, ccache_is_sccache): + return ccache and not ccache_is_sccache + + +@depends_if(ccache, ccache_is_sccache) +def using_sccache(ccache, ccache_is_sccache): + return ccache and ccache_is_sccache + + +option(env="RUSTC_WRAPPER", nargs=1, help="Wrap rust compilation with given tool") + + +@depends(ccache, ccache_is_sccache, "RUSTC_WRAPPER") +@imports(_from="textwrap", _import="dedent") +@imports("os") +def check_sccache_version(ccache, ccache_is_sccache, rustc_wrapper): + sccache_min_version = Version("0.2.13") + + def check_version(path): + out = check_cmd_output(path, "--version") + version = Version(out.rstrip().split()[-1]) + if version < sccache_min_version: + die( + dedent( + """\ + sccache %s or later is required. sccache in use at %s has + version %s. + + Please upgrade or acquire a new version with |./mach bootstrap|. + """ + ), + sccache_min_version, + path, + version, + ) + + if ccache and ccache_is_sccache: + check_version(ccache) + + if rustc_wrapper and ( + os.path.splitext(os.path.basename(rustc_wrapper[0]))[0].lower() == "sccache" + ): + check_version(rustc_wrapper[0]) + + +set_config("MOZ_USING_CCACHE", using_ccache) +set_config("MOZ_USING_SCCACHE", using_sccache) + +option(env="SCCACHE_VERBOSE_STATS", help="Print verbose sccache stats after build") + + +@depends(using_sccache, "SCCACHE_VERBOSE_STATS") +def sccache_verbose_stats(using_sccache, verbose_stats): + return using_sccache and bool(verbose_stats) + + +set_config("SCCACHE_VERBOSE_STATS", sccache_verbose_stats) + + +@depends("--with-compiler-wrapper", ccache) +@imports(_from="mozbuild.shellutil", _import="split", _as="shell_split") +def compiler_wrapper(wrapper, ccache): + if wrapper: + raw_wrapper = wrapper[0] + wrapper = shell_split(raw_wrapper) + wrapper_program = find_program(wrapper[0]) + if not wrapper_program: + die( + "Cannot find `%s` from the given compiler wrapper `%s`", + wrapper[0], + raw_wrapper, + ) + wrapper[0] = wrapper_program + + if ccache: + if wrapper: + return tuple([ccache] + wrapper) + else: + return (ccache,) + elif wrapper: + return tuple(wrapper) + + +@depends_if(compiler_wrapper) +def using_compiler_wrapper(compiler_wrapper): + return True + + +set_config("MOZ_USING_COMPILER_WRAPPER", using_compiler_wrapper) + + +@template +def default_c_compilers(host_or_target, other_c_compiler=None): + """Template defining the set of default C compilers for the host and + target platforms. + `host_or_target` is either `host` or `target` (the @depends functions + from init.configure. + `other_c_compiler` is the `target` C compiler when `host_or_target` is `host`. + """ + assert host_or_target in {host, target} + + other_c_compiler = () if other_c_compiler is None else (other_c_compiler,) + + @depends(host_or_target, target, toolchain_prefix, *other_c_compiler) + def default_c_compilers( + host_or_target, target, toolchain_prefix, *other_c_compiler + ): + if host_or_target.kernel == "WINNT": + supported = types = ("clang-cl", "clang") + elif host_or_target.kernel == "Darwin": + types = ("clang",) + supported = ("clang", "gcc") + else: + supported = types = ("clang", "gcc") + + info = other_c_compiler[0] if other_c_compiler else None + if info and info.type in supported: + # When getting default C compilers for the host, we prioritize the + # same compiler as the target C compiler. + prioritized = info.compiler + if info.type == "gcc": + same_arch = same_arch_different_bits() + if ( + target.cpu != host_or_target.cpu + and (target.cpu, host_or_target.cpu) not in same_arch + and (host_or_target.cpu, target.cpu) not in same_arch + ): + # If the target C compiler is GCC, and it can't be used with + # -m32/-m64 for the host, it's probably toolchain-prefixed, + # so we prioritize a raw 'gcc' instead. + prioritized = info.type + + types = [prioritized] + [t for t in types if t != info.type] + + gcc = ("gcc",) + if toolchain_prefix and host_or_target is target: + gcc = tuple("%sgcc" % p for p in toolchain_prefix) + gcc + + result = [] + for type in types: + if type == "gcc": + result.extend(gcc) + else: + result.append(type) + + return tuple(result) + + return default_c_compilers + + +@template +def default_cxx_compilers(c_compiler, other_c_compiler=None, other_cxx_compiler=None): + """Template defining the set of default C++ compilers for the host and + target platforms. + `c_compiler` is the @depends function returning a Compiler instance for + the desired platform. + + Because the build system expects the C and C++ compilers to be from the + same compiler suite, we derive the default C++ compilers from the C + compiler that was found if none was provided. + + We also factor in the target C++ compiler when getting the default host + C++ compiler, using the target C++ compiler if the host and target C + compilers are the same. + """ + + assert (other_c_compiler is None) == (other_cxx_compiler is None) + if other_c_compiler is not None: + other_compilers = (other_c_compiler, other_cxx_compiler) + else: + other_compilers = () + + @depends(c_compiler, *other_compilers) + def default_cxx_compilers(c_compiler, *other_compilers): + if other_compilers: + other_c_compiler, other_cxx_compiler = other_compilers + if other_c_compiler.compiler == c_compiler.compiler: + return (other_cxx_compiler.compiler,) + + dir = os.path.dirname(c_compiler.compiler) + file = os.path.basename(c_compiler.compiler) + + if c_compiler.type == "gcc": + return (os.path.join(dir, file.replace("gcc", "g++")),) + + if c_compiler.type == "clang": + return (os.path.join(dir, file.replace("clang", "clang++")),) + + return (c_compiler.compiler,) + + return default_cxx_compilers + + +@template +def provided_program(env_var, when=None): + """Template handling cases where a program can be specified either as a + path or as a path with applicable arguments. + """ + + @depends_if(env_var, when=when) + @imports(_from="itertools", _import="takewhile") + @imports(_from="mozbuild.shellutil", _import="split", _as="shell_split") + def provided(cmd): + # Assume the first dash-prefixed item (and any subsequent items) are + # command-line options, the item before the dash-prefixed item is + # the program we're looking for, and anything before that is a wrapper + # of some kind (e.g. sccache). + cmd = shell_split(cmd[0]) + + without_flags = list(takewhile(lambda x: not x.startswith("-"), cmd)) + + return namespace( + wrapper=without_flags[:-1], + program=without_flags[-1], + flags=cmd[len(without_flags) :], + ) + + return provided + + +def prepare_flags(host_or_target, macos_sdk): + if macos_sdk and host_or_target.os == "OSX": + return ["-isysroot", macos_sdk] + return [] + + +def minimum_gcc_version(): + return Version("7.1.0") + + +@template +def compiler( + language, + host_or_target, + c_compiler=None, + other_compiler=None, + other_c_compiler=None, +): + """Template handling the generic base checks for the compiler for the + given `language` on the given platform (`host_or_target`). + `host_or_target` is either `host` or `target` (the @depends functions + from init.configure. + When the language is 'C++', `c_compiler` is the result of the `compiler` + template for the language 'C' for the same `host_or_target`. + When `host_or_target` is `host`, `other_compiler` is the result of the + `compiler` template for the same `language` for `target`. + When `host_or_target` is `host` and the language is 'C++', + `other_c_compiler` is the result of the `compiler` template for the + language 'C' for `target`. + """ + assert host_or_target in {host, target} + assert language in ("C", "C++") + assert language == "C" or c_compiler is not None + assert host_or_target is target or other_compiler is not None + assert language == "C" or host_or_target is target or other_c_compiler is not None + + host_or_target_str = { + host: "host", + target: "target", + }[host_or_target] + + var = { + ("C", target): "CC", + ("C++", target): "CXX", + ("C", host): "HOST_CC", + ("C++", host): "HOST_CXX", + }[language, host_or_target] + + default_compilers = { + "C": lambda: default_c_compilers(host_or_target, other_compiler), + "C++": lambda: default_cxx_compilers( + c_compiler, other_c_compiler, other_compiler + ), + }[language]() + + what = "the %s %s compiler" % (host_or_target_str, language) + + option(env=var, nargs=1, help="Path to %s" % what) + + # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/ + # HOST_CXX variables. + provided_compiler = provided_program(var) + + # Normally, we'd use `var` instead of `_var`, but the interaction with + # old-configure complicates things, and for now, we a) can't take the plain + # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let + # old-configure AC_SUBST it (because it's autoconf doing it, not us) + compiler = check_prog( + "_%s" % var, + what=what, + progs=default_compilers, + input=provided_compiler.program, + paths=clang_search_path, + ) + + @depends(compiler, provided_compiler, compiler_wrapper, host_or_target, macos_sdk) + @checking("whether %s can be used" % what, lambda x: bool(x)) + @imports(_from="mozbuild.shellutil", _import="quote") + def valid_compiler( + compiler, provided_compiler, compiler_wrapper, host_or_target, macos_sdk + ): + wrapper = list(compiler_wrapper or ()) + flags = prepare_flags(host_or_target, macos_sdk) + if provided_compiler: + provided_wrapper = list(provided_compiler.wrapper) + # When doing a subconfigure, the compiler is set by old-configure + # and it contains the wrappers from --with-compiler-wrapper and + # --with-ccache. + if provided_wrapper[: len(wrapper)] == wrapper: + provided_wrapper = provided_wrapper[len(wrapper) :] + wrapper.extend(provided_wrapper) + flags.extend(provided_compiler.flags) + + info = check_compiler(wrapper + [compiler] + flags, language, host_or_target) + + # Check that the additional flags we got are enough to not require any + # more flags. If we get an exception, just ignore it; it's liable to be + # invalid command-line flags, which means the compiler we're checking + # doesn't support those command-line flags and will fail one or more of + # the checks below. + try: + if info.flags: + flags += info.flags + info = check_compiler( + wrapper + [compiler] + flags, language, host_or_target + ) + except FatalCheckError: + pass + + if not info.target_cpu or info.target_cpu != host_or_target.cpu: + raise FatalCheckError( + "%s %s compiler target CPU (%s) does not match --%s CPU (%s)" + % ( + host_or_target_str.capitalize(), + language, + info.target_cpu or "unknown", + host_or_target_str, + host_or_target.raw_cpu, + ) + ) + + if not info.target_kernel or (info.target_kernel != host_or_target.kernel): + raise FatalCheckError( + "%s %s compiler target kernel (%s) does not match --%s kernel (%s)" + % ( + host_or_target_str.capitalize(), + language, + info.target_kernel or "unknown", + host_or_target_str, + host_or_target.kernel, + ) + ) + + if not info.target_endianness or ( + info.target_endianness != host_or_target.endianness + ): + raise FatalCheckError( + "%s %s compiler target endianness (%s) does not match --%s " + "endianness (%s)" + % ( + host_or_target_str.capitalize(), + language, + info.target_endianness or "unknown", + host_or_target_str, + host_or_target.endianness, + ) + ) + + # Compiler version checks + # =================================================== + # Check the compiler version here instead of in `compiler_version` so + # that the `checking` message doesn't pretend the compiler can be used + # to then bail out one line later. + if info.type == "gcc": + if host_or_target.os == "Android": + raise FatalCheckError( + "GCC is not supported on Android.\n" + "Please use clang from the Android NDK instead." + ) + gcc_version = minimum_gcc_version() + if info.version < gcc_version: + raise FatalCheckError( + "Only GCC %d.%d or newer is supported (found version %s)." + % (gcc_version.major, gcc_version.minor, info.version) + ) + + if info.type == "clang-cl": + if info.version < "8.0.0": + raise FatalCheckError( + "Only clang-cl 8.0 or newer is supported (found version %s)" + % info.version + ) + + # If you want to bump the version check here ensure the version + # is known for Xcode in get_compiler_info. + if info.type == "clang" and info.version < "5.0": + raise FatalCheckError( + "Only clang/llvm 5.0 or newer is supported (found version %s)." + % info.version + ) + + if info.flags: + raise FatalCheckError("Unknown compiler or compiler not supported.") + + return namespace( + wrapper=wrapper, + compiler=compiler, + flags=flags, + type=info.type, + version=info.version, + language=language, + ) + + @depends(valid_compiler) + @checking("%s version" % what) + def compiler_version(compiler): + return compiler.version + + if language == "C++": + + @depends(valid_compiler, c_compiler) + def valid_compiler(compiler, c_compiler): + if compiler.type != c_compiler.type: + die( + "The %s C compiler is %s, while the %s C++ compiler is " + "%s. Need to use the same compiler suite.", + host_or_target_str, + c_compiler.type, + host_or_target_str, + compiler.type, + ) + + if compiler.version != c_compiler.version: + die( + "The %s C compiler is version %s, while the %s C++ " + "compiler is version %s. Need to use the same compiler " + "version.", + host_or_target_str, + c_compiler.version, + host_or_target_str, + compiler.version, + ) + return compiler + + # Set CC/CXX/HOST_CC/HOST_CXX for old-configure, which needs the wrapper + # and the flags that were part of the user input for those variables to + # be provided. + add_old_configure_assignment( + var, + depends_if(valid_compiler)( + lambda x: list(x.wrapper) + [x.compiler] + list(x.flags) + ), + ) + + if host_or_target is target: + add_old_configure_assignment( + "ac_cv_prog_%s" % var, + depends_if(valid_compiler)( + lambda x: list(x.wrapper) + [x.compiler] + list(x.flags) + ), + ) + # We check that it works in python configure already. + add_old_configure_assignment("ac_cv_prog_%s_works" % var.lower(), "yes") + add_old_configure_assignment( + "ac_cv_prog_%s_cross" % var.lower(), + depends(cross_compiling)(lambda x: "yes" if x else "no"), + ) + gcc_like = depends(valid_compiler.type)( + lambda x: "yes" if x in ("gcc", "clang") else "no" + ) + add_old_configure_assignment("ac_cv_prog_%s_g" % var.lower(), gcc_like) + if language == "C": + add_old_configure_assignment("ac_cv_prog_gcc", gcc_like) + if language == "C++": + add_old_configure_assignment("ac_cv_prog_gxx", gcc_like) + + # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow + # old-configure to do some of its still existing checks. + if language == "C": + set_config("%s_TYPE" % var, valid_compiler.type) + add_old_configure_assignment("%s_TYPE" % var, valid_compiler.type) + set_config( + "%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v)) + ) + + valid_compiler = compiler_class(valid_compiler, host_or_target) + + def compiler_error(): + raise FatalCheckError( + "Failed compiling a simple %s source with %s" % (language, what) + ) + + valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error) + + set_config("%s_BASE_FLAGS" % var, valid_compiler.flags) + + # Set CPP/CXXCPP for both the build system and old-configure. We don't + # need to check this works for preprocessing, because we already relied + # on $CC -E/$CXX -E doing preprocessing work to validate the compiler + # in the first place. + if host_or_target is target: + pp_var = { + "C": "CPP", + "C++": "CXXCPP", + }[language] + + preprocessor = depends_if(valid_compiler)( + lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags) + ) + + set_config(pp_var, preprocessor) + add_old_configure_assignment(pp_var, preprocessor) + + if language == "C": + linker_var = { + target: "LD", + host: "HOST_LD", + }[host_or_target] + + @deprecated_option(env=linker_var, nargs=1) + def linker(value): + if value: + return value[0] + + @depends(linker) + def unused_linker(linker): + if linker: + log.warning( + "The value of %s is not used by this build system." % linker_var + ) + + return valid_compiler + + +c_compiler = compiler("C", target) +cxx_compiler = compiler("C++", target, c_compiler=c_compiler) +host_c_compiler = compiler("C", host, other_compiler=c_compiler) +host_cxx_compiler = compiler( + "C++", + host, + c_compiler=host_c_compiler, + other_compiler=cxx_compiler, + other_c_compiler=c_compiler, +) + +# Generic compiler-based conditions. +building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc") + + +@depends(cxx_compiler, ccache_prefix) +@imports("os") +def cxx_is_icecream(info, ccache_prefix): + if ( + os.path.islink(info.compiler) + and os.path.basename(os.readlink(info.compiler)) == "icecc" + ): + return True + if ccache_prefix and os.path.basename(ccache_prefix) == "icecc": + return True + + +set_config("CXX_IS_ICECREAM", cxx_is_icecream) + + +@depends(c_compiler) +def msvs_version(info): + # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to + # be set for GYP on Windows. + if info.type == "clang-cl": + return "2017" + + return "" + + +set_config("MSVS_VERSION", msvs_version) + +include("compile-checks.configure") +include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm")) + + +@depends(host, host_os_kernel_major_version, target) +def needs_macos_sdk_headers_check(host, version, target): + # Only an issue on Mac OS X 10.14 (and probably above). + if host.kernel != "Darwin" or target.kernel != "Darwin" or version < "18": + return + + return True + + +@depends( + cxx_compiler.try_run( + header="#include_next <inttypes.h>", + check_msg="for macOS SDK headers", + when=needs_macos_sdk_headers_check, + ), + when=needs_macos_sdk_headers_check, +) +def check_have_mac_10_14_sdk(value): + if value: + return + + die( + "System inttypes.h not found. Please try running " + "`open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg` " + "and following the instructions to install the necessary headers" + ) + + +@depends( + have_64_bit, + try_compile( + body='static_assert(sizeof(void *) == 8, "")', check_msg="for 64-bit OS" + ), +) +def check_have_64_bit(have_64_bit, compiler_have_64_bit): + if have_64_bit != compiler_have_64_bit: + configure_error( + "The target compiler does not agree with configure " + "about the target bitness." + ) + + +@depends(cxx_compiler, target) +def needs_libstdcxx_newness_check(cxx_compiler, target): + # We only have to care about this on Linux and MinGW. + if cxx_compiler.type == "clang-cl": + return + + if target.kernel not in ("Linux", "WINNT"): + return + + if target.os == "Android": + return + + return True + + +def die_on_old_libstdcxx(): + die( + "The libstdc++ in use is not new enough. Please run " + "./mach bootstrap to update your compiler, or update your system " + "libstdc++ installation." + ) + + +try_compile( + includes=["cstddef"], + body="\n".join( + [ + # _GLIBCXX_RELEASE showed up in libstdc++ 7. + "#if defined(__GLIBCXX__) && !defined(_GLIBCXX_RELEASE)", + "# error libstdc++ not new enough", + "#endif", + "#if defined(_GLIBCXX_RELEASE)", + "# if _GLIBCXX_RELEASE < %d" % minimum_gcc_version().major, + "# error libstdc++ not new enough", + "# else", + " (void) 0", + "# endif", + "#endif", + ] + ), + check_msg="for new enough STL headers from libstdc++", + when=needs_libstdcxx_newness_check, + onerror=die_on_old_libstdcxx, +) + + +@depends(c_compiler, target) +def default_debug_flags(compiler_info, target): + # Debug info is ON by default. + if compiler_info.type == "clang-cl": + return "-Z7" + elif target.kernel == "WINNT" and compiler_info.type == "clang": + return "-g -gcodeview" + return "-g" + + +option(env="MOZ_DEBUG_FLAGS", nargs=1, help="Debug compiler flags") + +imply_option("--enable-debug-symbols", depends_if("--enable-debug")(lambda v: v)) + +option( + "--disable-debug-symbols", + nargs="?", + help="Disable debug symbols using the given compiler flags", +) + +set_config("MOZ_DEBUG_SYMBOLS", depends_if("--enable-debug-symbols")(lambda _: True)) + + +@depends("MOZ_DEBUG_FLAGS", "--enable-debug-symbols", default_debug_flags) +def debug_flags(env_debug_flags, enable_debug_flags, default_debug_flags): + # If MOZ_DEBUG_FLAGS is set, and --enable-debug-symbols is set to a value, + # --enable-debug-symbols takes precedence. Note, the value of + # --enable-debug-symbols may be implied by --enable-debug. + if len(enable_debug_flags): + return enable_debug_flags[0] + if env_debug_flags: + return env_debug_flags[0] + return default_debug_flags + + +set_config("MOZ_DEBUG_FLAGS", debug_flags) +add_old_configure_assignment("MOZ_DEBUG_FLAGS", debug_flags) + + +@depends(c_compiler) +def color_cflags(info): + # We could test compiling with flags. By why incur the overhead when + # color support should always be present in a specific toolchain + # version? + + # Code for auto-adding this flag to compiler invocations needs to + # determine if an existing flag isn't already present. That is likely + # using exact string matching on the returned value. So if the return + # value changes to e.g. "<x>=always", exact string match may fail and + # multiple color flags could be added. So examine downstream consumers + # before adding flags to return values. + if info.type == "gcc": + return "-fdiagnostics-color" + elif info.type == "clang": + return "-fcolor-diagnostics" + else: + return "" + + +set_config("COLOR_CFLAGS", color_cflags) + +# Some standard library headers (notably bionic on Android) declare standard +# functions (e.g. getchar()) and also #define macros for those standard +# functions. libc++ deals with this by doing something like the following +# (explanatory comments added): +# +# #ifdef FUNC +# // Capture the definition of FUNC. +# inline _LIBCPP_INLINE_VISIBILITY int __libcpp_FUNC(...) { return FUNC(...); } +# #undef FUNC +# // Use a real inline definition. +# inline _LIBCPP_INLINE_VISIBILITY int FUNC(...) { return _libcpp_FUNC(...); } +# #endif +# +# _LIBCPP_INLINE_VISIBILITY is typically defined as: +# +# __attribute__((__visibility__("hidden"), __always_inline__)) +# +# Unfortunately, this interacts badly with our system header wrappers, as the: +# +# #pragma GCC visibility push(default) +# +# that they do prior to including the actual system header is treated by the +# compiler as an explicit declaration of visibility on every function declared +# in the header. Therefore, when the libc++ code above is encountered, it is +# as though the compiler has effectively seen: +# +# int FUNC(...) __attribute__((__visibility__("default"))); +# int FUNC(...) __attribute__((__visibility__("hidden"))); +# +# and the compiler complains about the mismatched visibility declarations. +# +# However, libc++ will only define _LIBCPP_INLINE_VISIBILITY if there is no +# existing definition. We can therefore define it to the empty string (since +# we are properly managing visibility ourselves) and avoid this whole mess. +# Note that we don't need to do this with gcc, as libc++ detects gcc and +# effectively does the same thing we are doing here. +# +# _LIBCPP_ALWAYS_INLINE needs a similar workarounds, since it too declares +# hidden visibility. +# +# _LIBCPP_HIDE_FROM_ABI is a macro in libc++ versions in NDKs >=r19. It too +# declares hidden visibility, but it also declares functions as excluded from +# explicit instantiation (roughly: the function can be unused in the current +# compilation, but does not then trigger an actual definition of the function; +# it is assumed the real definition comes from elsewhere). We need to replicate +# this setup. + + +@depends(c_compiler, target) +def libcxx_override_visibility(c_compiler, target): + if c_compiler.type == "clang" and target.os == "Android": + return namespace( + empty="", + hide_from_abi="__attribute__((__exclude_from_explicit_instantiation__))", + ) + + +set_define("_LIBCPP_INLINE_VISIBILITY", libcxx_override_visibility.empty) +set_define("_LIBCPP_ALWAYS_INLINE", libcxx_override_visibility.empty) + +set_define("_LIBCPP_HIDE_FROM_ABI", libcxx_override_visibility.hide_from_abi) + + +@depends(target, check_build_environment) +def visibility_flags(target, env): + if target.os != "WINNT": + if target.kernel == "Darwin": + return ("-fvisibility=hidden", "-fvisibility-inlines-hidden") + return ( + "-I%s/system_wrappers" % os.path.join(env.dist), + "-include", + "%s/config/gcc_hidden.h" % env.topsrcdir, + ) + + +@depends(target, visibility_flags) +def wrap_system_includes(target, visibility_flags): + if visibility_flags and target.kernel != "Darwin": + return True + + +set_define( + "HAVE_VISIBILITY_HIDDEN_ATTRIBUTE", + depends(visibility_flags)(lambda v: bool(v) or None), +) +set_define( + "HAVE_VISIBILITY_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None) +) +set_config("WRAP_SYSTEM_INCLUDES", wrap_system_includes) +set_config("VISIBILITY_FLAGS", visibility_flags) + + +@template +def depend_cflags(host_or_target_c_compiler): + @depends(host_or_target_c_compiler) + def depend_cflags(host_or_target_c_compiler): + if host_or_target_c_compiler.type != "clang-cl": + return ["-MD", "-MP", "-MF $(MDDEPDIR)/$(@F).pp"] + else: + # clang-cl doesn't accept the normal -MD -MP -MF options that clang + # does, but the underlying cc1 binary understands how to generate + # dependency files. These options are based on analyzing what the + # normal clang driver sends to cc1 when given the "correct" + # dependency options. + return [ + "-Xclang", + "-MP", + "-Xclang", + "-dependency-file", + "-Xclang", + "$(MDDEPDIR)/$(@F).pp", + "-Xclang", + "-MT", + "-Xclang", + "$@", + ] + + return depend_cflags + + +set_config("_DEPEND_CFLAGS", depend_cflags(c_compiler)) +set_config("_HOST_DEPEND_CFLAGS", depend_cflags(host_c_compiler)) + + +@depends(c_compiler) +def preprocess_option(compiler): + # The uses of PREPROCESS_OPTION depend on the spacing for -o/-Fi. + if compiler.type in ("gcc", "clang"): + return "-E -o " + else: + return "-P -Fi" + + +set_config("PREPROCESS_OPTION", preprocess_option) + + +# We only want to include windows.configure when we are compiling on +# Windows, or for Windows. + + +@depends(target, host) +def is_windows(target, host): + return host.kernel == "WINNT" or target.kernel == "WINNT" + + +include("windows.configure", when=is_windows) + + +# On Power ISA, determine compiler flags for VMX, VSX and VSX-3. + +set_config( + "PPC_VMX_FLAGS", + ["-maltivec"], + when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), +) + +set_config( + "PPC_VSX_FLAGS", + ["-mvsx"], + when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), +) + +set_config( + "PPC_VSX3_FLAGS", + ["-mvsx", "-mcpu=power9"], + when=depends(target.cpu)(lambda cpu: cpu.startswith("ppc")), +) + +# ASAN +# ============================================================== + +option("--enable-address-sanitizer", help="Enable Address Sanitizer") + + +@depends(when="--enable-address-sanitizer") +def asan(): + return True + + +add_old_configure_assignment("MOZ_ASAN", asan) + +# MSAN +# ============================================================== + +option("--enable-memory-sanitizer", help="Enable Memory Sanitizer") + + +@depends(when="--enable-memory-sanitizer") +def msan(): + return True + + +add_old_configure_assignment("MOZ_MSAN", msan) + +# TSAN +# ============================================================== + +option("--enable-thread-sanitizer", help="Enable Thread Sanitizer") + + +@depends(when="--enable-thread-sanitizer") +def tsan(): + return True + + +add_old_configure_assignment("MOZ_TSAN", tsan) + +# UBSAN +# ============================================================== + +option( + "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer" +) + + +@depends_if("--enable-undefined-sanitizer") +def ubsan(options): + default_checks = [ + "bool", + "bounds", + "enum", + "integer-divide-by-zero", + "object-size", + "pointer-overflow", + "return", + "vla-bound", + ] + + checks = options if len(options) else default_checks + + return ",".join(checks) + + +add_old_configure_assignment("MOZ_UBSAN_CHECKS", ubsan) + + +option( + "--enable-signed-overflow-sanitizer", + help="Enable UndefinedBehavior Sanitizer (Signed Integer Overflow Parts)", +) + + +@depends(when="--enable-signed-overflow-sanitizer") +def ub_signed_overflow_san(): + return True + + +add_old_configure_assignment("MOZ_SIGNED_OVERFLOW_SANITIZE", ub_signed_overflow_san) + + +option( + "--enable-unsigned-overflow-sanitizer", + help="Enable UndefinedBehavior Sanitizer (Unsigned Integer Overflow Parts)", +) + + +@depends(when="--enable-unsigned-overflow-sanitizer") +def ub_unsigned_overflow_san(): + return True + + +add_old_configure_assignment("MOZ_UNSIGNED_OVERFLOW_SANITIZE", ub_unsigned_overflow_san) + +# Security Hardening +# ============================================================== + +option( + "--enable-hardening", + env="MOZ_SECURITY_HARDENING", + help="Enables security hardening compiler options", +) + + +# This function is a bit confusing. It adds or removes hardening flags in +# three stuations: if --enable-hardening is passed; if --disable-hardening +# is passed, and if no flag is passed. +# +# At time of this comment writing, all flags are actually added in the +# default no-flag case; making --enable-hardening the same as omitting the +# flag. --disable-hardening will omit the security flags. (However, not all +# possible security flags will be omitted by --disable-hardening, as many are +# compiler-default options we do not explicitly enable.) +@depends( + "--enable-hardening", + "--enable-address-sanitizer", + "--enable-debug", + "--enable-optimize", + c_compiler, + target, +) +def security_hardening_cflags( + hardening_flag, asan, debug, optimize, c_compiler, target +): + compiler_is_gccish = c_compiler.type in ("gcc", "clang") + mingw_clang = c_compiler.type == "clang" and target.os == "WINNT" + + flags = [] + ldflags = [] + js_flags = [] + js_ldflags = [] + + # ---------------------------------------------------------- + # If hardening is explicitly enabled, or not explicitly disabled + if hardening_flag.origin == "default" or hardening_flag: + # FORTIFY_SOURCE ------------------------------------ + # Require optimization for FORTIFY_SOURCE. See Bug 1417452 + # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398 + if compiler_is_gccish and optimize and not asan: + # Don't enable FORTIFY_SOURCE on Android on the top-level, but do enable in js/ + if target.os != "Android": + flags.append("-U_FORTIFY_SOURCE") + flags.append("-D_FORTIFY_SOURCE=2") + js_flags.append("-U_FORTIFY_SOURCE") + js_flags.append("-D_FORTIFY_SOURCE=2") + if mingw_clang: + # mingw-clang needs to link in ssp which is not done by default + ldflags.append("-lssp") + js_ldflags.append("-lssp") + + # fstack-protector ------------------------------------ + # Enable only if hardening is not disabled and ASAN is + # not on as ASAN will catch the crashes for us + if compiler_is_gccish and not asan: + flags.append("-fstack-protector-strong") + ldflags.append("-fstack-protector-strong") + js_flags.append("-fstack-protector-strong") + js_ldflags.append("-fstack-protector-strong") + + if ( + c_compiler.type == "clang" + and c_compiler.version >= "11.0.1" + and target.os not in ("WINNT", "OSX") + and target.cpu in ("x86", "x86_64", "ppc64", "s390x") + ): + flags.append("-fstack-clash-protection") + ldflags.append("-fstack-clash-protection") + js_flags.append("-fstack-clash-protection") + js_ldflags.append("-fstack-clash-protection") + + # ftrivial-auto-var-init ------------------------------ + # Initialize local variables with a 0xAA pattern in clang debug builds. + # Linux32 fails some xpcshell tests with -ftrivial-auto-var-init + linux32 = target.kernel == "Linux" and target.cpu == "x86" + if ( + (c_compiler.type == "clang" or c_compiler.type == "clang-cl") + and c_compiler.version >= "8" + and debug + and not linux32 + ): + if c_compiler.type == "clang-cl": + flags.append("-Xclang") + js_flags.append("-Xclang") + flags.append("-ftrivial-auto-var-init=pattern") + js_flags.append("-ftrivial-auto-var-init=pattern") + + # ASLR ------------------------------------------------ + # ASLR (dynamicbase) is enabled by default in clang-cl; but the + # mingw-clang build requires it to be explicitly enabled + if mingw_clang: + ldflags.append("-Wl,--dynamicbase") + js_ldflags.append("-Wl,--dynamicbase") + + # Control Flow Guard (CFG) ---------------------------- + if ( + c_compiler.type == "clang-cl" + and c_compiler.version >= "8" + and (target.cpu != "aarch64" or c_compiler.version >= "8.0.1") + ): + if target.cpu == "aarch64" and c_compiler.version >= "10.0.0": + # The added checks in clang 10 make arm64 builds crash. (Bug 1639318) + flags.append("-guard:cf,nochecks") + js_flags.append("-guard:cf,nochecks") + else: + flags.append("-guard:cf") + js_flags.append("-guard:cf") + # nolongjmp is needed because clang doesn't emit the CFG tables of + # setjmp return addresses https://bugs.llvm.org/show_bug.cgi?id=40057 + ldflags.append("-guard:cf,nolongjmp") + js_ldflags.append("-guard:cf,nolongjmp") + + # ---------------------------------------------------------- + # If ASAN _is_ on, undefine FORTIFY_SOURCE just to be safe + if asan: + flags.append("-U_FORTIFY_SOURCE") + js_flags.append("-U_FORTIFY_SOURCE") + + # fno-common ----------------------------------------- + # Do not merge variables for ASAN; can detect some subtle bugs + if asan: + # clang-cl does not recognize the flag, it must be passed down to clang + if c_compiler.type == "clang-cl": + flags.append("-Xclang") + flags.append("-fno-common") + + return namespace( + flags=flags, + ldflags=ldflags, + js_flags=js_flags, + js_ldflags=js_ldflags, + ) + + +set_config("MOZ_HARDENING_CFLAGS", security_hardening_cflags.flags) +set_config("MOZ_HARDENING_LDFLAGS", security_hardening_cflags.ldflags) +set_config("MOZ_HARDENING_CFLAGS_JS", security_hardening_cflags.js_flags) +set_config("MOZ_HARDENING_LDFLAGS_JS", security_hardening_cflags.js_ldflags) + + +# Frame pointers +# ============================================================== +@depends(c_compiler) +def frame_pointer_flags(compiler): + if compiler.type == "clang-cl": + return namespace( + enable=["-Oy-"], + disable=["-Oy"], + ) + return namespace( + enable=["-fno-omit-frame-pointer", "-funwind-tables"], + disable=["-fomit-frame-pointer", "-funwind-tables"], + ) + + +@depends( + moz_optimize.optimize, + moz_debug, + target, + "--enable-memory-sanitizer", + "--enable-address-sanitizer", + "--enable-undefined-sanitizer", +) +def frame_pointer_default(optimize, debug, target, msan, asan, ubsan): + return bool( + not optimize + or debug + or msan + or asan + or ubsan + or (target.os == "WINNT" and target.cpu in ("x86", "aarch64")) + ) + + +option( + "--enable-frame-pointers", + default=frame_pointer_default, + help="{Enable|Disable} frame pointers", +) + + +@depends("--enable-frame-pointers", frame_pointer_flags) +def frame_pointer_flags(enable, flags): + if enable: + return flags.enable + return flags.disable + + +set_config("MOZ_FRAMEPTR_FLAGS", frame_pointer_flags) + + +# nasm detection +# ============================================================== +nasm = check_prog( + "NASM", ["nasm"], allow_missing=True, paths=bootstrap_search_path("nasm") +) + + +@depends_if(nasm) +@checking("nasm version") +def nasm_version(nasm): + (retcode, stdout, _) = get_cmd_output(nasm, "-v") + if retcode: + # mac stub binary + return None + + version = stdout.splitlines()[0].split()[2] + return Version(version) + + +@depends_if(nasm_version) +def nasm_major_version(nasm_version): + return str(nasm_version.major) + + +@depends_if(nasm_version) +def nasm_minor_version(nasm_version): + return str(nasm_version.minor) + + +set_config("NASM_MAJOR_VERSION", nasm_major_version) +set_config("NASM_MINOR_VERSION", nasm_minor_version) + + +@depends(nasm, target) +def nasm_asflags(nasm, target): + if nasm: + asflags = { + ("OSX", "x86"): ["-f", "macho32"], + ("OSX", "x86_64"): ["-f", "macho64"], + ("WINNT", "x86"): ["-f", "win32"], + ("WINNT", "x86_64"): ["-f", "win64"], + }.get((target.os, target.cpu), None) + if asflags is None: + # We're assuming every x86 platform we support that's + # not Windows or Mac is ELF. + if target.cpu == "x86": + asflags = ["-f", "elf32"] + elif target.cpu == "x86_64": + asflags = ["-f", "elf64"] + return asflags + + +set_config("NASM_ASFLAGS", nasm_asflags) + + +@depends(nasm_asflags) +def have_nasm(value): + if value: + return True + + +@depends(yasm_asflags) +def have_yasm(yasm_asflags): + if yasm_asflags: + return True + + +set_config("HAVE_NASM", have_nasm) + +set_config("HAVE_YASM", have_yasm) +# Until the YASM variable is not necessary in old-configure. +add_old_configure_assignment("YASM", have_yasm) + + +# Code Coverage +# ============================================================== + +option("--enable-coverage", env="MOZ_CODE_COVERAGE", help="Enable code coverage") + + +@depends("--enable-coverage") +def code_coverage(value): + if value: + return True + + +set_config("MOZ_CODE_COVERAGE", code_coverage) +set_define("MOZ_CODE_COVERAGE", code_coverage) + + +@depends(target, c_compiler, vc_path, check_build_environment, when=code_coverage) +@imports("os") +@imports("re") +@imports(_from="__builtin__", _import="open") +def coverage_cflags(target, c_compiler, vc_path, build_env): + cflags = ["--coverage"] + + # clang 11 no longer accepts this flag (its behavior became the default) + if c_compiler.type in ("clang", "clang-cl") and c_compiler.version < "11.0.0": + cflags += [ + "-Xclang", + "-coverage-no-function-names-in-data", + ] + + if target.os == "WINNT" and c_compiler.type == "clang-cl": + # The Visual Studio directory is the parent of the Visual C++ directory. + vs_path = os.path.dirname(vc_path) + + # We need to get the real path of Visual Studio, which can be in a + # symlinked directory (for example, on automation). + vs_path = os.path.realpath(vs_path) + + cflags += [ + "-fprofile-exclude-files=^{}.*$".format(re.escape(vs_path)), + ] + + response_file_path = os.path.join(build_env.topobjdir, "code_coverage_cflags") + + with open(response_file_path, "w") as f: + f.write(" ".join(cflags)) + + return ["@{}".format(response_file_path)] + + +set_config("COVERAGE_CFLAGS", coverage_cflags) + +# ============================================================== + +option(env="RUSTFLAGS", nargs=1, help="Rust compiler flags") +set_config("RUSTFLAGS", depends("RUSTFLAGS")(lambda flags: flags)) + + +# Rust compiler flags +# ============================================================== + +option( + env="RUSTC_OPT_LEVEL", + nargs=1, + help="Rust compiler optimization level (-C opt-level=%s)", +) + +# --enable-release kicks in full optimizations. +imply_option("RUSTC_OPT_LEVEL", "2", when="--enable-release") + + +@depends("RUSTC_OPT_LEVEL", moz_optimize) +def rustc_opt_level(opt_level_option, moz_optimize): + if opt_level_option: + return opt_level_option[0] + else: + return "1" if moz_optimize.optimize else "0" + + +@depends( + rustc_opt_level, + debug_rust, + target, + "--enable-debug-symbols", + "--enable-frame-pointers", +) +def rust_compile_flags(opt_level, debug_rust, target, debug_symbols, frame_pointers): + # Cargo currently supports only two interesting profiles for building: + # development and release. Those map (roughly) to --enable-debug and + # --disable-debug in Gecko, respectively. + # + # But we'd also like to support an additional axis of control for + # optimization level. Since Cargo only supports 2 profiles, we're in + # a bit of a bind. + # + # Code here derives various compiler options given other configure options. + # The options defined here effectively override defaults specified in + # Cargo.toml files. + + debug_assertions = None + debug_info = None + + # opt-level=0 implies -C debug-assertions, which may not be desired + # unless Rust debugging is enabled. + if opt_level == "0" and not debug_rust: + debug_assertions = False + + if debug_symbols: + debug_info = "2" + + opts = [] + + if opt_level is not None: + opts.append("opt-level=%s" % opt_level) + if debug_assertions is not None: + opts.append("debug-assertions=%s" % ("yes" if debug_assertions else "no")) + if debug_info is not None: + opts.append("debuginfo=%s" % debug_info) + if frame_pointers: + opts.append("force-frame-pointers=yes") + # CFG for arm64 is crashy, see `def security_hardening_cflags`. + if target.kernel == "WINNT" and target.cpu != "aarch64": + opts.append("control-flow-guard=yes") + + flags = [] + for opt in opts: + flags.extend(["-C", opt]) + + return flags + + +# Rust incremental compilation +# ============================================================== + + +option("--disable-cargo-incremental", help="Disable incremental rust compilation.") + + +@depends( + rustc_opt_level, + debug_rust, + "MOZ_AUTOMATION", + code_coverage, + "--disable-cargo-incremental", + using_sccache, + "RUSTC_WRAPPER", +) +@imports("os") +def cargo_incremental( + opt_level, + debug_rust, + automation, + code_coverage, + enabled, + using_sccache, + rustc_wrapper, +): + """Return a value for the CARGO_INCREMENTAL environment variable.""" + + if not enabled: + return "0" + + # We never want to use incremental compilation in automation. sccache + # handles our automation use case much better than incremental compilation + # would. + if automation: + return "0" + + # Coverage instrumentation doesn't play well with incremental compilation + # https://github.com/rust-lang/rust/issues/50203. + if code_coverage: + return "0" + + # Incremental compilation doesn't work as well as it should, and if we're + # using sccache, it's better to use sccache than incremental compilation. + if not using_sccache and rustc_wrapper: + rustc_wrapper = os.path.basename(rustc_wrapper[0]) + if os.path.splitext(rustc_wrapper)[0].lower() == "sccache": + using_sccache = True + if using_sccache: + return "0" + + # Incremental compilation is automatically turned on for debug builds, so + # we don't need to do anything special here. + if debug_rust: + return + + # --enable-release automatically sets -O2 for Rust code, and people can + # set RUSTC_OPT_LEVEL to 2 or even 3 if they want to profile Rust code. + # Let's assume that if Rust code is using -O2 or higher, we shouldn't + # be using incremental compilation, because we'd be imposing a + # significant runtime cost. + if opt_level not in ("0", "1"): + return + + # We're clear to use incremental compilation! + return "1" + + +set_config("CARGO_INCREMENTAL", cargo_incremental) + +# Linker detection +# ============================================================== +# The policy is as follows: +# For Windows: +# - the linker is picked via the LINKER environment variable per windows.configure, +# but ought to be llvm-lld in any case. +# For macOS: +# - the linker is ld64, either from XCode on macOS, or from cctools-ports when +# cross-compiling. lld can be enabled manually, but as of writing, mach-o support +# for lld is incomplete. +# For other OSes: +# - on local developer builds: lld is used if present. Otherwise gold is used if present +# otherwise, BFD ld is used. +# - on release/official builds: whatever "ld" resolves to is used, except on Android x86/x86_64 +# where BFD ld is used. Usually, "ld" resolves to BFD ld, except with the Android NDK, +# where it resolves to gold. lld is not used by default on Linux and Android because +# it introduces layout changes that prevent elfhack from working. See e.g. +# https://bugzilla.mozilla.org/show_bug.cgi?id=1563654#c2. +@depends(target) +def is_linker_option_enabled(target): + if target.kernel not in ("WINNT", "SunOS"): + return True + + +option( + "--enable-gold", + env="MOZ_FORCE_GOLD", + help="Enable GNU Gold Linker when it is not already the default", + when=is_linker_option_enabled, +) + +imply_option("--enable-linker", "gold", when="--enable-gold") + + +@depends(target, developer_options) +def enable_linker_default(target, developer_options): + # x86-64 gold has bugs in how it lays out .note.* sections. See bug 1573820. + # x86-32 gold has a bug when assembly files are built. See bug 1651699. + # lld is faster, so prefer that for developer builds. + if target.os == "Android" and target.cpu in ("x86", "x86_64"): + return "lld" if developer_options else "bfd" + + +option( + "--enable-linker", + nargs=1, + help="Select the linker {bfd, gold, ld64, lld, lld-*}{|}", + default=enable_linker_default, + when=is_linker_option_enabled, +) + + +# No-op to enable depending on --enable-linker from default_elfhack in +# toolkit/moz.configure. +@depends("--enable-linker", when=is_linker_option_enabled) +def enable_linker(linker): + return linker + + +@depends( + "--enable-linker", + c_compiler, + developer_options, + "--enable-gold", + extra_toolchain_flags, + target, + when=is_linker_option_enabled, +) +@checking("for linker", lambda x: x.KIND) +@imports("os") +@imports("shutil") +def select_linker( + linker, c_compiler, developer_options, enable_gold, toolchain_flags, target +): + + if linker: + linker = linker[0] + else: + linker = None + + def is_valid_linker(linker): + if target.kernel == "Darwin": + valid_linkers = ("ld64", "lld") + else: + valid_linkers = ("bfd", "gold", "lld") + if linker in valid_linkers: + return True + if "lld" in valid_linkers and linker.startswith("lld-"): + return True + return False + + if linker and not is_valid_linker(linker): + # Check that we are trying to use a supported linker + die("Unsupported linker " + linker) + + # Check the kind of linker + version_check = ["-Wl,--version"] + cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags + + def try_linker(linker): + # Generate the compiler flag + if linker == "ld64": + linker_flag = ["-fuse-ld=ld"] + elif linker: + linker_flag = ["-fuse-ld=" + linker] + else: + linker_flag = [] + cmd = cmd_base + linker_flag + version_check + if toolchain_flags: + cmd += toolchain_flags + + # ld64 doesn't have anything to print out a version. It does print out + # "ld64: For information on command line options please use 'man ld'." + # but that would require doing two attempts, one with --version, that + # would fail, and another with --help. + # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message + # specific to it on stderr when it fails to process --version. + env = dict(os.environ) + env["LD_PRINT_OPTIONS"] = "1" + # Some locales might not print out the strings we are looking for, so + # ensure consistent output. + env["LC_ALL"] = "C" + retcode, stdout, stderr = get_cmd_output(*cmd, env=env) + if retcode == 1 and "Logging ld64 options" in stderr: + kind = "ld64" + + elif retcode != 0: + return None + + elif "GNU ld" in stdout: + # We are using the normal linker + kind = "bfd" + + elif "GNU gold" in stdout: + kind = "gold" + + elif "LLD" in stdout: + kind = "lld" + + else: + kind = "unknown" + + return namespace( + KIND=kind, + LINKER_FLAG=linker_flag, + ) + + result = try_linker(linker) + if result is None: + if linker: + die("Could not use {} as linker".format(linker)) + die("Failed to find a linker") + + if ( + linker is None + and enable_gold.origin == "default" + and developer_options + and result.KIND in ("bfd", "gold") + ): + # try and use lld if available. + tried = try_linker("lld") + if result.KIND != "gold" and (tried is None or tried.KIND != "lld"): + tried = try_linker("gold") + if tried is None or tried.KIND != "gold": + tried = None + if tried: + result = tried + + # If an explicit linker was given, error out if what we found is different. + if linker and not linker.startswith(result.KIND): + die("Could not use {} as linker".format(linker)) + + return result + + +set_config("LINKER_KIND", select_linker.KIND) + + +@depends_if(select_linker, macos_sdk) +def linker_ldflags(linker, macos_sdk): + flags = list((linker and linker.LINKER_FLAG) or []) + if macos_sdk: + if linker and linker.KIND == "ld64": + flags.append("-Wl,-syslibroot,%s" % macos_sdk) + else: + flags.append("-Wl,--sysroot=%s" % macos_sdk) + + return flags + + +add_old_configure_assignment("LINKER_LDFLAGS", linker_ldflags) + + +# There's a wrinkle with MinGW: linker configuration is not enabled, so +# `select_linker` is never invoked. Hard-code around it. +@depends(select_linker, target, c_compiler) +def gcc_use_gnu_ld(select_linker, target, c_compiler): + if select_linker is not None: + return select_linker.KIND in ("bfd", "gold", "lld") + if target.kernel == "WINNT" and c_compiler.type == "clang": + return True + return None + + +# GCC_USE_GNU_LD=1 means the linker is command line compatible with GNU ld. +set_config("GCC_USE_GNU_LD", gcc_use_gnu_ld) +add_old_configure_assignment("GCC_USE_GNU_LD", gcc_use_gnu_ld) + +# Assembler detection +# ============================================================== + +option(env="AS", nargs=1, help="Path to the assembler") + + +@depends(target, c_compiler) +def as_info(target, c_compiler): + if c_compiler.type == "clang-cl": + ml = { + "x86": "ml.exe", + "x86_64": "ml64.exe", + "aarch64": "armasm64.exe", + }.get(target.cpu) + return namespace(type="masm", names=(ml,)) + # When building with anything but clang-cl, we just use the C compiler as the assembler. + return namespace(type="gcc", names=(c_compiler.compiler,)) + + +# One would expect the assembler to be specified merely as a program. But in +# cases where the assembler is passed down into js/, it can be specified in +# the same way as CC: a program + a list of argument flags. We might as well +# permit the same behavior in general, even though it seems somewhat unusual. +# So we have to do the same sort of dance as we did above with +# `provided_compiler`. +provided_assembler = provided_program("AS") +assembler = check_prog( + "_AS", + input=provided_assembler.program, + what="the assembler", + progs=as_info.names, + paths=vc_toolchain_search_path, +) + + +@depends(as_info, assembler, provided_assembler, c_compiler) +def as_with_flags(as_info, assembler, provided_assembler, c_compiler): + if provided_assembler: + return provided_assembler.wrapper + [assembler] + provided_assembler.flags + + if as_info.type == "masm": + return assembler + + assert as_info.type == "gcc" + + # Need to add compiler wrappers and flags as appropriate. + return c_compiler.wrapper + [assembler] + c_compiler.flags + + +add_old_configure_assignment("AS", as_with_flags) +add_old_configure_assignment("ac_cv_prog_AS", as_with_flags) + + +@depends(assembler, c_compiler, extra_toolchain_flags) +@imports("subprocess") +@imports(_from="os", _import="devnull") +def gnu_as(assembler, c_compiler, toolchain_flags): + # clang uses a compatible GNU assembler. + if c_compiler.type == "clang": + return True + + if c_compiler.type == "gcc": + cmd = [assembler] + c_compiler.flags + if toolchain_flags: + cmd += toolchain_flags + cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"] + # We don't actually have to provide any input on stdin, `Popen.communicate` will + # close the stdin pipe. + # clang will error if it uses its integrated assembler for this target, + # so handle failures gracefully. + if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""): + return True + + +set_config("GNU_AS", gnu_as) +add_old_configure_assignment("GNU_AS", gnu_as) + + +@depends(as_info, target) +def as_dash_c_flag(as_info, target): + # armasm64 doesn't understand -c. + if as_info.type == "masm" and target.cpu == "aarch64": + return "" + else: + return "-c" + + +set_config("AS_DASH_C_FLAG", as_dash_c_flag) + + +@depends(as_info, target) +def as_outoption(as_info, target): + # The uses of ASOUTOPTION depend on the spacing for -o/-Fo. + if as_info.type == "masm" and target.cpu != "aarch64": + return "-Fo" + + return "-o " + + +set_config("ASOUTOPTION", as_outoption) + +# clang plugin handling +# ============================================================== + +option( + "--enable-clang-plugin", + env="ENABLE_CLANG_PLUGIN", + help="Enable building with the Clang plugin (gecko specific static analyzers)", +) + +add_old_configure_assignment( + "ENABLE_CLANG_PLUGIN", depends_if("--enable-clang-plugin")(lambda _: True) +) + + +@depends(host_c_compiler, c_compiler, when="--enable-clang-plugin") +def llvm_config(host_c_compiler, c_compiler): + clang = None + for compiler in (host_c_compiler, c_compiler): + if compiler and compiler.type == "clang": + clang = compiler.compiler + break + elif compiler and compiler.type == "clang-cl": + clang = os.path.join(os.path.dirname(compiler.compiler), "clang") + break + + if not clang: + die("Cannot --enable-clang-plugin when not building with clang") + llvm_config = "llvm-config" + out = check_cmd_output(clang, "--print-prog-name=llvm-config", onerror=lambda: None) + if out: + llvm_config = out.rstrip() + return (llvm_config,) + + +llvm_config = check_prog( + "LLVM_CONFIG", + llvm_config, + what="llvm-config", + when="--enable-clang-plugin", + paths=clang_search_path, +) + +add_old_configure_assignment("LLVM_CONFIG", llvm_config) + + +option( + "--enable-clang-plugin-alpha", + env="ENABLE_CLANG_PLUGIN_ALPHA", + help="Enable static analysis with clang-plugin alpha checks.", +) + + +@depends("--enable-clang-plugin", "--enable-clang-plugin-alpha") +def check_clang_plugin_alpha(enable_clang_plugin, enable_clang_plugin_alpha): + if enable_clang_plugin_alpha: + if enable_clang_plugin: + return True + die("Cannot enable clang-plugin alpha checkers without --enable-clang-plugin.") + + +add_old_configure_assignment("ENABLE_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha) +set_define("MOZ_CLANG_PLUGIN_ALPHA", check_clang_plugin_alpha) + +option( + "--enable-mozsearch-plugin", + env="ENABLE_MOZSEARCH_PLUGIN", + help="Enable building with the mozsearch indexer plugin", +) + +add_old_configure_assignment( + "ENABLE_MOZSEARCH_PLUGIN", depends_if("--enable-mozsearch-plugin")(lambda _: True) +) + +# Libstdc++ compatibility hacks +# ============================================================== +# +option( + "--enable-stdcxx-compat", + env="MOZ_STDCXX_COMPAT", + help="Enable compatibility with older libstdc++", +) + + +@template +def libstdcxx_version(var, compiler): + @depends(compiler, when="--enable-stdcxx-compat") + @checking(var, lambda v: v and "GLIBCXX_%s" % v.dotted) + @imports(_from="mozbuild.configure.libstdcxx", _import="find_version") + @imports(_from="__builtin__", _import="Exception") + def version(compiler): + try: + result = find_version( + compiler.wrapper + [compiler.compiler] + compiler.flags + ) + except Exception: + die("Couldn't determine libstdc++ version") + if result: + return namespace( + dotted=result[0], + encoded=str(result[1]), + ) + + set_config(var, version.encoded) + return version + + +add_gcc_flag( + "-D_GLIBCXX_USE_CXX11_ABI=0", + cxx_compiler, + when=libstdcxx_version("MOZ_LIBSTDCXX_TARGET_VERSION", cxx_compiler), +) +add_gcc_flag( + "-D_GLIBCXX_USE_CXX11_ABI=0", + host_cxx_compiler, + when=libstdcxx_version("MOZ_LIBSTDCXX_HOST_VERSION", host_cxx_compiler), +) + + +# Support various fuzzing options +# ============================================================== +option("--enable-fuzzing", help="Enable fuzzing support") + + +@depends("--enable-fuzzing") +def enable_fuzzing(value): + if value: + return True + + +@depends( + try_compile( + body="__AFL_COMPILER;", check_msg="for AFL compiler", when="--enable-fuzzing" + ) +) +def enable_aflfuzzer(afl): + if afl: + return True + + +@depends(enable_fuzzing, enable_aflfuzzer, c_compiler, target) +def enable_libfuzzer(fuzzing, afl, c_compiler, target): + if fuzzing and not afl and c_compiler.type == "clang" and target.os != "Android": + return True + + +@depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer) +def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer): + if fuzzing and (afl or libfuzzer): + return True + + +set_config("FUZZING", enable_fuzzing) +set_define("FUZZING", enable_fuzzing) + +set_config("LIBFUZZER", enable_libfuzzer) +set_define("LIBFUZZER", enable_libfuzzer) +add_old_configure_assignment("LIBFUZZER", enable_libfuzzer) + +set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces) +set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces) +add_old_configure_assignment("FUZZING_INTERFACES", enable_fuzzing_interfaces) + + +@depends( + c_compiler.try_compile( + flags=["-fsanitize=fuzzer-no-link"], + when=enable_fuzzing, + check_msg="whether the C compiler supports -fsanitize=fuzzer-no-link", + ), + tsan, +) +def libfuzzer_flags(value, tsan): + if tsan: + # With ThreadSanitizer, we should not use any libFuzzer instrumentation because + # it is incompatible (e.g. there are races on global sanitizer coverage counters). + # Instead we use an empty set of flags here but still build the fuzzing targets. + # With this setup, we can still run files through these targets in TSan builds, + # e.g. those obtained from regular fuzzing. + # This code can be removed once libFuzzer has been made compatible with TSan. + # + # Also, this code needs to be kept in sync with certain gyp files, currently: + # - dom/media/webrtc/transport/third_party/nICEr/nicer.gyp + return namespace(no_link_flag_supported=False, use_flags=[]) + + if value: + no_link_flag_supported = True + # recommended for (and only supported by) clang >= 6 + use_flags = ["-fsanitize=fuzzer-no-link"] + else: + no_link_flag_supported = False + use_flags = ["-fsanitize-coverage=trace-pc-guard,trace-cmp"] + + return namespace( + no_link_flag_supported=no_link_flag_supported, + use_flags=use_flags, + ) + + +set_config("HAVE_LIBFUZZER_FLAG_FUZZER_NO_LINK", libfuzzer_flags.no_link_flag_supported) +set_config("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags) +add_old_configure_assignment("LIBFUZZER_FLAGS", libfuzzer_flags.use_flags) + +# Shared library building +# ============================================================== + +# XXX: The use of makefile constructs in these variables is awful. +@depends(target, c_compiler) +def make_shared_library(target, compiler): + if target.os == "WINNT": + if compiler.type == "gcc": + return namespace( + mkshlib=["$(CXX)", "$(DSO_LDOPTS)", "-o", "$@"], + mkcshlib=["$(CC)", "$(DSO_LDOPTS)", "-o", "$@"], + ) + elif compiler.type == "clang": + return namespace( + mkshlib=[ + "$(CXX)", + "$(DSO_LDOPTS)", + "-Wl,-pdb,$(LINK_PDBFILE)", + "-o", + "$@", + ], + mkcshlib=[ + "$(CC)", + "$(DSO_LDOPTS)", + "-Wl,-pdb,$(LINK_PDBFILE)", + "-o", + "$@", + ], + ) + else: + linker = [ + "$(LINKER)", + "-NOLOGO", + "-DLL", + "-OUT:$@", + "-PDB:$(LINK_PDBFILE)", + "$(DSO_LDOPTS)", + ] + return namespace( + mkshlib=linker, + mkcshlib=linker, + ) + + cc = ["$(CC)", "$(COMPUTED_C_LDFLAGS)"] + cxx = ["$(CXX)", "$(COMPUTED_CXX_LDFLAGS)"] + flags = ["$(PGO_CFLAGS)", "$(DSO_PIC_CFLAGS)", "$(DSO_LDOPTS)"] + output = ["-o", "$@"] + + if target.kernel == "Darwin": + soname = [] + elif target.os == "NetBSD": + soname = ["-Wl,-soname,$(DSO_SONAME)"] + else: + assert compiler.type in ("gcc", "clang") + + soname = ["-Wl,-h,$(DSO_SONAME)"] + + return namespace( + mkshlib=cxx + flags + soname + output, + mkcshlib=cc + flags + soname + output, + ) + + +set_config("MKSHLIB", make_shared_library.mkshlib) +set_config("MKCSHLIB", make_shared_library.mkcshlib) + + +@depends(c_compiler, toolchain_prefix, when=target_is_windows) +def rc_names(c_compiler, toolchain_prefix): + if c_compiler.type in ("gcc", "clang"): + return tuple("%s%s" % (p, "windres") for p in ("",) + (toolchain_prefix or ())) + return ("llvm-rc",) + + +check_prog("RC", rc_names, paths=clang_search_path, when=target_is_windows) + + +@depends(toolchain_prefix, c_compiler) +def ar_config(toolchain_prefix, c_compiler): + if c_compiler.type == "clang-cl": + return namespace( + names=("llvm-lib",), + flags=("-llvmlibthin", "-out:$@"), + ) + + names = tuple("%s%s" % (p, "ar") for p in (toolchain_prefix or ()) + ("",)) + if c_compiler.type == "clang": + # Get the llvm-ar path as per the output from clang --print-prog-name=llvm-ar + # so that we directly get the one under the clang directory, rather than one + # that might be in /usr/bin and that might point to one from a different version + # of clang. + out = check_cmd_output( + c_compiler.compiler, "--print-prog-name=llvm-ar", onerror=lambda: None + ) + llvm_ar = out.rstrip() if out else "llvm-ar" + names = (llvm_ar,) + names + + return namespace( + names=names, + flags=("crs", "$@"), + ) + + +ar = check_prog("AR", ar_config.names, paths=clang_search_path) + +add_old_configure_assignment("AR", ar) + +set_config("AR_FLAGS", ar_config.flags) + + +@depends(toolchain_prefix, c_compiler) +def nm_names(toolchain_prefix, c_compiler): + names = tuple("%s%s" % (p, "nm") for p in (toolchain_prefix or ()) + ("",)) + if c_compiler.type == "clang": + # Get the llvm-nm path as per the output from clang --print-prog-name=llvm-nm + # so that we directly get the one under the clang directory, rather than one + # that might be in /usr/bin and that might point to one from a different version + # of clang. + out = check_cmd_output( + c_compiler.compiler, "--print-prog-name=llvm-nm", onerror=lambda: None + ) + llvm_nm = out.rstrip() if out else "llvm-nm" + names = (llvm_nm,) + names + + return names + + +check_prog("NM", nm_names, paths=clang_search_path, when=target_is_linux) + + +option("--enable-cpp-rtti", help="Enable C++ RTTI") + +add_old_configure_assignment("_MOZ_USE_RTTI", "1", when="--enable-cpp-rtti") diff --git a/build/moz.configure/update-programs.configure b/build/moz.configure/update-programs.configure new file mode 100644 index 0000000000..d5a75b9ac8 --- /dev/null +++ b/build/moz.configure/update-programs.configure @@ -0,0 +1,83 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Verify MAR signatures +# ============================================================== + +option("--disable-verify-mar", help="Disable verifying MAR signatures") + +set_define( + "MOZ_VERIFY_MAR_SIGNATURE", depends_if("--enable-verify-mar")(lambda _: True) +) +set_config( + "MOZ_VERIFY_MAR_SIGNATURE", depends_if("--enable-verify-mar")(lambda _: True) +) + +# Maintenance service (Windows only) +# ============================================================== + +option( + "--enable-maintenance-service", + when=target_is_windows, + default=target_is_windows, + help="{Enable|Disable} building of maintenance service", +) + +set_define( + "MOZ_MAINTENANCE_SERVICE", + depends_if("--enable-maintenance-service", when=target_is_windows)(lambda _: True), +) +set_config( + "MOZ_MAINTENANCE_SERVICE", + depends_if("--enable-maintenance-service", when=target_is_windows)(lambda _: True), +) + +# Update agent (currently Windows only) +# This is an independent task that runs on a schedule to +# check for, download, and install updates. +# ============================================================== + +option( + "--enable-update-agent", + when=target_is_windows, + default=False, + help="{Enable|Disable} building update agent", +) + +set_define( + "MOZ_UPDATE_AGENT", + depends_if("--enable-update-agent", when=target_is_windows)(lambda _: True), +) + +set_config( + "MOZ_UPDATE_AGENT", + depends_if("--enable-update-agent", when=target_is_windows)(lambda _: True), +) + +# Enable or disable the default browser agent, which monitors the user's default +# browser setting on Windows. +# ============================================================================== + + +@depends(target) +def default_browser_agent_default(target): + return target.os == "WINNT" + + +option( + "--enable-default-browser-agent", + default=default_browser_agent_default, + help="{Enable|Disable} building the default browser agent", +) + + +@depends("--enable-default-browser-agent", when=target_is_windows) +def default_agent_flag(enabled): + if enabled: + return True + + +set_config("MOZ_DEFAULT_BROWSER_AGENT", default_agent_flag) diff --git a/build/moz.configure/util.configure b/build/moz.configure/util.configure new file mode 100644 index 0000000000..fe82698c62 --- /dev/null +++ b/build/moz.configure/util.configure @@ -0,0 +1,494 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +@imports("sys") +def die(*args): + "Print an error and terminate configure." + log.error(*args) + sys.exit(1) + + +@imports(_from="mozbuild.configure", _import="ConfigureError") +def configure_error(message): + """Raise a programming error and terminate configure. + Primarily for use in moz.configure templates to sanity check + their inputs from moz.configure usage.""" + raise ConfigureError(message) + + +# A wrapper to obtain a process' output and return code. +# Returns a tuple (retcode, stdout, stderr). +@imports("os") +@imports("six") +@imports("subprocess") +@imports(_from="mozbuild.shellutil", _import="quote") +@imports(_from="mozbuild.util", _import="system_encoding") +def get_cmd_output(*args, **kwargs): + log.debug("Executing: `%s`", quote(*args)) + proc = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + # On Python 2 on Windows, close_fds prevents the process from inheriting + # stdout/stderr. Elsewhere, it simply prevents it from inheriting extra + # file descriptors, which is what we want. + close_fds=os.name != "nt", + **kwargs + ) + stdout, stderr = proc.communicate() + # Normally we would set the `encoding` and `errors` arguments in the + # constructor to subprocess.Popen, but those arguments were added in 3.6 + # and we need to support back to 3.5, so instead we need to do this + # nonsense. + stdout = six.ensure_text( + stdout, encoding=system_encoding, errors="replace" + ).replace("\r\n", "\n") + stderr = six.ensure_text( + stderr, encoding=system_encoding, errors="replace" + ).replace("\r\n", "\n") + return proc.wait(), stdout, stderr + + +# A wrapper to obtain a process' output that returns the output generated +# by running the given command if it exits normally, and streams that +# output to log.debug and calls die or the given error callback if it +# does not. +@imports(_from="mozbuild.configure.util", _import="LineIO") +@imports(_from="mozbuild.shellutil", _import="quote") +def check_cmd_output(*args, **kwargs): + onerror = kwargs.pop("onerror", None) + + with log.queue_debug(): + retcode, stdout, stderr = get_cmd_output(*args, **kwargs) + if retcode == 0: + return stdout + + log.debug("The command returned non-zero exit status %d.", retcode) + for out, desc in ((stdout, "output"), (stderr, "error output")): + if out: + log.debug("Its %s was:", desc) + with LineIO(lambda l: log.debug("| %s", l)) as o: + o.write(out) + if onerror: + return onerror() + die("Command `%s` failed with exit status %d." % (quote(*args), retcode)) + + +@imports("os") +def is_absolute_or_relative(path): + if os.altsep and os.altsep in path: + return True + return os.sep in path + + +@imports(_import="mozpack.path", _as="mozpath") +def normsep(path): + return mozpath.normsep(path) + + +@imports("ctypes") +@imports(_from="ctypes", _import="wintypes") +@imports(_from="mozbuild.configure.constants", _import="WindowsBinaryType") +def windows_binary_type(path): + """Obtain the type of a binary on Windows. + + Returns WindowsBinaryType constant. + """ + GetBinaryTypeW = ctypes.windll.kernel32.GetBinaryTypeW + GetBinaryTypeW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(wintypes.DWORD)] + GetBinaryTypeW.restype = wintypes.BOOL + + bin_type = wintypes.DWORD() + res = GetBinaryTypeW(path, ctypes.byref(bin_type)) + if not res: + die("could not obtain binary type of %s" % path) + + if bin_type.value == 0: + return WindowsBinaryType("win32") + elif bin_type.value == 6: + return WindowsBinaryType("win64") + # If we see another binary type, something is likely horribly wrong. + else: + die("unsupported binary type on %s: %s" % (path, bin_type)) + + +@imports("ctypes") +@imports(_from="ctypes", _import="wintypes") +def get_GetShortPathNameW(): + GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW + GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] + GetShortPathNameW.restype = wintypes.DWORD + return GetShortPathNameW + + +@template +@imports("ctypes") +@imports("platform") +@imports(_from="mozbuild.shellutil", _import="quote") +def normalize_path(): + # Until the build system can properly handle programs that need quoting, + # transform those paths into their short version on Windows (e.g. + # c:\PROGRA~1...). + if platform.system() == "Windows": + GetShortPathNameW = get_GetShortPathNameW() + + def normalize_path(path): + path = normsep(path) + if quote(path) == path: + return path + size = 0 + while True: + out = ctypes.create_unicode_buffer(size) + needed = GetShortPathNameW(path, out, size) + if size >= needed: + if " " in out.value: + die( + "GetShortPathName returned a long path name: `%s`. " + "Use `fsutil file setshortname' " + "to create a short name " + "for any components of this path " + "that have spaces.", + out.value, + ) + return normsep(out.value) + size = needed + + else: + + def normalize_path(path): + return normsep(path) + + return normalize_path + + +normalize_path = normalize_path() + + +# Locates the given program using which, or returns the given path if it +# exists. +# The `paths` parameter may be passed to search the given paths instead of +# $PATH. +@imports("sys") +@imports(_from="os", _import="pathsep") +@imports(_from="os", _import="environ") +@imports(_from="mozfile", _import="which") +def find_program(file, paths=None): + # The following snippet comes from `which` itself, with a slight + # modification to use lowercase extensions, because it's confusing rustup + # (on top of making results not really appealing to the eye). + + # Windows has the concept of a list of extensions (PATHEXT env var). + if sys.platform.startswith("win"): + exts = [e.lower() for e in environ.get("PATHEXT", "").split(pathsep)] + # If '.exe' is not in exts then obviously this is Win9x and + # or a bogus PATHEXT, then use a reasonable default. + if ".exe" not in exts: + exts = [".com", ".exe", ".bat"] + else: + exts = None + + if is_absolute_or_relative(file): + path = which(os.path.basename(file), path=os.path.dirname(file), exts=exts) + return normalize_path(path) if path else None + + if paths: + if not isinstance(paths, (list, tuple)): + die( + "Paths provided to find_program must be a list of strings, " "not %r", + paths, + ) + paths = pathsep.join(paths) + + path = which(file, path=paths, exts=exts) + return normalize_path(path) if path else None + + +@imports("os") +@imports(_from="mozbuild.configure.util", _import="LineIO") +@imports(_from="six", _import="ensure_binary") +@imports(_from="tempfile", _import="mkstemp") +def try_invoke_compiler(compiler, language, source, flags=None, onerror=None): + flags = flags or [] + + if not isinstance(flags, (list, tuple)): + die("Flags provided to try_compile must be a list of strings, " "not %r", flags) + + suffix = { + "C": ".c", + "C++": ".cpp", + }[language] + + fd, path = mkstemp(prefix="conftest.", suffix=suffix, text=True) + try: + source = source.encode("ascii", "replace") + + log.debug("Creating `%s` with content:", path) + with LineIO(lambda l: log.debug("| %s", l)) as out: + out.write(source) + + os.write(fd, ensure_binary(source)) + os.close(fd) + cmd = compiler + [path] + list(flags) + kwargs = {"onerror": onerror} + return check_cmd_output(*cmd, **kwargs) + finally: + os.remove(path) + + +def unique_list(l): + result = [] + for i in l: + if l not in result: + result.append(i) + return result + + +# Get values out of the Windows registry. This function can only be called on +# Windows. +# The `pattern` argument is a string starting with HKEY_ and giving the full +# "path" of the registry key to get the value for, with backslash separators. +# The string can contains wildcards ('*'). +# The result of this functions is an enumerator yielding tuples for each +# match. Each of these tuples contains the key name matching wildcards +# followed by the value. +# +# The `get_32_and_64_bit` argument is a boolean, if True then it will return the +# values from the 32-bit and 64-bit registry views. This defaults to False, +# which will return the view depending on the bitness of python. +# +# Examples: +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\Installed Roots\KitsRoot*') +# yields e.g.: +# ('KitsRoot81', r'C:\Program Files (x86)\Windows Kits\8.1\') +# ('KitsRoot10', r'C:\Program Files (x86)\Windows Kits\10\') +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\Installed Roots\KitsRoot8.1') +# yields e.g.: +# (r'C:\Program Files (x86)\Windows Kits\8.1\',) +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\Installed Roots\KitsRoot8.1', +# get_32_and_64_bit=True) +# yields e.g.: +# (r'C:\Program Files (x86)\Windows Kits\8.1\',) +# (r'C:\Program Files\Windows Kits\8.1\',) +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'Windows Kits\*\KitsRoot*') +# yields e.g.: +# ('Installed Roots', 'KitsRoot81', +# r'C:\Program Files (x86)\Windows Kits\8.1\') +# ('Installed Roots', 'KitsRoot10', +# r'C:\Program Files (x86)\Windows Kits\10\') +# +# get_registry_values(r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\' +# r'VisualStudio\VC\*\x86\*\Compiler') +# yields e.g.: +# ('19.0', 'arm', r'C:\...\amd64_arm\cl.exe') +# ('19.0', 'x64', r'C:\...\amd64\cl.exe') +# ('19.0', 'x86', r'C:\...\amd64_x86\cl.exe') +@imports(_import="winreg") +@imports(_from="__builtin__", _import="WindowsError") +@imports(_from="fnmatch", _import="fnmatch") +def get_registry_values(pattern, get_32_and_64_bit=False): + def enum_helper(func, key): + i = 0 + while True: + try: + yield func(key, i) + except WindowsError: + break + i += 1 + + def get_keys(key, pattern, access_mask): + try: + s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask) + except WindowsError: + return + for k in enum_helper(winreg.EnumKey, s): + if fnmatch(k, pattern[-1]): + try: + yield k, winreg.OpenKey(s, k, 0, access_mask) + except WindowsError: + pass + + def get_values(key, pattern, access_mask): + try: + s = winreg.OpenKey(key, "\\".join(pattern[:-1]), 0, access_mask) + except WindowsError: + return + for k, v, t in enum_helper(winreg.EnumValue, s): + if fnmatch(k, pattern[-1]): + yield k, v + + def split_pattern(pattern): + subpattern = [] + for p in pattern: + subpattern.append(p) + if "*" in p: + yield subpattern + subpattern = [] + if subpattern: + yield subpattern + + def get_all_values(keys, pattern, access_mask): + for i, p in enumerate(pattern): + next_keys = [] + for base_key in keys: + matches = base_key[:-1] + base_key = base_key[-1] + if i == len(pattern) - 1: + want_name = "*" in p[-1] + for name, value in get_values(base_key, p, access_mask): + yield matches + ((name, value) if want_name else (value,)) + else: + for name, k in get_keys(base_key, p, access_mask): + next_keys.append(matches + (name, k)) + keys = next_keys + + pattern = pattern.split("\\") + assert pattern[0].startswith("HKEY_") + keys = [(getattr(winreg, pattern[0]),)] + pattern = list(split_pattern(pattern[1:])) + if get_32_and_64_bit: + for match in get_all_values( + keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_32KEY + ): + yield match + for match in get_all_values( + keys, pattern, winreg.KEY_READ | winreg.KEY_WOW64_64KEY + ): + yield match + else: + for match in get_all_values(keys, pattern, winreg.KEY_READ): + yield match + + +@imports(_from="mozbuild.configure.util", _import="Version", _as="_Version") +def Version(v): + "A version number that can be compared usefully." + return _Version(v) + + +# Denotes a deprecated option. Combines option() and @depends: +# @deprecated_option('--option') +# def option(value): +# ... +# @deprecated_option() takes the same arguments as option(), except `help`. +# The function may handle the option like a typical @depends function would, +# but it is recommended it emits a deprecation error message suggesting an +# alternative option to use if there is one. + + +@template +def deprecated_option(*args, **kwargs): + assert "help" not in kwargs + kwargs["help"] = "Deprecated" + opt = option(*args, **kwargs) + + def decorator(func): + @depends(opt.option) + def deprecated(value): + if value.origin != "default": + return func(value) + + return deprecated + + return decorator + + +# from mozbuild.util import ReadOnlyNamespace as namespace +@imports(_from="mozbuild.util", _import="ReadOnlyNamespace") +def namespace(**kwargs): + return ReadOnlyNamespace(**kwargs) + + +# Turn an object into an object that can be used as an argument to @depends. +# The given object can be a literal value, a function that takes no argument, +# or, for convenience, a @depends function. +@template +@imports(_from="inspect", _import="isfunction") +@imports(_from="mozbuild.configure", _import="SandboxDependsFunction") +def dependable(obj): + if isinstance(obj, SandboxDependsFunction): + return obj + if isfunction(obj): + return depends(when=True)(obj) + # Depend on --help to make lint happy if the dependable is used as an input + # to an option(). + return depends("--help", when=True)(lambda _: obj) + + +always = dependable(True) +never = dependable(False) + + +# Create a decorator that will only execute the body of a function +# if the passed function returns True when passed all positional +# arguments. +@template +def depends_tmpl(eval_args_fn, *args, **kwargs): + if kwargs: + assert len(kwargs) == 1 + when = kwargs["when"] + else: + when = None + + def decorator(func): + @depends(*args, when=when) + def wrapper(*args): + if eval_args_fn(args): + return func(*args) + + return wrapper + + return decorator + + +# Like @depends, but the decorated function is only called if one of the +# arguments it would be called with has a positive value (bool(value) is True) +@template +def depends_if(*args, **kwargs): + return depends_tmpl(any, *args, **kwargs) + + +# Like @depends, but the decorated function is only called if all of the +# arguments it would be called with have a positive value. +@template +def depends_all(*args, **kwargs): + return depends_tmpl(all, *args, **kwargs) + + +# Hacks related to old-configure +# ============================== + + +@dependable +def old_configure_assignments(): + return [] + + +@template +def add_old_configure_assignment(var, value, when=None): + var = dependable(var) + value = dependable(value) + + @depends(old_configure_assignments, var, value, when=when) + @imports(_from="mozbuild.shellutil", _import="quote") + def add_assignment(assignments, var, value): + if var is None or value is None: + return + if value is True: + assignments.append((var, "1")) + elif value is False: + assignments.append((var, "")) + else: + if isinstance(value, (list, tuple)): + value = quote(*value) + assignments.append((var, str(value))) diff --git a/build/moz.configure/warnings.configure b/build/moz.configure/warnings.configure new file mode 100755 index 0000000000..d0db70f6d3 --- /dev/null +++ b/build/moz.configure/warnings.configure @@ -0,0 +1,253 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +option( + "--enable-warnings-as-errors", + env="MOZ_ENABLE_WARNINGS_AS_ERRORS", + default=depends("MOZ_AUTOMATION")(lambda x: bool(x)), + help="{Enable|Disable} treating warnings as errors", +) + + +@depends("--enable-warnings-as-errors") +def rust_warning_flags(warnings_as_errors): + flags = [] + + # Note that cargo passes --cap-lints warn to rustc for third-party code, so + # we don't need a very complicated setup. + if warnings_as_errors: + flags.append("-Dwarnings") + else: + flags.extend(("--cap-lints", "warn")) + + return flags + + +c_warning_flag = dependable("-Werror") + + +@depends("--enable-warnings-as-errors", c_warning_flag) +def warnings_as_errors(warnings_as_errors, c_warning_flag): + if not warnings_as_errors: + return "" + + return c_warning_flag + + +set_config("WARNINGS_AS_ERRORS", warnings_as_errors) +# We have a peculiar setup in old-configure.in where some compilation tests +# depend on enabling warnings-as-errors even if it's disabled for Firefox +# compilation. We therefore need this assignment. +add_old_configure_assignment("WARNINGS_AS_ERRORS", c_warning_flag) + + +# GCC/Clang warnings: +# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html +# https://clang.llvm.org/docs/DiagnosticsReference.html + +# lots of useful warnings +add_gcc_warning("-Wall") + +# catch implicit truncation of enum values assigned to smaller bit fields +check_and_add_gcc_warning("-Wbitfield-enum-conversion") + +# catches bugs, e.g. "if (c); foo();", few false positives +add_gcc_warning("-Wempty-body") + +# catches return types with qualifiers like const +add_gcc_warning("-Wignored-qualifiers") + +# function declaration hides virtual function from base class. +# Don't enable for GCC, since it's more strict than clang, +# and the additional cases it covers are not valuable. +add_gcc_warning( + "-Woverloaded-virtual", + cxx_compiler, + when=depends(cxx_compiler)(lambda c: c.type != "gcc"), +) + +# catches pointer arithmetic using NULL or sizeof(void) +add_gcc_warning("-Wpointer-arith") + +# catch modifying constructor parameter that shadows member variable +check_and_add_gcc_warning("-Wshadow-field-in-constructor-modified") + +# catches comparing signed/unsigned ints +add_gcc_warning("-Wsign-compare") + +# catches overflow bugs, few false positives +add_gcc_warning("-Wtype-limits") + +# catches some dead code +add_gcc_warning("-Wunreachable-code") +check_and_add_gcc_warning("-Wunreachable-code-return") + +# catches treating string literals as non-const +add_gcc_warning("-Wwrite-strings", cxx_compiler) + +# turned on by -Wall, but we use offsetof on non-POD types frequently +add_gcc_warning("-Wno-invalid-offsetof", cxx_compiler) + +# catches objects passed by value to variadic functions. +check_and_add_gcc_warning("-Wclass-varargs") + +# catches empty if/switch/for initialization statements that have no effect +check_and_add_gcc_warning("-Wempty-init-stmt", cxx_compiler) + +# catches some implicit conversion of floats to ints +check_and_add_gcc_warning("-Wfloat-overflow-conversion") +check_and_add_gcc_warning("-Wfloat-zero-conversion") + +# catches issues around loops +check_and_add_gcc_warning("-Wloop-analysis") +# But, disable range-loop-analysis because it can raise unhelpful false +# positives. +check_and_add_gcc_warning("-Wno-range-loop-analysis") + +# catches C++ version forward-compat issues +check_and_add_gcc_warning("-Wc++2a-compat", cxx_compiler) + +# catches possible misuse of the comma operator +check_and_add_gcc_warning("-Wcomma", cxx_compiler) + +# catches duplicated conditions in if-else-if chains +check_and_add_gcc_warning("-Wduplicated-cond") + +# catches unintentional switch case fallthroughs +check_and_add_gcc_warning("-Wimplicit-fallthrough", cxx_compiler) + +# catches unused variable/function declarations +check_and_add_gcc_warning("-Wunused-function", cxx_compiler) +check_and_add_gcc_warning("-Wunused-variable", cxx_compiler) + +# catches expressions used as a null pointer constant +# XXX: at the time of writing, the version of clang used on the OS X test +# machines has a bug that causes it to reject some valid files if both +# -Wnon-literal-null-conversion and -Wsometimes-uninitialized are +# specified. We work around this by instead using +# -Werror=non-literal-null-conversion, but we only do that when +# --enable-warnings-as-errors is specified so that no unexpected fatal +# warnings are produced. +check_and_add_gcc_warning( + "-Werror=non-literal-null-conversion", when="--enable-warnings-as-errors" +) + +# catches string literals used in boolean expressions +check_and_add_gcc_warning("-Wstring-conversion") + +# catches comparisons that are always true or false +check_and_add_gcc_warning("-Wtautological-overlap-compare") +check_and_add_gcc_warning("-Wtautological-unsigned-enum-zero-compare") +check_and_add_gcc_warning("-Wtautological-unsigned-zero-compare") +# This can be triggered by certain patterns used deliberately in portable code +check_and_add_gcc_warning("-Wno-error=tautological-type-limit-compare") + +# we inline 'new' and 'delete' in mozalloc +check_and_add_gcc_warning("-Wno-inline-new-delete", cxx_compiler) + +# Prevent the following GCC warnings from being treated as errors: +# too many false positives +check_and_add_gcc_warning("-Wno-error=maybe-uninitialized") + +# we don't want our builds held hostage when a platform-specific API +# becomes deprecated. +check_and_add_gcc_warning("-Wno-error=deprecated-declarations") + +# false positives depending on optimization +check_and_add_gcc_warning("-Wno-error=array-bounds") + +# can't get rid of those PGO warnings +check_and_add_gcc_warning("-Wno-error=coverage-mismatch") + +# -Wbackend-plugin warnings from Android PGO profile-use builds: +# error: /builds/worker/workspace/build/src/mozglue/misc/AutoProfilerLabel.cpp: +# Function control flow change detected (hash mismatch) +# _ZN7mozilla17AutoProfilerLabelD2Ev [-Werror,-Wbackend-plugin] +check_and_add_gcc_warning("-Wno-error=backend-plugin") + +# false positives depending on optimizations +check_and_add_gcc_warning("-Wno-error=free-nonheap-object") + +# Would be a pain to fix all occurrences, for very little gain +check_and_add_gcc_warning("-Wno-multistatement-macros") + +# Disable the -Werror for return-std-move because of a false positive +# on nsTAutoStringN: https://bugs.llvm.org/show_bug.cgi?id=37249 +check_and_add_gcc_warning("-Wno-error=return-std-move") + +# Disable the -Werror for -Wclass-memaccess as we have a long +# tail of issues to fix +check_and_add_gcc_warning("-Wno-error=class-memaccess") + +# -Watomic-alignment is a new warning in clang 7 that seems way too broad. +# https://bugs.llvm.org/show_bug.cgi?id=38593 +check_and_add_gcc_warning("-Wno-error=atomic-alignment") + +# New warning with gcc 9. Not useful +# https://bugzilla.mozilla.org/show_bug.cgi?id=1515356 +check_and_add_gcc_warning("-Wno-error=deprecated-copy") + +# catches format/argument mismatches with printf +c_format_warning, cxx_format_warning = check_and_add_gcc_warning( + "-Wformat", when=depends(target)(lambda t: t.kernel != "WINNT") +) + +# Add compile-time warnings for unprotected functions and format functions +# that represent possible security problems. Enable this only when -Wformat +# is enabled, otherwise it is an error +check_and_add_gcc_warning( + "-Wformat-security", when=c_format_warning & cxx_format_warning +) +check_and_add_gcc_warning( + "-Wformat-overflow=2", when=c_format_warning & cxx_format_warning +) + +# Other MinGW specific things +with only_when(depends(target)(lambda t: t.kernel == "WINNT")): + # When compiling for Windows with gcc, we encounter lots of "#pragma warning"'s + # which is an MSVC-only pragma that GCC does not recognize. + check_and_add_gcc_warning("-Wno-unknown-pragmas") + + # When compiling for Windows with gcc, gcc throws false positives and true + # positives where the callsite is ifdef-ed out + check_and_add_gcc_warning("-Wno-unused-function") + + # When compiling for Windows with gcc, gcc cannot produce this warning + # correctly: it mistakes DWORD_PTR and ULONG_PTR as types you cannot + # give NULL to. (You can in fact do that.) + check_and_add_gcc_warning("-Wno-conversion-null") + + # Throughout the codebase we regularly have switch statements off of enums + # without covering every value in the enum. We don't care about these warnings. + check_and_add_gcc_warning("-Wno-switch") + + # Another code pattern we have is using start and end constants in enums of + # different types. We do this for safety, but then when comparing it throws + # an error, which we would like to ignore. This seems to only affect the MinGW + # build, but we're not sure why. + check_and_add_gcc_warning("-Wno-enum-compare") + +# We hit this all over the place with the gtest INSTANTIATE_TEST_CASE_P macro +check_and_add_gcc_warning("-Wno-gnu-zero-variadic-macro-arguments") + +# Make it an error to be missing function declarations for C code. +check_and_add_gcc_warning("-Werror=implicit-function-declaration", c_compiler) + +# New in clang 11. We can't really do anything about this warning. +check_and_add_gcc_warning("-Wno-psabi") + +# Disable broken missing-braces warning on old clang versions +check_and_add_gcc_warning( + "-Wno-missing-braces", + when=depends(c_compiler)(lambda c: c.type == "clang" and c.version < "6.0"), +) + + +# Please keep these last in this file +add_old_configure_assignment("_WARNINGS_CFLAGS", warnings_flags.cflags) +add_old_configure_assignment("_WARNINGS_CXXFLAGS", warnings_flags.cxxflags) +add_old_configure_assignment("_WARNINGS_HOST_CFLAGS", warnings_flags.host_cflags) +add_old_configure_assignment("_WARNINGS_HOST_CXXFLAGS", warnings_flags.host_cxxflags) diff --git a/build/moz.configure/windows.configure b/build/moz.configure/windows.configure new file mode 100644 index 0000000000..568046c2e5 --- /dev/null +++ b/build/moz.configure/windows.configure @@ -0,0 +1,535 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +option( + "--with-windows-version", + nargs=1, + default="603", + help="Windows SDK version to target. Win 8.1 (603) is currently" + "the minimum supported version.", +) + + +@depends("--with-windows-version") +@imports(_from="__builtin__", _import="ValueError") +def valid_windows_version(value): + if not value: + die("Cannot build with --without-windows-version") + try: + version = int(value[0], 16) + if version in (0x603,): + return version + except ValueError: + pass + + die("Invalid value for --with-windows-version (%s)", value[0]) + + +option(env="WINDOWSSDKDIR", nargs=1, help="Directory containing the Windows SDK") + + +@depends("WINDOWSSDKDIR", host, c_compiler) +def windows_sdk_dir(value, host, compiler): + if value: + return value + # Ideally, we'd actually check for host/target ABI being MSVC, but + # that's waiting for bug 1617793. + if host.kernel != "WINNT" or compiler.type != "clang-cl": + return () + + return set( + x[1] + for x in get_registry_values( + r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots" + r"\KitsRoot*", + get_32_and_64_bit=True, + ) + ) + + +# The Windows SDK 8.1 and 10 have different layouts. The former has +# $SDK/include/$subdir, while the latter has $SDK/include/$version/$subdir. +# The vcvars* scripts don't actually care about the version, they just take +# the last alphanumerically. +# The $SDK/lib directories always have version subdirectories, but while the +# versions match the one in $SDK/include for SDK 10, it's "winv6.3" for SDK +# 8.1. + + +@imports("os") +@imports("re") +@imports(_from="__builtin__", _import="sorted") +@imports(_from="__builtin__", _import="Exception") +def get_sdk_dirs(sdk, subdir): + def get_dirs_containing(sdk, stem, subdir): + base = os.path.join(sdk, stem) + try: + subdirs = [ + d for d in os.listdir(base) if os.path.isdir(os.path.join(base, d)) + ] + except Exception: + subdirs = [] + if not subdirs: + return () + if subdir in subdirs: + return (base,) + # At this point, either we have an incomplete or invalid SDK directory, + # or we exclusively have version numbers in subdirs. + return tuple( + os.path.join(base, s) + for s in subdirs + if os.path.isdir(os.path.join(base, s, subdir)) + ) + + def categorize(dirs): + return {os.path.basename(d): d for d in dirs} + + include_dirs = categorize(get_dirs_containing(sdk, "include", subdir)) + lib_dirs = categorize(get_dirs_containing(sdk, "lib", subdir)) + + if "include" in include_dirs: + include_dirs["winv6.3"] = include_dirs["include"] + del include_dirs["include"] + + valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True) + if valid_versions: + return namespace( + path=sdk, + lib=lib_dirs[valid_versions[0]], + include=include_dirs[valid_versions[0]], + ) + + +@imports(_from="mozbuild.shellutil", _import="quote") +def valid_windows_sdk_dir_result(value): + if value: + return "0x%04x in %s" % (value.version, quote(value.path)) + + +@depends(c_compiler, windows_sdk_dir, valid_windows_version, "WINDOWSSDKDIR") +@checking("for Windows SDK", valid_windows_sdk_dir_result) +@imports(_from="__builtin__", _import="sorted") +@imports(_from="__builtin__", _import="Exception") +@imports(_from="textwrap", _import="dedent") +def valid_windows_sdk_dir( + compiler, windows_sdk_dir, target_version, windows_sdk_dir_env +): + # Ideally, we'd actually check for host/target ABI being MSVC, but + # that's waiting for bug 1617793. + if compiler.type != "clang-cl": + return None + if windows_sdk_dir_env: + windows_sdk_dir_env = windows_sdk_dir_env[0] + sdks = {} + for d in windows_sdk_dir: + sdk = get_sdk_dirs(d, "um") + if sdk: + check = dedent( + """\ + #include <winsdkver.h> + WINVER_MAXVER + """ + ) + um_dir = os.path.join(sdk.include, "um") + shared_dir = os.path.join(sdk.include, "shared") + result = try_preprocess( + compiler.wrapper + + [compiler.compiler] + + compiler.flags + + ["-X", "-I", um_dir, "-I", shared_dir], + "C", + check, + onerror=lambda: "", + ) + if result: + maxver = result.splitlines()[-1] + try: + maxver = int(maxver, 0) + except Exception: + pass + else: + sdks[d] = maxver, sdk + continue + if d == windows_sdk_dir_env: + raise FatalCheckError( + "Error while checking the version of the SDK in " + "WINDOWSSDKDIR (%s). Please verify it contains a valid and " + "complete SDK installation." % windows_sdk_dir_env + ) + + valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) + if valid_sdks: + biggest_version, sdk = sdks[valid_sdks[0]] + if not valid_sdks or biggest_version < target_version: + if windows_sdk_dir_env: + raise FatalCheckError( + "You are targeting Windows version 0x%04x, but your SDK only " + "supports up to version 0x%04x. Install and use an updated SDK, " + "or target a lower version using --with-windows-version. " + "Alternatively, try running the Windows SDK Configuration Tool " + "and selecting a newer SDK. See " + "https://developer.mozilla.org/En/Windows_SDK_versions for " + "details on fixing this." % (target_version, biggest_version) + ) + + raise FatalCheckError( + "Cannot find a Windows SDK for version >= 0x%04x." % target_version + ) + + return namespace( + path=sdk.path, + include=sdk.include, + lib=sdk.lib, + version=biggest_version, + ) + + +@imports(_from="mozbuild.shellutil", _import="quote") +def valid_ucrt_sdk_dir_result(value): + if value: + return "%s in %s" % (value.version, quote(value.path)) + + +@depends(windows_sdk_dir, "WINDOWSSDKDIR", c_compiler) +@checking("for Universal CRT SDK", valid_ucrt_sdk_dir_result) +@imports("os") +@imports(_from="__builtin__", _import="sorted") +@imports(_import="mozpack.path", _as="mozpath") +def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env, compiler): + # Ideally, we'd actually check for host/target ABI being MSVC, but + # that's waiting for bug 1617793. + if compiler.type != "clang-cl": + return None + if windows_sdk_dir_env: + windows_sdk_dir_env = windows_sdk_dir_env[0] + sdks = {} + for d in windows_sdk_dir: + sdk = get_sdk_dirs(d, "ucrt") + if sdk: + version = os.path.basename(sdk.include) + # We're supposed to always find a version in the directory, because + # the 8.1 SDK, which doesn't have a version in the directory, doesn't + # contain the Universal CRT SDK. When the main SDK is 8.1, there + # is, however, supposed to be a reduced install of the SDK 10 + # with the UCRT. + if version != "include": + sdks[d] = Version(version), sdk + continue + if d == windows_sdk_dir_env: + # When WINDOWSSDKDIR is set in the environment and we can't find the + # Universal CRT SDK, chances are this is a start-shell-msvc*.bat + # setup, where INCLUDE and LIB already contain the UCRT paths. + ucrt_includes = [ + p + for p in os.environ.get("INCLUDE", "").split(";") + if os.path.basename(p).lower() == "ucrt" + ] + ucrt_libs = [ + p + for p in os.environ.get("LIB", "").split(";") + if os.path.basename(os.path.dirname(p)).lower() == "ucrt" + ] + if ucrt_includes and ucrt_libs: + # Pick the first of each, since they are the ones that the + # compiler would look first. Assume they contain the SDK files. + include = os.path.dirname(ucrt_includes[0]) + lib = os.path.dirname(os.path.dirname(ucrt_libs[0])) + path = os.path.dirname(os.path.dirname(include)) + version = os.path.basename(include) + if version != "include" and mozpath.basedir(lib, [path]): + sdks[d] = ( + Version(version), + namespace( + path=path, + include=include, + lib=lib, + ), + ) + continue + raise FatalCheckError( + "The SDK in WINDOWSSDKDIR (%s) does not contain the Universal " + "CRT." % windows_sdk_dir_env + ) + + valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True) + if not valid_sdks: + raise FatalCheckError( + "Cannot find the Universal CRT SDK. " "Please install it." + ) + + version, sdk = sdks[valid_sdks[0]] + minimum_ucrt_version = Version("10.0.17134.0") + if version < minimum_ucrt_version: + raise FatalCheckError( + "Latest Universal CRT SDK version found %s" + " and minimum required is %s. This or a later" + " version can be installed using the Visual" + " Studio installer." % (version, minimum_ucrt_version) + ) + + return namespace( + path=sdk.path, + include=sdk.include, + lib=sdk.lib, + version=version, + ) + + +@depends(c_compiler, host_c_compiler, vc_toolchain_search_path) +@imports("os") +def vc_path(c_compiler, host_c_compiler, vc_toolchain_search_path): + if c_compiler.type != "clang-cl" and host_c_compiler.type != "clang-cl": + return + + # In clang-cl builds, we need the headers and libraries from an MSVC installation. + vc_program = find_program("cl.exe", paths=vc_toolchain_search_path) + if not vc_program: + die("Cannot find a Visual C++ install for e.g. ATL headers.") + + result = os.path.dirname(vc_program) + while True: + next, p = os.path.split(result) + if next == result: + die( + "Cannot determine the Visual C++ directory the compiler (%s) " + "is in" % vc_program + ) + result = next + if p.lower() == "bin": + break + return os.path.normpath(result) + + +option(env="DIA_SDK_PATH", nargs=1, help="Path to the Debug Interface Access SDK") + + +@depends(vc_path, "DIA_SDK_PATH") +@checking("for the Debug Interface Access SDK", lambda x: x or "not found") +@imports("os") +def dia_sdk_dir(vc_path, dia_sdk_path): + if dia_sdk_path: + path = os.path.normpath(dia_sdk_path[0]) + + elif vc_path: + # This would be easier if we had the installationPath that + # get_vc_paths works with, since 'DIA SDK' is relative to that. + path = os.path.normpath( + os.path.join(vc_path, "..", "..", "..", "..", "DIA SDK") + ) + else: + return + + if os.path.exists(os.path.join(path, "include", "dia2.h")): + return path + + +@depends(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir) +@imports("os") +def include_path(vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir): + if not vc_path: + return + atlmfc_dir = os.path.join(vc_path, "atlmfc", "include") + if not os.path.isdir(atlmfc_dir): + die( + "Cannot find the ATL/MFC headers in the Visual C++ directory (%s). " + "Please install them." % vc_path + ) + + winrt_dir = os.path.join(windows_sdk_dir.include, "winrt") + if not os.path.isdir(winrt_dir): + die( + "Cannot find the WinRT headers in the Windows SDK directory (%s). " + "Please install them." % windows_sdk_dir.path + ) + + includes = [] + include_env = os.environ.get("INCLUDE") + if include_env: + includes.append(include_env) + includes.extend( + ( + os.path.join(vc_path, "include"), + atlmfc_dir, + os.path.join(windows_sdk_dir.include, "shared"), + os.path.join(windows_sdk_dir.include, "um"), + winrt_dir, + os.path.join(ucrt_sdk_dir.include, "ucrt"), + ) + ) + if dia_sdk_dir: + includes.append(os.path.join(dia_sdk_dir, "include")) + # Set in the environment for old-configure + includes = ";".join(includes) + os.environ["INCLUDE"] = includes + return includes + + +set_config("INCLUDE", include_path) + + +@template +def dia_sdk_subdir(host_or_target, subdir): + @depends(dia_sdk_dir, host_or_target, dependable(subdir)) + def dia_sdk_subdir(dia_sdk_dir, target, subdir): + if not dia_sdk_dir: + return + # For some reason the DIA SDK still uses the old-style targets + # even in a newer MSVC. + old_target = { + "x86": "", + "x86_64": "amd64", + "arm": "arm", + "aarch64": "arm64", + }.get(target.cpu) + if old_target is None: + return + # As old_target can be '', and os.path.join will happily use the empty + # string, leading to a string ending with a backslash, that Make will + # interpret as a "string continues on next line" indicator, use variable + # args. + old_target = (old_target,) if old_target else () + return os.path.join(dia_sdk_dir, subdir, *old_target) + + return dia_sdk_subdir + + +set_config("WIN_DIA_SDK_BIN_DIR", dia_sdk_subdir(host, "bin")) + + +@template +def lib_path_for(host_or_target): + @depends( + host_or_target, + dependable(host_or_target is host), + vc_path, + valid_windows_sdk_dir, + valid_ucrt_sdk_dir, + dia_sdk_subdir(host_or_target, "lib"), + ) + @imports("os") + def lib_path( + target, is_host, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_lib_dir + ): + if not vc_path: + return + sdk_target = { + "x86": "x86", + "x86_64": "x64", + "arm": "arm", + "aarch64": "arm64", + }.get(target.cpu) + + # MSVC2017 switched to use the same target naming as the sdk. + atlmfc_dir = os.path.join(vc_path, "atlmfc", "lib", sdk_target) + if not os.path.isdir(atlmfc_dir): + die( + "Cannot find the ATL/MFC libraries in the Visual C++ directory " + "(%s). Please install them." % vc_path + ) + + libs = [] + lib_env = os.environ.get("LIB") + if lib_env and not is_host: + libs.extend(lib_env.split(";")) + libs.extend( + ( + os.path.join(vc_path, "lib", sdk_target), + atlmfc_dir, + os.path.join(windows_sdk_dir.lib, "um", sdk_target), + os.path.join(ucrt_sdk_dir.lib, "ucrt", sdk_target), + ) + ) + if dia_sdk_lib_dir: + libs.append(dia_sdk_lib_dir) + return libs + + return lib_path + + +@depends_if(lib_path_for(target)) +@imports("os") +def lib_path(libs): + # Set in the environment for old-configure + libs = ";".join(libs) + os.environ["LIB"] = libs + return libs + + +set_config("LIB", lib_path) + + +lib_path_for_host = lib_path_for(host) + + +@depends_if(lib_path_for_host) +@imports(_from="mozbuild.shellutil", _import="quote") +def host_linker_libpaths(libs): + return ["-LIBPATH:%s" % quote(l) for l in libs] + + +@depends_if(lib_path_for_host) +@imports(_from="mozbuild.shellutil", _import="quote") +def host_linker_libpaths_bat(libs): + # .bat files need a different style of quoting. Batch quoting is actually + # not defined, and up to applications to handle, so it's not really clear + # what should be escaped and what not, but most paths should work just + # fine without escaping. And we don't care about double-quotes possibly + # having to be escaped because they're not allowed in file names on + # Windows. + return ['"-LIBPATH:%s"' % l for l in libs] + + +set_config("HOST_LINKER_LIBPATHS", host_linker_libpaths) +set_config("HOST_LINKER_LIBPATHS_BAT", host_linker_libpaths_bat) + + +@depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host) +@imports(_from="os", _import="environ") +def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host): + if not valid_windows_sdk_dir: + return + + vc_host = { + "x86": "x86", + "x86_64": "x64", + }.get(host.cpu) + + # From version 10.0.15063.0 onwards the bin path contains the version number. + versioned_bin = ( + "bin" + if valid_ucrt_sdk_dir.version < "10.0.15063.0" + else os.path.join("bin", str(valid_ucrt_sdk_dir.version)) + ) + result = [ + environ["PATH"], + os.path.join(valid_windows_sdk_dir.path, versioned_bin, vc_host), + ] + if vc_host == "x64": + result.append(os.path.join(valid_windows_sdk_dir.path, versioned_bin, "x86")) + return result + + +option(env="LINKER", nargs=1, when=target_is_windows, help="Path to the linker") + +link = check_prog( + "LINKER", + ("lld-link",), + input="LINKER", + when=target_is_windows, + paths=clang_search_path, +) + +option(env="HOST_LINKER", nargs=1, when=host_is_windows, help="Path to the host linker") + +host_link = check_prog( + "HOST_LINKER", + ("lld-link",), + input="HOST_LINKER", + when=host_is_windows, + paths=clang_search_path, +) + +add_old_configure_assignment("LINKER", link) |