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


"""Scons interface.

Interaction with scons. Find the binary, and run it with a set of given
options.

"""

import contextlib
import copy
import os
import subprocess
import sys

from nuitka.__past__ import unicode
from nuitka.containers.OrderedDicts import OrderedDict
from nuitka.options.Options import (
    assumeYesForDownloads,
    getDebugModeIndications,
    getExperimentalIndications,
    getFcfProtectionMode,
    getFileVersion,
    getJobLimit,
    getLtoMode,
    getMacOSTargetArch,
    getMsvcVersion,
    getNoDeploymentIndications,
    getOnefileChildGraceTime,
    getProductVersion,
    getProgressBar,
    getPythonPathForScons,
    getTargetArch,
    getWindowsConsoleMode,
    getWindowsSplashScreen,
    getWindowsVersionInfoStrings,
    isClang,
    isDeploymentMode,
    isExperimental,
    isMingw64,
    isOnefileMode,
    isOnefileTempDirMode,
    isReproducibleBuild,
    isShowScons,
    isStandaloneMode,
    isUnstripped,
    isZig,
    shallCompileWithoutBuildDirectory,
    shallCreateAppBundle,
    shallCreateDiffableCompilationReport,
    shallDisableCCacheUsage,
    shallMakeDll,
    shallMakeExe,
    shallMakeModule,
    shallRunInDebugger,
    shallUsePythonDebug,
)
from nuitka.plugins.Hooks import (
    getExtraIncludeDirectories,
    getExtraLinkDirectories,
    getExtraLinkLibraries,
    getPreprocessorSymbols,
)
from nuitka.PythonFlavors import (
    isAnacondaPython,
    isMonolithPy,
    isMSYS2MingwPython,
    isSelfCompiledPythonUninstalled,
)
from nuitka.PythonVersions import (
    getSconsSupportingVersions,
    getSystemPrefixPath,
    getTargetPythonIncludePath,
    isPythonWithGil,
    isRunningInInterpreter,
    python_version,
    python_version_full_str,
    python_version_str,
)
from nuitka.reports.Reports import getCompilationReportFilename
from nuitka.Tracing import flushStandardOutputs, general, isQuiet, scons_logger
from nuitka.utils.AppDirs import (
    getCacheDir,
    getCacheDirEnvironmentVariableName,
)
from nuitka.utils.Download import getDownloadCacheDir, getDownloadCacheName
from nuitka.utils.Execution import (
    getExecutablePath,
    withEnvironmentVarsOverridden,
)
from nuitka.utils.FileOperations import (
    areSamePaths,
    changeFilenameExtension,
    cheapCopyFile,
    deleteFile,
    encodeToFilesystemEncoding,
    getDirectoryRealPath,
    getExternalUsePath,
    getNormalizedPath,
    getNormalizedPathJoin,
    getWindowsShortPathName,
    hasFilenameExtension,
    listDir,
    makePath,
    putTextFileContents,
    renameFile,
    withDirectoryChange,
)
from nuitka.utils.InstalledPythons import findInstalledPython
from nuitka.utils.Json import loadJsonFromFilename
from nuitka.utils.PrivatePipSpace import getZigBinaryPath
from nuitka.utils.SharedLibraries import detectBinaryMinMacOS
from nuitka.utils.Utils import (
    getArchitecture,
    isElfUsingPlatform,
    isMacOS,
    isWin32OrPosixWindows,
    isWin32Windows,
)

from .SconsCaching import checkCachingSuccess
from .SconsUtils import flushSconsReports, getSconsReportValue


def getSconsDataPath():
    """Return path to where data for scons lives, e.g. static C source files."""

    return os.path.dirname(__file__)


def _provideStaticSourceFiles(source_dir, filenames):
    scons_data_path = getSconsDataPath()
    static_src_dir = os.path.join(scons_data_path, "static_src")
    target_static_dir = os.path.join(source_dir, "static_src")

    for filename in filenames:
        src = os.path.join(static_src_dir, filename)
        dst = os.path.join(target_static_dir, filename)

        cheapCopyFile(src, dst)


def provideStaticSourceFilesBackend(source_dir):
    """Provide static source files for the backend build."""
    filenames = ["CompiledFunctionType.c"]

    if shallMakeExe() or shallMakeDll():
        filenames.append("MainProgram.c")

    _provideStaticSourceFiles(source_dir, filenames)


def provideStaticSourceFilesOnefile(source_dir):
    """Provide static source files for the onefile build."""
    filenames = ["OnefileBootstrap.c"]

    if getWindowsSplashScreen():
        filenames.append("OnefileSplashScreen.cpp")

    _provideStaticSourceFiles(source_dir, filenames)


def _checkWindowsSourceDirPathLength(source_dir_external, scons_filename):
    # Keep source file paths within 240 characters on Windows, leaving room
    # for derived suffixes like object names and resource usage reports.
    max_source_dir_length = 240 - 30

    if len(source_dir_external) > max_source_dir_length:
        return general.sysexit(
            """\
Error, the external source directory path '%s' is too long on Windows \
(%d characters, maximum %d) for '%s'. This check reserves 30 characters \
for source filenames within the safe 240 character limit. Use a shorter \
(as absolute value) output path or build basename."""
            % (
                source_dir_external,
                len(source_dir_external),
                max_source_dir_length,
                scons_filename,
            )
        )


def _getSconsInlinePath():
    """Return path to inline copy of scons."""

    return getNormalizedPathJoin(getSconsDataPath(), "inline_copy")


def _getInlineSconsVersionForPythonVersion(python_version_info):
    if os.name == "nt" and python_version_info < (3, 5):
        return scons_logger.sysexit(
            "Error, on Windows scons must be run with Python 3.5 or higher."
        )

    if (3, 0) <= python_version_info < (3, 5):
        return scons_logger.sysexit(
            "Error, scons must not be run with Python3 older than 3.5."
        )

    if python_version_info < (2, 7):
        # Non-Windows, Python 2.6, mostly older RHEL
        return "scons-2.3.2"
    elif os.name == "nt" and python_version_info >= (3, 7):
        # Windows can use latest, supported MSVC 2026 this way
        return "scons-4.10.1"
    elif os.name == "nt" and python_version_info >= (3, 5):
        # Windows can use latest, supported MSVC 2022 this way
        return "scons-4.3.0"
    else:
        # Everything else 2.7 or higher works with this.
        return "scons-3.1.2"


def getInlineSconsVersion():
    """Return Scons inline copy version for current Python runtime."""

    return _getInlineSconsVersionForPythonVersion(sys.version_info)


def getInlineSconsLibPath():
    """Return path to inline copy of selected Scons package."""

    return getNormalizedPathJoin(_getSconsInlinePath(), "lib", getInlineSconsVersion())


def _getCompiledSconsBinaryCall():
    if isRunningInInterpreter():
        return None

    executable = sys.modules["__main__"].__compiled__.original_argv0
    executable_dir = os.path.dirname(executable)
    _binary_name, binary_ext = os.path.splitext(os.path.basename(executable))

    scons_binary = os.path.join(executable_dir, "scons" + binary_ext)

    if os.path.exists(scons_binary):
        return [scons_binary]

    return None


def _getSconsBinaryCall():
    """Return a way to execute Scons.

    Using potentially in-line copy if no system Scons is available
    or if we are on Windows, there it is mandatory.
    """

    # Handle the case where we are running from a compiled binary,
    # and Scons is included as multidist binary.
    compiled_scons_command = _getCompiledSconsBinaryCall()
    if compiled_scons_command is not None:
        return compiled_scons_command

    inline_path = getNormalizedPathJoin(_getSconsInlinePath(), "bin", "scons.py")

    if os.path.exists(inline_path) and not isExperimental("force-system-scons"):
        return [
            _getPythonForSconsExePath(),
            "-W",
            "ignore",  # Disable Python warnings in case of debug Python.
            getExternalUsePath(inline_path),
        ]
    else:
        scons_path = getExecutablePath("scons")

        if scons_path is not None:
            return [scons_path]
        else:
            scons_logger.sysexit(
                "Error, the inline copy of scons is not present, nor a scons binary in the PATH."
            )


def _getPythonForSconsExePath():
    """Find a way to call any Python that works for Scons.

    Scons needs it as it doesn't support all Python versions.
    """
    python_exe = getPythonPathForScons()

    if python_exe is not None:
        return python_exe

    # Our inline copy needs no other module, just the right version of Python is needed.
    python_for_scons = findInstalledPython(
        python_versions=reversed(getSconsSupportingVersions()),
        module_specs=None,
    )

    if python_for_scons is None:
        if isWin32Windows():
            scons_python_requirement = "Python 3.5 or higher"
        else:
            scons_python_requirement = "Python 2.6, 2.7 or Python >= 3.5"

        scons_logger.sysexit("""\
Error, while Nuitka works with older Python, Scons does not, and therefore
Nuitka needs to find a %s executable, so please install it.

You may provide it using option "--python-for-scons=path_to_python.exe"
in case it is not visible in registry or PATH, e.g. due to using an
uninstalled Anaconda Python.
""" % scons_python_requirement)

    return python_for_scons.getPythonExe()


@contextlib.contextmanager
def _setupSconsEnvironment2():
    os.environ["NUITKA_PYTHON_EXE_PATH"] = sys.executable

    # Remove environment variables that can only harm if we have to switch
    # major Python versions, these cannot help Python2 to execute scons, this
    # is a bit of noise, but helpful.
    old_pythonpath = None
    old_pythonhome = None

    if python_version >= 0x300:
        if "PYTHONPATH" in os.environ:
            old_pythonpath = os.environ["PYTHONPATH"]
            del os.environ["PYTHONPATH"]

        if "PYTHONHOME" in os.environ:
            old_pythonhome = os.environ["PYTHONHOME"]
            del os.environ["PYTHONHOME"]

    os.environ["NUITKA_PACKAGE_DIR"] = os.path.normpath(
        os.path.join(getSconsDataPath(), "..")
    )

    # When downloading in Scons, use the external path.
    if isWin32Windows():
        download_cache_dir = getDownloadCacheDir()
        makePath(download_cache_dir)
        os.environ[getCacheDirEnvironmentVariableName(getDownloadCacheName())] = (
            getExternalUsePath(download_cache_dir)
        )

        msvc_config_cache_dir = getCacheDir("scons-msvc-config")
        makePath(msvc_config_cache_dir)

        try:
            os.environ["SCONS_CACHE_MSVC_CONFIG"] = encodeToFilesystemEncoding(
                getNormalizedPath(msvc_config_cache_dir)
            )
        except UnicodeEncodeError:
            pass

    yield

    if old_pythonpath is not None:
        os.environ["PYTHONPATH"] = old_pythonpath

    if old_pythonhome is not None:
        os.environ["PYTHONHOME"] = old_pythonhome

    del os.environ["NUITKA_PYTHON_EXE_PATH"]

    del os.environ["NUITKA_PACKAGE_DIR"]


@contextlib.contextmanager
def _setupSconsEnvironment():
    """Setup the scons execution environment.

    For the target Python we provide "NUITKA_PYTHON_DLL_PATH" to see where the
    Python DLL lives, in case it needs to be copied, and then also the
    "NUITKA_PYTHON_EXE_PATH" to find the Python binary itself.

    We also need to preserve "PYTHONPATH" and "PYTHONHOME", but remove it
    potentially as well, so not to confuse the other Python binary used to run
    scons.
    """

    # For Python2, and bad Python3 setups, avoid unicode working directory.
    if isWin32Windows():
        change_dir = getWindowsShortPathName(os.getcwd())
    else:
        change_dir = None

    with withDirectoryChange(change_dir, allow_none=True):
        with _setupSconsEnvironment2():
            yield


def _buildSconsCommand(options, scons_filename):
    """Build the scons command to run.

    The options are a dictionary to be passed to scons as a command line,
    and other scons stuff is set.
    """

    scons_command = _getSconsBinaryCall()

    if not isShowScons():
        scons_command.append("--quiet")

    scons_command += [
        # The scons file
        "-f",
        getExternalUsePath(os.path.join(getSconsDataPath(), scons_filename)),
        # Parallel compilation.
        "--jobs",
        str(getJobLimit()),
        # Do not warn about deprecation from Scons
        "--warn=no-deprecated",
        # Don't load "site_scons" at all.
        "--no-site-dir",
    ]

    if isShowScons():
        scons_command.append("--debug=stacktrace")

    # Python2, encoding unicode values
    def encode(value):
        if str is bytes and type(value) is unicode:
            return value.encode("utf8")
        else:
            return value

    # Option values to provide to scons. Find these in the caller.
    for key, value in options.items():
        if value is None:
            scons_logger.sysexit(
                "Error, failure to provide argument for '%s', please report bug." % key
            )

        scons_command.append(key + "=" + encode(value))

    # Python2, make argument encoding recognizable.
    if str is bytes:
        scons_command.append("arg_encoding=utf8")

    return scons_command


def _createSconsDebugScript(source_dir, scons_command):
    # we wrote debug shell script only if build process called, not "--version" call.
    scons_debug_python_name = "scons-debug.py"

    # Reduce environment variables from unusable stuff that is also very runtime
    # dependent, spell-checker: ignore COLORTERM
    env = OrderedDict(
        (key, value)
        for (key, value) in os.environ.items()
        if not key.startswith(("VSCODE", "GIT", "WSL", "COLORTERM", "TERM"))
    )

    putTextFileContents(
        filename=getNormalizedPathJoin(source_dir, scons_debug_python_name),
        contents="""\
# -*- coding: utf-8 -*-

import sys
import os
import subprocess

exit_code = subprocess.call(
    %(scons_command)r,
    env={%(env)s},
    shell=False
)"""
        % {
            "scons_command": scons_command,
            "env": ",".join("%r: %r" % (key, value) for key, value in env.items()),
        },
        encoding="utf8",
    )

    if isWin32Windows():
        script_extension = ".bat"

        script_prelude = """\
cd "%~dp0"
                    """
    else:
        script_extension = ".sh"

        script_prelude = """\
#!/bin/bash
cd "${0%/*}"
"""

    putTextFileContents(
        filename=getNormalizedPathJoin(
            source_dir,
            changeFilenameExtension(scons_debug_python_name, script_extension),
        ),
        contents="""\
%(script_prelude)s

"%(scons_python)s" "%(scons_debug_script_name)s"
"""
        % {
            "script_prelude": script_prelude,
            "scons_python": scons_command[0],
            "scons_debug_script_name": scons_debug_python_name,
        },
        encoding="utf8",
    )


def _removeUnwantedArtifacts(scons_created_exe):
    if isWin32Windows():
        for extension in (".exp", ".lib"):
            linker_left_over = changeFilenameExtension(scons_created_exe, extension)

            deleteFile(linker_left_over, must_exist=False)

        if not isUnstripped():
            pdb_filename = changeFilenameExtension(scons_created_exe, ".pdb")

            if os.path.exists(pdb_filename):
                return scons_logger.sysexit(
                    "Error, unwanted '.pdb' file '%s' was created during the build. Report the bug."
                    % pdb_filename
                )


def applyPreprocessorSymbols(scons_options, onefile):
    cpp_defines = getPreprocessorSymbols(onefile=onefile)
    if cpp_defines:
        scons_options["cpp_defines"] = ",".join(
            "%s%s%s" % (key, "=" if value else "", value or "")
            for key, value in cpp_defines.items()
        )


def runScons(
    scons_options,
    env_values,
    scons_filename,
    source_dir,
    source_dir_external,
):
    # We are handling quite a few error cases, as this contains transfer of
    # exceptions, workarounds for non-encodable filenames, and other error
    # handling. pylint: disable=too-many-branches,too-many-statements

    with _setupSconsEnvironment():
        env_values["_NUITKA_BUILD_DEFINITIONS_CATALOG"] = ",".join(env_values.keys())
        compile_without_build_directory = (
            source_dir is not None and shallCompileWithoutBuildDirectory()
        )

        if compile_without_build_directory and isWin32Windows():
            assert source_dir_external is not None
            _checkWindowsSourceDirPathLength(source_dir_external, scons_filename)

        if source_dir is not None:
            scons_options = copy.deepcopy(scons_options)
            scons_options["source_dir"] = source_dir

        orig_result_exe = scons_options.get("result_exe")

        if compile_without_build_directory:
            # Make sure we become non-local, by changing all paths to be
            # absolute, but ones that can be resolved by any program
            # externally, as the Python of Scons may not be good at unicode.

            scons_options["source_dir"] = "."
            scons_options["nuitka_src"] = getExternalUsePath(
                scons_options["nuitka_src"]
            )

            if orig_result_exe is not None:
                scons_options["result_exe"] = getExternalUsePath(
                    scons_options["result_exe"], only_dirname=True
                )

        env_values = OrderedDict(env_values)
        env_values["_NUITKA_BUILD_DEFINITIONS_CATALOG"] = ",".join(env_values.keys())

        if "zig_exe_path" in scons_options and "CC" not in env_values:
            env_values["CC"] = scons_options["zig_exe_path"]

        # Avoid Visual Studio developer shell telemetry from leaking to build
        # output. spell-checker: ignore VSCMD_SKIP_SENDTELEMETRY
        if isWin32Windows():
            env_values["VSCMD_SKIP_SENDTELEMETRY"] = "1"

        # Pass quiet setting to scons via environment variable.
        env_values["NUITKA_QUIET"] = "1" if isQuiet() else "0"

        if isShowScons():
            env_values["NUITKA_SCONS_CHECK_MSVC_CACHE"] = "1"

        scons_command = _buildSconsCommand(
            options=scons_options, scons_filename=scons_filename
        )

        if isShowScons():
            scons_logger.info("Scons command: %s" % " ".join(scons_command))

        flushStandardOutputs()

        with withEnvironmentVarsOverridden(env_values):
            # Create debug script to quickly re-run this step only.
            if compile_without_build_directory:
                assert source_dir_external is not None

                _createSconsDebugScript(
                    source_dir=source_dir, scons_command=scons_command
                )

                source_dir_cwd = source_dir_external
            else:
                source_dir_cwd = None

            try:
                result = subprocess.call(
                    scons_command,
                    shell=False,
                    cwd=source_dir_cwd,
                )
            except KeyboardInterrupt:
                scons_logger.sysexit("User interrupted scons build.")
            else:
                # TODO: We might want to make a difference for where reporting makes sense or not.
                if result == 27:
                    error_json_dir = source_dir
                    if error_json_dir and error_json_dir != os.devnull:
                        scons_error_json = getNormalizedPathJoin(
                            error_json_dir, "scons-error.json"
                        )
                        if os.path.exists(scons_error_json):
                            error_info = loadJsonFromFilename(scons_error_json)

                            if error_info is not None:
                                return general.sysexit(**error_info)

        # TODO: Actually this should only flush one of these, namely the one for
        # current source_dir.
        flushSconsReports()

        if source_dir is not None and result == 0:
            if "result_exe" in scons_options:
                scons_created_exe = getNormalizedPath(
                    getSconsReportValue(source_dir, "TARGET")
                )

                if not os.path.exists(scons_created_exe):
                    scons_logger.sysexit(
                        "Error, scons failed to create the expected file %r. "
                        % scons_created_exe
                    )

                _removeUnwantedArtifacts(scons_created_exe)

                if not areSamePaths(scons_options["result_exe"], scons_created_exe):
                    renameFile(scons_created_exe, orig_result_exe)

            checkCachingSuccess(source_dir)

    return result == 0


def asBoolStr(value):
    """Encode booleans for transfer via command line."""

    return "true" if value else "false"


def cleanSconsDirectory(source_dir):
    """Clean scons build directory."""

    # spell-checker: ignore gcda
    extensions = (
        ".bin",
        ".c",
        ".cpp",
        ".exp",
        ".h",
        ".lib",
        ".manifest",
        ".o",
        ".obj",
        ".os",
        ".rc",
        ".res",
        ".S",
        ".txt",
        ".pickle",
        ".const",
        ".gcda",
        ".pgd",
        ".pgc",
        ".json",
    )

    def check(path):
        if hasFilenameExtension(path, extensions):
            deleteFile(path, must_exist=True)

    if os.path.isdir(source_dir):
        for path, _filename in listDir(source_dir):
            check(path)

        static_dir = getNormalizedPathJoin(source_dir, "static_src")

        if os.path.exists(static_dir):
            for path, _filename in listDir(static_dir):
                check(path)

        plugins_dir = getNormalizedPathJoin(source_dir, "plugins")

        if os.path.exists(plugins_dir):
            for path, _filename in listDir(plugins_dir):
                check(path)

        blobs_dir = getNormalizedPathJoin(source_dir, "blobs")

        if os.path.exists(blobs_dir):
            for path, _filename in listDir(blobs_dir):
                check(path)


def getCommonSconsOptions():
    # Scons gets transported many details, that we express as variables, and
    # have checks for them, leading to many branches and statements.
    # pylint: disable=too-many-branches,too-many-statements

    scons_options = OrderedDict()

    scons_options["nuitka_src"] = getSconsDataPath()

    scons_options["python_version"] = python_version_str
    scons_options["python_version_full_str"] = python_version_full_str

    scons_options["python_prefix"] = getDirectoryRealPath(getSystemPrefixPath())
    scons_options["python_include_path"] = getTargetPythonIncludePath(
        logger=scons_logger,
        python_debug=shallUsePythonDebug(),
        self_compiled_python_uninstalled=isSelfCompiledPythonUninstalled(),
    )

    scons_options["experimental"] = ",".join(getExperimentalIndications())

    scons_options["debug_modes"] = ",".join(getDebugModeIndications())

    scons_options["deployment"] = asBoolStr(isDeploymentMode())

    scons_options["no_deployment"] = ",".join(getNoDeploymentIndications())

    if (
        getCompilationReportFilename() is not None
        and not shallCreateDiffableCompilationReport()
    ):
        scons_options["collect_resources"] = asBoolStr(True)

    scons_options["gil_mode"] = asBoolStr(isPythonWithGil())

    if shallRunInDebugger():
        scons_options["full_names"] = asBoolStr(True)

    if assumeYesForDownloads():
        scons_options["assume_yes_for_downloads"] = asBoolStr(True)

    if getProgressBar() != "auto":
        scons_options["progress_bar"] = getProgressBar()

    if isClang():
        scons_options["clang_mode"] = asBoolStr(True)

    if isShowScons():
        scons_options["show_scons"] = asBoolStr(True)

    if isMingw64():
        scons_options["mingw_mode"] = asBoolStr(True)

    if isZig():
        if "CC" not in os.environ:
            zig_exe_path = getExecutablePath("zig")

            if zig_exe_path is None:
                zig_exe_path = getZigBinaryPath(
                    logger=scons_logger,
                    assume_yes_for_downloads=assumeYesForDownloads(),
                    reject_message="Nuitka with '--zig' depends on 'zig' to compile.",
                )

            scons_options["zig_exe_path"] = zig_exe_path

            if scons_options["zig_exe_path"] is None:
                scons_logger.sysexit("Nuitka with '--zig' depends on 'zig' to compile.")

    if getMsvcVersion():
        scons_options["msvc_version"] = getMsvcVersion()

    if shallDisableCCacheUsage():
        scons_options["disable_ccache"] = asBoolStr(True)

    if isWin32Windows() and getWindowsConsoleMode() != "attach":
        scons_options["console_mode"] = getWindowsConsoleMode()

    if getLtoMode() != "auto":
        scons_options["lto_mode"] = getLtoMode()

    scons_options["reproducible_mode"] = asBoolStr(isReproducibleBuild())

    if not isElfUsingPlatform():
        scons_options["noelf_mode"] = asBoolStr(True)

    if isUnstripped():
        scons_options["unstripped_mode"] = asBoolStr(True)

    if isAnacondaPython():
        scons_options["anaconda_python"] = asBoolStr(True)

    if isMSYS2MingwPython():
        scons_options["msys2_mingw_python"] = asBoolStr(True)

    if isSelfCompiledPythonUninstalled():
        scons_options["self_compiled_python_uninstalled"] = asBoolStr(True)

    cpp_include_dirs = getExtraIncludeDirectories()
    if cpp_include_dirs:
        scons_options["cpp_include_dirs"] = ",".join(cpp_include_dirs)

    link_dirs = getExtraLinkDirectories()
    if link_dirs:
        scons_options["link_dirs"] = ",".join(link_dirs)

    link_libraries = getExtraLinkLibraries()
    if link_libraries:
        scons_options["link_libraries"] = ",".join(link_libraries)

    if isMacOS():
        macos_min_version = detectBinaryMinMacOS(sys.executable)

        if macos_min_version is None:
            general.sysexit(
                "Could not detect minimum macOS version for '%s'." % sys.executable
            )

        scons_options["macos_min_version"] = macos_min_version

        scons_options["macos_target_arch"] = getMacOSTargetArch()

    scons_options["target_arch"] = getArchitecture()
    c_target_arch = getTargetArch()
    if c_target_arch is not None:
        scons_options["c_target_arch"] = c_target_arch

    if getFcfProtectionMode() != "auto":
        scons_options["cf_protection"] = getFcfProtectionMode()

    env_values = OrderedDict()

    string_values = getWindowsVersionInfoStrings()
    if "CompanyName" in string_values:
        env_values["NUITKA_COMPANY_NAME"] = string_values["CompanyName"]
    if "ProductName" in string_values:
        env_values["NUITKA_PRODUCT_NAME"] = string_values["ProductName"]

    # Merge version information if possible, to avoid collisions, or deep nesting
    # in file system.
    product_version = getProductVersion()
    file_version = getFileVersion()

    if product_version is None:
        product_version = file_version
    if file_version is None:
        file_version = product_version

    if product_version != file_version:
        effective_version = "%s-%s" % (
            product_version,
            file_version,
        )
    else:
        effective_version = file_version

    if effective_version:
        env_values["NUITKA_VERSION_COMBINED"] = effective_version

    if product_version:
        env_values["NUITKA_PRODUCT_VERSION"] = product_version

    if file_version:
        env_values["NUITKA_FILE_VERSION"] = file_version

    if isMonolithPy() and not isWin32OrPosixWindows():
        # Override environment CC and CXX to match build compiler.
        import sysconfig

        env_values["CC"] = sysconfig.get_config_var("CC").split()[0]
        env_values["CXX"] = sysconfig.get_config_var("CXX").split()[0]

    scons_options["module_mode"] = asBoolStr(shallMakeModule())
    scons_options["dll_mode"] = asBoolStr(shallMakeDll())
    scons_options["exe_mode"] = asBoolStr(shallMakeExe())

    if isStandaloneMode():
        scons_options["standalone_mode"] = asBoolStr(True)

    if isOnefileMode():
        scons_options["onefile_mode"] = asBoolStr(True)

        # Onefile grace time is shared, because client will also suicide based
        # on it.
        env_values["_NUITKA_ONEFILE_CHILD_GRACE_TIME_INT"] = str(
            getOnefileChildGraceTime()
        )

        if isOnefileTempDirMode():
            scons_options["onefile_temp_mode"] = asBoolStr(True)

    # TODO: Some things are going to hate that, we might need to bundle
    # for accelerated mode still.
    if shallCreateAppBundle():
        scons_options["macos_bundle_mode"] = asBoolStr(True)

    return scons_options, env_values


#     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.
