Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/build/moz.configure/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 123 kB image not shown  

Impressum toolchain.configure   Interaktion und
Portierbarkeitunbekannt

 
Spracherkennung für: .configure vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# Environment variables that impact the compilation step
# ==============================================================
option(
    env="HOST_CPPFLAGS",
    help="Extra flags for Preprocessing host sources",
    nargs=1,
    default="",
)


option(
    env="HOST_CFLAGS",
    help="Extra flags for compiling host C sources",
    nargs=1,
    default="",
)


option(
    env="HOST_CXXFLAGS",
    help="Extra flags for compiling host C++ sources",
    nargs=1,
    default="",
)


option(
    env="HOST_LDFLAGS",
    help="Extra flags for linking host object files",
    nargs=1,
    default="",
)


option(
    env="CPPFLAGS",
    help="Extra flags for preprocessing sources",
    nargs=1,
    default="",
)


option(
    env="CFLAGS",
    help="Extra flags for compiling C sources",
    nargs=1,
    default="",
)


option(
    env="CXXFLAGS",
    help="Extra flags for compiling C++ sources",
    nargs=1,
    default="",
)


option(
    env="ASFLAGS",
    help="Extra flags for assembling sources",
    nargs=1,
    default="",
)


option(
    env="LDFLAGS",
    help="Extra flags for linking object files",
    nargs=1,
    default="",
)


option(
    env="MOZ_OPTIMIZE_FLAGS",
    help="Extra optimization flags",
    nargs=1,
)


# Code optimization
# ==============================================================

option("--disable-optimize", nargs="?", help="Disable optimizations via compiler flags")


@depends(target, when="MOZ_PGO")
def forced_pgo_optimization_level(target):
    if target.kernel == "Linux" and target.os != "Android":
        return "-O3"


@imports(_from="mozbuild.shellutil", _import="quote")
def check_optimize_flags(src, flags):
    for flag in reversed(flags):
        if flag.startswith(("-O", "/O")):
            if flag[2:] == "0":
                die(
                    f"Optimization enabled through {src} but last optimization flag is {flag} which disables optimizations"
                )
            break
    else:
        die(
            f"Optimization enabled through {src} but no optimization flag found in {quote(*flags)}"
        )
    return flags


@depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS")
@imports(_from="mozbuild.shellutil", _import="split")
def configured_moz_optimize_flags(enable_optimize, env_flags):
    if len(enable_optimize):
        return check_optimize_flags("--enable-optimize", split(enable_optimize[0]))
    if len(env_flags):
        return check_optimize_flags("MOZ_OPTIMIZE_FLAGS", split(env_flags[0]))


@depends("--enable-optimize", "MOZ_OPTIMIZE_FLAGS")
def moz_optimize(enable_optimize, env_flags):
    return "1" if enable_optimize or env_flags else None


set_config("MOZ_OPTIMIZE", moz_optimize)


@depends(
    target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level
)
@imports(_from="mozbuild.shellutil", _import="split")
def moz_optimize_flags(
    target, moz_optimize, configured_moz_optimize_flags, forced_pgo_optimization_level
):
    if configured_moz_optimize_flags:
        return configured_moz_optimize_flags

    if moz_optimize and forced_pgo_optimization_level:
        return [forced_pgo_optimization_level]

    if target.kernel == "Darwin":
        return ["-O3"]
    elif target.kernel in ("Linux", "WINNT"):
        return ["-O2"]
    else:
        return ["-O"]


# Android NDK
# ==============================================================


@depends("--disable-compile-environment", target)
def compiling_android(compile_env, target):
    return compile_env and target.os == "Android"


include("android-ndk.configure", when=compiling_android)

with only_when(target_is_osx):
    # MacOS deployment target version
    # ==============================================================
    # This needs to happen before any compilation test is done.

    option(
        "--enable-macos-target",
        env="MACOSX_DEPLOYMENT_TARGET",
        nargs=1,
        default=depends(target, developer_options)
        # We continue to target 10.15 on Intel, but can target 11.0 for
        # aarch64 since the earliest hardware was released alongside 11.0.
        # For local builds, we want to target 10.15 regardless of the
        # underlying platform to catch any errors or warnings that wouldn't
        # show up when targeting 11.0, since these would later show up on
        # CI for Intel builds.
        (lambda t, d: "11.0" if (t.cpu == "aarch64" and not d) else "10.15"),
        help="Set the minimum MacOS version needed at runtime{|}",
    )

    @depends_if("--enable-macos-target", developer_options)
    def macos_target(value, _):
        return value[0]


@imports("plistlib")
@imports(_from="__builtin__", _import="open")
@imports(_from="__builtin__", _import="Exception")
def get_sdk_settings(sdk):
    with open(os.path.join(sdk, "SDKSettings.plist"), "rb") as plist:
        obj = plistlib.load(plist)
    if not obj:
        raise Exception(
            "Error parsing SDKSettings.plist in the SDK directory: %s" % sdk
        )
    return obj


@imports(_from="__builtin__", _import="Exception")
def get_sdk_version_from_settings(sdk, settings):
    if "Version" not in settings:
        raise Exception(
            "Error finding Version information in SDKSettings.plist from the SDK: %s"
            % sdk
        )
    return Version(settings["Version"])


def get_sdk_version(sdk):
    return get_sdk_version_from_settings(sdk, get_sdk_settings(sdk))


with only_when(host_is_osx | target_is_osx):
    # MacOS SDK
    # =========
    option(
        "--with-macos-sdk",
        env="MACOS_SDK_DIR",
        nargs=1,
        help="Location of platform SDK to use",
    )

    def mac_sdk_min_version():
        return "14.4"

    @depends(
        "--with-macos-sdk",
        host,
        bootstrap_path(
            "MacOSX{}.sdk".format(mac_sdk_min_version()),
            when=depends("--with-macos-sdk")(lambda x: not x),
            allow_failure=True,
        ),
    )
    @imports(_from="__builtin__", _import="Exception")
    @imports(_from="os.path", _import="isdir")
    @imports(_from="os", _import="listdir")
    def macos_sdk(sdk, host, bootstrapped):
        if bootstrapped:
            sdk = [bootstrapped]
        if sdk:
            sdk = sdk[0]
            try:
                version = get_sdk_version(sdk)
            except Exception as e:
                die(e)
        elif host.os == "OSX":
            sdk = check_cmd_output(
                "xcrun", "--show-sdk-path", onerror=lambda: ""
            ).rstrip()
            if not sdk:
                die(
                    "Could not find the macOS SDK. Please use --with-macos-sdk to give "
                    "the path to a macOS SDK."
                )
            # Scan the parent directory xcrun returns for the most recent SDK.
            sdk_dir = os.path.dirname(sdk)
            versions = []
            for d in listdir(sdk_dir):
                if d.lower().startswith("macos"):
                    try:
                        sdk = os.path.join(sdk_dir, d)
                        versions.append((get_sdk_version(sdk), sdk))
                    except Exception:
                        pass
            version, sdk = max(versions)
        else:
            die(
                "Need a macOS SDK when targeting macOS. Please use --with-macos-sdk "
                "to give the path to a macOS SDK."
            )

        if not isdir(sdk):
            die(
                "SDK not found in %s. When using --with-macos-sdk, you must specify a "
                "valid SDK. SDKs are installed when the optional cross-development "
                "tools are selected during the Xcode/Developer Tools installation."
                % sdk
            )
        if version < Version(mac_sdk_min_version()):
            die(
                'SDK version "%s" is too old. Please upgrade to at least %s. Try '
                "updating your system Xcode." % (version, mac_sdk_min_version())
            )
        return sdk

    set_config("MACOS_SDK_DIR", macos_sdk)


with only_when(target_is_osx):
    with only_when(cross_compiling):
        option(
            "--with-macos-private-frameworks",
            env="MACOS_PRIVATE_FRAMEWORKS_DIR",
            nargs=1,
            help="Location of private frameworks to use",
        )

        @depends_if("--with-macos-private-frameworks")
        @imports(_from="os.path", _import="isdir")
        def macos_private_frameworks(value):
            if value and not isdir(value[0]):
                die(
                    "PrivateFrameworks not found not found in %s. When using "
                    "--with-macos-private-frameworks, you must specify a valid "
                    "directory",
                    value[0],
                )
            return value[0]

    @depends(macos_private_frameworks, macos_sdk)
    def macos_private_frameworks(value, sdk):
        if value:
            return value
        return os.path.join(sdk or "/", "System/Library/PrivateFrameworks")

    set_config("MACOS_PRIVATE_FRAMEWORKS_DIR", macos_private_frameworks)


with only_when(target_is_ios):
    # iOS deployment target version
    # ==============================================================
    # This needs to happen before any compilation test is done.

    option(
        "--enable-ios-target",
        env="IPHONEOS_DEPLOYMENT_TARGET",
        nargs=1,
        default="17.4",
        help="Set the minimum iOS version needed at runtime",
    )

    @depends_if("--enable-ios-target")
    def ios_target(value):
        return value[0]


with only_when(target_is_ios):
    # iOS SDK
    # =======
    option(
        "--with-ios-sdk",
        env="IPHONEOS_SDK_DIR",
        nargs=1,
        help="Location of platform SDK to use",
    )

    @depends(target)
    def target_is_ios_simulator(target):
        # x86_64-apple-ios is simulator
        # aarch64-apple-ios is iphone
        # aarch64-apple-ios-sim is simulator
        return target.cpu == "x86_64" or target.raw_os == "ios-sim"

    def ios_sdk_min_version():
        return "17.4"

    @depends(target_is_ios_simulator)
    def ios_sdk_name(target_is_ios_simulator):
        return "iPhone{}{}.sdk".format(
            "Simulator" if target_is_ios_simulator else "OS",
            ios_sdk_min_version(),
        )

    @depends(
        "--with-ios-sdk",
        host,
        target,
        target_is_ios_simulator,
        bootstrap_path(ios_sdk_name, when=depends("--with-ios-sdk")(lambda x: not x)),
    )
    @imports(_from="__builtin__", _import="Exception")
    @imports(_from="os.path", _import="isdir")
    @imports(_from="os", _import="listdir")
    def ios_sdk(sdk, host, target, target_is_ios_simulator, bootstrapped):
        if bootstrapped:
            sdk = [bootstrapped]
        sdk_name = "iphonesimulator" if target_is_ios_simulator else "iphoneos"
        if sdk:
            sdk = sdk[0]
            try:
                settings = get_sdk_settings(sdk)
                version = get_sdk_version_from_settings(sdk, settings)
            except Exception as e:
                die(e)
        elif host.os == "OSX":
            sdk = check_cmd_output(
                "xcrun", "--show-sdk-path", "--sdk", sdk_name, onerror=lambda: ""
            ).rstrip()
            if not sdk:
                die(
                    "Could not find the iOS SDK. Please use --with-ios-sdk to give "
                    "the path to a iOS SDK."
                )
            # Scan the parent directory xcrun returns for the most recent SDK.
            sdk_dir = os.path.dirname(sdk)
            versions = []
            for d in listdir(sdk_dir):
                if d.lower().startswith(sdk_name):
                    try:
                        sdk = os.path.join(sdk_dir, d)
                        settings = get_sdk_settings(sdk)
                        version = get_sdk_version_from_settings(sdk, settings)
                        versions.append((version, sdk, settings))
                    except Exception:
                        pass
            version, sdk, settings = max(versions)
        else:
            die(
                "Need an iOS SDK when targeting iOS. Please use --with-ios-sdk "
                "to give the path to a iOS SDK."
            )

        if not isdir(sdk):
            die(
                "SDK not found in %s. When using --with-ios-sdk, you must specify a "
                "valid SDK. SDKs are installed when the optional cross-development "
                "tools are selected during the Xcode installation." % sdk
            )

        supported_targets = settings.get("SupportedTargets")
        if supported_targets:
            supported_archs = supported_targets.get(sdk_name, {}).get("Archs", [])
            cpu = {
                "aarch64": "arm64",
            }.get(target.cpu, str(target.cpu))
            if cpu not in supported_archs:
                die("The SDK in %s does not support target %s" % (sdk, target.alias))
        else:
            log.warning(
                "Cannot check whether the iOS SDK is for the right target, "
                "assuming it is."
            )
        if version < Version(ios_sdk_min_version()):
            die(
                'SDK version "%s" is too old. Please upgrade to at least %s. Try '
                "updating your system Xcode." % (version, ios_sdk_min_version())
            )
        return sdk

    set_config("IPHONEOS_SDK_DIR", ios_sdk)


# GC rooting and hazard analysis.
# ==============================================================
option(env="MOZ_HAZARD", help="Build for the GC rooting hazard analysis")


@depends("MOZ_HAZARD")
def hazard_analysis(value):
    if value:
        return True


# Cross-compilation related things.
# ==============================================================
option(
    "--with-toolchain-prefix",
    env="TOOLCHAIN_PREFIX",
    nargs=1,
    help="Prefix for the target toolchain",
)


@depends("--with-toolchain-prefix", host, target, cross_compiling)
def toolchain_prefix(value, host, target, cross_compiling):
    if value:
        return tuple(value)
    # We don't want a toolchain prefix by default when building on mac for mac.
    if cross_compiling and not (target.os == "OSX" and host.os == "OSX"):
        return ("%s-" % target.toolchain, "%s-" % target.alias)


# Compilers
# ==============================================================
include("compilers-util.configure")


def try_preprocess(
    configure_cache, compiler, language, source, onerror=None, wrapper=[]
):
    return try_invoke_compiler(
        configure_cache, compiler, language, source, ["-E"], onerror, wrapper
    )


@imports(_from="mozbuild.configure.constants", _import="CompilerType")
@imports(_from="mozbuild.configure.constants", _import="CPU_preprocessor_checks")
@imports(_from="mozbuild.configure.constants", _import="kernel_preprocessor_checks")
@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
@imports(_from="textwrap", _import="dedent")
@imports(_from="__builtin__", _import="Exception")
def get_compiler_info(configure_cache, compiler, language):
    """Returns information about the given `compiler` (command line in the
    form of a list or tuple), in the given `language`.

    The returned information includes:
    - the compiler type (clang-cl, clang or gcc)
    - the compiler version
    - the compiler supported language
    - the compiler supported language version
    """
    # Xcode clang versions are different from the underlying llvm version (they
    # instead are aligned with the Xcode version). Fortunately, we can tell
    # apart plain clang from Xcode clang, and convert the Xcode clang version
    # into the more or less corresponding plain clang version.
    check = dedent(
        """\
        #if defined(_MSC_VER) && defined(__clang__) && defined(_MT)
        %COMPILER "clang-cl"
        %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
        #elif defined(__clang__)
        %COMPILER "clang"
        %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
        #  ifdef __apple_build_version__
        %XCODE 1
        #  endif
        #elif defined(__GNUC__) && !defined(__MINGW32__)
        %COMPILER "gcc"
        %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
        #endif

        #if __cplusplus
        %cplusplus __cplusplus
        #elif __STDC_VERSION__
        %STDC_VERSION __STDC_VERSION__
        #endif
    """
    )

    # While we're doing some preprocessing, we might as well do some more
    # preprocessor-based tests at the same time, to check the toolchain
    # matches what we want.
    for name, preprocessor_checks in (
        ("CPU", CPU_preprocessor_checks),
        ("KERNEL", kernel_preprocessor_checks),
        ("OS", OS_preprocessor_checks),
    ):
        for n, (value, condition) in enumerate(preprocessor_checks.items()):
            check += dedent(
                """\
                #%(if)s %(condition)s
                %%%(name)s "%(value)s"
            """
                % {
                    "if": "elif" if n else "if",
                    "condition": condition,
                    "name": name,
                    "value": value,
                }
            )
        check += "#endif\n"

    # Also check for endianness. The advantage of living in modern times is
    # that all the modern compilers we support now have __BYTE_ORDER__ defined
    # by the preprocessor.
    check += dedent(
        """\
        #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
        %ENDIANNESS "little"
        #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        %ENDIANNESS "big"
        #endif
    """
    )

    result = try_preprocess(configure_cache, compiler, language, check)

    if not result:
        raise FatalCheckError("Unknown compiler or compiler not supported.")

    # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
    # have non-ASCII characters. Treat the output as bytearray.
    data = {}
    for line in result.splitlines():
        if line.startswith("%"):
            k, _, v = line.partition(" ")
            k = k.lstrip("%")
            data[k] = v.replace(" ", "").lstrip('"').rstrip('"')
            log.debug("%s = %s", k, data[k])

    try:
        type = CompilerType(data["COMPILER"])
    except Exception:
        raise FatalCheckError("Unknown compiler or compiler not supported.")

    cplusplus = int(data.get("cplusplus", "0L").rstrip("L"))
    stdc_version = int(data.get("STDC_VERSION", "0L").rstrip("L"))

    version = data.get("VERSION")
    if version:
        version = Version(version)
        if data.get("XCODE"):
            # Derived from https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
            # with enough granularity for major.minor version checks further
            # down the line
            if version < "9.1":
                version = Version("4.0.0.or.less")
            elif version < "10.0":
                version = Version("5.0.2")
            elif version < "10.0.1":
                version = Version("6.0.1")
            elif version < "11.0":
                version = Version("7.0.0")
            elif version < "11.0.3":
                version = Version("8.0.0")
            elif version < "12.0":
                version = Version("9.0.0")
            elif version < "12.0.5":
                version = Version("10.0.0")
            elif version < "13.0":
                version = Version("11.1.0")
            elif version < "13.0.1":
                version = Version("12.0.0")
            elif version < "14.0":
                version = Version("13.0.0")
            elif version < "14.3":
                version = Version("14.0.0")
            elif version < "15.0":
                version = Version("15.0.0")
            elif version < "16.0":
                version = Version("16.0.0")
            else:
                version = Version("17.0.6.or.more")

    return namespace(
        type=type,
        version=version,
        cpu=data.get("CPU"),
        kernel=data.get("KERNEL"),
        endianness=data.get("ENDIANNESS"),
        os=data.get("OS"),
        language="C++" if cplusplus else "C",
        language_version=cplusplus if cplusplus else stdc_version,
        xcode=bool(data.get("XCODE")),
    )


def same_arch_different_bits():
    return (
        ("x86", "x86_64"),
        ("ppc", "ppc64"),
        ("sparc", "sparc64"),
    )


@imports(_from="mozbuild.shellutil", _import="quote")
@imports(_from="mozbuild.configure.constants", _import="OS_preprocessor_checks")
def check_compiler(configure_cache, compiler, language, target, android_version):
    info = get_compiler_info(configure_cache, compiler, language)

    flags = []

    # Check language standards
    # --------------------------------------------------------------------
    if language != info.language:
        raise FatalCheckError(
            "`%s` is not a %s compiler." % (quote(*compiler), language)
        )

    # Note: We do a strict version check because there sometimes are backwards
    # incompatible changes in the standard, and not all code that compiles as
    # C17 compiles as C23.
    c17_version = 201710
    if info.language == "C" and info.language_version != c17_version:
        if info.type == "clang-cl":
            flags.append("-Xclang")
        flags.append("-std=gnu17")

    cxx17_version = 201703
    if info.language == "C++":
        if info.language_version != cxx17_version:
            # MSVC headers include C++17 features, but don't guard them
            # with appropriate checks.
            if info.type == "clang-cl":
                flags.append("-std:c++17")
            else:
                flags.append("-std=gnu++17")

    # Check compiler target
    # --------------------------------------------------------------------
    has_target = False
    if target.os == "Android" and android_version:
        # This makes clang define __ANDROID_API__ and use versioned library
        # directories from the NDK.
        toolchain = "%s%d" % (target.toolchain, android_version)
    else:
        toolchain = target.toolchain

    if info.type == "clang":
        # Add the target explicitly when the target is aarch64 macosx, because
        # the Xcode clang target is named differently, and we need to work around
        # 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 target.os == "iOS":
            target_flag = "--target=%s" % toolchain
            if target_flag not in compiler:
                flags.append(target_flag)
            has_target = True
        elif (
            not info.kernel
            or info.kernel != target.kernel
            or not info.endianness
            or info.endianness != target.endianness
        ):
            flags.append("--target=%s" % toolchain)
            has_target = True

        # Add target flag when there is an OS mismatch (e.g. building for Android on
        # Linux). However, only do this if the target OS is in our whitelist, to
        # keep things the same on other platforms.
        elif target.os in OS_preprocessor_checks and (
            not info.os or info.os != target.os
        ):
            flags.append("--target=%s" % toolchain)
            has_target = True

    if not has_target and (not info.cpu or info.cpu != target.cpu):
        same_arch = same_arch_different_bits()
        if (target.cpu, info.cpu) in same_arch:
            flags.append("-m32")
        elif (info.cpu, target.cpu) in same_arch:
            flags.append("-m64")
        elif info.type == "clang-cl" and target.cpu == "aarch64":
            flags.append("--target=%s" % toolchain)
        elif info.type == "clang":
            flags.append("--target=%s" % toolchain)

    return namespace(
        type=info.type,
        version=info.version,
        target_cpu=info.cpu,
        target_kernel=info.kernel,
        target_endianness=info.endianness,
        target_os=info.os,
        flags=flags,
    )


@imports(_from="__builtin__", _import="open")
@imports("json")
@imports("os")
def get_vc_paths(host, topsrcdir):
    def vswhere(args):
        program_files = os.environ.get("PROGRAMFILES(X86)") or os.environ.get(
            "PROGRAMFILES"
        )
        if not program_files:
            return []
        vswhere = os.path.join(
            program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe"
        )
        if not os.path.exists(vswhere):
            return []
        return json.loads(check_cmd_output(vswhere, "-format", "json", *args))

    variant = "arm64" if host.cpu == "aarch64" else "x86.x64"
    for install in vswhere(
        [
            "-products",
            "*",
            "-requires",
            f"Microsoft.VisualStudio.Component.VC.Tools.{variant}",
        ]
    ):
        path = install["installationPath"]
        tools_version = (
            open(
                os.path.join(
                    path, r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
                ),
                "r",
            )
            .read()
            .strip()
        )
        tools_path = os.path.join(path, r"VC\Tools\MSVC", tools_version)
        yield (Version(install["installationVersion"]), tools_path)


@depends(target, host)
def is_windows(target, host):
    return host.kernel == "WINNT" or target.kernel == "WINNT"


# Calling this a sysroot is a little weird, but it's the terminology clang went
# with with its -winsysroot flag.
option(
    env="WINSYSROOT",
    nargs=1,
    when=is_windows,
    help='Path to a Windows "sysroot" (directory containing MSVC, SDKs)',
)


@depends(
    "WINSYSROOT",
    bootstrap_path(
        "vs",
        when=depends("WINSYSROOT", when=is_windows)(lambda x: not x),
    ),
    when=is_windows,
)
def winsysroot(winsysroot, bootstrapped):
    if bootstrapped:
        return bootstrapped
    if winsysroot:
        return winsysroot[0]


option(
    env="VC_PATH",
    nargs=1,
    when=is_windows,
    help="Path to the Microsoft Visual C/C++ compiler",
)


@depends(
    host,
    build_environment,
    "VC_PATH",
    winsysroot,
    when=is_windows,
)
@imports("os")
@imports(_from="operator", _import="itemgetter")
def vc_compiler_paths_for_version(host, env, vc_path, winsysroot):
    if winsysroot:
        if vc_path:
            die("WINSYSROOT and VC_PATH cannot be set together.")
        base_vc_path = os.path.join(winsysroot, "VC", "Tools", "MSVC")
        versions = os.listdir(base_vc_path)
        vc_path = [os.path.join(base_vc_path, str(max(Version(v) for v in versions)))]
    if vc_path:
        # Use an arbitrary version, it doesn't matter.
        all_versions = [(Version("15"), vc_path[0])]
    elif host.kernel != "WINNT":
        # Don't try to do anything when VC_PATH is not set on cross-compiles.
        return
    else:
        all_versions = sorted(get_vc_paths(host, env.topsrcdir), key=itemgetter(0))
    if not all_versions:
        return
    # Choose the newest version.
    path = all_versions[-1][1]
    host_dir = {
        "x86_64": "Hostx64",
        "x86": "Hostx86",
        "aarch64": "Hostarm64",
    }.get(host.cpu)
    if host_dir:
        path = os.path.join(path, "bin", host_dir)
        return {
            "x64": [os.path.join(path, "x64")],
            # The cross toolchains require DLLs from the native x64 toolchain.
            "x86": [os.path.join(path, "x86"), os.path.join(path, "x64")],
            "arm64": [os.path.join(path, "arm64"), os.path.join(path, "x64")],
        }


@depends(target, host, vc_compiler_paths_for_version, when=is_windows)
def vc_compiler_path(target, host, paths):
    cpu = target.cpu if target.os == "WINNT" else host.cpu
    vc_target = {
        "x86": "x86",
        "x86_64": "x64",
        "arm": "arm",
        "aarch64": "arm64",
    }.get(cpu)
    if not paths:
        return
    return paths.get(vc_target)


@depends(vc_compiler_path, original_path)
@imports("os")
@imports(_from="os", _import="environ")
def vc_toolchain_search_path(vc_compiler_path, original_path):
    result = list(original_path)

    if vc_compiler_path:
        # The second item, if there is one, is necessary to have in $PATH for
        # Windows to load the required DLLs from there.
        if len(vc_compiler_path) > 1:
            environ["PATH"] = os.pathsep.join(result + vc_compiler_path[1:])

        # The first item is where the programs are going to be
        result.append(vc_compiler_path[0])

    return result


@depends_if(vc_compiler_path, when=is_windows)
def vc_compiler_version(vc_compiler_path):
    version = Version(
        os.path.basename(
            os.path.dirname(os.path.dirname(os.path.dirname(vc_compiler_path[0])))
        )
    )
    # MSVC path with version 14.x is actually version 19.x
    if version.major == 14:
        return Version(f"19.{version.minor}")


@depends_if(vc_compiler_version)
def msvs_version(vc_compiler_version):
    # clang-cl emulates the same version scheme as cl. And MSVS_VERSION needs to
    # be set for GYP on Windows.
    if vc_compiler_version >= Version("19.30"):
        return "2022"
    configure_error("Only Visual Studio 2022 or newer are supported")

    return ""


set_config("MSVS_VERSION", msvs_version)


clang_search_path = bootstrap_search_path("clang/bin")


@depends(
    bootstrap_search_path("rustc/bin", when="MOZ_AUTOMATION"),
    original_path,
)
@imports("os")
@imports(_from="os", _import="environ")
def rust_search_path(rust_path, original_path):
    result = list(rust_path or original_path)
    # Also add the rustup install directory for cargo/rustc.
    cargo_home = environ.get("CARGO_HOME", "")
    if cargo_home:
        cargo_home = os.path.abspath(cargo_home)
    else:
        cargo_home = os.path.expanduser(os.path.join("~", ".cargo"))
    rustup_path = os.path.join(cargo_home, "bin")
    result.insert(0, rustup_path)
    return result


# Prepend the mozilla-build msys2 path, since otherwise we can get mismatched
# cygwin dll errors during configure if we get called from another msys2
# environment, see bug 1801826.
@depends(
    mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path
)
@imports("os")
def altered_path(
    mozillabuild_bin_paths, clang_search_path, rust_search_path, target, original_path
):
    altered_path = mozillabuild_bin_paths
    if target.kernel == "Darwin":
        # The rust compiler wants to execute dsymutil, but it does so in a
        # non-configurable way (https://github.com/rust-lang/rust/issues/52728)
        # so we add the clang path.
        path = clang_search_path
    else:
        path = original_path
    # cargo needs the rust search path to find cargo-$subcommand.
    path += rust_search_path
    for p in path:
        if p not in altered_path:
            altered_path.append(p)
    return os.pathsep.join(altered_path)


set_config("PATH", altered_path)


# Compiler wrappers
# ==============================================================
option(
    "--with-compiler-wrapper",
    env="COMPILER_WRAPPER",
    nargs=1,
    help="Enable compiling with wrappers such as distcc and ccache",
)

option("--with-ccache", env="CCACHE", nargs="?", help="Enable compiling with ccache")


@depends_if("--with-ccache")
def ccache(value):
    if len(value):
        return value
    # If --with-ccache was given without an explicit value, we default to
    # 'ccache'.
    return "ccache"


ccache = check_prog(
    "CCACHE",
    progs=(),
    input=ccache,
    paths=bootstrap_search_path(
        "sccache", when=depends("CCACHE")(lambda c: len(c) and c[0] == "sccache")
    ),
    allow_missing=True,
)

option(env="CCACHE_PREFIX", nargs=1, help="Compiler prefix to use when using ccache")

ccache_prefix = depends_if("CCACHE_PREFIX")(lambda prefix: prefix[0])
set_config("CCACHE_PREFIX", ccache_prefix)

# Distinguish ccache from sccache.


@depends_if(ccache)
def ccache_is_sccache(ccache):
    return check_cmd_output(ccache, "--version").startswith("sccache")


@depends(ccache, ccache_is_sccache)
def using_ccache(ccache, ccache_is_sccache):
    return ccache and not ccache_is_sccache


@depends_if(ccache, ccache_is_sccache)
def using_sccache(ccache, ccache_is_sccache):
    return ccache and ccache_is_sccache


option(env="RUSTC_WRAPPER", nargs=1, help="Wrap rust compilation with given tool")


@depends(ccache, ccache_is_sccache, "RUSTC_WRAPPER")
@imports(_from="textwrap", _import="dedent")
@imports("os")
def check_sccache_version(ccache, ccache_is_sccache, rustc_wrapper):
    sccache_min_version = Version("0.2.13")

    def check_version(path):
        out = check_cmd_output(path, "--version")
        version = Version(out.rstrip().split()[-1])
        if version < sccache_min_version:
            die(
                dedent(
                    """\
            sccache %s or later is required. sccache in use at %s has
            version %s.

            Please upgrade or acquire a new version with |./mach bootstrap|.
            """
                ),
                sccache_min_version,
                path,
                version,
            )

    if ccache and ccache_is_sccache:
        check_version(ccache)

    if rustc_wrapper and (
        os.path.splitext(os.path.basename(rustc_wrapper[0]))[0].lower() == "sccache"
    ):
        check_version(rustc_wrapper[0])


set_config("MOZ_USING_CCACHE", using_ccache)
set_config("MOZ_USING_SCCACHE", using_sccache)

option(env="SCCACHE_VERBOSE_STATS", help="Print verbose sccache stats after build")


@depends(using_sccache, "SCCACHE_VERBOSE_STATS")
def sccache_verbose_stats(using_sccache, verbose_stats):
    return using_sccache and bool(verbose_stats)


set_config("SCCACHE_VERBOSE_STATS", sccache_verbose_stats)


@depends("--with-compiler-wrapper", ccache)
@imports(_from="mozbuild.shellutil", _import="split", _as="shell_split")
def compiler_wrapper(wrapper, ccache):
    if wrapper:
        raw_wrapper = wrapper[0]
        wrapper = shell_split(raw_wrapper)
        wrapper_program = find_program(wrapper[0])
        if not wrapper_program:
            die(
                "Cannot find `%s` from the given compiler wrapper `%s`",
                wrapper[0],
                raw_wrapper,
            )
        wrapper[0] = wrapper_program

    if ccache:
        if wrapper:
            return tuple([ccache] + wrapper)
        else:
            return (ccache,)
    elif wrapper:
        return tuple(wrapper)


@dependable
def wasm():
    return split_triplet("wasm32-wasi", allow_wasi=True)


@template
def default_c_compilers(host_or_target, other_c_compiler=None):
    """Template defining the set of default C compilers for the host and
    target platforms.
    `host_or_target` is either `host` or `target` (the @depends functions
    from init.configure.
    `other_c_compiler` is the `target` C compiler when `host_or_target` is `host`.
    """
    assert host_or_target in {host, target, wasm}

    other_c_compiler = () if other_c_compiler is None else (other_c_compiler,)

    @depends(host_or_target, target, toolchain_prefix, *other_c_compiler)
    def default_c_compilers(
        host_or_target, target, toolchain_prefix, *other_c_compiler
    ):
        if host_or_target.kernel == "WINNT":
            if host_or_target.abi:
                if host_or_target.abi == "msvc":
                    supported = types = ("clang-cl",)
                elif host_or_target.abi == "mingw":
                    supported = types = ("clang",)
            else:
                supported = types = ("clang-cl", "clang")
        elif host_or_target.kernel == "Darwin":
            types = ("clang",)
            supported = ("clang", "gcc")
        elif host_or_target.kernel == "WASI":
            supported = types = ("clang",)
        else:
            supported = types = ("clang", "gcc")

        info = other_c_compiler[0] if other_c_compiler else None
        if info and info.type in supported:
            # When getting default C compilers for the host, we prioritize the
            # same compiler as the target C compiler.
            prioritized = info.compiler
            if info.type == "gcc":
                same_arch = same_arch_different_bits()
                if (
                    target.cpu != host_or_target.cpu
                    and (target.cpu, host_or_target.cpu) not in same_arch
                    and (host_or_target.cpu, target.cpu) not in same_arch
                ):
                    # If the target C compiler is GCC, and it can't be used with
                    # -m32/-m64 for the host, it's probably toolchain-prefixed,
                    # so we prioritize a raw 'gcc' instead.
                    prioritized = info.type
            if target.os != "WINNT" and host_or_target.os == "WINNT":
                # When cross-compiling on Windows, don't prioritize. We'll fallback
                # to checking for clang-cl first.
                pass
            else:
                types = [prioritized] + [t for t in types if t != info.type]

        gcc = ("gcc",)
        if toolchain_prefix and host_or_target is target:
            gcc = tuple("%sgcc" % p for p in toolchain_prefix) + gcc

        result = []
        for type in types:
            if type == "gcc":
                result.extend(gcc)
            else:
                result.append(type)

        return tuple(result)

    return default_c_compilers


@template
def default_cxx_compilers(c_compiler, other_c_compiler=None, other_cxx_compiler=None):
    """Template defining the set of default C++ compilers for the host and
    target platforms.
    `c_compiler` is the @depends function returning a Compiler instance for
    the desired platform.

    Because the build system expects the C and C++ compilers to be from the
    same compiler suite, we derive the default C++ compilers from the C
    compiler that was found if none was provided.

    We also factor in the target C++ compiler when getting the default host
    C++ compiler, using the target C++ compiler if the host and target C
    compilers are the same.
    """

    assert (other_c_compiler is None) == (other_cxx_compiler is None)
    if other_c_compiler is not None:
        other_compilers = (other_c_compiler, other_cxx_compiler)
    else:
        other_compilers = ()

    @depends(c_compiler, *other_compilers)
    def default_cxx_compilers(c_compiler, *other_compilers):
        if other_compilers:
            other_c_compiler, other_cxx_compiler = other_compilers
            if other_c_compiler.compiler == c_compiler.compiler:
                return (other_cxx_compiler.compiler,)

        dir = os.path.dirname(c_compiler.compiler)
        file = os.path.basename(c_compiler.compiler)

        if c_compiler.type == "gcc":
            return (os.path.join(dir, file.replace("gcc", "g++")),)

        if c_compiler.type == "clang":
            return (os.path.join(dir, file.replace("clang", "clang++")),)

        return (c_compiler.compiler,)

    return default_cxx_compilers


@template
def provided_program(env_var, when=None):
    """Template handling cases where a program can be specified either as a
    path or as a path with applicable arguments.
    """

    @depends_if(env_var, when=when)
    @imports(_from="itertools", _import="takewhile")
    @imports(_from="mozbuild.shellutil", _import="split", _as="shell_split")
    def provided(cmd):
        # Assume the first dash-prefixed item (and any subsequent items) are
        # command-line options, the item before the dash-prefixed item is
        # the program we're looking for, and anything before that is a wrapper
        # of some kind (e.g. sccache).
        cmd = shell_split(cmd[0])

        without_flags = list(takewhile(lambda x: not x.startswith("-"), cmd))

        return namespace(
            wrapper=without_flags[:-1],
            program=without_flags[-1],
            flags=cmd[len(without_flags) :],
        )

    return provided


@template
def sysroot(host_or_target, target_sysroot=None):
    assert target_sysroot or host_or_target is target
    bootstrap_target_when = target_is_linux_or_wasi
    if host_or_target is host:
        host_or_target_str = "host"
        opt = "--with-host-sysroot"
        env = "HOST_SYSROOT"
        when = depends(host)(lambda h: h.kernel == "Linux")

        # Only bootstrap a host sysroot when using a bootstrapped target sysroot
        # or when the target doesn't use a bootstrapped sysroot in the first place.
        @depends(when, bootstrap_target_when, target_sysroot.bootstrapped)
        def bootstrap_when(when, bootstrap_target_when, bootstrapped):
            return when and (bootstrapped or not bootstrap_target_when)

    else:
        assert host_or_target is target
        host_or_target_str = "target"
        opt = "--with-sysroot"
        env = "SYSROOT"
        when = target_is_linux_or_wasi
        bootstrap_when = bootstrap_target_when

    option(
        opt,
        env=env,
        nargs=1,
        when=when,
        help="Use the given sysroot directory for %s build" % host_or_target_str,
    )

    sysroot_input = depends(opt, when=when)(lambda x: x)
    bootstrap_sysroot = depends(bootstrap_when, sysroot_input)(
        # Only bootstrap when no flag was explicitly given (either --with or --without)
        lambda bootstrap, input: bootstrap
        and not input
        and input.origin == "default"
    )

    @depends(
        sysroot_input,
        host_or_target,
        macos_sdk,
        ios_sdk,
        bootstrap_path(
            depends(host_or_target)(lambda t: "sysroot-{}".format(t.toolchain)),
            when=bootstrap_sysroot,
        ),
    )
    @imports("os")
    def sysroot(sysroot_input, host_or_target, macos_sdk, ios_sdk, path):
        version = None
        if sysroot_input:
            path = sysroot_input[0]
        elif host_or_target.os == "OSX" and macos_sdk:
            path = macos_sdk
        elif host_or_target.os == "iOS" and ios_sdk:
            path = ios_sdk
        if path:
            # Find the version of libstdc++ headears in the sysroot
            include = os.path.join(path, "usr/include/c++")
            if os.path.isdir(include):
                with os.scandir(include) as d:
                    version = max(Version(e.name) for e in d if e.is_dir())
            log.info("Using %s sysroot in %s", host_or_target_str, path)
        return namespace(
            path=path,
            bootstrapped=bool(path and not sysroot_input),
            stdcxx_version=version,
        )

    return sysroot


target_sysroot = sysroot(target)


# Use `system_lib_option` instead of `option` for options that enable building
# with a system library for which the development headers are not available in
# the bootstrapped sysroots.
@template
def system_lib_option(name, *args, **kwargs):
    option(name, *args, **kwargs)

    @depends(
        name,
        target_sysroot.bootstrapped,
        when=kwargs.get("when"),
    )
    def no_system_lib_in_sysroot(value, bootstrapped):
        if bootstrapped and value:
            die(
                "%s is not supported with bootstrapped sysroot. "
                "Drop the option, or use --without-sysroot or --disable-bootstrap",
                value.format(name),
            )


host_sysroot = sysroot(host, target_sysroot)


@template
def multiarch_dir(host_or_target):
    sysroot = {
        host: host_sysroot,
        target: target_sysroot,
    }[host_or_target]

    @depends(host_or_target, when=sysroot.path)
    def multiarch_dir(target):
        if target.cpu == "x86":
            # Turn e.g. i686-linux-gnu into i386-linux-gnu
            return target.toolchain.replace(target.raw_cpu, "i386")
        return target.toolchain

    return multiarch_dir


target_multiarch_dir = multiarch_dir(target)
host_multiarch_dir = multiarch_dir(host)


def minimum_gcc_version():
    return Version("8.1.0")


@template
def compiler(
    language,
    host_or_target,
    c_compiler=None,
    other_compiler=None,
    other_c_compiler=None,
):
    """Template handling the generic base checks for the compiler for the
    given `language` on the given platform (`host_or_target`).
    `host_or_target` is either `host` or `target` (the @depends functions
    from init.configure.
    When the language is 'C++', `c_compiler` is the result of the `compiler`
    template for the language 'C' for the same `host_or_target`.
    When `host_or_target` is `host`, `other_compiler` is the result of the
    `compiler` template for the same `language` for `target`.
    When `host_or_target` is `host` and the language is 'C++',
    `other_c_compiler` is the result of the `compiler` template for the
    language 'C' for `target`.
    """
    assert host_or_target in {host, target, wasm}
    assert language in ("C", "C++")
    assert language == "C" or c_compiler is not None
    assert host_or_target is target or other_compiler is not None
    assert language == "C" or host_or_target is target or other_c_compiler is not None

    host_or_target_str = {
        host: "host",
        target: "target",
        wasm: "wasm",
    }[host_or_target]

    sysroot = {
        host: host_sysroot,
        target: target_sysroot,
        wasm: dependable(lambda: namespace(path=None)),
    }[host_or_target]

    multiarch_dir = {
        host: host_multiarch_dir,
        target: target_multiarch_dir,
        wasm: never,
    }[host_or_target]

    var = {
        ("C", target): "CC",
        ("C++", target): "CXX",
        ("C", host): "HOST_CC",
        ("C++", host): "HOST_CXX",
        ("C", wasm): "WASM_CC",
        ("C++", wasm): "WASM_CXX",
    }[language, host_or_target]

    default_compilers = {
        "C": lambda: default_c_compilers(host_or_target, other_compiler),
        "C++": lambda: default_cxx_compilers(
            c_compiler, other_c_compiler, other_compiler
        ),
    }[language]()

    what = "the %s %s compiler" % (host_or_target_str, language)

    option(env=var, nargs=1, help="Path to %s" % what)

    # Handle the compiler given by the user through one of the CC/CXX/HOST_CC/
    # HOST_CXX variables.
    provided_compiler = provided_program(var)

    # Normally, we'd use `var` instead of `_var`, but the interaction with
    # old-configure complicates things, and for now, we a) can't take the plain
    # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
    # old-configure AC_SUBST it (because it's autoconf doing it, not us)
    compiler = check_prog(
        "_%s" % var,
        what=what,
        progs=default_compilers,
        input=provided_compiler.program,
        paths=clang_search_path,
    )

    @depends(
        configure_cache,
        compiler,
        provided_compiler,
        compiler_wrapper,
        host_or_target,
        sysroot,
        macos_target,
        ios_target,
        android_version,
        vc_compiler_version,
        multiarch_dir,
        winsysroot,
        host,
    )
    @checking("whether %s can be used" % what, lambda x: bool(x))
    @imports(_from="mozbuild.shellutil", _import="quote")
    @imports("os")
    def valid_compiler(
        configure_cache,
        compiler,
        provided_compiler,
        compiler_wrapper,
        host_or_target,
        sysroot,
        macos_target,
        ios_target,
        android_version,
        vc_compiler_version,
        multiarch_dir,
        winsysroot,
        host,
    ):
        wrapper = list(compiler_wrapper or ())
        flags = []
        if sysroot.path:
            if host_or_target.kernel == "Darwin":
                # While --sysroot and -isysroot are roughly equivalent, when not using
                # -isysroot on mac, clang takes the SDKROOT environment variable into
                # consideration, which may be set by python and break things.
                flags.extend(("-isysroot", sysroot.path))
            else:
                flags.extend(("--sysroot", sysroot.path))
        if provided_compiler:
            wrapper.extend(provided_compiler.wrapper)
            flags.extend(provided_compiler.flags)

        info = check_compiler(
            configure_cache,
            wrapper + [compiler] + flags,
            language,
            host_or_target,
            android_version,
        )

        if host_or_target.os == "OSX" and macos_target:
            flags.append("-mmacosx-version-min=%s" % macos_target)
        if host_or_target.os == "iOS" and ios_target:
            flags.append("-mios-version-min=%s" % ios_target)

        # When not given an explicit compatibility version, clang-cl tries
        # to get one from MSVC, which might not even be the one used by the
        # build. And when it can't find one, its default might also not match
        # what the build is using. So if we were able to figure out the version
        # we're building with, explicitly use that.
        # This also means that, as a side effect, clang-cl will not try to find
        # MSVC, which saves a little overhead.
        if info.type == "clang-cl" and vc_compiler_version:
            flags.append(f"-fms-compatibility-version={vc_compiler_version}")

        if info.type == "clang" and language == "C++" and host_or_target.os == "OSX":
            flags.append("-stdlib=libc++")

        # Check that the additional flags we got are enough to not require any
        # more flags. If we get an exception, just ignore it; it's liable to be
        # invalid command-line flags, which means the compiler we're checking
        # doesn't support those command-line flags and will fail one or more of
        # the checks below.
        try:
            if info.flags:
                flags += info.flags
                info = check_compiler(
                    configure_cache,
                    wrapper + [compiler] + flags,
                    language,
                    host_or_target,
                    android_version,
                )
        except FatalCheckError:
            pass

        if not info.target_cpu or info.target_cpu != host_or_target.cpu:
            raise FatalCheckError(
                "%s %s compiler target CPU (%s) does not match --%s CPU (%s)"
                % (
                    host_or_target_str.capitalize(),
                    language,
                    info.target_cpu or "unknown",
                    host_or_target_str,
                    host_or_target.raw_cpu,
                )
            )

        if not info.target_kernel or (info.target_kernel != host_or_target.kernel):
            raise FatalCheckError(
                "%s %s compiler target kernel (%s) does not match --%s kernel (%s)"
                % (
                    host_or_target_str.capitalize(),
                    language,
                    info.target_kernel or "unknown",
                    host_or_target_str,
                    host_or_target.kernel,
                )
            )

        if not info.target_endianness or (
            info.target_endianness != host_or_target.endianness
        ):
            raise FatalCheckError(
                "%s %s compiler target endianness (%s) does not match --%s "
                "endianness (%s)"
                % (
                    host_or_target_str.capitalize(),
                    language,
                    info.target_endianness or "unknown",
                    host_or_target_str,
                    host_or_target.endianness,
                )
            )

        # Compiler version checks
        # ===================================================
        # Check the compiler version here instead of in `compiler_version` so
        # that the `checking` message doesn't pretend the compiler can be used
        # to then bail out one line later.
        if info.type == "gcc":
            if host_or_target.os == "Android":
                raise FatalCheckError(
                    "GCC is not supported on Android.\n"
                    "Please use clang from the Android NDK instead."
                )
            gcc_version = minimum_gcc_version()
            if info.version < gcc_version:
                raise FatalCheckError(
                    "Only GCC %d.%d or newer is supported (found version %s)."
                    % (gcc_version.major, gcc_version.minor, info.version)
                )

            # Force GCC to use the C++ headers from the sysroot, and to prefer the
            # sysroot system headers to /usr/include.
            # Non-Debian GCC also doesn't look at headers in multiarch directory.
            if sysroot.bootstrapped and sysroot.stdcxx_version:
                version = sysroot.stdcxx_version
                for path in (
                    "usr/include/c++/{}".format(version),
                    "usr/include/{}/c++/{}".format(multiarch_dir, version),
                    "usr/include/{}".format(multiarch_dir),
                    "usr/include",
                ):
                    flags.extend(("-isystem", os.path.join(sysroot.path, path)))

        if info.type == "clang-cl":
            if info.version < "9.0.0":
                raise FatalCheckError(
                    "Only clang-cl 9.0 or newer is supported (found version %s)"
                    % info.version
                )
            if winsysroot and host.os != "WINNT":
                overlay = os.path.join(winsysroot, "overlay.yaml")
                if os.path.exists(overlay):
                    overlay_flags = ["-Xclang", "-ivfsoverlay", "-Xclang", overlay]
                    if info.version >= "16.0" or (
                        # clang-cl 15 normally doesn't support the root-relative
                        # overlay we use, but the bootstrapped clang-cl 15 is patched
                        # to support it, so check we're using a patched version.
                        info.version >= "15.0"
                        and try_preprocess(
                            configure_cache,
                            [compiler] + flags + overlay_flags,
                            language,
                            "",
                            onerror=lambda: False,
                            wrapper=wrapper,
                        )
                    ):
                        flags.extend(overlay_flags)

        if (info.type, host_or_target.abi) in (
            ("clang", "msvc"),
            ("clang-cl", "mingw"),
        ):
            raise FatalCheckError("Unknown compiler or compiler not supported.")

        # If you want to bump the version check here ensure the version
        # is known for Xcode in get_compiler_info.
        if info.type == "clang" and info.version < "8.0":
            raise FatalCheckError(
                "Only clang/llvm 8.0 or newer is supported (found version %s)."
                % info.version
            )

        if host_or_target.kernel == "WASI":
            if info.type != "clang":
                raise FatalCheckError(
                    "Only clang is supported for %s" % host_or_target.alias
                )
            if info.version < "8.0":
                raise FatalCheckError(
                    "Only clang/llvm 8.0 or newer is supported for %s (found version %s)."
                    % (host_or_target.alias, info.version)
                )

        if host_or_target.os == "Android":
            # Need at least clang 13 for compiler-rt/libunwind being the default.
            if info.type == "clang" and info.version < "13.0":
                raise FatalCheckError(
                    "Only clang/llvm 13.0 or newer is supported for %s (found version %s)."
                    % (host_or_target.alias, info.version)
                )

        if host_or_target.os == "WINNT" and info.type == "gcc":
            raise FatalCheckError(
                "Firefox cannot be built with mingw-gcc and requires a mingw-clang toolchain to work."
            )

        if info.flags:
            raise FatalCheckError("Unknown compiler or compiler not supported.")

        return namespace(
            wrapper=wrapper,
            compiler=compiler,
            flags=flags,
            type=info.type,
            version=info.version,
            language=language,
        )

    @depends(valid_compiler)
    @checking("%s version" % what)
    def compiler_version(compiler):
        return compiler.version

    if language == "C++":

        @depends(valid_compiler, c_compiler)
        def valid_compiler(compiler, c_compiler):
            if compiler.type != c_compiler.type:
                die(
                    "The %s C compiler is %s, while the %s C++ compiler is "
                    "%s. Need to use the same compiler suite.",
                    host_or_target_str,
                    c_compiler.type,
                    host_or_target_str,
                    compiler.type,
                )

            if compiler.version != c_compiler.version:
                die(
                    "The %s C compiler is version %s, while the %s C++ "
                    "compiler is version %s. Need to use the same compiler "
                    "version.",
                    host_or_target_str,
                    c_compiler.version,
                    host_or_target_str,
                    compiler.version,
                )
            return compiler

    # This excludes WASM_CC from the list.
    if var in ("CC", "CXX", "HOST_CC", "HOST_CXX"):
        # FIXME: we should return a plain list here.
        @depends_if(valid_compiler)
        @imports(_from="mozbuild.shellutil", _import="quote")
        def value(x):
            return quote(*x.wrapper, x.compiler, *x.flags)

        set_config(var, value)

    # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
    # old-configure to do some of its still existing checks.
    if language == "C":
        set_config("%s_TYPE" % var, valid_compiler.type)
        set_config(
            "%s_VERSION" % var, depends(valid_compiler.version)(lambda v: str(v))
        )

    valid_compiler = compiler_class(valid_compiler, host_or_target)

    def compiler_error():
        raise FatalCheckError(
            "Failed compiling a simple %s source with %s" % (language, what)
        )

    valid_compiler.try_compile(check_msg="%s works" % what, onerror=compiler_error)

    set_config("%s_BASE_FLAGS" % var, valid_compiler.flags)

    # Set CPP/CXXCPP for both the build system and old-configure. We don't
    # need to check this works for preprocessing, because we already relied
    # on $CC -E/$CXX -E doing preprocessing work to validate the compiler
    # in the first place.
    if host_or_target is target:
        pp_var = {
            "C": "CPP",
            "C++": "CXXCPP",
        }[language]

        preprocessor = depends_if(valid_compiler)(
            lambda x: list(x.wrapper) + [x.compiler, "-E"] + list(x.flags)
        )

        set_config(pp_var, preprocessor)

    if language == "C":
        linker_var = {
            target: "LD",
            host: "HOST_LD",
        }.get(host_or_target)

        if linker_var:

            @deprecated_option(env=linker_var, nargs=1)
            def linker(value):
                if value:
                    return value[0]

            @depends(linker)
            def unused_linker(linker):
                if linker:
                    log.warning(
                        "The value of %s is not used by this build system." % linker_var
                    )

    return valid_compiler


c_compiler = compiler("C", target)
cxx_compiler = compiler("C++", target, c_compiler=c_compiler)
host_c_compiler = compiler("C", host, other_compiler=c_compiler)
host_cxx_compiler = compiler(
    "C++",
    host,
    c_compiler=host_c_compiler,
    other_compiler=cxx_compiler,
    other_c_compiler=c_compiler,
)


@template
def windows_abi(host_or_target, c_compiler):
    @depends(host_or_target)
    def windows_abi(host_or_target):
        if host_or_target.os == "WINNT":
            return host_or_target.abi

    @depends(host_or_target, windows_abi)
    def need_windows_abi_from_compiler(host_or_target, windows_abi):
        return host_or_target.os == "WINNT" and windows_abi is None

    @depends(host_or_target, c_compiler, when=need_windows_abi_from_compiler)
    def windows_abi_from_compiler(host_or_target, c_compiler):
        if host_or_target.os == "WINNT":
            if c_compiler.type == "clang-cl":
                return "msvc"
            return "mingw"

    return windows_abi | windows_abi_from_compiler


target_windows_abi = windows_abi(target, c_compiler)
host_windows_abi = windows_abi(host, host_c_compiler)


# Generic compiler-based conditions.
building_with_gcc = depends(c_compiler)(lambda info: info.type == "gcc")
building_with_gnu_compatible_cc = depends(c_compiler)(
    lambda info: info.type != "clang-cl"
)


@depends(cxx_compiler, ccache_prefix)
@imports("os")
def cxx_is_icecream(info, ccache_prefix):
    if (
        os.path.islink(info.compiler)
        and os.path.basename(os.readlink(info.compiler)) == "icecc"
    ):
        return True
    if ccache_prefix and os.path.basename(ccache_prefix) == "icecc":
        return True


set_config("CXX_IS_ICECREAM", cxx_is_icecream)


# Libstdc++ compatibility hacks
# ==============================================================
#
@depends(target, host)
def target_or_host_is_linux(target, host):
    return any(t.os == "GNU" and t.kernel == "Linux" for t in (target, host))


option(
    "--enable-stdcxx-compat",
    env="MOZ_STDCXX_COMPAT",
    help="Enable compatibility with older libstdc++",
    when=target_or_host_is_linux,
)


@depends("--enable-stdcxx-compat", when=target_or_host_is_linux)
def stdcxx_compat(value):
    if value:
        return True


set_config("MOZ_STDCXX_COMPAT", True, when=stdcxx_compat)


# Linker detection
# ==============================================================
# The policy is as follows:
# For Windows:
# - the linker is picked via the LINKER environment variable per windows.configure,
#   but ought to be lld-link in any case.
# For macOS:
# - the linker is lld if the clang used is >= 15 (per LLVM version, not Xcode version).
# - the linker is also lld on local developer builds if the clang used is >= 13 (per LLVM
#   version, not Xcode version)
# - otherwise the linker is ld64, either from XCode on macOS, or from cctools-ports when
#   cross-compiling.
# For other OSes:
# - on local developer builds: lld if present and the compiler is clang. Otherwise gold
#   is used if present otherwise, whatever the compiler uses by default.
# - on release/official builds: whatever the compiler uses by default, except when the
#   compiler is clang, in which case lld is preferred when it's new enough.
@template
def is_not_winnt_or_sunos(host_or_target):
    @depends(host_or_target)
    def is_not_winnt_or_sunos(host_or_target):
        if host_or_target.kernel not in ("WINNT", "SunOS"):
            return True

    return is_not_winnt_or_sunos


is_linker_option_enabled = is_not_winnt_or_sunos(target)


@deprecated_option("--enable-gold", env="MOZ_FORCE_GOLD", when=is_linker_option_enabled)
def enable_gold(value):
    if value:
        die("--enable-gold is deprecated, use --enable-linker=gold instead")
    else:
        die("--disable-gold is deprecated, use --enable-linker=something_else instead")


option(
    "--enable-linker",
    nargs=1,
    help="Select the linker {bfd, gold, ld64, lld, lld-*, mold}",
    when=is_linker_option_enabled,
)


# No-op to enable depending on --enable-linker from default_elfhack in
# toolkit/moz.configure.
@depends("--enable-linker", when=is_linker_option_enabled)
def enable_linker(linker):
    return linker


@template
def select_linker_tmpl(host_or_target):
    if host_or_target is target:
        deps = depends(
            "--enable-linker",
            c_compiler,
            developer_options,
            extra_toolchain_flags,
            target,
            stdcxx_compat,
            when=is_linker_option_enabled,
        )
        host_or_target_str = "target"
    else:
        deps = depends(
            dependable(None),
            host_c_compiler,
            developer_options,
            dependable(None),
            host,
            stdcxx_compat,
            when=is_not_winnt_or_sunos(host_or_target),
        )
        host_or_target_str = "host"

    @deps
    @checking(f"for {host_or_target_str} linker", lambda x: x.KIND)
    @imports("os")
    @imports("shutil")
    def select_linker(
        linker, c_compiler, developer_options, toolchain_flags, target, stdcxx_compat
    ):
        if linker:
            linker = linker[0]
        else:
            linker = None

        def is_valid_linker(linker):
            if target.kernel == "Darwin":
                valid_linkers = ("ld64", "lld")
            else:
                valid_linkers = ("bfd", "gold", "lld", "mold")
            if linker in valid_linkers:
                return True
            if "lld" in valid_linkers and linker.startswith("lld-"):
                return True
            return False

        if linker and not is_valid_linker(linker):
            # Check that we are trying to use a supported linker
            die("Unsupported linker " + linker)

        # Check the kind of linker
        version_check = ["-Wl,--version"]
        cmd_base = c_compiler.wrapper + [c_compiler.compiler] + c_compiler.flags

        def try_linker(linker):
            # Generate the compiler flag
            if linker == "ld64":
                linker_flag = ["-fuse-ld=ld"]
            elif linker:
                linker_flag = ["-fuse-ld=" + linker]
            else:
                linker_flag = []
            cmd = cmd_base + linker_flag + version_check
            if toolchain_flags:
                cmd += toolchain_flags

            # ld64 doesn't have anything to print out a version. It does print out
            # "ld64: For information on command line options please use 'man ld'."
            # but that would require doing two attempts, one with --version, that
            # would fail, and another with --help.
            # Instead, abuse its LD_PRINT_OPTIONS feature to detect a message
            # specific to it on stderr when it fails to process --version.
            env = dict(os.environ)
            env["LD_PRINT_OPTIONS"] = "1"
            # Some locales might not print out the strings we are looking for, so
            # ensure consistent output.
            env["LC_ALL"] = "C"
            retcode, stdout, stderr = get_cmd_output(*cmd, env=env)
            if retcode == 1 and "Logging ld64 options" in stderr:
                kind = "ld64"

            elif retcode != 0:
                return None

            elif "mold" in stdout:
                kind = "mold"

            elif "GNU ld" in stdout:
                # We are using the normal linker
                kind = "bfd"

            elif "GNU gold" in stdout:
                kind = "gold"

            elif "LLD" in stdout:
                kind = "lld"

            else:
                kind = "unknown"

            if kind == "unknown" or is_valid_linker(kind):
                return namespace(
                    KIND=kind,
                    LINKER_FLAG=linker_flag,
                )

        result = None
        if linker:
            result = try_linker(linker)
            if result is None:
                die("Could not use {} as linker".format(linker))

        if (
            result is None
            and c_compiler.type == "clang"
            and (
                (
                    target.kernel != "Darwin"
                    and (
                        developer_options
                        or host_or_target_str == "host"
                        or c_compiler.version >= "15.0"
                    )
                )
                or (
                    target.kernel == "Darwin"
                    and (
                        (developer_options and c_compiler.version >= "13.0")
                        or c_compiler.version >= "15.0"
                    )
                )
            )
        ):
            result = try_linker("lld")

        if result is None and developer_options and not stdcxx_compat:
            result = try_linker("gold")

        if result is None:
            result = try_linker(None)

        if result is None:
            die("Failed to find an adequate linker")

        if stdcxx_compat and result.KIND == "gold":
            die("--enable-stdcxx-compat is not compatible with the gold linker")

        # If an explicit linker was given, error out if what we found is different.
        if linker and not linker.startswith(result.KIND):
            die("Could not use {} as linker".format(linker))

        return result

    return select_linker


select_linker = select_linker_tmpl(target)


@template
def linker_ldflags_tmpl(host_or_target):
    if host_or_target is target:
        deps = depends_if(
            select_linker,
            target,
            target_sysroot,
            target_multiarch_dir,
            android_sysroot,
            android_version,
            c_compiler,
            developer_options,
        )
    else:
        deps = depends_if(
            select_linker_tmpl(host),
            host,
            host_sysroot,
            host_multiarch_dir,
            dependable(None),
            dependable(None),
            host_c_compiler,
            developer_options,
        )

    @deps
    @imports("os")
    def linker_ldflags(
        linker,
        target,
        sysroot,
        multiarch_dir,
        android_sysroot,
        android_version,
        c_compiler,
        developer_options,
    ):
        flags = list((linker and linker.LINKER_FLAG) or [])
        # rpath-link is irrelevant to wasm, see for more info https://github.com/emscripten-core/emscripten/issues/11076.
        if sysroot.path and multiarch_dir and target.os != "WASI":
            for d in ("lib", "usr/lib"):
                multiarch_lib_dir = os.path.join(sysroot.path, d, multiarch_dir)
                if os.path.exists(multiarch_lib_dir):
                    # Non-Debian-patched binutils linkers (both BFD and gold) don't lookup
                    # in multi-arch directories.
                    flags.append("-Wl,-rpath-link,%s" % multiarch_lib_dir)
                    # GCC also needs -L.
                    if c_compiler.type == "gcc":
                        flags.append("-L%s" % multiarch_lib_dir)
            if (
                c_compiler.type == "gcc"
                and sysroot.bootstrapped
                and sysroot.stdcxx_version
            ):
                flags.append(
                    "-L{}/usr/lib/gcc/{}/{}".format(
                        sysroot.path, multiarch_dir, sysroot.stdcxx_version
                    )
                )
        if android_sysroot:
            # BFD/gold linkers need a manual --rpath-link for indirect
            # dependencies.
            flags += [
                "-Wl,--rpath-link={}/usr/lib/{}".format(
                    android_sysroot, target.toolchain
                ),
                "-Wl,--rpath-link={}/usr/lib/{}/{}".format(
                    android_sysroot, target.toolchain, android_version
                ),
            ]
        if (
            developer_options
            and linker
            and linker.KIND == "lld"
            and target.kernel != "WINNT"
        ):
            flags.append("-Wl,-O0")
        return flags

    return linker_ldflags


linker_ldflags = linker_ldflags_tmpl(target)
host_linker_ldflags = linker_ldflags_tmpl(host)


# There's a wrinkle with MinGW: linker configuration is not enabled, so
# `select_linker` is never invoked.  Hard-code around it.
@depends(select_linker, target, c_compiler)
def gcc_use_gnu_ld(select_linker, target, c_compiler):
    if select_linker is not None and target.kernel != "Darwin":
        return select_linker.KIND in ("bfd", "gold", "lld", "mold")
    if target.kernel == "WINNT" and c_compiler.type == "clang":
        return True
    return None


# GCC_USE_GNU_LD=1 means the linker is command line compatible with GNU ld.
set_config("GCC_USE_GNU_LD", gcc_use_gnu_ld)


include("compile-checks.configure")
include("arm.configure", when=depends(target.cpu)(lambda cpu: cpu == "arm"))

# Libstdc++ feature detection
# ==============================================================

with only_when("--enable-debug"):
    using_libstdcxx = try_compile(
        language="C++",
        includes=["new"],
        body="#ifndef __GLIBCXX__\n#error 1\n#endif",
        when=depends(c_compiler)(lambda c: c.type != "clang-cl"),
    )

    libstdcxx_assertions = depends(when=using_libstdcxx)("_GLIBCXX_ASSERTIONS")

    using_libcxx = try_compile(
        language="C++",
        includes=["new"],
        body="#ifndef _LIBCPP_VERSION\n#error 1\n#endif",
        when=~using_libstdcxx,
    )

    @depends(
        try_compile(language="C++", body="#if _LIBCPP_STD_VER >= 18\n#error 1\n#endif"),
        when=using_libcxx,
    )
    def libcxx_assertions(modern_libcxx):
        if modern_libcxx:
            return "_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG"
        else:
            return "_LIBCPP_HARDENING_MODE_DEBUG"

    @depends(
        libstdcxx_assertions,
        libcxx_assertions,
        when=libstdcxx_assertions | libcxx_assertions,
    )
    @checking("which standard c++ library assertions to use")
    def stdlibcxx_assertions(libstdcxx, libcxx):
        return libstdcxx or libcxx


@depends(
    have_64_bit,
    try_compile(
        body='static_assert(sizeof(void *) == 8, "")', check_msg="for 64-bit OS"
    ),
)
def check_have_64_bit(have_64_bit, compiler_have_64_bit):
    if have_64_bit != compiler_have_64_bit:
        configure_error(
            "The target compiler does not agree with configure "
            "about the target bitness."
        )


@depends(cxx_compiler, target)
def needs_libstdcxx_newness_check(cxx_compiler, target):
    # We only have to care about this on Linux and MinGW.
    if cxx_compiler.type == "clang-cl":
        return

    if target.kernel not in ("Linux", "WINNT"):
        return

    if target.os == "Android":
        return

    return True


def die_on_old_libstdcxx():
    die(
        "The libstdc++ in use is not new enough.  Please run "
        "./mach bootstrap to update your compiler, or update your system "
        "libstdc++ installation."
    )


try_compile(
    includes=["cstddef"],
    body="\n".join(
        [
            # _GLIBCXX_RELEASE showed up in libstdc++ 7.
            "#if defined(__GLIBCXX__) && !defined(_GLIBCXX_RELEASE)",
            "#  error libstdc++ not new enough",
            "#endif",
            "#if defined(_GLIBCXX_RELEASE)",
            "#  if _GLIBCXX_RELEASE < %d" % minimum_gcc_version().major,
            "#    error libstdc++ not new enough",
            "#  else",
            "     (void) 0",
            "#  endif",
            "#endif",
        ]
    ),
    check_msg="for new enough STL headers from libstdc++",
    when=needs_libstdcxx_newness_check,
    onerror=die_on_old_libstdcxx,
)


@depends(c_compiler, target)
def default_debug_flags(compiler_info, target):
    # Debug info is ON by default.
    if compiler_info.type == "clang-cl":
        return ("-Z7",)
    elif target.kernel == "WINNT" and compiler_info.type == "clang":
        return ("-g", "-gcodeview")
    # The oldest versions of supported compilers default to DWARF-4, but
    # newer versions may default to DWARF-5 or newer (e.g. clang 14), which
    # Valgrind doesn't support. Force-use DWARF-4.
    return ("-gdwarf-4",)


option(env="MOZ_DEBUG_FLAGS", nargs=1, help="Debug compiler flags")

imply_option("--enable-debug-symbols", depends_if("--enable-debug")(lambda v: v))

option(
    "--disable-debug-symbols",
    nargs="?",
    help="Disable debug symbols using the given compiler flags",
)

set_config("MOZ_DEBUG_SYMBOLS", depends_if("--enable-debug-symbols")(lambda _: True))


@depends("MOZ_DEBUG_FLAGS", "--enable-debug-symbols", default_debug_flags)
@imports(_from="mozbuild.shellutil", _import="split")
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 split(enable_debug_flags[0])
    if env_debug_flags:
        return split(env_debug_flags[0])
    return default_debug_flags


set_config("MOZ_DEBUG_FLAGS", debug_flags)


@depends(c_compiler, host)
@imports(
    _from="mach.logging", _import="enable_blessed", _as="_enable_ansi_escape_codes"
)
def color_cflags(info, host):
    # We could test compiling with flags. By why incur the overhead when
    # color support should always be present in a specific toolchain
    # version?

    # Code for auto-adding this flag to compiler invocations needs to
    # determine if an existing flag isn't already present. That is likely
    # using exact string matching on the returned value. So if the return
    # value changes to e.g. "<x>=always", exact string match may fail and
    # multiple color flags could be added. So examine downstream consumers
    # before adding flags to return values.
    if info.type == "gcc":
        return "-fdiagnostics-color"
    elif info.type in ["clang", "clang-cl"]:
        if host.os == "WINNT" and _enable_ansi_escape_codes():
            return "-fcolor-diagnostics -fansi-escape-codes"
        else:
            return "-fcolor-diagnostics"
    else:
        return ""


set_config("COLOR_CFLAGS", color_cflags)

# Some standard library headers (notably bionic on Android) declare standard
# functions (e.g. getchar()) and also #define macros for those standard
# functions.  libc++ deals with this by doing something like the following
# (explanatory comments added):
#
#   #ifdef FUNC
#   // Capture the definition of FUNC.
#   inline _LIBCPP_INLINE_VISIBILITY int __libcpp_FUNC(...) { return FUNC(...); }
#   #undef FUNC
#   // Use a real inline definition.
#   inline _LIBCPP_INLINE_VISIBILITY int FUNC(...) { return _libcpp_FUNC(...); }
#   #endif
#
# _LIBCPP_INLINE_VISIBILITY is typically defined as:
#
#   __attribute__((__visibility__("hidden"), __always_inline__))
#
# Unfortunately, this interacts badly with our system header wrappers, as the:
#
#   #pragma GCC visibility push(default)
#
# that they do prior to including the actual system header is treated by the
# compiler as an explicit declaration of visibility on every function declared
# in the header.  Therefore, when the libc++ code above is encountered, it is
# as though the compiler has effectively seen:
#
#   int FUNC(...) __attribute__((__visibility__("default")));
#   int FUNC(...) __attribute__((__visibility__("hidden")));
#
# and the compiler complains about the mismatched visibility declarations.
#
# However, libc++ will only define _LIBCPP_INLINE_VISIBILITY if there is no
# existing definition.  We can therefore define it to the empty string (since
# we are properly managing visibility ourselves) and avoid this whole mess.
# Note that we don't need to do this with gcc, as libc++ detects gcc and
# effectively does the same thing we are doing here.
#
# _LIBCPP_ALWAYS_INLINE needs a similar workarounds, since it too declares
# hidden visibility.
#
# _LIBCPP_HIDE_FROM_ABI is a macro in libc++ versions in NDKs >=r19.  It too
# declares hidden visibility, but it also declares functions as excluded from
# explicit instantiation (roughly: the function can be unused in the current
# compilation, but does not then trigger an actual definition of the function;
# it is assumed the real definition comes from elsewhere).  We need to replicate
# this setup.


@depends(c_compiler, target)
def libcxx_override_visibility(c_compiler, target):
    if c_compiler.type == "clang" and target.os == "Android":
        return namespace(
            empty="",
            hide_from_abi="__attribute__((__exclude_from_explicit_instantiation__))",
        )


set_define("_LIBCPP_INLINE_VISIBILITY", libcxx_override_visibility.empty)
set_define("_LIBCPP_ALWAYS_INLINE", libcxx_override_visibility.empty)

set_define("_LIBCPP_HIDE_FROM_ABI", libcxx_override_visibility.hide_from_abi)


@depends(target, build_environment)
def visibility_flags(target, env):
    if target.os != "WINNT":
        if target.kernel == "Darwin":
            return ("-fvisibility=hidden", "-fvisibility-inlines-hidden")
        return (
            "-I%s/system_wrappers" % os.path.join(env.dist),
            "-include",
            "%s/config/gcc_hidden.h" % env.topsrcdir,
        )


@depends(target, visibility_flags)
def wrap_system_includes(target, visibility_flags):
    if visibility_flags and target.kernel != "Darwin":
        return True


set_define(
    "HAVE_VISIBILITY_HIDDEN_ATTRIBUTE",
    depends(visibility_flags)(lambda v: bool(v) or None),
)
set_define(
    "HAVE_VISIBILITY_ATTRIBUTE", depends(visibility_flags)(lambda v: bool(v) or None)
)
set_config("WRAP_SYSTEM_INCLUDES", wrap_system_includes)
set_config("VISIBILITY_FLAGS", visibility_flags)


# try harder, when checking for __thread support, see bug 521750 comment #33 and below
# We pass linker_optimize_flags to the linker because if dead_strip is
# enabled, the linker in xcode 4.1 will crash. Without this it would crash when
# linking XUL.


@depends(target, c_compiler)
def check_thread(target, c_compiler):
    if target.cpu in ("mips32", "mips64"):
        # mips builds fail with TLS variables because of a binutils bug.
        # See bug 528687
        return False
    if target.os == "Android":
        # The custom dynamic linker doesn't support TLS variables
        return False
    if target.kernel == "OpenBSD":
        # OpenBSD doesn't have TLS support, and the test succeeds with clang++
        return False
    return c_compiler.type != "clang-cl"


set_define(
    "HAVE_THREAD_TLS_KEYWORD",
    try_link(
        body="static __thread bool tlsIsMainThread = false; return tlsIsMainThread;",
        flags=linker_optimize_flags.ldflags,
        check_msg="for __thread keyword for TLS variables",
        when=check_thread,
    ),
)


@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)


# 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")),
)

# TARGET_XPCOM_ABI
# ==============================================================

is_arm_eabi = c_compiler.try_compile(
    body="""
        #if defined(__ARM_EABI__)
          return 0;
        #else
        #error Not ARM EABI.
        #endif""",
    check_msg="for ARM EABI",
    when=building_with_gnu_compatible_cc
    & depends(target.cpu)(lambda cpu: cpu == "arm"),
)


@depends(target, is_arm_eabi, c_compiler)
def target_xpcom_abi(target, is_arm_eabi, compiler):
    if compiler.type == "clang-cl":
        return f"{target.cpu}-msvc"
    elif target.cpu == "arm":
        target_compiler_abi = "eabi" if is_arm_eabi else "oabi"
        return f"{target.cpu}-{target_compiler_abi}-gcc3"
    else:
        return f"{target.cpu}-gcc3"


set_config("TARGET_XPCOM_ABI", target_xpcom_abi)
set_define("TARGET_XPCOM_ABI", depends(target_xpcom_abi)(lambda v: f'"{v}"'))

# ASAN
# ==============================================================

option("--enable-address-sanitizer", help="Enable Address Sanitizer")


@depends(when="--enable-address-sanitizer")
def asan():
    return True


with only_when(asan):
    option(
        env="MOZ_CLANG_RT_ASAN_LIB_PATH",
        nargs=1,
        help="Path to clang runtime asan library",
    )

    @depends(
        c_compiler,
        target,
        "MOZ_CLANG_RT_ASAN_LIB_PATH",
    )
    @imports("os")
    @imports("glob")
    def clang_rt_asan_lib_path(c_compiler, target, clang_rt_asan_lib):
        if clang_rt_asan_lib:
            if os.path.exists(clang_rt_asan_lib[0]):
                return clang_rt_asan_lib[0]
            else:
                die(
                    f"Specified MOZ_CLANG_RT_ASAN_LIB_PATH value '{clang_rt_asan_lib}' doesn't exist. "
                )

        # Look for the ASan runtime binary
        if c_compiler.type == "clang-cl":
            cpu = {"x86": "i386"}.get(target.cpu, target.cpu)
            clang_rt_asan_lib = f"clang_rt.asan_dynamic-{cpu}.dll"
            subdir = "windows"
        elif target.os == "Android":
            cpu = {"x86": "i686"}.get(target.cpu, target.cpu)
            clang_rt_asan_lib = f"libclang_rt.asan-{cpu}-android.so"
            subdir = "linux"
        else:
            return

        search_path = os.path.join(
            os.path.dirname(c_compiler.compiler),
            "..",
            "lib",
            "clang",
            "*",
            "lib",
            subdir,
            clang_rt_asan_lib,
        )
        if candidates := glob.glob(search_path):
            return candidates[0]

        die(
            f"Couldn't find {clang_rt_asan_lib}. "
            f"It should be available in the same location as {c_compiler.type}."
        )

    set_config("MOZ_CLANG_RT_ASAN_LIB_PATH", clang_rt_asan_lib_path)


@depends(
    c_compiler,
    target,
    compilation_flags,
    linker_flags,
    build_environment,
    when=asan,
)
def asan_flags(c_compiler, target, compilation_flags, linker_flags, build_env):
    if c_compiler.type == "clang-cl":
        # Suppressing errors in recompiled code.
        if target.os == "WINNT":
            flag = f"-fsanitize-blacklist={build_env.topsrcdir}/build/sanitizers/asan_blacklist_win.txt"
            compilation_flags.cflags.append(flag)
            compilation_flags.cxxflags.append(flag)

    asan_flag = "-fsanitize=address"
    compilation_flags.cflags.append(asan_flag)
    compilation_flags.cxxflags.append(asan_flag)

    if c_compiler.type != "clang-cl":
        linker_flags.ldflags.extend([asan_flag, "-rdynamic"])


set_define("MOZ_ASAN", True, when=asan)
set_config("MOZ_ASAN", True, when=asan)

# MSAN
# ==============================================================

option("--enable-memory-sanitizer", help="Enable Memory Sanitizer")


@depends(when="--enable-memory-sanitizer")
def msan():
    return True


@depends(c_compiler, compilation_flags, linker_flags, when=msan)
def msan_flags(c_compiler, compilation_flags, linker_flags):
    flags = ["-fsanitize=memory", "-fsanitize-memory-track-origins"]
    compilation_flags.cflags.extend(flags)
    compilation_flags.cxxflags.extend(flags)
    if c_compiler.type != "clang-cl":
        linker_flags.ldflags.extend(flags + ["-rdynamic"])


set_define("MOZ_MSAN", True, when=msan)
set_config("MOZ_MSAN", True, when=msan)

# TSAN
# ==============================================================

option("--enable-thread-sanitizer", help="Enable Thread Sanitizer")


@depends(when="--enable-thread-sanitizer")
def tsan():
    return True


@depends(c_compiler, compilation_flags, linker_flags, when=tsan)
def tsan_flags(c_compiler, compilation_flags, linker_flags):
    flag = "-fsanitize=thread"
    compilation_flags.cflags.append(flag)
    compilation_flags.cxxflags.append(flag)
    if c_compiler.type != "clang-cl":
        linker_flags.ldflags.extend(["-fsanitize=thread", "-rdynamic"])


set_define("MOZ_TSAN", True, when=tsan)
set_config("MOZ_TSAN", True, when=tsan)

# UBSAN
# ==============================================================

option(
    "--enable-undefined-sanitizer", nargs="*", help="Enable UndefinedBehavior Sanitizer"
)


@depends("--enable-undefined-sanitizer", moz_optimize)
def ubsan(options, optimize):
    if not options:
        return

    default_checks = [
        "bool",
        "bounds",
        "enum",
        "function",
        "integer-divide-by-zero",
        "pointer-overflow",
        "return",
        "vla-bound",
    ]

    # adding object-size generates a warning if -O0 is set
    if optimize:
        default_checks.append("object-size")

    checks = options if len(options) else default_checks

    return checks


@depends(
    ubsan, c_compiler, compilation_flags, linker_flags, build_environment, when=ubsan
)
@imports(_from="__builtin__", _import="open")
@imports(_from="glob", _import="glob")
@imports("shutil")
def ubsan_flags(ubsan_checks, c_compiler, compilation_flags, linker_flags, build_env):
    ubsan_txt = os.path.join(build_env.topobjdir, "ubsan_blacklist.txt")
    with open(ubsan_txt, "w") as out_fd:
        for blacklist in glob(
            os.path.join(
                build_env.topsrcdir, "build", "sanitizers", "ubsan_*_blacklist.txt"
            )
        ):
            with open(blacklist) as in_fd:
                shutil.copyfileobj(in_fd, out_fd)

    joined_ubsan_checks = ",".join(ubsan_checks)

    flags = [
        f"-fsanitize={joined_ubsan_checks}",
        f"-fno-sanitize-recover={joined_ubsan_checks}",
        f"-fsanitize-blacklist={ubsan_txt}",
    ]
    compilation_flags.cflags.extend(flags)
    compilation_flags.cxxflags.extend(flags)
    if c_compiler.type != "clang-cl":
        linker_flags.ldflags.extend(["-fsanitize=undefined", "-rdynamic"])


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


@depends(
    c_compiler,
    compilation_flags,
    linker_flags,
    build_environment,
    when=ub_signed_overflow_san,
)
def ub_signed_overflow_san_flags(
    c_compiler, compilation_flags, linker_flags, build_env
):
    sanitizer_blacklist = os.path.join(
        build_env.topsrcdir,
        "build",
        "sanitizers",
        "ubsan_signed_overflow_blacklist.txt",
    )
    flags = [
        f"-fsanitize=signed-integer-overflow",
        f"-fsanitize-blacklist={sanitizer_blacklist}",
    ]
    compilation_flags.cflags.extend(flags)
    compilation_flags.cxxflags.extend(flags)
    if c_compiler.type != "clang-cl":
        linker_flags.ldflags.extend(["-fsanitize=signed-integer-overflow", "-rdynamic"])


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


@depends(
    c_compiler,
    compilation_flags,
    linker_flags,
    build_environment,
    when=ub_unsigned_overflow_san,
)
def ub_unsigned_overflow_san_flags(
    c_compiler, compilation_flags, linker_flags, build_env
):
    sanitizer_blacklist = os.path.join(
        build_env.topsrcdir,
        "build",
        "sanitizers",
        "ubsan_unsigned_overflow_blacklist.txt",
    )
    flags = [
        f"-fsanitize=unsigned-integer-overflow",
        f"-fsanitize-blacklist={sanitizer_blacklist}",
    ]
    compilation_flags.cflags.extend(flags)
    compilation_flags.cxxflags.extend(flags)
    if c_compiler.type != "clang-cl":
        linker_flags.ldflags.extend(
            ["-fsanitize=unsigned-integer-overflow", "-rdynamic"]
        )


#

any_ubsan = ubsan | ub_signed_overflow_san | ub_unsigned_overflow_san
set_define("MOZ_UBSAN", True, when=any_ubsan)
set_config("MOZ_UBSAN", any_ubsan)


# We only want to include windows.configure when we are compiling on
# Windows, or for Windows.
include("windows.configure", when=is_windows)

# 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,
    stdlibcxx_assertions,
)
def security_hardening_cflags(
    hardening_flag, asan, debug, optimize, c_compiler, target, cxx_assert
):
    compiler_is_gccish = c_compiler.type in ("gcc", "clang")
    mingw_clang = c_compiler.type == "clang" and target.os == "WINNT"

    flags = []
    ldflags = []
    trivial_auto_var_init = []

    # WASI compiler doesn't support security hardening cflags
    if target.os == "WASI":
        return

    # ----------------------------------------------------------
    # If hardening is explicitly enabled, or not explicitly disabled
    if hardening_flag.origin == "default" or hardening_flag:
        # FORTIFY_SOURCE ------------------------------------
        # Require optimization for FORTIFY_SOURCE. See Bug 1417452
        # Also, undefine it before defining it just in case a distro adds it, see Bug 1418398
        if compiler_is_gccish and optimize and not asan:
            flags.append("-U_FORTIFY_SOURCE")
            flags.append("-D_FORTIFY_SOURCE=2")

        # Lib c++ debug mode
        if cxx_assert:
            flags.append(f"-D{cxx_assert}")

        # fstack-protector ------------------------------------
        # Enable only if hardening is not disabled and ASAN is
        # not on as ASAN will catch the crashes for us
        if compiler_is_gccish and not asan:
            flags.append("-fstack-protector-strong")
            ldflags.append("-fstack-protector-strong")

            if (
                c_compiler.type == "clang"
                and c_compiler.version >= "11.0.1"
                and target.os not in ("WINNT", "OSX", "OpenBSD")
                and target.cpu in ("x86", "x86_64", "ppc64", "s390x")
            ):
                flags.append("-fstack-clash-protection")
                ldflags.append("-fstack-clash-protection")

        # ftrivial-auto-var-init ------------------------------
        # Initialize local variables with a 0xAA pattern in clang builds.
        # Linux32 fails some xpcshell tests with -ftrivial-auto-var-init
        linux32 = target.kernel == "Linux" and target.cpu == "x86"
        if (
            (c_compiler.type == "clang" or c_compiler.type == "clang-cl")
            and c_compiler.version >= "8"
            and not linux32
        ):
            if c_compiler.type == "clang-cl":
                trivial_auto_var_init.append("-Xclang")
            trivial_auto_var_init.append("-ftrivial-auto-var-init=pattern")
            # Always enable on debug builds.
            if debug:
                flags.extend(trivial_auto_var_init)

        if (c_compiler.type == "clang" and c_compiler.version >= "16") or (
            c_compiler.type == "gcc" and c_compiler.version >= "13"
        ):
            # Cannot use level 3 because we have many uses of the [0] GNU syntax.
            # Cannot use level 2 because sqlite3 and icu use the [1] GNU syntax.
            flags.append("-fstrict-flex-arrays=1")

        # ASLR ------------------------------------------------
        # ASLR (dynamicbase) is enabled by default in clang-cl; but the
        # mingw-clang build requires it to be explicitly enabled
        if mingw_clang:
            ldflags.append("-Wl,--dynamicbase")

        # Control Flow Guard (CFG) ----------------------------
        if (
            c_compiler.type == "clang-cl"
            and c_compiler.version >= "8"
            and (target.cpu != "aarch64" or c_compiler.version >= "8.0.1")
        ):
            if target.cpu == "aarch64" and c_compiler.version >= "10.0.0":
                # The added checks in clang 10 make arm64 builds crash. (Bug 1639318)
                flags.append("-guard:cf,nochecks")
            else:
                flags.append("-guard:cf")
            # nolongjmp is needed because clang doesn't emit the CFG tables of
            # setjmp return addresses https://bugs.llvm.org/show_bug.cgi?id=40057
            ldflags.append("-guard:cf,nolongjmp")

    # ----------------------------------------------------------
    # If ASAN _is_ on, disable FORTIFY_SOURCE just to be safe
    if asan:
        flags.append("-D_FORTIFY_SOURCE=0")

    # fno-common -----------------------------------------
    # Do not merge variables for ASAN; can detect some subtle bugs
    if asan:
        # clang-cl does not recognize the flag, it must be passed down to clang
        if c_compiler.type == "clang-cl":
            flags.append("-Xclang")
        flags.append("-fno-common")

    return namespace(
        flags=flags,
        ldflags=ldflags,
        trivial_auto_var_init=trivial_auto_var_init,
    )


set_config("MOZ_HARDENING_CFLAGS", security_hardening_cflags.flags)
set_config("MOZ_HARDENING_LDFLAGS", security_hardening_cflags.ldflags)
set_config(
    "MOZ_TRIVIAL_AUTO_VAR_INIT",
    security_hardening_cflags.trivial_auto_var_init,
)


# Intel Control-flow Enforcement Technology
# ==============================================================
# We keep this separate from the hardening flags above, because we want to be
# able to easily remove the flags in the build files for certain executables.
@depends(c_compiler, target)
def cet_ldflags(c_compiler, target):
    ldflags = []
    if (
        c_compiler.type == "clang-cl"
        and c_compiler.version >= "11"
        and target.cpu == "x86_64"
    ):
        ldflags.append("-CETCOMPAT")
    return ldflags


set_config("MOZ_CETCOMPAT_LDFLAGS", cet_ldflags)


# Frame pointers
# ==============================================================
@depends(c_compiler)
def frame_pointer_flags(compiler):
    if compiler.type == "clang-cl":
        return namespace(
            enable=["-Oy-"],
            disable=["-Oy"],
        )
    return namespace(
        enable=["-fno-omit-frame-pointer", "-funwind-tables"],
        disable=["-fomit-frame-pointer", "-funwind-tables"],
    )


@depends(
    moz_optimize,
    moz_debug,
    target,
    "--enable-memory-sanitizer",
    "--enable-address-sanitizer",
    "--enable-undefined-sanitizer",
)
def frame_pointer_default(optimize, debug, target, msan, asan, ubsan):
    return bool(
        not optimize
        or debug
        or msan
        or asan
        or ubsan
        or (target.os == "WINNT" and target.cpu in ("x86", "aarch64"))
        or target.os == "OSX"
    )


option(
    "--enable-frame-pointers",
    default=frame_pointer_default,
    help="{Enable|Disable} frame pointers",
)


@depends("--enable-frame-pointers", frame_pointer_flags)
def frame_pointer_flags(enable, flags):
    if enable:
        return flags.enable
    return flags.disable


set_config("MOZ_FRAMEPTR_FLAGS", frame_pointer_flags)


# Stack unwinding without frame pointers
# ==============================================================


have_unwind = check_symbol(
    "_Unwind_Backtrace", when=check_header("unwind.h", when=target_is_unix)
)


# Code Coverage
# ==============================================================

option("--enable-coverage", env="MOZ_CODE_COVERAGE", help="Enable code coverage")


@depends("--enable-coverage")
def code_coverage(value):
    if value:
        return True


set_config("MOZ_CODE_COVERAGE", code_coverage)
set_define("MOZ_CODE_COVERAGE", code_coverage)


@depends(target, c_compiler, build_environment, when=code_coverage)
@imports("os")
@imports("re")
@imports(_from="__builtin__", _import="open")
def coverage_cflags(target, c_compiler, build_env):
    cflags = ["--coverage"]

    # clang 11 no longer accepts this flag (its behavior became the default)
    if c_compiler.type in ("clang", "clang-cl") and c_compiler.version < "11.0.0":
        cflags += [
            "-Xclang",
            "-coverage-no-function-names-in-data",
        ]

    exclude = []
    if target.os == "WINNT" and c_compiler.type == "clang-cl":
        # VS files
        exclude.append("^.*[vV][sS]20[0-9]{2}.*$")
        # Files in fetches directory.
        exclude.append("^.*[\\\\/]fetches[\\\\/].*$")
    elif target.os == "OSX":
        # Files in fetches directory.
        exclude.append("^.*/fetches/.*$")
    elif target.os == "GNU":
        # Files in fetches directory.
        exclude.append("^.*/fetches/.*$")
        # Files in /usr/
        exclude.append("^/usr/.*$")

    if exclude:
        exclude = ";".join(exclude)
        cflags += [
            f"-fprofile-exclude-files={exclude}",
        ]

    response_file_path = os.path.join(build_env.topobjdir, "code_coverage_cflags")

    with open(response_file_path, "w") as f:
        f.write(" ".join(cflags))

    return ["@{}".format(response_file_path)]


set_config("COVERAGE_CFLAGS", coverage_cflags)

# Assembler detection
# ==============================================================

option(env="AS", nargs=1, help="Path to the assembler")


@depends(target, c_compiler)
def as_info(target, c_compiler):
    if c_compiler.type == "clang-cl":
        ml = {
            "x86": "ml.exe",
            "x86_64": "ml64.exe",
            "aarch64": "armasm64.exe",
        }.get(target.cpu)
        return namespace(type="masm", names=(ml,))
    # When building with anything but clang-cl, we just use the C compiler as the assembler.
    return namespace(type="gcc", names=(c_compiler.compiler,))


# One would expect the assembler to be specified merely as a program.  But in
# cases where the assembler is passed down into js/, it can be specified in
# the same way as CC: a program + a list of argument flags.  We might as well
# permit the same behavior in general, even though it seems somewhat unusual.
# So we have to do the same sort of dance as we did above with
# `provided_compiler`.
provided_assembler = provided_program("AS")
assembler = check_prog(
    "_AS",
    input=provided_assembler.program,
    what="the assembler",
    progs=as_info.names,
    paths=vc_toolchain_search_path,
)


@depends(as_info, assembler, provided_assembler, c_compiler)
def as_with_flags(as_info, assembler, provided_assembler, c_compiler):
    if provided_assembler:
        return provided_assembler.wrapper + [assembler] + provided_assembler.flags

    if as_info.type == "masm":
        return assembler

    assert as_info.type == "gcc"

    # Need to add compiler wrappers and flags as appropriate.
    return c_compiler.wrapper + [assembler] + c_compiler.flags


set_config("AS", as_with_flags)


@depends(assembler, c_compiler, extra_toolchain_flags)
@imports("subprocess")
@imports(_from="os", _import="devnull")
def gnu_as(assembler, c_compiler, toolchain_flags):
    # clang uses a compatible GNU assembler.
    if c_compiler.type == "clang":
        return True

    if c_compiler.type == "gcc":
        cmd = [assembler] + c_compiler.flags
        if toolchain_flags:
            cmd += toolchain_flags
        cmd += ["-Wa,--version", "-c", "-o", devnull, "-x", "assembler", "-"]
        # We don't actually have to provide any input on stdin, `Popen.communicate` will
        # close the stdin pipe.
        # clang will error if it uses its integrated assembler for this target,
        # so handle failures gracefully.
        if "GNU" in check_cmd_output(*cmd, stdin=subprocess.PIPE, onerror=lambda: ""):
            return True


set_config("GNU_AS", gnu_as)


@depends(as_info, target)
def as_dash_c_flag(as_info, target):
    # armasm64 doesn't understand -c.
    if as_info.type == "masm" and target.cpu == "aarch64":
        return ""
    else:
        return "-c"


set_config("AS_DASH_C_FLAG", as_dash_c_flag)


@depends(as_info, target)
def as_outoption(as_info, target):
    # The uses of ASOUTOPTION depend on the spacing for -o/-Fo.
    if as_info.type == "masm" and target.cpu != "aarch64":
        return "-Fo"

    return "-o "


set_config("ASOUTOPTION", as_outoption)

# clang plugin handling
# ==============================================================

option(
    "--enable-clang-plugin",
    env="ENABLE_CLANG_PLUGIN",
    help="Enable building with the Clang plugin (gecko specific static analyzers)",
)


set_config("ENABLE_CLANG_PLUGIN", True, when="--enable-clang-plugin")
set_define("MOZ_CLANG_PLUGIN", True, when="--enable-clang-plugin")


@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,
)


@template
def llvm_tool(name):
    @depends(host_c_compiler, c_compiler, bindgen_config_paths)
    def llvm_tool(host_c_compiler, c_compiler, bindgen_config_paths):
        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 and bindgen_config_paths:
            clang = bindgen_config_paths.clang_path
        tool = name
        if clang:
            out = check_cmd_output(
                clang, "--print-prog-name=%s" % tool, onerror=lambda: None
            )
            if out:
                tool = out.rstrip()
        return (tool,)

    return llvm_tool


llvm_objdump = check_prog(
    "LLVM_OBJDUMP",
    llvm_tool("llvm-objdump"),
    what="llvm-objdump",
    when="--enable-compile-environment",
    paths=clang_search_path,
)


# Force clang-cl compiler to treat input as C++
# ==============================================================
add_flag("-TP", cxx_compiler, when=target_is_windows & ~building_with_gnu_compatible_cc)

# Use the old libstdc++ ABI
# ==============================================================
add_flag(
    "-D_GLIBCXX_USE_CXX11_ABI=0",
    cxx_compiler,
    when=stdcxx_compat,
)
add_flag(
    "-D_GLIBCXX_USE_CXX11_ABI=0",
    host_cxx_compiler,
    when=stdcxx_compat,
)


# Always included configuration file
# ==============================================================
@depends(c_compiler, build_environment, build_project)
def defines_cpp_flags(c_compiler, build_environment, build_project):
    if build_project == "js":
        config_h = "js/src/js-confdefs.h"
    else:
        config_h = "mozilla-config.h"

    if c_compiler.type == "clang-cl":
        flag = "-FI"
    else:
        flag = "-include"

    return ["-DMOZILLA_CLIENT", flag, f"{build_environment.topobjdir}/{config_h}"]


set_config("OS_COMPILE_CFLAGS", defines_cpp_flags)
set_config("OS_COMPILE_CXXFLAGS", defines_cpp_flags)


# Support various fuzzing options
# ==============================================================
option("--enable-fuzzing", help="Enable fuzzing support")


@depends(build_project)
def js_build(build_project):
    return build_project == "js"


option(
    "--enable-js-fuzzilli",
    when=js_build,
    help="Enable fuzzilli support for the JS engine",
)


option(
    "--enable-snapshot-fuzzing",
    help="Enable experimental snapshot fuzzing support",
)


imply_option("--enable-fuzzing", True, when="--enable-snapshot-fuzzing")


@depends("--enable-snapshot-fuzzing")
def enable_snapshot_fuzzing(value):
    if value:
        return True


@depends("--enable-fuzzing", enable_snapshot_fuzzing)
def enable_fuzzing(value, snapshot_fuzzing):
    if value or snapshot_fuzzing:
        return True


@depends("--enable-js-fuzzilli", when=js_build)
def enable_js_fuzzilli(value):
    if value:
        return True


@depends(enable_fuzzing, enable_snapshot_fuzzing)
def check_aflfuzzer(fuzzing, snapshot_fuzzing):
    if fuzzing and not snapshot_fuzzing:
        return True


@depends(
    try_compile(
        body="__AFL_COMPILER;", check_msg="for AFL compiler", when=check_aflfuzzer
    )
)
def enable_aflfuzzer(afl):
    if afl:
        return True


@depends(enable_fuzzing, enable_aflfuzzer, enable_snapshot_fuzzing, c_compiler, target)
def enable_libfuzzer(fuzzing, afl, snapshot_fuzzing, c_compiler, target):
    if (
        fuzzing
        and not afl
        and not snapshot_fuzzing
        and c_compiler.type == "clang"
        and target.os != "Android"
    ):
        return True


@depends(enable_fuzzing, enable_aflfuzzer, enable_libfuzzer, enable_js_fuzzilli)
def enable_fuzzing_interfaces(fuzzing, afl, libfuzzer, enable_js_fuzzilli):
    if fuzzing and (afl or libfuzzer) and not enable_js_fuzzilli:
        return True


set_config("FUZZING", enable_fuzzing)
set_define("FUZZING", enable_fuzzing)

set_config("LIBFUZZER", enable_libfuzzer)
set_define("LIBFUZZER", enable_libfuzzer)

set_config("AFLFUZZ", enable_aflfuzzer)
set_define("AFLFUZZ", enable_aflfuzzer)

set_config("FUZZING_INTERFACES", enable_fuzzing_interfaces)
set_define("FUZZING_INTERFACES", enable_fuzzing_interfaces)

set_config("FUZZING_JS_FUZZILLI", enable_js_fuzzilli)
set_define("FUZZING_JS_FUZZILLI", enable_js_fuzzilli)

set_config("FUZZING_SNAPSHOT", enable_snapshot_fuzzing)
set_define("FUZZING_SNAPSHOT", enable_snapshot_fuzzing)


@depends(
    c_compiler.try_compile(
        flags=["-fsanitize=fuzzer-no-link"],
        when=enable_fuzzing,
        check_msg="whether the C compiler supports -fsanitize=fuzzer-no-link",
    ),
    tsan,
    enable_js_fuzzilli,
)
def libfuzzer_flags(value, tsan, enable_js_fuzzilli):
    if tsan:
        # With ThreadSanitizer, we should not use any libFuzzer instrumentation because
        # it is incompatible (e.g. there are races on global sanitizer coverage counters).
        # Instead we use an empty set of flags here but still build the fuzzing targets.
        # With this setup, we can still run files through these targets in TSan builds,
        # e.g. those obtained from regular fuzzing.
        # This code can be removed once libFuzzer has been made compatible with TSan.
        #
        # Also, this code needs to be kept in sync with certain gyp files, currently:
        #   - dom/media/webrtc/transport/third_party/nICEr/nicer.gyp
        return namespace(no_link_flag_supported=False, use_flags=[])

    if enable_js_fuzzilli:
        # Fuzzilli comes with its own trace-pc interceptors and flag requirements.
        no_link_flag_supported = False
        use_flags = ["-fsanitize-coverage=trace-pc-guard", "-g"]
    elif value:
        no_link_flag_supported = True
        # recommended for (and only supported by) clang >= 6
        use_flags = ["-fsanitize=fuzzer-no-link"]
    else:
        no_link_flag_supported = False
        use_flags = ["-fsanitize-coverage=trace-pc-guard,trace-cmp"]

    return namespace(
        no_link_flag_supported=no_link_flag_supported,
        use_flags=use_flags,
    )


@depends(libfuzzer_flags, when=enable_libfuzzer)
def sancov(libfuzzer_flags):
    return any(
        flag.startswith(head)
        for head in ("-fsanitize-coverage", "-fsanitize=fuzzer")
        for flag in libfuzzer_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)


# Required for stand-alone (sanitizer-less) libFuzzer.
# ==============================================================
@depends(libfuzzer_flags, linker_flags, when=enable_libfuzzer)
def add_libfuzzer_flags(libfuzzer_flags, linker_flags):
    linker_flags.ldflags.extend(libfuzzer_flags.use_flags)
    linker_flags.ldflags.append("-rdynamic")


# The LLVM symbolizer is used by all sanitizers
check_prog(
    "LLVM_SYMBOLIZER",
    ("llvm-symbolizer",),
    allow_missing=True,
    paths=clang_search_path,
    when=asan | msan | tsan | any_ubsan | enable_fuzzing,
)


# 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 = ["$(DSO_LDOPTS)"]
    output = ["-o", "$@"]

    if target.kernel == "Darwin":
        soname = []
    elif target.os == "NetBSD":
        soname = ["-Wl,-soname,$(DSO_SONAME)"]
    else:
        assert compiler.type in ("gcc", "clang")

        soname = ["-Wl,-h,$(DSO_SONAME)"]

    return namespace(
        mkshlib=cxx + flags + soname + output,
        mkcshlib=cc + flags + soname + output,
    )


set_config("MKSHLIB", make_shared_library.mkshlib)
set_config("MKCSHLIB", make_shared_library.mkcshlib)


@depends(c_compiler, toolchain_prefix, when=target_is_windows)
def rc_names(c_compiler, toolchain_prefix):
    if c_compiler.type in ("gcc", "clang"):
        return tuple("%s%s" % (p, "windres") for p in ("",) + (toolchain_prefix or ()))
    return ("llvm-rc",)


check_prog("RC", rc_names, paths=clang_search_path, when=target_is_windows)


@template
def ar_config(c_compiler, toolchain_prefix=None):
    if not toolchain_prefix:
        toolchain_prefix = dependable(None)

    @depends(toolchain_prefix, c_compiler)
    def ar_config(toolchain_prefix, c_compiler):
        if c_compiler.type == "clang-cl":
            return namespace(
                names=("llvm-lib",),
                flags=("-llvmlibthin", "-out:$@"),
            )

        names = tuple("%s%s" % (p, "ar") for p in (toolchain_prefix or ()) + ("",))
        if c_compiler.type == "clang":
            # Get the llvm-ar path as per the output from clang --print-prog-name=llvm-ar
            # so that we directly get the one under the clang directory, rather than one
            # that might be in /usr/bin and that might point to one from a different version
            # of clang.
            out = check_cmd_output(
                c_compiler.compiler, "--print-prog-name=llvm-ar", onerror=lambda: None
            )
            llvm_ar = out.rstrip() if out else "llvm-ar"
            names = (llvm_ar,) + names

        return namespace(
            names=names,
            flags=("crs", "$@"),
        )

    return ar_config


target_ar_config = ar_config(c_compiler, toolchain_prefix)

target_ar = check_prog("AR", target_ar_config.names, paths=clang_search_path)

set_config("AR_FLAGS", target_ar_config.flags)


@depends(c_compiler, extra_toolchain_flags, target_ar, target_ar_config)
@checking("whether ar supports response files")
@imports("os")
@imports(_from="__builtin__", _import="FileNotFoundError")
@imports(_from="__builtin__", _import="open")
@imports(_from="mozbuild.configure.util", _import="LineIO")
def ar_supports_response_files(c_compiler, extra_toolchain_flags, ar, ar_config):
    lib_path = list_path = None
    with create_temporary_file(suffix=".o") as obj_path:
        if (
            try_invoke_compiler(
                # No configure_cache because it would not create the
                # expected output file.
                None,
                [c_compiler.compiler] + c_compiler.flags,
                c_compiler.language,
                "void foo() {}",
                ["-c", "-o", obj_path] + (extra_toolchain_flags or []),
                wrapper=c_compiler.wrapper,
                onerror=lambda: None,
            )
            is not None
        ):
            with create_temporary_file(suffix=".list") as list_path:
                with open(list_path, "w") as fd:
                    fd.write(obj_path)

            log.debug("Creating `%s` with content:", list_path)
            log.debug("| %s", obj_path)

            with create_temporary_file(suffix=".a") as lib_path:
                os.remove(lib_path)
                ar_command = (
                    [ar]
                    + [x.replace("$@", lib_path) for x in ar_config.flags]
                    + ["@" + list_path]
                )
                result = check_cmd_output(*ar_command, onerror=lambda: None)
                return result is not None


set_config("AR_SUPPORTS_RESPONSE_FILE", True, when=ar_supports_response_files)

host_ar_config = ar_config(host_c_compiler)

check_prog("HOST_AR", host_ar_config.names, paths=clang_search_path)


@depends(toolchain_prefix, c_compiler)
def nm_names(toolchain_prefix, c_compiler):
    names = tuple("%s%s" % (p, "nm") for p in (toolchain_prefix or ()) + ("",))
    if c_compiler.type == "clang":
        # Get the llvm-nm path as per the output from clang --print-prog-name=llvm-nm
        # so that we directly get the one under the clang directory, rather than one
        # that might be in /usr/bin and that might point to one from a different version
        # of clang.
        out = check_cmd_output(
            c_compiler.compiler, "--print-prog-name=llvm-nm", onerror=lambda: None
        )
        llvm_nm = out.rstrip() if out else "llvm-nm"
        names = (llvm_nm,) + names

    return names


check_prog("NM", nm_names, paths=clang_search_path, when=target_has_linux_kernel)


# We don't use it in the code, but it can be useful for debugging, so give
# the user the option of enabling it.
option("--enable-cpp-rtti", help="Enable C++ RTTI")


@depends(compilation_flags, c_compiler, "--enable-cpp-rtti")
def enable_cpp_rtti(compilation_flags, c_compiler, enable_rtti):
    if enable_rtti:
        return
    if c_compiler.type == "clang-cl":
        compilation_flags.cxxflags.append("-GR-")
    else:
        compilation_flags.cxxflags.append("-fno-rtti")


option(
    "--enable-path-remapping",
    nargs="*",
    choices=("c", "rust"),
    help="Enable remapping source and object paths in compiled outputs",
)


@depends("--enable-path-remapping")
def path_remapping(value):
    if len(value):
        return value
    if bool(value):
        return ["c", "rust"]
    return []


@depends(
    target,
    build_environment,
    target_sysroot.path,
    valid_windows_sdk_dir,
    vc_path,
    when="--enable-path-remapping",
)
def path_remappings(target, build_env, sysroot_path, windows_sdk_dir, vc_path):
    win = target.kernel == "WINNT"

    # The prefix maps are processed in the order they're specified on the
    # command line.  Therefore, to accommodate object directories in the source
    # directory, it's important that we map the topobjdir before the topsrcdir,
    # 'cuz we might have /src/obj/=/o/ and /src/=/s/.  The various other
    # directories might be subdirectories of topsrcdir as well, so they come
    # earlier still.

    path_remappings = []

    # We will have only one sysroot or SDK, so all can have the same mnemonic: K
    # for "kit" (since S is taken for "source").  See
    # https://blog.llvm.org/2019/11/deterministic-builds-with-clang-and-lld.html
    # for how to use the Windows `subst` command to map these in debuggers and
    # IDEs.
    if sysroot_path:
        path_remappings.append((sysroot_path, "k:/" if win else "/sysroot/"))
    if windows_sdk_dir:
        path_remappings.append(
            (windows_sdk_dir.path, "k:/" if win else "/windows_sdk/")
        )
    if vc_path:
        path_remappings.append((vc_path, "v:/" if win else "/vc/"))

    path_remappings += [
        (build_env.topobjdir, "o:/" if win else "/topobjdir/"),
        (build_env.topsrcdir, "s:/" if win else "/topsrcdir/"),
    ]

    path_remappings = [
        (normsep(old).rstrip("/") + "/", new) for old, new in path_remappings
    ]

    # It is tempting to sort these, but we want the order to be the same across
    # machines so that we can share cache hits.  Therefore we reject bad
    # configurations rather than trying to make the configuration good.
    for i in range(len(path_remappings) - 1):
        p = path_remappings[i][0]
        for q, _ in path_remappings[i + 1 :]:
            if q.startswith(p):
                die(f"Cannot remap paths because {p} is an ancestor of {q}")

    return path_remappings


@depends(target)
def is_intel_target(target):
    return target.cpu in ("x86", "x86_64")


@depends(target)
def is_aarch64_target(target):
    return target.cpu == "aarch64"


set_config("MMX_FLAGS", ["-mmmx"])
set_config("SSE_FLAGS", ["-msse"])
set_config("SSE2_FLAGS", ["-msse2"])
set_config("SSSE3_FLAGS", ["-mssse3"])
set_config("SSE4_2_FLAGS", ["-msse4.2"])
set_config("FMA_FLAGS", ["-mfma"])
set_config("AVX2_FLAGS", ["-mavx2"])
set_config(
    "AVXVNNI_FLAGS",
    ["-mavxvnni"],
    try_compile(
        check_msg="for -mavxvnni support", flags=["-mavxvnni"], when=is_intel_target
    ),
)
set_config(
    "AVX512BW_FLAGS",
    ["-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"],
    try_compile(
        check_msg="for -mavx512bw support",
        flags=["-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"],
        when=is_intel_target,
    ),
)

# AVX512VNNI can be based on either avx512bw or avx512vbmi. We choose the
# former.
set_config(
    "AVX512VNNI_FLAGS",
    ["-mavx512vnni", "-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"],
    try_compile(
        check_msg="for -mavx512vnni support",
        flags=["-mavx512vnni", "-mavx512bw", "-mavx512f", "-mavx512dq", "-mavx512cd"],
        when=is_intel_target,
    ),
)


set_config(
    "NEON_I8MM_FLAGS",
    ["-march=armv8.2-a+i8mm"],
    try_compile(
        check_msg="for i8mm target feature",
        flags=["-march=armv8.2-a+i8mm"],
        when=is_aarch64_target,
    ),
)

set_config(
    "SVE2_FLAGS",
    ["-march=armv9-a+sve2"],
    try_compile(
        check_msg="for ARM SVE2 target feature",
        flags=["-march=armv9-a+sve2"],
        when=is_aarch64_target,
    ),
)

set_config(
    "DOTPROD_FLAGS",
    ["-march=armv8.2-a+dotprod"],
    try_compile(
        check_msg="for ARM dotprod target feature",
        flags=["-march=armv8.2-a+dotprod"],
        when=is_aarch64_target,
    ),
)

# dtrace support
##
option("--enable-dtrace", help="Build with dtrace support")

dtrace = check_header(
    "sys/sdt.h",
    when="--enable-dtrace",
    onerror=lambda: die("dtrace enabled but sys/sdt.h not found"),
)

set_config("HAVE_DTRACE", True, when=dtrace)
set_define("INCLUDE_MOZILLA_DTRACE", True, when=dtrace)

[Seitenstruktur0.86Druckenetwas mehr zur Ethik2026-04-25]