#     Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""This contains the tuning of the compilers towards defined goals."""

import os
import re

from SCons.Script import (  # type: ignore # pylint: disable=I0021,import-error
    ARGUMENTS,
    Environment,
    GetOption,
)

from nuitka.Tracing import scons_details_logger, scons_logger
from nuitka.utils.Download import getCachedDownloadedMinGW64
from nuitka.utils.FileOperations import (
    getFileSize,
    getNormalizedPathJoin,
    getReportPath,
    listDir,
    openTextFile,
    putTextFileContents,
)
from nuitka.utils.PrivatePipSpace import getZigBinaryPath
from nuitka.utils.Utils import (
    isAIX,
    isFedoraBasedLinux,
    isLinux,
    isMacOS,
    isPosixWindows,
    isWin32Windows,
)

from .SconsCaching import enableCcache, enableClcache
from .SconsHacks import getEnhancedToolDetect, myDetectVersion
from .SconsProgress import enableSconsProgressBar
from .SconsUtils import (
    addToPATH,
    createEnvironment,
    decideArchMismatch,
    enableFlagSettings,
    getArgumentBool,
    getArgumentDefaulted,
    getArgumentInt,
    getArgumentList,
    getArgumentRequired,
    getExecutablePath,
    getLinkerArch,
    getMsvcVersion,
    getMsvcVersionString,
    initScons,
    isClangName,
    isGccName,
    isZigName,
    raiseNoCompilerFoundErrorExit,
    setEnvironmentVariable,
    setupScons,
)
from .WindowsObjGenerator import generateWindowsCoffObject

# spell-checker: ignore LIBPATH,CPPDEFINES,CPPPATH,CXXVERSION,CCFLAGS,LINKFLAGS,CXXFLAGS
# spell-checker: ignore -flto,-fpartial-inlining,-freorder-functions,-defsym,-fprofile
# spell-checker: ignore -fwrapv,-Wunused,-ftrack,-fvisibility,-municode
# spell-checker: ignore -feliminate,noexecstack,implib,bexpall,blibpath
# spell-checker: ignore LTCG,GENPROFILE,USEPROFILE,CGTHREADS,CCVERSION


def _detectWindowsSDK(env):
    # Caching
    if hasattr(env, "windows_sdk_version"):
        return env.windows_sdk_version

    # Check if there is a Windows SDK installed.
    if "WindowsSDKVersion" not in env:
        if "WindowsSDKVersion" in os.environ:
            windows_sdk_version = os.environ["WindowsSDKVersion"].rstrip("\\")
        else:
            windows_sdk_version = None
    else:
        windows_sdk_version = env["WindowsSDKVersion"]

    if windows_sdk_version:
        scons_details_logger.info("Using Windows SDK '%s'." % windows_sdk_version)

        env.windows_sdk_version = tuple(int(x) for x in windows_sdk_version.split("."))
    else:
        scons_logger.warning("""\
Windows SDK must be installed in Visual Studio for it to \
be usable with Nuitka. Use the Visual Studio installer for \
adding it.""")

        env.windows_sdk_version = None

    return env.windows_sdk_version


_windows_sdk_c11_mode_min_version = (10, 0, 19041, 0)


def _enableC11Settings(env):
    """Decide if C11 mode can be used and enable the C compile flags for it.

    Args:
        env - scons environment with compiler information

    Returns:
        bool - c11_mode flag
    """

    # Lots of cases to deal with

    if "force-c11-mode" in env.experimental_flags:
        c11_mode = True
    elif "force-cpp-mode" in env.experimental_flags:
        c11_mode = False
    elif env.clangcl_mode:
        c11_mode = True
    elif (
        env.msvc_mode
        and env.windows_sdk_version >= _windows_sdk_c11_mode_min_version
        and getMsvcVersion(env) >= (14, 3)
    ):
        c11_mode = True
    elif env.clang_mode or env.zig_mode:
        c11_mode = True
    elif env.gcc_mode and env.gcc_version >= (5,):
        c11_mode = True
    else:
        c11_mode = False

    if c11_mode:
        if env.gcc_mode or env.zig_mode:
            env.Append(CFLAGS=["-std=c11"])
        elif env.msvc_mode:
            env.Append(CFLAGS=["/std:c11"])

    if env.msvc_mode and c11_mode:
        # Windows SDK shows this even in non-debug mode in C11 mode.
        env.Append(CCFLAGS=["/wd5105", "/wd4391"])

    if not c11_mode:
        env.Append(CPPDEFINES=["_NUITKA_NON_C11_MODE"])

    scons_details_logger.info("Using C11 mode: %s" % c11_mode)

    env.c11_mode = c11_mode


def _enableLtoSettings(
    env,
    lto_mode,
    pgo_mode,
):
    # This is driven by branches on purpose and pylint: disable=too-many-branches,too-many-statements

    orig_lto_mode = lto_mode

    if lto_mode == "no":
        lto_mode = False
        reason = "disabled"
    elif lto_mode == "yes":
        lto_mode = True
        reason = "enabled"
    elif pgo_mode in ("use", "generate"):
        lto_mode = True
        reason = "PGO implies LTO"
    elif env.msvc_mode and getMsvcVersion(env) >= (14,):
        lto_mode = True
        reason = "known to be supported"
    elif env.monolithpy:
        lto_mode = True
        reason = "known to be supported (MonolithPy)"
    elif env.fedora_python:
        lto_mode = True
        reason = "known to be supported (Fedora Python)"
    elif env.arch_python:
        lto_mode = True
        reason = "known to be supported (Arch Python)"
    elif (
        env.debian_python
        and (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and env.gcc_version >= (6,)
    ):
        lto_mode = True
        reason = "known to be supported (Debian)"
    elif env.gcc_mode and "gnu-cc" in env.the_cc_name and env.anaconda_python:
        lto_mode = False
        reason = "known to be not supported (CondaCC)"
    elif isMacOS() and (env.gcc_mode or env.zig_mode) and env.clang_mode:
        if env.zig_mode:
            lto_mode = False
            reason = "known to be not supported (zig on macOS)"
        elif env.debugger_mode:
            lto_mode = False
            reason = "must be disabled to see line numbers (macOS clang)"
        else:
            lto_mode = True
            reason = "known to be supported (macOS clang)"
    elif env.mingw_mode and env.clang_mode:
        lto_mode = False
        reason = "known to not be supported (new MinGW64 Clang)"
    elif env.gcc_mode and env.mingw_mode and env.gcc_version >= (11, 2):
        lto_mode = True
        reason = "known to be supported (new MinGW64)"
    else:
        lto_mode = False
        reason = "not known to be supported"

    # Do not default to LTO for large compilations, unless asked for it.
    compiled_module_count_threshold = 250
    if (
        orig_lto_mode == "auto"
        and lto_mode
        and env.compiled_module_count > compiled_module_count_threshold
        and not env.monolithpy
    ):
        lto_mode = False
        reason = (
            "might to be too slow %s (>= %d threshold), force it with '--lto=yes'"
            % (
                env.compiled_module_count,
                compiled_module_count_threshold,
            )
        )

    if lto_mode and env.gcc_mode and not env.clang_mode and env.gcc_version < (4, 6):
        scons_logger.warning("""\
The gcc compiler %s (version %s) doesn't have the sufficient \
version for lto mode (>= 4.6). Disabled.""" % (env["CXX"], env["CXXVERSION"]))

        lto_mode = False
        reason = "gcc 4.6 is doesn't have good enough LTO support"

    if (
        lto_mode
        and (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and not isWin32Windows()
        and not isMacOS()
        and getExecutablePath("make", env=env) is None
    ):
        scons_logger.warning("""\
The gcc compiler for LTO mode requires 'make' to be installed \
for parallel linking to be used, compilation might be a lot \
slower without it.
""")

    if (env.gcc_mode or env.zig_mode) and lto_mode:
        if env.clang_mode:
            if "thin-lto" not in env.experimental_flags:
                env.Append(CCFLAGS=["-flto"])
                env.Append(LINKFLAGS=["-flto"])
            else:
                env.Append(CCFLAGS=["-flto=thin"])
                env.Append(LINKFLAGS=["-flto=thin"])
        else:
            env.Append(CCFLAGS=["-flto=%d" % env.job_count])
            env.Append(LINKFLAGS=["-flto=%d" % env.job_count])

            env.Append(CCFLAGS=["-fuse-linker-plugin", "-fno-fat-lto-objects"])
            env.Append(LINKFLAGS=["-fuse-linker-plugin"])

            # Need to tell the linker these things are OK.
            env.Append(LINKFLAGS=["-fpartial-inlining", "-freorder-functions"])

            if env.mingw_mode and "MAKE" not in os.environ:
                setEnvironmentVariable(env, "MAKE", "mingw32-make.exe")

    # Tell compiler to use link time optimization for MSVC
    if env.msvc_mode and lto_mode:
        if not env.clangcl_mode:
            env.Append(CCFLAGS=["/GL"])

            if getMsvcVersion(env) >= (14, 3):
                env.Append(LINKFLAGS=["/CGTHREADS:%d" % env.job_count])

        env.Append(LINKFLAGS=["/LTCG"])

    if orig_lto_mode == "auto":
        scons_details_logger.info(
            "LTO mode auto was resolved to mode: '%s' (%s)."
            % ("yes" if lto_mode else "no", reason)
        )

    env.lto_mode = lto_mode
    env.orig_lto_mode = orig_lto_mode
    env.pgo_mode = pgo_mode

    # PGO configuration
    _enablePgoSettings(env)


_python311_min_msvc_version = (14, 3)


def checkWindowsCompilerFound(
    env,
    target_arch,
    mingw_mode,
    clang_mode,
    msvc_version,
    download_ok,
    assume_yes_for_downloads,
):
    """Remove compiler of wrong arch or too old gcc and replace with downloaded winlibs gcc."""
    # Many cases to deal with, pylint: disable=too-many-branches,too-many-statements

    if os.name != "nt" or isZigName(env["CC"]):
        return env

    # On Windows, in case MSVC was not found and not previously forced, use the
    # winlibs MinGW64 or zig as a download, and use it as a fallback.
    compiler_path = getExecutablePath(env["CC"], env=env)

    scons_details_logger.info(
        "Checking usability of binary '%s' from environment '%s'."
        % (compiler_path, env["CC"])
    )

    # On MSYS2, cannot use the POSIX compiler, drop that even before we check arches, since that
    # will of course still match.
    if (
        env.msys2_mingw_python
        and compiler_path is not None
        and compiler_path.endswith("/usr/bin/gcc.exe")
    ):
        scons_details_logger.info(
            "MSYS2 gcc found, but not used as it'S considered unusable."
        )
        compiler_path = None

    if compiler_path is not None:
        the_cc_name = os.path.basename(compiler_path)

        # The MSVC can only be used with an Windows SDK installed, and for 3.11 we need it
        # to be a least a minimum version.
        if (
            not isGccName(the_cc_name)
            and not isClangName(the_cc_name)
            and (
                _detectWindowsSDK(env) is None
                or (
                    env.python_version is not None
                    and env.python_version >= (3, 11)
                    and _detectWindowsSDK(env) < _windows_sdk_c11_mode_min_version
                )
            )
        ):
            # This will trigger using it to use our own gcc in branch below.
            compiler_path = None
            env["CC"] = None

    if compiler_path is not None:
        the_cc_name = os.path.basename(compiler_path)

        if clang_mode and not isClangName(the_cc_name):
            # This will trigger using it to use our own clang in branch below.
            compiler_path = None
            env["CC"] = None

    # Drop wrong arch compiler, most often found by scans. There might be wrong gcc or cl on the PATH.
    if compiler_path is not None:
        the_cc_name = os.path.basename(compiler_path)

        decision, linker_arch, compiler_arch = decideArchMismatch(
            target_arch=target_arch,
            the_cc_name=the_cc_name,
            compiler_path=compiler_path,
        )

        if decision:
            scons_logger.info(
                """\
Mismatch between Python binary ('%s' -> '%s') and \
C compiler ('%s' -> '%s') arches, that compiler is ignored!"""
                % (
                    os.environ["NUITKA_PYTHON_EXE_PATH"],
                    linker_arch,
                    compiler_path,
                    compiler_arch,
                )
            )

            # This will trigger using it to use our own gcc in branch below.
            compiler_path = None
            env["CC"] = None

    if compiler_path is not None and msvc_version is not None:
        if msvc_version == "latest":
            scons_logger.info(
                "MSVC version resolved to %s." % getMsvcVersionString(env)
            )
        # Requested a specific MSVC version, check if that worked.
        elif msvc_version != getMsvcVersionString(env):
            scons_logger.info(
                "Failed to find requested MSVC version ('%s' != '%s')."
                % (msvc_version, getMsvcVersionString(env))
            )

            # This will trigger error exit in branch below.
            compiler_path = None
            env["CC"] = None

    # For Python3.11+ we need to check Windows SDK version.
    if compiler_path is not None:
        the_cc_name = os.path.basename(compiler_path)

        # The MSVC can only be used with an Windows SDK installed, and for 3.11 we need it
        # to be a least a minimum version.
        if (
            # This is actually OK to use like this, pylint: disable=bad-chained-comparison
            not isGccName(the_cc_name)
            and None is not env.python_version >= (3, 11)
            and getMsvcVersion(env) < _python311_min_msvc_version
        ):
            scons_logger.info(
                """\
For Python version %s MSVC %s or later is required, not %s which is too old."""
                % (
                    ".".join(str(d) for d in env.python_version),
                    ".".join(str(d) for d in _python311_min_msvc_version),
                    getMsvcVersionString(env),
                )
            )

            # This will trigger error exit in branch below.
            compiler_path = None
            env["CC"] = None

    if compiler_path is not None:
        the_cc_name = os.path.basename(compiler_path)

        if isGccName(the_cc_name):
            if "force-accept-windows-gcc" not in env.experimental_flags:
                scons_logger.info(
                    "Non downloaded winlibs-gcc '%s' is being ignored, Nuitka is very dependent on the precise one."
                    % (compiler_path,)
                )

                # This also will trigger using it to use our own gcc in branch below.
                compiler_path = None
                env["CC"] = None

    # This will download "gcc.exe" (or "zig.exe") when all others have been
    # rejected and MSVC is not enforced.

    if compiler_path is None and msvc_version is None:
        if env.python_version < (3, 13) or "force-mingw64" in env.experimental_flags:
            scons_details_logger.info(
                "No usable C compiler, attempt fallback to winlibs gcc."
            )

            compiler_path = getCachedDownloadedMinGW64(
                target_arch=target_arch,
                assume_yes_for_downloads=assume_yes_for_downloads,
                download_ok=download_ok,
                experimental="winlibs-new" in env.experimental_flags,
            )
        elif target_arch != "x86" and not mingw_mode:
            scons_details_logger.info("No usable C compiler, attempt fallback to zig.")

            # This will download "zig.exe" when all others have been rejected
            # and MSVC is not enforced.
            compiler_path = getZigBinaryPath(
                logger=scons_logger,
                assume_yes_for_downloads=env.assume_yes_for_downloads,
                reject_message=None,
            )

        if compiler_path is not None:
            addToPATH(env=env, dirname=os.path.dirname(compiler_path), prefix=True)

            env = createEnvironment(
                mingw_mode=env.python_version < (3, 13)
                or "force-mingw64" in env.experimental_flags,
                msvc_version=None,
                clang_mode=False,
                clangcl_mode=False,
                zig_exe_path=(
                    None
                    if (
                        env.python_version < (3, 13)
                        or "force-mingw64" in env.experimental_flags
                    )
                    else compiler_path
                ),
                target_arch=target_arch,
                consider_environ_variables=False,
                assume_yes_for_downloads=env.assume_yes_for_downloads,
                experimental_flags=env.experimental_flags,
                source_dir=env.source_dir,
            )

            if clang_mode:
                env["CC"] = os.path.join(os.path.dirname(compiler_path), "clang.exe")
        else:
            raiseNoCompilerFoundErrorExit()

    return env


def _disableCcacheInEnvironment(env):
    """Disable ccache in the Scons environment.

    Setting 'CCACHE_DISABLE=1' causes ccache to transparently forward
    to the real compiler, even when the compiler binary is a ccache
    symlink (e.g. Debian's ccache package).
    """
    setEnvironmentVariable(env, "CCACHE_DISABLE", "1")


def createEnvironmentAndCheckCompiler(
    mingw_mode,
    msvc_version,
    clang_mode,
    clangcl_mode,
    target_arch,
    consider_environ_variables,
    assume_yes_for_downloads,
    download_ok,
    experimental_flags,
    source_dir,
):
    anaconda_python = getArgumentBool("anaconda_python", False)

    if isLinux() and anaconda_python:
        python_prefix = getArgumentRequired("python_prefix")
        addToPATH(None, getNormalizedPathJoin(python_prefix, "bin"), prefix=True)

    env = createEnvironment(
        mingw_mode=mingw_mode,
        msvc_version=msvc_version,
        clang_mode=clang_mode,
        clangcl_mode=clangcl_mode,
        zig_exe_path=None,
        target_arch=target_arch,
        consider_environ_variables=consider_environ_variables,
        assume_yes_for_downloads=assume_yes_for_downloads,
        experimental_flags=experimental_flags,
        source_dir=source_dir,
    )

    env = checkWindowsCompilerFound(
        env=env,
        target_arch=target_arch,
        mingw_mode=mingw_mode,
        clang_mode=clang_mode,
        msvc_version=msvc_version,
        download_ok=download_ok,
        assume_yes_for_downloads=assume_yes_for_downloads,
    )

    env.the_compiler = env["CC"] or env["CXX"]
    env.the_cc_name = os.path.normcase(os.path.basename(env.the_compiler))

    # Requested or user provided, detect if it's clang even from environment
    if isClangName(env.the_cc_name):
        clang_mode = True
        env["CCVERSION"] = None

    # We consider clang to be a form of gcc for the most things, they strive to
    # be compatible.
    env.zig_mode = isZigName(env.the_cc_name)
    env.gcc_mode = (
        isGccName(env.the_cc_name)
        or clang_mode
        or (env.zig_mode and not os.name == "nt")
    )
    env.clang_mode = clang_mode

    # Only use MSVC if not already clear, we are using MinGW.
    env.msvc_mode = os.name == "nt" and not env.gcc_mode and not env.zig_mode
    env.mingw_mode = os.name == "nt" and env.gcc_mode and not env.zig_mode
    env.clangcl_mode = clangcl_mode

    # For Python3.13, we need to enforce it for now to use MSVC
    if (
        os.name == "nt"
        and not env.msvc_mode
        and not env.zig_mode
        and env.python_version >= (3, 13)
        and "force-mingw64" not in env.experimental_flags
    ):
        return scons_logger.sysexit(
            """\
Sorry, MinGW64 is not currently supported with Python 3.13,
due to differences in layout internal structures of Python.

Newer Nuitka will work eventually to solve this. Either use
Python 3.12 or option "--zig" (another Free Software compiler)
as a workaround for now and wait for updates of Nuitka to add
MinGW64 support back. Alternatively you can also install MSVC
as described in the documentation.""",
            env=env,
        )

    # Detect versions, ensuring they are always set to a tuple, so comparisons
    # do not fail with TypeError.
    if env.zig_mode:
        env.zig_version = myDetectVersion(env, env.the_compiler)

        if env.zig_version is None:
            scons_logger.sysexit(
                "Error, failed to detect Zig version of backend compiler '%s'."
                % env.the_compiler
            )

        env.clang_version = None
        env.gcc_version = None
    elif env.clang_mode:
        env.clang_version = myDetectVersion(env, env.the_compiler)

        if env.clang_version is None:
            scons_logger.sysexit(
                "Error, failed to detect Clang version of backend compiler '%s'."
                % env.the_compiler
            )

        env.zig_version = None
        env.gcc_version = None
    elif env.gcc_mode:
        env.gcc_version = myDetectVersion(env, env.the_compiler)

        if env.gcc_version is None:
            scons_logger.sysexit(
                "Error, failed to detect GCC version of backend compiler '%s'."
                % env.the_compiler
            )

        env.clang_version = None
        env.zig_version = None
    else:
        env.clang_version = None
        env.zig_version = None
        env.gcc_version = None

    return env


_supported_resource_modes = (
    "c23_embed",
    "linker",
    "incbin",
    "coff_obj",
    "win_resource",
    "mac_section",
    "code",
)


def _decideBlobResourceMode(env):
    # This is a complicated decision with a lot of cases, as there are many
    # compiler, mode, OS and their versions related decisions.
    # pylint: disable=too-many-branches,too-many-statements

    if "NUITKA_RESOURCE_MODE" in os.environ:
        resource_mode = os.environ["NUITKA_RESOURCE_MODE"]
        reason = "user provided"
    elif env.zig_mode:
        resource_mode = "c23_embed"
        reason = "default for zig"
    elif isWin32Windows():
        if env.clangcl_mode and getMsvcVersion(env) >= (14, 3):
            resource_mode = "c23_embed"
            reason = "default for ClangCL"
        elif env.msvc_mode:
            resource_mode = "coff_obj"
            reason = "default for MSVC"
        elif env.mingw_mode:
            resource_mode = "coff_obj"
            reason = "default for MinGW"
        else:
            resource_mode = "win_resource"
            reason = "default for Windows on old MSVC or ClangCL"
    elif isPosixWindows():
        resource_mode = "linker"
        reason = "default MSYS2 Posix"
    elif isMacOS():
        if env.clang_mode and env.clang_version >= (19,):
            resource_mode = "c23_embed"
            reason = "default for macOS with clang 19 or later"
        else:
            resource_mode = "mac_section"
            reason = "default for macOS"
    elif env.gcc_mode and env.clang_mode and env.clang_version >= (19,):
        resource_mode = "c23_embed"
        reason = "default for newer clang"
    elif env.gcc_mode and not env.clang_mode and env.gcc_version >= (15,):
        resource_mode = "c23_embed"
        reason = "default for newer gcc"
    elif isAIX():
        resource_mode = "code"
        reason = "AIX is not compatible with incbin"
    elif env.lto_mode and env.gcc_mode and not env.clang_mode:
        if env.module_mode:
            resource_mode = "code"
        else:
            resource_mode = "linker"

        reason = "default for lto gcc with '--lto' and bugs for incbin"
    else:
        # All is done already, this is for most platforms.
        resource_mode = "incbin"
        reason = "default"

    if resource_mode not in _supported_resource_modes:
        return scons_logger.sysexit(
            "Unknown resource mode '%s', supported modes are: %s"
            % (resource_mode, _supported_resource_modes),
            env=env,
        )

    if resource_mode in ("linker", "incbin") and env.msvc_mode:
        return scons_logger.sysexit(
            "Resource mode '%s' is not supported with MSVC or ClangCL." % resource_mode,
            env=env,
        )

    if resource_mode == "mac_section" and not isMacOS():
        return scons_logger.sysexit(
            "Resource mode 'mac_section' is not supported on non-macOS platforms.",
            env=env,
        )

    if resource_mode == "win_resource" and not isWin32Windows():
        return scons_logger.sysexit(
            "Resource mode 'win_resource' is not supported on non-Windows platforms.",
            env=env,
        )

    if resource_mode == "coff_obj" and not isWin32Windows():
        return scons_logger.sysexit(
            "Resource mode 'coff_obj' is not supported on non-Windows platforms.",
            env=env,
        )

    env.resource_mode = resource_mode
    return resource_mode, reason


def _getSymbolName(blob_filename):
    blob_filename = os.path.basename(blob_filename)
    assert blob_filename.endswith(".bin") and blob_filename.startswith(
        "__"
    ), blob_filename

    # TODO: Get rid of the strip, it ought not to be needed, but right now there
    # is a disconnect between symbol names and the filename.
    return blob_filename[2:-4] + "_bin"


def _getBlobNameCamelCase(blob_filename):
    symbol_name = _getSymbolName(blob_filename)
    return (
        "".join(word.title() for word in symbol_name.split("_") if word != "bin")
        + "Blob"
    )


def _isWriteableConstantsBlob(blob_filename):
    if os.path.basename(blob_filename) != "__constant.bin":
        return False

    for cpp_define in getArgumentList("cpp_defines", ""):
        if cpp_define.split("=", 1)[0] == "_NUITKA_EXPERIMENTAL_WRITEABLE_CONSTANTS":
            return True

    return False


def _addConstantBlobFileCoffObj(env, blob_filename):
    env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_COFF_OBJ"])

    obj_filename = blob_filename + ".obj"

    generateWindowsCoffObject(
        in_filename=blob_filename,
        out_filename=obj_filename,
        symbol_name=_getSymbolName(blob_filename) + "_data",
        architecture=env.target_arch,
        writeable=_isWriteableConstantsBlob(blob_filename),
    )

    # Link the generated object file
    env.Append(LINKFLAGS=[obj_filename])


def _addConstantBlobFileIncbin(env, blob_filename):
    env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_INCBIN"])

    constants_generated_filename = os.path.join(env.source_dir, "__constants_data.c")

    symbol_name = _getSymbolName(blob_filename)
    camel_name = _getBlobNameCamelCase(blob_filename)

    putTextFileContents(
        constants_generated_filename,
        contents=r"""
#define INCBIN_PREFIX
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_LOCAL
#ifdef _NUITKA_EXPERIMENTAL_WRITEABLE_CONSTANTS
#define INCBIN_OUTPUT_SECTION ".data"
#define CONST_CONSTANT
#else
#define CONST_CONSTANT const
#endif

#include "nuitka/incbin.h"

INCBIN(%(symbol_name)s, "%(blob_filename)s");

#ifdef __cplusplus
extern "C" {
#endif
unsigned CONST_CONSTANT char *get%(camel_name)sData(void) {
    return (unsigned CONST_CONSTANT char *)%(symbol_name)s_data;
}
#ifdef __cplusplus
}
#endif
"""
        % {
            "blob_filename": blob_filename,
            "symbol_name": symbol_name,
            "camel_name": camel_name,
        },
    )


def _addConstantBlobFileLinker(env, blob_filename):
    # Indicate "linker" resource mode.
    env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_LINKER"])

    # For MinGW the symbol name to be used is more low level.
    constant_bin_link_name = _getSymbolName(blob_filename) + "_data"
    if env.mingw_mode:
        constant_bin_link_name = "_" + constant_bin_link_name

    # At least some gcc versions need this path to contain a path element,
    # but it's normalized when it gets to here.
    if env.source_dir == ".":
        blob_filename = "./%s" % blob_filename

    env.Append(
        LINKFLAGS=[
            "-Wl,-b",
            "-Wl,binary",
            "-Wl,%s" % blob_filename,
            "-Wl,-b",
            "-Wl,%s"
            % getLinkerArch(
                target_arch=env.target_arch,
                mingw_mode=env.mingw_mode or isPosixWindows(),
            ),
            "-Wl,-defsym",
            "-Wl,%s=_binary_%s_start"
            % (
                constant_bin_link_name,
                "".join(re.sub("[^a-zA-Z0-9_]", "_", c) for c in blob_filename),
            ),
        ]
    )


def _addConstantBlobFileCode(env, blob_filename):
    # Indicate "code" resource mode.
    env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_CODE"])

    constants_generated_filename = os.path.join(env.source_dir, "__constants_data.c")

    def writeConstantsDataSource():
        with openTextFile(constants_generated_filename, "w") as output:
            if not env.c11_mode:
                output.write('extern "C" {')

            output.write("""\
// Constant data for the program.
""")

            if env.clang_mode or env.clangcl_mode:
                output.write("""
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wc23-extensions"
#endif
""")

            output.write("""
#ifdef __cplusplus
extern "C"
#endif
#if !defined(_NUITKA_EXPERIMENTAL_WRITEABLE_CONSTANTS)
const
#endif
unsigned char %s_data[] =
{
""" % _getSymbolName(blob_filename))

            if env.resource_mode == "code":
                with open(blob_filename, "rb") as f:
                    content = f.read()
                for count, stream_byte in enumerate(content):
                    if count % 16 == 0:
                        if count > 0:
                            output.write("\n")

                        output.write("   ")

                    if str is bytes:
                        stream_byte = ord(stream_byte)

                    output.write(" 0x%02x," % stream_byte)
            else:
                output.write('#embed "%s"\n' % blob_filename)

            output.write("\n};\n")

            if not env.c11_mode:
                output.write("}")

    writeConstantsDataSource()


def _addConstantBlobFileWinResource(env):
    # On Windows constants can be accessed as a resource by Nuitka at run time afterwards.
    env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_RESOURCE"])


def _addConstantBlobFileMacSection(env, blob_filename):
    assert isMacOS()

    env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_MACOS_SECTION"])

    section_name = _getSymbolName(blob_filename)

    # spell-checker: ignore linkflags, sectcreate
    env.Append(
        LINKFLAGS=[
            "-Wl,-sectcreate,%(section_name)s,%(section_name)s,%(blob_filename)s"
            % {
                "section_name": section_name,
                "blob_filename": blob_filename,
            }
        ]
    )


def addConstantBlobFile(env, blob_filename):
    assert blob_filename.endswith(".bin"), blob_filename

    if env.resource_mode == "absent":
        env.resource_mode, reason = _decideBlobResourceMode(env)

        scons_details_logger.info(
            "Using resource mode: '%s' (%s)." % (env.resource_mode, reason)
        )

    if env.resource_mode == "coff_obj":
        _addConstantBlobFileCoffObj(env, blob_filename)
    elif env.resource_mode == "win_resource":
        _addConstantBlobFileWinResource(env)
    elif env.resource_mode == "mac_section":
        _addConstantBlobFileMacSection(env, blob_filename)
    elif env.resource_mode == "incbin":
        _addConstantBlobFileIncbin(env, blob_filename)
    elif env.resource_mode == "linker":
        _addConstantBlobFileLinker(env, blob_filename)
    elif env.resource_mode in ("code", "c23_embed"):
        _addConstantBlobFileCode(env, blob_filename)
    else:
        return scons_logger.sysexit(
            "Error, illegal resource mode '%s' specified" % env.resource_mode,
            env=env,
        )


def _addConstantBlobFiles(env, source_dir):
    """Add constant blob files to the build environment.

    Args:
        env: The Scons environment.
        source_dir: The source directory containing the blobs/ subdirectory.

    Returns:
        int: Total size of all blob .bin files in bytes.
    """
    env.resource_mode = "absent"

    total_blob_size = 0
    blobs_dir = os.path.join(source_dir, "blobs")
    if os.path.exists(blobs_dir):
        blob_filenames = []

        for filename_full, filename in listDir(blobs_dir):
            if filename.endswith(".bin"):
                blob_filenames.append(filename_full)
                total_blob_size += getFileSize(filename_full)

        for blob_filename in blob_filenames:
            addConstantBlobFile(
                env=env,
                blob_filename=blob_filename,
            )

    return total_blob_size


def _enableMacOSTargetSettings(env):
    """Set up environment for macOS target settings."""
    assert isMacOS()

    setEnvironmentVariable(env, "MACOSX_DEPLOYMENT_TARGET", env.macos_min_version)

    if not env.zig_mode:
        target_flag = "--target=%s-apple-macos%s" % (
            env.macos_target_arch,
            env.macos_min_version,
        )
        env.Append(CCFLAGS=[target_flag])
        env.Append(LINKFLAGS=[target_flag])


def _enableAIXTargetSettings(env):
    """Set up environment for AIX target settings."""
    assert isAIX()

    if env.target_arch in ("64", "32"):
        env.Append(CCFLAGS=["-m%s" % env.target_arch])
        env.Append(LINKFLAGS=["-m%s" % env.target_arch])


def _enableWin32TargetSettings(env):
    """Set up environment for Windows target settings."""
    assert isWin32Windows()
    # The MinGW64 and ClangCL do not default for API level properly, so
    # help it. For zig, it is hard coded.
    if not env.zig_mode:
        env.Append(CPPDEFINES=["_WIN32_WINNT=0x0601"])


def enableWindowsStackSize(env, target_arch):
    # Stack size 4MB or 8MB, we might need more than the default 1MB.
    if target_arch == "x86_64":
        stack_size = 1024 * 1204 * 8
    else:
        stack_size = 1024 * 1204 * 4

    if env.msvc_mode:
        env.Append(LINKFLAGS=["/STACK:%d" % stack_size])

    if env.mingw_mode:
        env.Append(LINKFLAGS=["-Wl,--stack,%d" % stack_size])


def _enableOutputSettings(env):
    if env.msvc_mode:
        # For onefile bootstrap, we have to force static runtime as we don't
        # want to bring a second file and MonolithPy requires it.
        force_static = (
            env.onefile_compile and env.onefile_windows_static_runtime
        ) or env.monolithpy

        if force_static:
            env.Append(CCFLAGS=["/MT"])  # Multithreaded, static version of C run time.
        else:
            env.Append(CCFLAGS=["/MD"])  # Multithreaded, dynamic version of C run time.


def createNuitkaSconsEnvironment(needs_source_dir=True):
    # This is handling the common setup of the Scons environment for Nuitka
    # and has many steps, pylint: disable=too-many-locals,too-many-statements

    # Set up the basic stuff.
    initScons(ARGUMENTS)

    # Helper used for many things.
    job_count = GetOption("num_jobs")

    if needs_source_dir:
        # The directory containing the C files generated by Nuitka to be built using
        # scons. They are referred to as sources from here on.
        source_dir = getArgumentRequired("source_dir")
    else:
        source_dir = os.devnull

    # The directory containing Nuitka provided C files to be built and where it
    # should be used.
    nuitka_src = getArgumentRequired("nuitka_src")

    # Debug mode: Less optimizations, debug information in the resulting binary.
    debug_mode = getArgumentBool("debug_mode", False)

    # Debugger mode: Debug information in the resulting binary and intention to run
    # in debugger.
    debugger_mode = getArgumentBool("debugger_mode", False)

    # Experimental indications. Do things that are not yet safe to do.
    experimental_flags = getArgumentList("experimental", "")

    # Deployment mode
    deployment_mode = getArgumentBool("deployment", False)

    # Experimental indications. Do things that are not yet safe to do.
    no_deployment = getArgumentList("no_deployment", "")

    # Debug mode indications. Do check things with fine granularity.
    debug_modes = getArgumentList("debug_modes", "")

    # Tracing mode. Output program progress.
    trace_mode = getArgumentBool("trace_mode", False)

    # LTO mode: Use link time optimizations of C compiler if available and known
    # good with the compiler in question.
    lto_mode = getArgumentDefaulted("lto_mode", "auto")

    # Reproducible mode
    reproducible_mode = getArgumentBool("reproducible_mode", False)

    # PGO mode: Use profile guided optimization of C compiler if available.
    pgo_mode = getArgumentDefaulted("pgo_mode", "no")

    # Console mode specified
    console_mode = getArgumentDefaulted("console_mode", "attach")

    # Windows might be running a Python whose DLL we have to use.
    uninstalled_python = getArgumentBool("uninstalled_python", False)

    # Unstripped mode: Do not remove debug symbols.
    unstripped_mode = getArgumentBool("unstripped_mode", False)

    # Target arch, uses for compiler choice and quick linking of constants binary
    # data.
    target_arch = getArgumentRequired("target_arch")

    # MinGW compiler mode, optional and interesting to Windows only.
    mingw_mode = getArgumentBool("mingw_mode", False)

    # Clang compiler mode, forced on macOS and FreeBSD (excluding PowerPC), optional on Linux.
    clang_mode = getArgumentBool("clang_mode", False)

    # Clang on Windows with no requirement to use MinGW64 or using MSYS2 MinGW flavor,
    # is changed to ClangCL from Visual Studio.
    clangcl_mode = False
    if os.name == "nt" and not mingw_mode and clang_mode:
        clang_mode = False
        clangcl_mode = True

    # Show scons mode, output information about Scons operation
    show_scons_mode = getArgumentBool("show_scons", False)
    scons_details_logger.is_quiet = not show_scons_mode

    if int(os.getenv("NUITKA_QUIET", "0")):
        from nuitka.Tracing import setQuiet

        setQuiet()

    # Forced MSVC version (windows-only)
    msvc_version = getArgumentDefaulted("msvc_version", None)

    # Allow automatic downloads for ccache, etc.
    assume_yes_for_downloads = getArgumentBool("assume_yes_for_downloads", False)

    # Low memory mode, compile using less memory if possible.
    low_memory = getArgumentBool("low_memory", False)

    # Minimum version required on macOS.
    macos_min_version = getArgumentDefaulted("macos_min_version", "")

    # Target arch for macOS.
    macos_target_arch = getArgumentDefaulted("macos_target_arch", "")

    # gcc compiler cf_protection option
    cf_protection = getArgumentDefaulted("cf_protection", "auto")

    # Amount of frozen modules.
    frozen_modules = getArgumentInt("frozen_modules", 0)

    # Windows runtime DLL inclusion.
    onefile_windows_static_runtime = getArgumentBool(
        "onefile_windows_static_runtime", False
    )

    progress_bar = getArgumentDefaulted("progress_bar", "auto")
    enableSconsProgressBar(progress_bar)

    disable_ccache = getArgumentBool("disable_ccache", False)

    # Patch the compiler detection.
    Environment.Detect = getEnhancedToolDetect()

    # Create Scons environment, the main control tool. Don't include "mingw" on
    # Windows immediately, we will default to MSVC if available.
    # On Windows, in case MSVC was not found and not previously forced, use the
    # winlibs MinGW64 as a download, and use it as a fallback.
    env = createEnvironmentAndCheckCompiler(
        mingw_mode=mingw_mode,
        msvc_version=msvc_version,
        clang_mode=clang_mode,
        clangcl_mode=clangcl_mode,
        target_arch=target_arch,
        consider_environ_variables=True,
        assume_yes_for_downloads=assume_yes_for_downloads,
        download_ok=True,
        experimental_flags=experimental_flags,
        source_dir=source_dir,
    )

    if disable_ccache:
        _disableCcacheInEnvironment(env=env)

    # Consider switching from gcc to its g++ compiler as a workaround that makes
    # us work without C11.
    switchFromGccToGpp(
        env=env,
    )

    enableFlagSettings(env, "no_deployment", no_deployment)
    env.no_deployment_flags = no_deployment

    enableFlagSettings(env, "experimental", experimental_flags)
    env.experimental_flags = experimental_flags

    enableFlagSettings(env, "debug", debug_modes)
    env.debug_modes_flags = debug_modes

    env.debug_mode = debug_mode
    env.debugger_mode = debugger_mode
    env.unstripped_mode = unstripped_mode
    env.console_mode = console_mode
    env.source_dir = source_dir
    env.deployment_mode = deployment_mode
    env.nuitka_src = nuitka_src
    env.low_memory = low_memory
    env.macos_min_version = macos_min_version
    env.macos_target_arch = macos_target_arch
    env.target_arch = target_arch
    env.no_deployment = no_deployment
    env.debug_modes = debug_modes
    env.trace_mode = trace_mode
    env.lto_mode = lto_mode
    env.reproducible_mode = reproducible_mode
    env.pgo_mode = pgo_mode
    env.job_count = job_count
    env.frozen_modules = frozen_modules
    env.uninstalled_python = uninstalled_python
    env.onefile_windows_static_runtime = onefile_windows_static_runtime
    env.cf_protection = cf_protection
    env.disable_ccache = disable_ccache

    if env.the_compiler is None or getExecutablePath(env.the_compiler, env=env) is None:
        raiseNoCompilerFoundErrorExit()

    scons_details_logger.info("Told to run compilation on %d CPUs." % env.job_count)

    if show_scons_mode:
        compiler_path = getExecutablePath(env.the_compiler, env=env)

        if env.msvc_mode:
            scons_logger.info(
                "Scons: Compiler used '%s' (MSVC %s)."
                % (compiler_path, getMsvcVersionString(env))
            )
        else:
            scons_logger.info("Scons: Compiler used '%s'." % compiler_path)

    # Set build directory and scons general settings.
    if needs_source_dir:
        setupScons(env, source_dir)

        if env.gcc_mode:
            enableCcache(
                env=env,
                source_dir=source_dir,
                python_prefix=env.python_prefix_external,
            )

        if env.msvc_mode and not env.disable_ccache:
            enableClcache(
                env=env,
                source_dir=source_dir,
            )

    return env


def setupCCompiler(env, pgo_mode, exe_target, onefile_compile):
    # This is driven by many branches on purpose and has a lot of things
    # to deal with for LTO checks and flags, pylint: disable=too-many-branches,too-many-statements

    env.exe_target = exe_target
    env.onefile_compile = onefile_compile

    # Enable LTO for compiler.
    _enableLtoSettings(
        env=env,
        lto_mode=env.lto_mode,
        pgo_mode=pgo_mode,
    )

    _enableC11Settings(env)
    _enableOutputSettings(env)

    # Some things are supposed to be only for use with zig, so we inject a
    # define that allows us to test for it.
    if env.zig_mode:
        env.Append(CPPDEFINES=["__ZIG__"])

    if env.reproducible_mode:
        # Make sure we use a fixed date for macros like "__DATE__" to ensure
        # reproducibility.
        if env.gcc_mode:
            setEnvironmentVariable(env, "SOURCE_DATE_EPOCH", "0")

        # Ask the MSVC linker to be reproducible.
        if env.clangcl_mode or env.msvc_mode:
            env.Append(LINKFLAGS=["/Brepro"])  # spell-checker: ignore Brepro

    if env.gcc_mode or env.zig_mode:
        # Support for gcc and clang, restricting visibility as much as possible.
        env.Append(CCFLAGS=["-fvisibility=hidden"])

        if not env.c11_mode:
            env.Append(CXXFLAGS=["-fvisibility-inlines-hidden"])

        if isWin32Windows() and hasattr(env, "source_dir"):
            # On Windows, exporting to DLL need to be controlled.
            if not env.zig_mode:
                env.Append(LINKFLAGS=["-Wl,--exclude-all-symbols"])

            # Make sure we handle import library on our own and put it into the
            # build directory.
            env.Append(
                LINKFLAGS=[
                    "-Wl,--out-implib,%s" % os.path.join(env.source_dir, "import.lib")
                ]
            )

        # Make it clear how to handle integer overflows, namely by wrapping around
        # to negative values.
        env.Append(CCFLAGS=["-fwrapv"])

        if not env.low_memory:
            # Avoid IO for compilation as much as possible, this should make the
            # compilation more memory hungry, but also faster.
            env.Append(CCFLAGS="-pipe")

        # gcc compiler cf_protection option
        if env.cf_protection != "auto":
            env.Append(CCFLAGS=["-fcf-protection=%s" % env.cf_protection])

    # Support for clang.
    if env.clang_mode or env.clangcl_mode:
        env.Append(CCFLAGS=["-Wno-deprecated-declarations"])

    if env.clang_mode:
        env.Append(CCFLAGS=["-Wno-missing-field-initializers"])
    elif env.clangcl_mode:
        # spell-checker: ignore Xclang
        env.Append(CCFLAGS=["-Xclang", "-Wno-missing-field-initializers"])

    if isClangName(env.the_cc_name):
        env.Append(CCFLAGS=["-Wno-constant-logical-operand"])

    env.warn_error_mode = (
        env.debug_mode
        and "debug_c_warnings" in env.debug_modes_flags
        and not env.debugger_mode
    )

    if env.warn_error_mode:
        # Allow gcc/clang/MSVC to point out all kinds of inconsistency to us by
        # raising an error.
        if env.gcc_mode or env.zig_mode:
            env.Append(
                CCFLAGS=[
                    "-Wall",
                    "-Werror",  # spell-checker: ignore Werror
                ]
            )

            if (
                isZigName(env.the_cc_name)
                or (isClangName(env.the_cc_name) and env.clang_version >= (13,))
                or (isGccName(env.the_cc_name) and env.gcc_version >= (4, 6))
            ):
                env.Append(CCFLAGS=["-Wunused-but-set-variable"])

        if env.msvc_mode:
            env.Append(CCFLAGS=["/WX"])
    else:
        if env.gcc_mode or env.zig_mode:
            if (
                isZigName(env.the_cc_name)
                or (isClangName(env.the_cc_name) and env.clang_version >= (13,))
                or (isGccName(env.the_cc_name) and env.gcc_version >= (4, 6))
            ):
                env.Append(CCFLAGS=["-Wno-unused-but-set-variable"])

    # Support for macOS standalone to run on older OS versions.
    if isMacOS():
        _enableMacOSTargetSettings(env)

    if isAIX():
        _enableAIXTargetSettings(env)

        # Otherwise no symbol is exported.
        if env.module_mode or env.dll_mode:
            env.Append(LINKFLAGS=["-Wl,-bexpall"])

        if env.standalone_mode:
            env.Append(LINKFLAGS=["-Wl,-blibpath:/usr/lib:/lib"])

    if isWin32Windows():
        _enableWin32TargetSettings(env)

    # Unicode entry points for programs.
    if env.mingw_mode or (env.zig_mode and isWin32Windows()):
        env.Append(LINKFLAGS=["-municode"])

    # Older g++ complains about aliasing with Py_True and Py_False, but we don't
    # care.
    if (
        (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and (env.gcc_version and env.gcc_version < (4, 5))
    ):
        env.Append(CCFLAGS=["-fno-strict-aliasing"])

    # For gcc 4.6 or higher, there this new interesting function.
    if (
        (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and (env.gcc_version and env.gcc_version >= (4, 6))
    ):
        env.Append(CCFLAGS=["-fpartial-inlining"])

    # Save some memory for gcc by not tracing macro code locations at all.
    if (
        not env.debug_mode
        and (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and env.gcc_version >= (5,)
    ):
        env.Append(CCFLAGS=["-ftrack-macro-expansion=0"])

    # We don't care about deprecations.
    if (env.gcc_mode or env.zig_mode) and not env.clang_mode:
        env.Append(CCFLAGS=["-Wno-deprecated-declarations"])

    # The var-tracking only improves GDB debugging information, but it does
    # not scale for Nuitka generated C code in terms of memory and compile
    # time. Since it has zero impact on runtime performance, we disable it.
    if (env.gcc_mode or env.zig_mode) and not env.clang_mode:
        env.Append(CCFLAGS=["-fno-var-tracking"])

        if env.gcc_mode and env.gcc_version >= (4, 4):
            env.Append(CCFLAGS=["-fno-var-tracking-assignments"])

    # For large files, these can issue warnings about disabling
    # itself, while we do not need it really.
    if (
        (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and (env.gcc_version and env.gcc_version >= (6,))
    ):
        env.Append(CCFLAGS=["-Wno-misleading-indentation"])

    # Disable output of notes, e.g. on struct alignment layout changes for
    # some arches, we don't care.
    if (env.gcc_mode or env.zig_mode) and not env.clang_mode:
        env.Append(CCFLAGS=["-Wno-psabi"])  # spell-checker: ignore psabi

    # Prevent using LTO when told not to use it, causes errors with some
    # static link libraries.
    if (
        (env.gcc_mode or env.zig_mode)
        and not env.clang_mode
        and env.static_libpython
        and not env.lto_mode
    ):
        env.Append(CCFLAGS=["-fno-lto"])
        env.Append(LINKFLAGS=["-fno-lto"])

    # Set optimization level for gcc and clang in LTO mode
    if (env.gcc_mode or env.zig_mode) and env.lto_mode:
        if env.debug_mode:
            env.Append(LINKFLAGS=["-Og"])
        else:
            # For LTO with static libpython combined, there are crashes with Python core
            # being inlined, so we must refrain from that. On Windows there is no such
            # thing, and MonolithPy is not affected.
            env.Append(
                LINKFLAGS=[
                    (
                        "-O3"
                        if env.monolithpy or os.name == "nt" or not env.static_libpython
                        else "-O2"
                    )
                ]
            )

    # When debugging, optimize less than when optimizing, when not remove
    # assertions.
    if env.debug_mode:
        if env.clang_mode or (
            (env.gcc_mode or env.zig_mode)
            and (env.gcc_version and env.gcc_version >= (4, 8))
        ):
            env.Append(CCFLAGS=["-Og"])
        elif env.gcc_mode or env.zig_mode:
            env.Append(CCFLAGS=["-O1"])
        elif env.msvc_mode:
            env.Append(CCFLAGS=["-O2"])
    else:
        if env.gcc_mode or env.zig_mode:
            env.Append(
                CCFLAGS=[
                    (
                        "-O3"
                        if env.monolithpy or os.name == "nt" or not env.static_libpython
                        else "-O2"
                    )
                ]
            )
        elif env.msvc_mode:
            env.Append(
                CCFLAGS=[
                    "/Ox",  # Enable most speed optimization
                    "/GF",  # Eliminate duplicate strings.
                    "/Gy",  # Function level object storage, to allow removing unused ones
                ]
            )

        env.Append(CPPDEFINES=["__NUITKA_NO_ASSERT__"])

    _enableDebugSystemSettings(env)

    if (env.gcc_mode or env.zig_mode) and not env.noelf_mode:
        env.Append(LINKFLAGS=["-z", "noexecstack"])

    # For MinGW64 we need to tell the subsystem to target as well as to
    # automatically import everything used.
    if env.mingw_mode:
        if not env.clang_mode:
            env.Append(LINKFLAGS=["-Wl,--enable-auto-import"])

    # Even if console is forced, for Win32 it means to specify Windows
    # subsystem, we can still attach or create.
    if env.console_mode in ("attach", "disable") and os.name == "nt":
        if env.mingw_mode or env.zig_mode:
            env.Append(LINKFLAGS=["-Wl,--subsystem,windows"])
            env.Append(CPPDEFINES=["_NUITKA_WINMAIN_ENTRY_POINT"])
        elif env.msvc_mode:
            env.Append(LINKFLAGS=["/SUBSYSTEM:windows"])
            env.Append(CPPDEFINES=["_NUITKA_WINMAIN_ENTRY_POINT"])

    if env.console_mode == "attach" and os.name == "nt":
        env.Append(CPPDEFINES=["_NUITKA_ATTACH_CONSOLE_WINDOW"])
        env.Append(LIBS=["User32"])

    if env.console_mode == "disable" and os.name == "nt":
        env.Append(CPPDEFINES=["_NUITKA_DISABLE_CONSOLE_WINDOW"])
        env.Append(LIBS=["User32"])

    if env.console_mode == "hide" and os.name == "nt":
        env.Append(CPPDEFINES=["_NUITKA_HIDE_CONSOLE_WINDOW"])
        env.Append(LIBS=["User32"])

    # Avoid dependency on MinGW libraries, spell-checker: ignore libgcc
    if env.mingw_mode and not env.clang_mode:
        env.Append(LINKFLAGS=["-static-libgcc"])

    # MinGW64 for 64 bits needs this due to CPython bugs.
    if env.mingw_mode and env.target_arch == "x86_64" and env.python_version < (3, 12):
        env.Append(CPPDEFINES=["MS_WIN64"])

    if env.msvc_mode:
        # For shell API usage to lookup app folders we need this.
        env.Append(LIBS=["Shell32"])

        # Make sure to find MSVC "link" in the right spot and to not use the
        # "link" of e.g. Git.
        addToPATH(
            env=env,
            dirname=os.path.dirname(getExecutablePath(env.the_compiler, env=env)),
            prefix=True,
        )

    # Since Fedora 36, the system Python will not link otherwise.
    if isFedoraBasedLinux():
        env.Append(CCFLAGS=["-fPIC"])

    # We use zlib for crc32 functionality
    zlib_inline_copy_dir = os.path.join(env.nuitka_src, "inline_copy", "zlib")
    if os.path.exists(os.path.join(zlib_inline_copy_dir, "crc32.c")):
        env.Append(
            CPPPATH=[
                zlib_inline_copy_dir,
            ],
        )
    else:
        # TODO: Should only happen for official Debian packages, and there we
        # can use the zlib static linking maybe, but for onefile it's not easy
        # to get it, so just use slow checksum for now.
        if onefile_compile:
            env.Append(CPPDEFINES=["_NUITKA_USE_OWN_CRC32"])
        else:
            env.Append(CPPDEFINES=["_NUITKA_USE_SYSTEM_CRC32"])
            env.Append(LIBS="z")

    if isAIX():
        aix_dll_addr_inline_copy_dir = os.path.join(
            env.nuitka_src, "inline_copy", "aix_dll_addr"
        )
        env.Append(
            CPPPATH=[
                aix_dll_addr_inline_copy_dir,
            ],
        )

    # Avoid them as appearing to be different files.
    if (
        env.gcc_mode
        and env.source_dir != "."
        and not getArgumentBool("full_names", False)
    ):
        if env.zig_mode:
            # Zig cc supports it
            file_prefix_map_supported = True
        elif env.clang_mode:
            # Clang 10+ supports --file-prefix-map
            file_prefix_map_supported = env.clang_version >= (10,)
        else:
            # GCC supports it starting from 8.0
            file_prefix_map_supported = env.gcc_version >= (8,)

        if file_prefix_map_supported:
            # TODO: This also lies for modules codes, the mapping should take
            # those into account as well.
            env.Append(
                CCFLAGS=[
                    "--file-prefix-map=%s=%s" % (os.path.normpath(env.source_dir), "."),
                    "--file-prefix-map=%s=%s"
                    % (
                        os.path.normpath(os.path.join(env.source_dir, "static_src")),
                        os.path.normpath(os.path.join(env.nuitka_src, "static_src")),
                    ),
                ]
            )

    if env.msvc_mode:
        # With Clang on Windows, there is also an linker to use.
        env.Append(
            CCFLAGS=[
                "/EHsc",  # No C++ exception handling code.
                "/J",  # default char type is unsigned.
                "/Gd",  # Use C calling convention by default.
                "/bigobj",  # Product object files with larger internal limits, spell-checker: ignore bigobj
            ]
        )

        # No incremental linking.
        env.Append(LINKFLAGS=["/INCREMENTAL:NO"])

        # Remember MSVC installation directory to find redist DLLs, spell-checker: ignore VCINSTALLDIR
        if "VCINSTALLDIR" in env["ENV"]:
            env["MSVC_InstallDirectory"] = getNormalizedPathJoin(
                env["ENV"]["VCINSTALLDIR"], ".."
            )

    # Use compiler/linker flags provided via environment variables, these
    # are always added and consider_environment_variables does not apply
    # since that's about selecting the compiler, not configuring it.
    importEnvironmentVariableSettings(env)

    # Add constant blob files and compute their total size. Must be after all
    # compiler settings are established so the resource mode decision is
    # correct. On x86_64 Linux, the default code model (small) limits
    # code+data to within 2GB, causing "relocation truncated to fit:
    # R_X86_64_PC32" linker errors when blobs are large. Use the medium code
    # model when blobs exceed a threshold.
    total_blob_size = _addConstantBlobFiles(
        env=env,
        source_dir=env.source_dir,
    )

    if (
        total_blob_size >= 1600 * 1024 * 1024
        and env.target_arch == "x86_64"
        and isLinux()
    ):
        if env.gcc_mode or env.clang_mode or env.zig_mode:
            scons_details_logger.info(
                "Total constant blob size is %.2f GB, using -mcmodel=medium"
                % (total_blob_size / (1024.0 * 1024.0 * 1024.0))
            )
            env.Append(CCFLAGS=["-mcmodel=medium"])


def _enablePgoSettings(env):
    if env.pgo_mode == "no":
        env.progressbar_name = "Backend"
    elif env.pgo_mode == "python":
        env.progressbar_name = "Python Profile"
        env.Append(CPPDEFINES=["_NUITKA_PGO_PYTHON"])
    elif env.pgo_mode == "generate":
        env.progressbar_name = "Profile"
        env.Append(CPPDEFINES=["_NUITKA_PGO_GENERATE"])

        if env.gcc_mode or env.zig_mode:
            env.Append(CCFLAGS=["-fprofile-generate"])
            env.Append(LINKFLAGS=["-fprofile-generate"])
        elif env.msvc_mode:
            env.Append(CCFLAGS=["/GL"])
            env.Append(LINKFLAGS=["/GENPROFILE:EXACT"])
            if not env.clangcl_mode:
                env.Append(LINKFLAGS=["/LTCG"])

        else:
            scons_logger.sysexit(
                "Error, PGO not supported for '%s' compiler." % env.the_cc_name
            )
    elif env.pgo_mode == "use":
        env.progressbar_name = "Backend"

        env.Append(CPPDEFINES=["_NUITKA_PGO_USE"])

        if env.gcc_mode or env.zig_mode:
            env.Append(CCFLAGS=["-fprofile-use"])
            env.Append(LINKFLAGS=["-fprofile-use"])
        elif env.msvc_mode:
            env.Append(CCFLAGS=["/GL"])
            env.Append(
                LINKFLAGS=[
                    "/USEPROFILE",
                ]
            )
        else:
            scons_logger.sysexit(
                "Error, PGO not supported for '%s' compiler." % env.the_cc_name
            )
    else:
        assert False, env.pgo_mode


def _enableDebugSystemSettings(env):
    if env.unstripped_mode:
        # Use debug format, so we get good tracebacks from it.
        if env.gcc_mode or env.zig_mode:
            env.Append(LINKFLAGS=["-g"])
            env.Append(CCFLAGS=["-g"])

            if not env.clang_mode:
                env.Append(CCFLAGS=["-feliminate-unused-debug-types"])
        elif env.msvc_mode:
            env.Append(CCFLAGS=["/Z7"])

            # Higher MSVC versions need this for parallel compilation
            if env.job_count > 1 and getMsvcVersion(env) >= (11,):
                env.Append(CCFLAGS=["/FS"])

            env.Append(LINKFLAGS=["/DEBUG"])
    else:
        if env.gcc_mode or env.zig_mode:
            if isMacOS():
                env.Append(LINKFLAGS=["-Wno-deprecated-declarations"])

            if not env.clangcl_mode and not isMacOS():
                env.Append(LINKFLAGS=["-s"])
        elif env.msvc_mode:
            env.Append(LINKFLAGS=["/DEBUG:NONE"])


def switchFromGccToGpp(env):
    if not env.gcc_mode or env.clang_mode:
        env.gcc_version = None
        return

    the_compiler = getExecutablePath(env.the_compiler, env)

    if the_compiler is None:
        return

    env.gcc_version = myDetectVersion(env, the_compiler)

    if env.gcc_version is None:
        scons_logger.sysexit("""\
Error, failed to detect gcc version of backend compiler '%s'.
""" % env.the_compiler)

    if "++" in env.the_cc_name:
        scons_logger.sysexit("""\
Error, compiler %s is apparently a C++ compiler, specify a C compiler instead.
""" % env.the_cc_name)

    # Enforce the minimum version, selecting a potentially existing g++-4.5
    # binary if it's not high enough. This is esp. useful under Debian which
    # allows all compiler to exist next to each other and where g++ might not be
    # good enough, but g++-4.5 would be.
    if env.gcc_version < (4, 4):
        scons_logger.sysexit("""\
The gcc compiler %s (version %s) doesn't have the sufficient \
version (>= 4.4).""" % (env.the_compiler, env.gcc_version))

    # CondaCC or newer.
    if env.mingw_mode and env.gcc_version < (5, 3):
        scons_logger.sysexit("""\
The MinGW64 compiler %s (version %s) doesn't have the sufficient \
version (>= 5.3).""" % (env.the_compiler, env.gcc_version))

    if env.gcc_version < (5,):
        if env.python_version < (3, 11):
            scons_logger.info(
                "The provided gcc is too old, switching to its g++ instead.",
                mnemonic="too-old-gcc",
            )

            # Switch to g++ from gcc then if possible, when C11 mode is false.
            the_gpp_compiler_name = os.path.basename(env.the_compiler).replace(
                "gcc", "g++"
            )

            the_gpp_compiler = os.path.join(
                os.path.dirname(env.the_compiler),
                the_gpp_compiler_name,
            )

            the_gpp_compiler_path = getExecutablePath(the_gpp_compiler, env=env)

            if the_gpp_compiler_path is None:
                the_gpp_compiler_path = getExecutablePath(
                    the_gpp_compiler_name, env=env
                )

            if the_gpp_compiler_path:
                env.the_compiler = the_gpp_compiler_path
                env.the_cc_name = env.the_cc_name.replace("gcc", "g++")

                env["CC"] = env.the_compiler
                env["CXX"] = env.the_compiler
            else:
                scons_logger.sysexit("""\
Error, your gcc is too old for C11 support, and no g++ ('%s') to
workaround that was found.""" % the_gpp_compiler_name)
        else:
            scons_logger.sysexit(
                "Error, your gcc is too old for C11 support, install a newer one.",
                mnemonic="too-old-gcc",
            )


def reportCCompiler(env, context, output_func):
    cc_output = env.the_cc_name

    if env.the_cc_name == "cl":
        cc_output = "%s %s" % (env.the_cc_name, getMsvcVersionString(env))
    elif isGccName(env.the_cc_name):
        cc_output = "%s %s" % (
            env.the_cc_name,
            (
                ".".join(str(d) for d in env.gcc_version)
                if env.gcc_version is not None
                else "not found"
            ),
        )
    elif isZigName(env.the_cc_name):
        cc_output = "%s %s" % (
            env.the_cc_name,
            (
                ".".join(str(d) for d in env.zig_version)
                if env.zig_version is not None
                else "not found"
            ),
        )
    elif isClangName(env.the_cc_name):
        cc_output = "%s %s" % (
            env.the_cc_name,
            (
                ".".join(str(d) for d in env.clang_version)
                if env.clang_version is not None
                else "not found"
            ),
        )
    else:
        cc_output = env.the_cc_name

    output_func(
        "%s C compiler: %s (%s)."
        % (context, getReportPath(env.the_compiler), cc_output)
    )


def importEnvironmentVariableSettings(env):
    """Import typical environment variables that compilation should use."""
    # spell-checker: ignore cppflags,cflags,ccflags,cxxflags,ldflags

    env.cpp_flags = os.getenv("CPPFLAGS")
    if env.cpp_flags:
        scons_logger.info("Scons: Inherited CPPFLAGS='%s' variable." % env.cpp_flags)
        env.Append(CPPFLAGS=env.cpp_flags.split())
    env.c_flags = os.getenv("CFLAGS")
    if env.c_flags:
        scons_logger.info("Inherited CFLAGS='%s' variable." % env.c_flags)
        env.Append(CCFLAGS=env.c_flags.split())
    env.cc_flags = os.getenv("CCFLAGS")
    if env.cc_flags:
        scons_logger.info("Inherited CCFLAGS='%s' variable." % env.cc_flags)
        env.Append(CCFLAGS=env.cc_flags.split())
    env.cxx_flags = os.getenv("CXXFLAGS")
    if env.cxx_flags:
        scons_logger.info("Scons: Inherited CXXFLAGS='%s' variable." % env.cxx_flags)
        env.Append(CXXFLAGS=env.cxx_flags.split())

    env.ld_flags = os.getenv("LDFLAGS")
    if env.ld_flags:
        scons_logger.info("Scons: Inherited LDFLAGS='%s' variable." % env.ld_flags)
        env.Append(LINKFLAGS=env.ld_flags.split())


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        https://www.gnu.org/licenses/agpl-3.0.txt
#
#     See also: "Nuitka Runtime Library Exception, Version 1.0" in file
#     "LICENSE-RUNTIME.txt" for additional permissions granted under Section 7.
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
