mirror of
https://github.com/Nuitka/Nuitka.git
synced 2025-12-14 20:35:49 +01:00
1093 lines
37 KiB
Python
1093 lines
37 KiB
Python
# Copyright 2025, 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 nuitka.Tracing import scons_details_logger, scons_logger
|
|
from nuitka.utils.Download import getCachedDownloadedMinGW64
|
|
from nuitka.utils.FileOperations import (
|
|
getReportPath,
|
|
openTextFile,
|
|
putTextFileContents,
|
|
)
|
|
from nuitka.utils.Utils import (
|
|
isFedoraBasedLinux,
|
|
isMacOS,
|
|
isPosixWindows,
|
|
isWin32Windows,
|
|
)
|
|
|
|
from .SconsHacks import myDetectVersion
|
|
from .SconsUtils import (
|
|
addBinaryBlobSection,
|
|
addToPATH,
|
|
createEnvironment,
|
|
decideArchMismatch,
|
|
getExecutablePath,
|
|
getLinkerArch,
|
|
getMsvcVersion,
|
|
getMsvcVersionString,
|
|
isClangName,
|
|
isGccName,
|
|
isZigName,
|
|
raiseNoCompilerFoundErrorExit,
|
|
setEnvironmentVariable,
|
|
)
|
|
|
|
# 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,fcompare,-ftrack,-fvisibility,-municode,
|
|
# spell-checker: ignore -feliminate,noexecstack,implib
|
|
# spell-checker: ignore LTCG,GENPROFILE,USEPROFILE,CGTHREADS
|
|
|
|
|
|
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
|
|
"""
|
|
|
|
if 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:
|
|
c11_mode = True
|
|
|
|
# For now, zig doesn't support C11 mode in the form needed by Nuitka
|
|
if isZigName(env.the_cc_name):
|
|
c11_mode = False
|
|
elif env.gcc_mode and env.gcc_version >= (5,):
|
|
c11_mode = True
|
|
else:
|
|
c11_mode = False
|
|
|
|
if c11_mode:
|
|
if env.gcc_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,
|
|
job_count,
|
|
):
|
|
# 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.nuitka_python:
|
|
lto_mode = True
|
|
reason = "known to be supported (Nuitka-Python)"
|
|
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
|
|
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 and env.clang_mode:
|
|
if 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.
|
|
module_count_threshold = 250
|
|
if (
|
|
orig_lto_mode == "auto"
|
|
and lto_mode
|
|
and env.module_count > module_count_threshold
|
|
and not env.nuitka_python
|
|
):
|
|
lto_mode = False
|
|
reason = "might to be too slow %s (>= %d threshold), force with --lto=yes" % (
|
|
env.module_count,
|
|
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
|
|
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 and lto_mode:
|
|
if env.clang_mode:
|
|
env.Append(CCFLAGS=["-flto"])
|
|
env.Append(LINKFLAGS=["-flto"])
|
|
else:
|
|
env.Append(CCFLAGS=["-flto=%d" % job_count])
|
|
env.Append(LINKFLAGS=["-flto=%d" % 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:
|
|
env.Append(CCFLAGS=["/GL"])
|
|
|
|
if not env.clangcl_mode:
|
|
env.Append(LINKFLAGS=["/LTCG"])
|
|
|
|
if getMsvcVersion(env) >= (14, 3):
|
|
env.Append(LINKFLAGS=["/CGTHREADS:%d" % job_count])
|
|
|
|
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, clang_mode, msvc_version, assume_yes_for_downloads, download_ok
|
|
):
|
|
"""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":
|
|
# 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.
|
|
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.endswith("/usr/bin/gcc.exe"):
|
|
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
|
|
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
|
|
|
|
if compiler_path is None and msvc_version is None:
|
|
scons_details_logger.info(
|
|
"No usable C compiler, attempt fallback to winlibs gcc or clang."
|
|
)
|
|
|
|
# This will download "gcc.exe" (and "clang.exe") when all others have been
|
|
# rejected and MSVC is not enforced.
|
|
compiler_path = getCachedDownloadedMinGW64(
|
|
target_arch=target_arch,
|
|
assume_yes_for_downloads=assume_yes_for_downloads,
|
|
download_ok=download_ok,
|
|
)
|
|
|
|
if compiler_path is not None:
|
|
addToPATH(env, os.path.dirname(compiler_path), prefix=True)
|
|
|
|
env = createEnvironment(
|
|
mingw_mode=True,
|
|
msvc_version=None,
|
|
target_arch=target_arch,
|
|
experimental=env.experimental_flags,
|
|
no_deployment=env.no_deployment_flags,
|
|
debug_modes=env.debug_modes_flags,
|
|
)
|
|
|
|
if clang_mode:
|
|
env["CC"] = os.path.join(
|
|
os.path.dirname(compiler_path), "clang.exe"
|
|
)
|
|
|
|
if env["CC"] is None:
|
|
raiseNoCompilerFoundErrorExit()
|
|
|
|
return env
|
|
|
|
|
|
def decideConstantsBlobResourceMode(env, module_mode):
|
|
if "NUITKA_RESOURCE_MODE" in os.environ:
|
|
resource_mode = os.environ["NUITKA_RESOURCE_MODE"]
|
|
reason = "user provided"
|
|
elif isWin32Windows():
|
|
resource_mode = "win_resource"
|
|
reason = "default for Windows"
|
|
elif isPosixWindows():
|
|
resource_mode = "linker"
|
|
reason = "default MSYS2 Posix"
|
|
elif isMacOS():
|
|
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 env.lto_mode and env.gcc_mode and not env.clang_mode:
|
|
if module_mode:
|
|
resource_mode = "code"
|
|
else:
|
|
resource_mode = "linker"
|
|
|
|
reason = "default for lto gcc with --lto bugs for incbin"
|
|
else:
|
|
# All is done already, this is for most platforms.
|
|
resource_mode = "incbin"
|
|
reason = "default"
|
|
|
|
return resource_mode, reason
|
|
|
|
|
|
def addConstantBlobFile(env, blob_filename, resource_desc, target_arch):
|
|
resource_mode, reason = resource_desc
|
|
|
|
assert blob_filename.endswith(".bin"), blob_filename
|
|
|
|
scons_details_logger.info(
|
|
"Using resource mode: '%s' (%s)." % (resource_mode, reason)
|
|
)
|
|
|
|
if resource_mode == "win_resource":
|
|
# On Windows constants can be accessed as a resource by Nuitka at run time afterwards.
|
|
env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_RESOURCE"])
|
|
elif resource_mode == "mac_section":
|
|
env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_MACOS_SECTION"])
|
|
|
|
addBinaryBlobSection(
|
|
env=env,
|
|
blob_filename=blob_filename,
|
|
section_name=os.path.basename(blob_filename)[:-4].lstrip("_"),
|
|
)
|
|
elif resource_mode == "incbin":
|
|
env.Append(CPPDEFINES=["_NUITKA_CONSTANTS_FROM_INCBIN"])
|
|
|
|
constants_generated_filename = os.path.join(
|
|
env.source_dir, "__constants_data.c"
|
|
)
|
|
|
|
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"
|
|
#endif
|
|
|
|
#include "nuitka/incbin.h"
|
|
|
|
INCBIN(constant_bin, "%(blob_filename)s");
|
|
|
|
unsigned char const *getConstantsBlobData(void) {
|
|
return constant_bin_data;
|
|
}
|
|
"""
|
|
% {"blob_filename": blob_filename},
|
|
)
|
|
|
|
elif resource_mode == "linker":
|
|
# 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 = "constant_bin_data"
|
|
if env.mingw_mode:
|
|
constant_bin_link_name = "_" + constant_bin_link_name
|
|
|
|
env.Append(
|
|
LINKFLAGS=[
|
|
"-Wl,-b",
|
|
"-Wl,binary",
|
|
"-Wl,%s" % blob_filename,
|
|
"-Wl,-b",
|
|
"-Wl,%s"
|
|
% getLinkerArch(
|
|
target_arch=target_arch,
|
|
mingw_mode=env.mingw_mode or isPosixWindows(),
|
|
),
|
|
"-Wl,-defsym",
|
|
"-Wl,%s=_binary_%s___constants_bin_start"
|
|
% (
|
|
constant_bin_link_name,
|
|
"".join(re.sub("[^a-zA-Z0-9_]", "_", c) for c in env.source_dir),
|
|
),
|
|
]
|
|
)
|
|
elif resource_mode in ("code", "c23_embed"):
|
|
# 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 !defined(_NUITKA_EXPERIMENTAL_WRITEABLE_CONSTANTS)
|
|
const
|
|
#endif
|
|
unsigned char constant_bin_data[] =\n{\n
|
|
"""
|
|
)
|
|
|
|
if 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()
|
|
else:
|
|
scons_logger.sysexit(
|
|
"Error, illegal resource mode '%s' specified" % resource_mode
|
|
)
|
|
|
|
|
|
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 setupCCompiler(env, lto_mode, pgo_mode, job_count, 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
|
|
|
|
# Enable LTO for compiler.
|
|
_enableLtoSettings(
|
|
env=env,
|
|
lto_mode=lto_mode,
|
|
pgo_mode=pgo_mode,
|
|
job_count=job_count,
|
|
)
|
|
|
|
_enableC11Settings(env)
|
|
|
|
if env.gcc_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.
|
|
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 "clang" in env.the_cc_name:
|
|
env.Append(CCFLAGS=["-w"])
|
|
env.Append(CPPDEFINES=["_XOPEN_SOURCE"])
|
|
|
|
# Don't export anything by default, this should create smaller executables.
|
|
env.Append(CCFLAGS=["-fvisibility=hidden", "-fvisibility-inlines-hidden"])
|
|
|
|
if env.debug_mode and "allow-c-warnings" not in env.experimental_flags:
|
|
env.Append(CCFLAGS=["-Wunused-but-set-variable"])
|
|
|
|
# Support for macOS standalone to run on older OS versions.
|
|
if isMacOS():
|
|
setEnvironmentVariable(env, "MACOSX_DEPLOYMENT_TARGET", env.macos_min_version)
|
|
|
|
target_flag = "--target=%s-macos%s" % (
|
|
env.macos_target_arch,
|
|
env.macos_min_version,
|
|
)
|
|
|
|
env.Append(CCFLAGS=[target_flag])
|
|
env.Append(LINKFLAGS=[target_flag])
|
|
|
|
# The 32 bits MinGW does not default for API level properly, so help it.
|
|
if env.mingw_mode:
|
|
# Windows XP
|
|
env.Append(CPPDEFINES=["_WIN32_WINNT=0x0501"])
|
|
|
|
# Unicode entry points for programs.
|
|
if env.mingw_mode:
|
|
env.Append(LINKFLAGS=["-municode"])
|
|
|
|
# Detect the gcc version
|
|
if env.gcc_version is None and env.gcc_mode and not env.clang_mode:
|
|
env.gcc_version = myDetectVersion(env, env.the_compiler)
|
|
|
|
# Older g++ complains about aliasing with Py_True and Py_False, but we don't
|
|
# care.
|
|
if env.gcc_mode and not env.clang_mode and env.gcc_version < (4, 5):
|
|
env.Append(CCFLAGS=["-fno-strict-aliasing"])
|
|
|
|
# For gcc 4.6 or higher, there are some new interesting functions.
|
|
if env.gcc_mode and not env.clang_mode and env.gcc_version >= (4, 6):
|
|
env.Append(CCFLAGS=["-fpartial-inlining"])
|
|
|
|
if env.debug_mode:
|
|
env.Append(CCFLAGS=["-Wunused-but-set-variable"])
|
|
|
|
# Save some memory for gcc by not tracing macro code locations at all.
|
|
if (
|
|
not env.debug_mode
|
|
and env.gcc_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 and not env.clang_mode:
|
|
env.Append(CCFLAGS=["-Wno-deprecated-declarations"])
|
|
|
|
# The var-tracking does not scale, disable it. Should we really need it, we
|
|
# can enable it. TODO: Does this cause a performance loss?
|
|
if env.gcc_mode and not env.clang_mode:
|
|
env.Append(CCFLAGS=["-fno-var-tracking"])
|
|
|
|
# For large files, these can issue warnings about disabling
|
|
# itself, while we do not need it really.
|
|
if env.gcc_mode and not env.clang_mode 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 and not env.clang_mode:
|
|
env.Append(CCFLAGS=["-fcompare-debug-second"])
|
|
|
|
# Prevent using LTO when told not to use it, causes errors with some
|
|
# static link libraries.
|
|
if (
|
|
env.gcc_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 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 Nuitka-Python is not affected.
|
|
env.Append(
|
|
LINKFLAGS=[
|
|
(
|
|
"-O3"
|
|
if env.nuitka_python
|
|
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 and env.gcc_version >= (4, 8)):
|
|
env.Append(CCFLAGS=["-Og"])
|
|
elif env.gcc_mode:
|
|
env.Append(CCFLAGS=["-O1"])
|
|
elif env.msvc_mode:
|
|
env.Append(CCFLAGS=["-O2"])
|
|
else:
|
|
if env.gcc_mode:
|
|
env.Append(
|
|
CCFLAGS=[
|
|
(
|
|
"-O3"
|
|
if env.nuitka_python
|
|
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, job_count=job_count)
|
|
|
|
if env.gcc_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"):
|
|
if env.mingw_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"])
|
|
|
|
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"])
|
|
|
|
# For shell API usage to lookup app folders we need this. Note that on Windows ARM
|
|
# we didn't manage to have a "shell32.lib" that is not considered corrupt, so we
|
|
# have to do this.
|
|
if env.msvc_mode and env.target_arch != "arm64":
|
|
env.Append(LIBS=["Shell32"])
|
|
|
|
# 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")
|
|
|
|
|
|
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:
|
|
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:
|
|
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, job_count):
|
|
if env.unstripped_mode:
|
|
# Use debug format, so we get good tracebacks from it.
|
|
if env.gcc_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 job_count > 1 and getMsvcVersion(env) >= (11,):
|
|
env.Append(CCFLAGS=["/FS"])
|
|
|
|
env.Append(LINKFLAGS=["/DEBUG"])
|
|
else:
|
|
if env.gcc_mode:
|
|
if isMacOS():
|
|
env.Append(LINKFLAGS=["-Wno-deprecated-declarations"])
|
|
elif not env.clang_mode:
|
|
env.Append(LINKFLAGS=["-s"])
|
|
|
|
|
|
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 = os.path.join(
|
|
os.path.dirname(env.the_compiler),
|
|
os.path.basename(env.the_compiler).replace("gcc", "g++"),
|
|
)
|
|
|
|
if getExecutablePath(the_gpp_compiler, env=env):
|
|
env.the_compiler = the_gpp_compiler
|
|
env.the_cc_name = env.the_cc_name.replace("gcc", "g++")
|
|
else:
|
|
scons_logger.sysexit(
|
|
"Error, your gcc is too old for C11 support, and no related g++ to workaround that is found."
|
|
)
|
|
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):
|
|
if env.gcc_version is None:
|
|
env.gcc_version = myDetectVersion(env, env.the_compiler)
|
|
|
|
cc_output = "%s %s" % (
|
|
env.the_cc_name,
|
|
".".join(str(d) for d in env.gcc_version),
|
|
)
|
|
elif isClangName(env.the_cc_name):
|
|
env.clang_version = myDetectVersion(env, 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
|
|
|
|
# Outside compiler settings are respected.
|
|
if "CPPFLAGS" in os.environ:
|
|
scons_logger.info(
|
|
"Scons: Inherited CPPFLAGS='%s' variable." % os.environ["CPPFLAGS"]
|
|
)
|
|
env.Append(CPPFLAGS=os.environ["CPPFLAGS"].split())
|
|
if "CFLAGS" in os.environ:
|
|
scons_logger.info("Inherited CFLAGS='%s' variable." % os.environ["CFLAGS"])
|
|
env.Append(CCFLAGS=os.environ["CFLAGS"].split())
|
|
if "CCFLAGS" in os.environ:
|
|
scons_logger.info("Inherited CCFLAGS='%s' variable." % os.environ["CCFLAGS"])
|
|
env.Append(CCFLAGS=os.environ["CCFLAGS"].split())
|
|
if "CXXFLAGS" in os.environ:
|
|
scons_logger.info(
|
|
"Scons: Inherited CXXFLAGS='%s' variable." % os.environ["CXXFLAGS"]
|
|
)
|
|
env.Append(CXXFLAGS=os.environ["CXXFLAGS"].split())
|
|
|
|
# Outside linker flags are respected.
|
|
if "LDFLAGS" in os.environ:
|
|
scons_logger.info(
|
|
"Scons: Inherited LDFLAGS='%s' variable." % os.environ["LDFLAGS"]
|
|
)
|
|
env.Append(LINKFLAGS=os.environ["LDFLAGS"].split())
|
|
|
|
|
|
# Part of "Nuitka", an optimizing Python compiler that is compatible and
|
|
# integrates with CPython, but also works on its own.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# 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.
|