Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  toolchain.configure   Sprache: unbekannt

 
# -*- 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:
--> --------------------

--> maximum size reached

--> --------------------

[ Dauer der Verarbeitung: 0.46 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge