Scons: Make zig work for Windows too (WIP)

* Need to consider if using mingw, clang, or zig more carefully.

* With this standalone and onefile mode are working.

* Also added control over zig cache file locations to be below
  Nuitka's cache directory.
This commit is contained in:
Kay Hayen
2025-10-19 15:50:06 +01:00
parent 85a6e2f821
commit bd01ee5af1
10 changed files with 91 additions and 94 deletions

View File

@@ -34,6 +34,7 @@ def _cleanCacheDirectory(cache_name, cache_dir):
def cleanCaches():
_cleanCacheDirectory("ccache", getCacheDir("ccache"))
_cleanCacheDirectory("clcache", getCacheDir("clcache"))
_cleanCacheDirectory("zig", getCacheDir("zig"))
_cleanCacheDirectory("bytecode", getBytecodeCacheDir())
_cleanCacheDirectory("dll-dependencies", getCacheDir("library_dependencies"))

View File

@@ -2086,7 +2086,7 @@ def getMsvcVersion():
def shallCleanCache(cache_name):
""":returns: bool derived from ``--clean-cache``"""
if cache_name == "clcache":
if cache_name in ("clcache", "zig"):
cache_name = "ccache"
return "all" in options.clean_caches or cache_name in options.clean_caches

View File

@@ -89,7 +89,6 @@ from .SconsUtils import (
isGccName,
isZigName,
makeResultPathFileSystemEncodable,
prepareEnvironment,
provideStaticSourceFile,
raiseNoCompilerFoundErrorExit,
scanSourceDir,
@@ -284,9 +283,6 @@ enableSconsProgressBar(progress_bar)
# Amount of jobs to use.
job_count = GetOption("num_jobs")
# Prepare environment for compiler detection.
mingw_mode = prepareEnvironment(mingw_mode=mingw_mode)
# Patch the compiler detection.
Environment.Detect = getEnhancedToolDetect()
@@ -360,8 +356,8 @@ env.zig_mode = isZigName(env.the_cc_name)
# Only use MSVC if not already clear, we are using MinGW.
env.msvc_mode = os.name == "nt" and not env.gcc_mode
env.mingw_mode = os.name == "nt" and env.gcc_mode
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
@@ -854,7 +850,7 @@ if env.module_mode and python_sysflag_verbose:
# Hack to make Scons use tempfile for gcc linking, to avoid line length limits,
# which can make linking fail with many modules otherwise. Most needed on Windows,
# but useful on other platforms too.
if env.gcc_mode:
if env.gcc_mode and (not env.clang_mode or env.zig_mode):
makeGccUseLinkerFile(
env=env,
source_files=source_files,

View File

@@ -60,7 +60,6 @@ from .SconsUtils import (
isClangName,
isGccName,
isZigName,
prepareEnvironment,
setArguments,
)
@@ -143,28 +142,6 @@ cf_protection = getArgumentDefaulted("cf_protection", "auto")
# Amount of jobs to use.
job_count = GetOption("num_jobs")
# Prepare environment for compiler detection.
mingw_mode = prepareEnvironment(mingw_mode=mingw_mode)
# TODO: Merge to prepareEnvironment as well.
if "CXX" in os.environ:
os.environ["CXX"] = os.path.normpath(os.environ["CXX"])
if os.path.isdir(os.environ["CXX"]):
sys.exit("Error, the CXX variable must point to file, not directory.")
cxx_dirname = os.path.dirname(os.environ["CXX"])
if os.name == "nt" and isGccName(os.path.basename(os.environ["CXX"])):
if show_scons_mode:
my_print("Scons: Environment CXX seems to be a gcc, enable mingw_mode.")
mingw_mode = True
if os.path.isdir(cxx_dirname):
os.environ["PATH"] = os.pathsep.join(
[cxx_dirname] + os.environ["PATH"].split(os.pathsep)
)
# Patch the compiler detection.
Environment.Detect = getEnhancedToolDetect()
@@ -234,8 +211,8 @@ env.clang_mode = clang_mode
env.zig_mode = isZigName(env.the_cc_name)
# Only use MSVC if not already clear, we are using MinGW.
env.msvc_mode = os.name == "nt" and not env.gcc_mode
env.mingw_mode = os.name == "nt" and env.gcc_mode
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
# gcc compiler cf_protection option

View File

@@ -75,7 +75,6 @@ from .SconsUtils import (
isGccName,
isZigName,
makeResultPathFileSystemEncodable,
prepareEnvironment,
provideStaticSourceFile,
raiseNoCompilerFoundErrorExit,
setArguments,
@@ -175,9 +174,6 @@ enableSconsProgressBar(progress_bar)
# Amount of jobs to use.
job_count = GetOption("num_jobs")
# Prepare environment for compiler detection.
mingw_mode = prepareEnvironment(mingw_mode=mingw_mode)
# Patch the compiler detection.
Environment.Detect = getEnhancedToolDetect()
@@ -249,8 +245,8 @@ env.clang_mode = clang_mode
env.zig_mode = isZigName(env.the_cc_name)
# Only use MSVC if not already clear, we are using MinGW.
env.msvc_mode = os.name == "nt" and not env.gcc_mode
env.mingw_mode = os.name == "nt" and env.gcc_mode
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

View File

@@ -69,7 +69,6 @@ from .SconsUtils import (
isClangName,
isGccName,
isZigName,
prepareEnvironment,
provideStaticSourceFile,
raiseNoCompilerFoundErrorExit,
scanSourceDir,
@@ -203,28 +202,6 @@ enableSconsProgressBar(progress_bar)
# Amount of jobs to use.
job_count = GetOption("num_jobs")
# Prepare environment for compiler detection.
mingw_mode = prepareEnvironment(mingw_mode=mingw_mode)
# TODO: Merge to prepareEnvironment as well.
if "CXX" in os.environ:
os.environ["CXX"] = os.path.normpath(os.environ["CXX"])
if os.path.isdir(os.environ["CXX"]):
sys.exit("Error, the CXX variable must point to file, not directory.")
cxx_dirname = os.path.dirname(os.environ["CXX"])
if os.name == "nt" and isGccName(os.path.basename(os.environ["CXX"])):
if show_scons_mode:
my_print("Scons: Environment CXX seems to be a gcc, enable mingw_mode.")
mingw_mode = True
if os.path.isdir(cxx_dirname):
os.environ["PATH"] = os.pathsep.join(
[cxx_dirname] + os.environ["PATH"].split(os.pathsep)
)
# Patch the compiler detection.
Environment.Detect = getEnhancedToolDetect()
@@ -295,8 +272,8 @@ env.clang_mode = clang_mode
env.zig_mode = isZigName(env.the_cc_name)
# Only use MSVC if not already clear, we are using MinGW.
env.msvc_mode = os.name == "nt" and not env.gcc_mode
env.mingw_mode = os.name == "nt" and env.gcc_mode
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
# gcc compiler cf_protection option

View File

@@ -30,7 +30,6 @@ from .SconsProgress import updateSconsProgressBar
from .SconsUtils import (
getExecutablePath,
getSconsReportValue,
isZigName,
setEnvironmentVariable,
)
@@ -144,7 +143,7 @@ def _injectCcache(env, cc_path, python_prefix, assume_yes_for_downloads):
def enableCcache(
env, source_dir, python_prefix, assume_yes_for_downloads, disable_ccache
):
inject_ccache = not disable_ccache
inject_ccache = not disable_ccache and not env.zig_mode
if inject_ccache:
# The ccache needs absolute path, otherwise it will not work.
@@ -198,12 +197,31 @@ def enableCcache(
# If we failed to inject zig argument into ccache command line, we need to
# do it now.
if env.zig_mode and inject_ccache is False:
if env.zig_mode:
cc_path = getExecutablePath(env.the_compiler, env=env)
env["CXX"] = env["CC"] = '"%s" "%s"' % (
cc_path,
"cc" if env.c11_mode else "c++",
)
if "CCACHE_DIR" not in os.environ:
zig_cache_dir = getCacheDir("zig")
if not os.getenv("ZIG_LOCAL_CACHE_DIR"):
makePath(zig_cache_dir)
zig_cache_dir = getExternalUsePath(zig_cache_dir)
setEnvironmentVariable(
env, "ZIG_LOCAL_CACHE_DIR", os.path.join(zig_cache_dir, "local")
)
if not os.getenv("ZIG_GLOBAL_CACHE_DIR"):
makePath(zig_cache_dir)
zig_cache_dir = getExternalUsePath(zig_cache_dir)
setEnvironmentVariable(
env, "ZIG_GLOBAL_CACHE_DIR", os.path.join(zig_cache_dir, "global")
)
def enableClcache(env, source_dir):
# We allow using Python2 still

View File

@@ -91,7 +91,7 @@ def _enableC11Settings(env):
bool - c11_mode flag
"""
# Lots of cases to deal with, pylint: disable=too-many-branches
# Lots of cases to deal with
if "force-c11-mode" in env.experimental_flags:
c11_mode = True
@@ -287,7 +287,7 @@ def checkWindowsCompilerFound(
"""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":
if os.name == "nt" and not isZigName(env["CC"]):
# 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)
@@ -682,8 +682,9 @@ 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.
env.Append(CPPDEFINES=["_WIN32_WINNT=0x0601"])
# help it. For zig, it is hard coded.
if not env.zig_mode:
env.Append(CPPDEFINES=["_WIN32_WINNT=0x0601"])
def enableWindowsStackSize(env, target_arch):
@@ -767,7 +768,8 @@ def setupCCompiler(env, lto_mode, pgo_mode, job_count, exe_target, onefile_compi
if isWin32Windows() and hasattr(env, "source_dir"):
# On Windows, exporting to DLL need to be controlled.
env.Append(LINKFLAGS=["-Wl,--exclude-all-symbols"])
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.
@@ -791,10 +793,10 @@ def setupCCompiler(env, lto_mode, pgo_mode, job_count, exe_target, onefile_compi
env.Append(CCFLAGS=["-fcf-protection=%s" % env.cf_protection])
# Support for clang.
if env.clang_mode:
if env.clang_mode or env.clangcl_mode:
env.Append(CCFLAGS=["-Wno-deprecated-declarations"])
if not isClangName(env.the_cc_name):
if not isZigName(env.the_cc_name):
env.Append(CPPDEFINES=["_XOPEN_SOURCE"])
if isClangName(env.the_cc_name):
@@ -845,7 +847,7 @@ def setupCCompiler(env, lto_mode, pgo_mode, job_count, exe_target, onefile_compi
_enableWin32TargetSettings(env)
# Unicode entry points for programs.
if env.mingw_mode:
if env.mingw_mode or (env.zig_mode and isWin32Windows()):
env.Append(LINKFLAGS=["-municode"])
# Detect the gcc version

View File

@@ -23,7 +23,7 @@ from nuitka.utils.Execution import executeProcess
from nuitka.utils.FileOperations import openTextFile
from nuitka.utils.Utils import isLinux, isMacOS
from .SconsUtils import decodeData, getExecutablePath, isGccName
from .SconsUtils import decodeData, getExecutablePath, isGccName, isZigName
# Cache for detected versions.
v_cache = {}
@@ -74,7 +74,7 @@ _blocked_tools = (
def _myDetectVersion(cc):
if isGccName(cc) or "clang" in cc:
if isGccName(cc) or "clang" in cc or isZigName(cc):
command = (
cc,
"-dumpversion",

View File

@@ -149,36 +149,54 @@ def _enableFlagSettings(env, name, experimental_flags):
env.Append(CPPDEFINES=["_NUITKA_%s" % flag_name])
def prepareEnvironment(mingw_mode):
def _prepareFromEnvironmentVar(var_name):
mingw_mode = False
zig_mode = False
# Add environment specified compilers to the PATH variable.
if "CC" in os.environ:
scons_details_logger.info("CC='%s'" % os.environ["CC"])
if var_name in os.environ:
scons_details_logger.info("%s='%s'" % (var_name, os.environ[var_name]))
os.environ["CC"] = os.path.normpath(os.path.expanduser(os.environ["CC"]))
os.environ[var_name] = os.path.normpath(
os.path.expanduser(os.environ[var_name])
)
if os.path.isdir(os.environ["CC"]):
if os.path.isdir(os.environ[var_name]):
scons_logger.sysexit(
"Error, the 'CC' variable must point to file, not directory."
"Error, the '%s' variable must point to file, not directory." % var_name
)
if os.path.sep in os.environ["CC"]:
cc_dirname = os.path.dirname(os.environ["CC"])
if os.path.sep in os.environ[var_name]:
cc_dirname = os.path.dirname(os.environ[var_name])
if os.path.isdir(cc_dirname):
addToPATH(None, cc_dirname, prefix=True)
if os.name == "nt" and isGccName(os.path.basename(os.environ["CC"])):
if os.name == "nt" and isGccName(os.path.basename(os.environ[var_name])):
scons_details_logger.info(
"Environment CC seems to be a gcc, enabling mingw_mode."
"Environment %s seems to be a gcc, enabling mingw_mode." % var_name
)
mingw_mode = True
else:
anaconda_python = getArgumentBool("anaconda_python", False)
if isLinux() and anaconda_python:
python_prefix = getArgumentRequired("python_prefix")
addToPATH(None, os.path.join(python_prefix, "bin"), prefix=True)
if isZigName(os.path.basename(os.environ[var_name])):
scons_details_logger.info(
"Environment %s seems to be a gcc, enabling zig_mode." % var_name
)
zig_mode = True
return mingw_mode
return mingw_mode, zig_mode
def _prepareEnvironment(mingw_mode):
mingw_mode, zig_mode = _prepareFromEnvironmentVar("CC")
_prepareFromEnvironmentVar("CXX")
anaconda_python = getArgumentBool("anaconda_python", False)
if isLinux() and anaconda_python:
python_prefix = getArgumentRequired("python_prefix")
addToPATH(None, os.path.join(python_prefix, "bin"), prefix=True)
return mingw_mode, zig_mode
def createEnvironment(
@@ -187,6 +205,9 @@ def createEnvironment(
# Many settings are directly handled here, getting us a lot of code in here.
# pylint: disable=too-many-branches,too-many-statements
# Prepare environment for compiler detection.
mingw_mode, zig_mode = _prepareEnvironment(mingw_mode=mingw_mode)
from SCons.Script import Environment # pylint: disable=I0021,import-error
args = {}
@@ -204,13 +225,14 @@ def createEnvironment(
if (
os.name == "nt"
and not mingw_mode
and not zig_mode
and msvc_version is None
and msvc_version != "latest"
and (getExecutablePath("cl", env=None) is not None)
):
args["MSVC_USE_SCRIPT"] = False
if mingw_mode or isPosixWindows():
if mingw_mode or zig_mode or isPosixWindows():
# Force usage of MinGW64, not using MSVC tools.
tools = ["mingw"]
@@ -222,6 +244,8 @@ def createEnvironment(
SCons.Tool.msvc.msvc_exists = SCons.Tool.MSCommon.vc.msvc_exists = (
lambda *args: False
)
elif zig_mode:
tools = ["gcc"]
else:
# Everything else should use default, that is MSVC tools, but not MinGW64.
tools = ["default"]
@@ -655,6 +679,8 @@ def addClangClPathFromMSVC(env):
def isGccName(cc_name):
cc_name = os.path.normcase(os.path.basename(cc_name))
return (
"gcc" in cc_name
or "g++" in cc_name
@@ -664,10 +690,14 @@ def isGccName(cc_name):
def isClangName(cc_name):
cc_name = os.path.normcase(os.path.basename(cc_name))
return ("clang" in cc_name and "-cl" not in cc_name) or isZigName(cc_name)
def isZigName(cc_name):
cc_name = os.path.normcase(os.path.basename(cc_name))
return "zig" in cc_name