import os import re import subprocess import sys from typing import TYPE_CHECKING import methods from methods import print_error, print_warning from platform_methods import detect_arch if TYPE_CHECKING: from SCons.Script.SConscript import SConsEnvironment # To match other platforms STACK_SIZE = 8388608 STACK_SIZE_SANITIZERS = 30 * 1024 * 1024 def get_name(): return "Windows" def try_cmd(test, prefix, arch, check_clang=False): archs = ["x86_64", "x86_32", "arm64", "arm32"] if arch: archs = [arch] for a in archs: try: out = subprocess.Popen( get_mingw_bin_prefix(prefix, a) + test, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) outs, errs = out.communicate() if out.returncode == 0: if check_clang and not outs.startswith(b"clang"): return False return True except Exception: pass return False def can_build(): if os.name == "nt": # Building natively on Windows # If VCINSTALLDIR is set in the OS environ, use traditional Godot logic to set up MSVC if os.getenv("VCINSTALLDIR"): # MSVC, manual setup return True # Otherwise, let SCons find MSVC if installed, or else MinGW. # Since we're just returning True here, if there's no compiler # installed, we'll get errors when it tries to build with the # null compiler. return True if os.name == "posix": # Cross-compiling with MinGW-w64 (old MinGW32 is not supported) prefix = os.getenv("MINGW_PREFIX", "") if try_cmd("gcc --version", prefix, "") or try_cmd("clang --version", prefix, ""): return True return False def get_mingw_bin_prefix(prefix, arch): if not prefix: mingw_bin_prefix = "" elif prefix[-1] != "/": mingw_bin_prefix = prefix + "/bin/" else: mingw_bin_prefix = prefix + "bin/" if arch == "x86_64": mingw_bin_prefix += "x86_64-w64-mingw32-" elif arch == "x86_32": mingw_bin_prefix += "i686-w64-mingw32-" elif arch == "arm32": mingw_bin_prefix += "armv7-w64-mingw32-" elif arch == "arm64": mingw_bin_prefix += "aarch64-w64-mingw32-" return mingw_bin_prefix def detect_build_env_arch(): msvc_target_aliases = { "amd64": "x86_64", "i386": "x86_32", "i486": "x86_32", "i586": "x86_32", "i686": "x86_32", "x86": "x86_32", "x64": "x86_64", "x86_64": "x86_64", "arm": "arm32", "arm64": "arm64", "aarch64": "arm64", } if os.getenv("VCINSTALLDIR") or os.getenv("VCTOOLSINSTALLDIR"): if os.getenv("Platform"): msvc_arch = os.getenv("Platform").lower() if msvc_arch in msvc_target_aliases.keys(): return msvc_target_aliases[msvc_arch] if os.getenv("VSCMD_ARG_TGT_ARCH"): msvc_arch = os.getenv("VSCMD_ARG_TGT_ARCH").lower() if msvc_arch in msvc_target_aliases.keys(): return msvc_target_aliases[msvc_arch] # Pre VS 2017 checks. if os.getenv("VCINSTALLDIR"): PATH = os.getenv("PATH").upper() VCINSTALLDIR = os.getenv("VCINSTALLDIR").upper() path_arch = { "BIN\\x86_ARM;": "arm32", "BIN\\amd64_ARM;": "arm32", "BIN\\x86_ARM64;": "arm64", "BIN\\amd64_ARM64;": "arm64", "BIN\\x86_amd64;": "a86_64", "BIN\\amd64;": "x86_64", "BIN\\amd64_x86;": "x86_32", "BIN;": "x86_32", } for path, arch in path_arch.items(): final_path = VCINSTALLDIR + path if final_path in PATH: return arch # VS 2017 and newer. if os.getenv("VCTOOLSINSTALLDIR"): host_path_index = os.getenv("PATH").upper().find(os.getenv("VCTOOLSINSTALLDIR").upper() + "BIN\\HOST") if host_path_index > -1: first_path_arch = os.getenv("PATH")[host_path_index:].split(";")[0].rsplit("\\", 1)[-1].lower() if first_path_arch in msvc_target_aliases.keys(): return msvc_target_aliases[first_path_arch] msys_target_aliases = { "mingw32": "x86_32", "mingw64": "x86_64", "ucrt64": "x86_64", "clang64": "x86_64", "clang32": "x86_32", "clangarm64": "arm64", } if os.getenv("MSYSTEM"): msys_arch = os.getenv("MSYSTEM").lower() if msys_arch in msys_target_aliases.keys(): return msys_target_aliases[msys_arch] return "" def get_opts(): from SCons.Variables import BoolVariable, EnumVariable mingw = os.getenv("MINGW_PREFIX", "") # Direct3D 12 SDK dependencies folder. d3d12_deps_folder = os.getenv("LOCALAPPDATA") if d3d12_deps_folder: d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps") else: # Cross-compiling, the deps install script puts things in `bin`. # Getting an absolute path to it is a bit hacky in Python. try: import inspect caller_frame = inspect.stack()[1] caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1])) d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps") except Exception: # Give up. d3d12_deps_folder = "" return [ ("mingw_prefix", "MinGW prefix", mingw), # Targeted Windows version: 7 (and later), minimum supported version # XP support dropped after EOL due to missing API for IPv6 and other issues # Vista support dropped after EOL due to GH-10243 ( "target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601", ), EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("gui", "console")), ( "msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None, ), BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed.", False), BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), BoolVariable("use_asan", "Use address sanitizer (ASAN)", False), BoolVariable("use_ubsan", "Use LLVM compiler undefined behavior sanitizer (UBSAN)", False), BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False), BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False), BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting any errors to stderr.", True), ("angle_libs", "Path to the ANGLE static libraries", ""), # Direct3D 12 support. ( "mesa_libs", "Path to the MESA/NIR static libraries (required for D3D12)", os.path.join(d3d12_deps_folder, "mesa"), ), ( "agility_sdk_path", "Path to the Agility SDK distribution (optional for D3D12)", os.path.join(d3d12_deps_folder, "agility_sdk"), ), BoolVariable( "agility_sdk_multiarch", "Whether the Agility SDK DLLs will be stored in arch-specific subdirectories", False, ), BoolVariable("use_pix", "Use PIX (Performance tuning and debugging for DirectX 12) runtime", False), ( "pix_path", "Path to the PIX runtime distribution (optional for D3D12)", os.path.join(d3d12_deps_folder, "pix"), ), ] def get_doc_classes(): return [ "EditorExportPlatformWindows", ] def get_doc_path(): return "doc_classes" def get_flags(): arch = detect_build_env_arch() or detect_arch() return { "arch": arch, "supported": ["d3d12", "mono", "xaudio2"], } def build_res_file(target, source, env: "SConsEnvironment"): arch_aliases = { "x86_32": "pe-i386", "x86_64": "pe-x86-64", "arm32": "armv7-w64-mingw32", "arm64": "aarch64-w64-mingw32", } cmdbase = "windres --include-dir . --target=" + arch_aliases[env["arch"]] mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) for x in range(len(source)): ok = True # Try prefixed executable (MinGW on Linux). cmd = mingw_bin_prefix + cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): ok = False except Exception: ok = False # Try generic executable (MSYS2). if not ok: cmd = cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): return -1 except Exception: return -1 return 0 def setup_msvc_manual(env: "SConsEnvironment"): """Running from VCVARS environment""" env_arch = detect_build_env_arch() if env["arch"] != env_arch: print_error( "Arch argument (%s) is not matching Native/Cross Compile Tools Prompt/Developer Console (or Visual Studio settings) that is being used to run SCons (%s).\n" "Run SCons again without arch argument (example: scons p=windows) and SCons will attempt to detect what MSVC compiler will be executed and inform you." % (env["arch"], env_arch) ) sys.exit(255) print("Using VCVARS-determined MSVC, arch %s" % (env_arch)) def setup_msvc_auto(env: "SConsEnvironment"): """Set up MSVC using SCons's auto-detection logic""" # If MSVC_VERSION is set by SCons, we know MSVC is installed. # But we may want a different version or target arch. # Valid architectures for MSVC's TARGET_ARCH: # ['amd64', 'emt64', 'i386', 'i486', 'i586', 'i686', 'ia64', 'itanium', 'x86', 'x86_64', 'arm', 'arm64', 'aarch64'] # Our x86_64 and arm64 are the same, and we need to map the 32-bit # architectures to other names since MSVC isn't as explicit. # The rest we don't need to worry about because they are # aliases or aren't supported by Godot (itanium & ia64). msvc_arch_aliases = {"x86_32": "x86", "arm32": "arm"} if env["arch"] in msvc_arch_aliases.keys(): env["TARGET_ARCH"] = msvc_arch_aliases[env["arch"]] else: env["TARGET_ARCH"] = env["arch"] # The env may have already been set up with default MSVC tools, so # reset a few things so we can set it up with the tools we want. # (Ideally we'd decide on the tool config before configuring any # environment, and just set the env up once, but this function runs # on an existing env so this is the simplest way.) env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool env["MSVS_VERSION"] = None env["MSVC_VERSION"] = None if "msvc_version" in env: env["MSVC_VERSION"] = env["msvc_version"] env.Tool("msvc") env.Tool("mssdk") # we want the MS SDK # Re-add potentially overwritten flags. env.AppendUnique(CCFLAGS=env.get("ccflags", "").split()) env.AppendUnique(CXXFLAGS=env.get("cxxflags", "").split()) env.AppendUnique(CFLAGS=env.get("cflags", "").split()) env.AppendUnique(RCFLAGS=env.get("rcflags", "").split()) # Note: actual compiler version can be found in env['MSVC_VERSION'], e.g. "14.1" for VS2015 print("Using SCons-detected MSVC version %s, arch %s" % (env["MSVC_VERSION"], env["arch"])) def setup_mingw(env: "SConsEnvironment"): """Set up env for use with mingw""" env_arch = detect_build_env_arch() if os.getenv("MSYSTEM") == "MSYS": print_error( "Running from base MSYS2 console/environment, use target specific environment instead (e.g., mingw32, mingw64, clang32, clang64)." ) sys.exit(255) if env_arch != "" and env["arch"] != env_arch: print_error( "Arch argument (%s) is not matching MSYS2 console/environment that is being used to run SCons (%s).\n" "Run SCons again without arch argument (example: scons p=windows) and SCons will attempt to detect what MSYS2 compiler will be executed and inform you." % (env["arch"], env_arch) ) sys.exit(255) if not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]) and not try_cmd( "clang --version", env["mingw_prefix"], env["arch"] ): print_error("No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path.") sys.exit(255) print("Using MinGW, arch %s" % (env["arch"])) def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): """Configure env to work with MSVC""" ## Build type # TODO: Re-evaluate the need for this / streamline with common config. if env["target"] == "template_release": env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) if env["windows_subsystem"] == "gui": env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) else: env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) ## Compile/link flags if env["use_llvm"]: env["CC"] = "clang-cl" env["CXX"] = "clang-cl" env["LINK"] = "lld-link" env["AR"] = "llvm-lib" env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"]) env.extra_suffix = ".llvm" + env.extra_suffix if env["silence_msvc"] and not env.GetOption("clean"): from tempfile import mkstemp # Ensure we have a location to write captured output to, in case of false positives. capture_path = methods.base_folder_path + "platform/windows/msvc_capture.log" with open(capture_path, "wt", encoding="utf-8"): pass old_spawn = env["SPAWN"] re_redirect_stream = re.compile(r"^[12]?>") re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE) re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)') def spawn_capture(sh, escape, cmd, args, env): # We only care about cl/link, process everything else as normal. if args[0] not in ["cl", "link"]: return old_spawn(sh, escape, cmd, args, env) # Process as normal if the user is manually rerouting output. for arg in args: if re_redirect_stream.match(arg): return old_spawn(sh, escape, cmd, args, env) tmp_stdout, tmp_stdout_name = mkstemp() os.close(tmp_stdout) args.append(f">{tmp_stdout_name}") ret = old_spawn(sh, escape, cmd, args, env) try: with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout: lines = tmp_stdout.read().splitlines() os.remove(tmp_stdout_name) except OSError: pass # Early process no lines (OSError) if not lines: return ret is_cl = args[0] == "cl" content = "" caught = False for line in lines: # These conditions are far from all-encompassing, but are specialized # for what can be reasonably expected to show up in the repository. if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)): caught = True try: with open(capture_path, "a", encoding=sys.stdout.encoding) as log: log.write(line + "\n") except OSError: print_warning(f'Failed to log captured line: "{line}".') continue content += line + "\n" # Content remaining assumed to be an error/warning. if content: sys.stderr.write(content) return ret env["SPAWN"] = spawn_capture if env["debug_crt"]: # Always use dynamic runtime, static debug CRT breaks thread_local. env.AppendUnique(CCFLAGS=["/MDd"]) else: if env["use_static_cpp"]: env.AppendUnique(CCFLAGS=["/MT"]) else: env.AppendUnique(CCFLAGS=["/MD"]) # MSVC incremental linking is broken and may _increase_ link time (GH-77968). if not env["incremental_link"]: env.Append(LINKFLAGS=["/INCREMENTAL:NO"]) if env["arch"] == "x86_32": env["x86_libtheora_opt_vc"] = True env.Append(CCFLAGS=["/fp:strict"]) env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"]) env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. # Once it was thought that only debug builds would be too large, # but this has recently stopped being true. See the mingw function # for notes on why this shouldn't be enabled for gcc env.AppendUnique(CCFLAGS=["/bigobj"]) if vcvars_msvc_config: # should be automatic if SCons found it if os.getenv("WindowsSdkDir") is not None: env.Prepend(CPPPATH=[str(os.getenv("WindowsSdkDir")) + "/Include"]) else: print_warning("Missing environment variable: WindowsSdkDir") validate_win_version(env) env.AppendUnique( CPPDEFINES=[ "WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED", "TYPED_METHOD_BIND", "WIN32", "WINVER=%s" % env["target_win_version"], "_WIN32_WINNT=%s" % env["target_win_version"], ] ) env.AppendUnique(CPPDEFINES=["NOMINMAX"]) # disable bogus min/max WinDef.h macros if env["arch"] == "x86_64": env.AppendUnique(CPPDEFINES=["_WIN64"]) # Sanitizers prebuilt_lib_extra_suffix = "" if env["use_asan"]: env.extra_suffix += ".san" prebuilt_lib_extra_suffix = ".san" env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"]) env.Append(CCFLAGS=["/fsanitize=address"]) env.Append(LINKFLAGS=["/INFERASANLIBS"]) ## Libs LIBS = [ "winmm", "dsound", "kernel32", "ole32", "oleaut32", "sapi", "user32", "gdi32", "IPHLPAPI", "Shlwapi", "wsock32", "Ws2_32", "shell32", "advapi32", "dinput8", "dxguid", "imm32", "bcrypt", "Crypt32", "Avrt", "dwmapi", "dwrite", "wbemuuid", "ntdll", ] if env.debug_features: LIBS += ["psapi", "dbghelp"] if env["vulkan"]: env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if not env["use_volk"]: LIBS += ["vulkan"] if env["d3d12"]: check_d3d12_installed(env) env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"]) LIBS += ["dxgi", "dxguid"] LIBS += ["version"] # Mesa dependency. # Needed for avoiding C1128. if env["target"] == "release_debug": env.Append(CXXFLAGS=["/bigobj"]) # PIX if env["arch"] not in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): env["use_pix"] = False if env["use_pix"]: arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir]) LIBS += ["WinPixEventRuntime"] env.Append(LIBPATH=[env["mesa_libs"] + "/bin"]) LIBS += ["libNIR.windows." + env["arch"] + prebuilt_lib_extra_suffix] if env["opengl3"]: env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"]) if env["angle_libs"] != "": env.AppendUnique(CPPDEFINES=["EGL_STATIC"]) env.Append(LIBPATH=[env["angle_libs"]]) LIBS += [ "libANGLE.windows." + env["arch"] + prebuilt_lib_extra_suffix, "libEGL.windows." + env["arch"] + prebuilt_lib_extra_suffix, "libGLES.windows." + env["arch"] + prebuilt_lib_extra_suffix, ] LIBS += ["dxgi", "d3d9", "d3d11"] env.Prepend(CPPPATH=["#thirdparty/angle/include"]) if env["target"] in ["editor", "template_debug"]: LIBS += ["psapi", "dbghelp"] if env["use_llvm"]: LIBS += [f"clang_rt.builtins-{env['arch']}"] env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) if vcvars_msvc_config: if os.getenv("WindowsSdkDir") is not None: env.Append(LIBPATH=[str(os.getenv("WindowsSdkDir")) + "/Lib"]) else: print_warning("Missing environment variable: WindowsSdkDir") ## LTO if env["lto"] == "auto": # No LTO by default for MSVC, doesn't help. env["lto"] = "none" if env["lto"] != "none": if env["lto"] == "thin": if not env["use_llvm"]: print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") sys.exit(255) env.AppendUnique(CCFLAGS=["-flto=thin"]) elif env["use_llvm"]: env.AppendUnique(CCFLAGS=["-flto"]) else: env.AppendUnique(CCFLAGS=["/GL"]) if env["progress"]: env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"]) else: env.AppendUnique(LINKFLAGS=["/LTCG"]) env.AppendUnique(ARFLAGS=["/LTCG"]) if vcvars_msvc_config: env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")]) env.Append(LIBPATH=[p for p in str(os.getenv("LIB")).split(";")]) # Incremental linking fix env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"] env["BUILDERS"]["Program"] = methods.precious_program env.Append(LINKFLAGS=["/NATVIS:platform\\windows\\godot.natvis"]) if env["use_asan"]: env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE_SANITIZERS)]) else: env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)]) def get_ar_version(env): ret = { "major": -1, "minor": -1, "patch": -1, "is_llvm": False, } try: output = ( subprocess.check_output([env.subst(env["AR"]), "--version"], shell=(os.name == "nt")) .strip() .decode("utf-8") ) except (subprocess.CalledProcessError, OSError): print_warning("Couldn't check version of `ar`.") return ret match = re.search(r"GNU ar(?: \(GNU Binutils\)| version) (\d+)\.(\d+)(?:\.(\d+))?", output) if match: ret["major"] = int(match[1]) ret["minor"] = int(match[2]) if match[3]: ret["patch"] = int(match[3]) else: ret["patch"] = 0 return ret match = re.search(r"LLVM version (\d+)\.(\d+)\.(\d+)", output) if match: ret["major"] = int(match[1]) ret["minor"] = int(match[2]) ret["patch"] = int(match[3]) ret["is_llvm"] = True return ret print_warning("Couldn't parse version of `ar`.") return ret def get_is_ar_thin_supported(env): """Check whether `ar --thin` is supported. It is only supported since Binutils 2.38 or LLVM 14.""" ar_version = get_ar_version(env) if ar_version["major"] == -1: return False if ar_version["is_llvm"]: return ar_version["major"] >= 14 if ar_version["major"] == 2: return ar_version["minor"] >= 38 print_warning("Unknown Binutils `ar` version.") return False WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") def tempfile_arg_esc_func(arg): from SCons.Subst import quote_spaces arg = quote_spaces(arg) # GCC requires double Windows slashes, let's use UNIX separator return WINPATHSEP_RE.sub(r"/\1", arg) def configure_mingw(env: "SConsEnvironment"): # Workaround for MinGW. See: # https://www.scons.org/wiki/LongCmdLinesOnWin32 env.use_windows_spawn_fix() # In case the command line to AR is too long, use a response file. env["ARCOM_ORIG"] = env["ARCOM"] env["ARCOM"] = "${TEMPFILE('$ARCOM_ORIG', '$ARCOMSTR')}" env["TEMPFILESUFFIX"] = ".rsp" if os.name == "nt": env["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func ## Build type if not env["use_llvm"] and not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = True if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = False if not env["use_llvm"] and try_cmd("gcc --version", env["mingw_prefix"], env["arch"], True): print("Detected GCC to be a wrapper for Clang.") env["use_llvm"] = True # TODO: Re-evaluate the need for this / streamline with common config. if env["target"] == "template_release": if env["arch"] != "arm64": env.Append(CCFLAGS=["-msse2"]) elif env.dev_build: # Allow big objects. It's supposed not to have drawbacks but seems to break # GCC LTO, so enabling for debug builds only (which are not built with LTO # and are the only ones with too big objects). env.Append(CCFLAGS=["-Wa,-mbig-obj"]) if env["windows_subsystem"] == "gui": env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) else: env.Append(LINKFLAGS=["-Wl,--subsystem,console"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) ## Compiler configuration if os.name != "nt": env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".exe" # for linux cross-compilation if env["arch"] == "x86_32": if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static"]) env.Append(LINKFLAGS=["-static-libgcc"]) env.Append(LINKFLAGS=["-static-libstdc++"]) else: if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static"]) if env["arch"] in ["x86_32", "x86_64"]: env["x86_libtheora_opt_gcc"] = True env.Append(CCFLAGS=["-ffp-contract=off"]) mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) if env["use_llvm"]: env["CC"] = mingw_bin_prefix + "clang" env["CXX"] = mingw_bin_prefix + "clang++" if try_cmd("as --version", env["mingw_prefix"], env["arch"]): env["AS"] = mingw_bin_prefix + "as" env.Append(ASFLAGS=["-c"]) if try_cmd("ar --version", env["mingw_prefix"], env["arch"]): env["AR"] = mingw_bin_prefix + "ar" if try_cmd("ranlib --version", env["mingw_prefix"], env["arch"]): env["RANLIB"] = mingw_bin_prefix + "ranlib" env.extra_suffix = ".llvm" + env.extra_suffix else: env["CC"] = mingw_bin_prefix + "gcc" env["CXX"] = mingw_bin_prefix + "g++" if try_cmd("as --version", env["mingw_prefix"], env["arch"]): env["AS"] = mingw_bin_prefix + "as" ar = "ar" if os.name == "nt" else "gcc-ar" if try_cmd(f"{ar} --version", env["mingw_prefix"], env["arch"]): env["AR"] = mingw_bin_prefix + ar if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]): env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib" ## LTO if env["lto"] == "auto": # Full LTO for production with MinGW. env["lto"] = "full" if env["lto"] != "none": if env["lto"] == "thin": if not env["use_llvm"]: print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") sys.exit(255) env.Append(CCFLAGS=["-flto=thin"]) env.Append(LINKFLAGS=["-flto=thin"]) elif not env["use_llvm"] and env.GetOption("num_jobs") > 1: env.Append(CCFLAGS=["-flto"]) env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))]) else: env.Append(CCFLAGS=["-flto"]) env.Append(LINKFLAGS=["-flto"]) if env["use_asan"]: env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE_SANITIZERS)]) else: env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)]) ## Compile flags validate_win_version(env) if not env["use_llvm"]: env.Append(CCFLAGS=["-mwindows"]) if env["use_asan"] or env["use_ubsan"]: if not env["use_llvm"]: print("GCC does not support sanitizers on Windows.") sys.exit(255) if env["arch"] not in ["x86_32", "x86_64"]: print("Sanitizers are only supported for x86_32 and x86_64.") sys.exit(255) env.extra_suffix += ".san" env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"]) san_flags = [] if env["use_asan"]: san_flags.append("-fsanitize=address") if env["use_ubsan"]: san_flags.append("-fsanitize=undefined") # Disable the vptr check since it gets triggered on any COM interface calls. san_flags.append("-fno-sanitize=vptr") env.Append(CFLAGS=san_flags) env.Append(CCFLAGS=san_flags) env.Append(LINKFLAGS=san_flags) if env["use_llvm"] and os.name == "nt" and methods._colorize: env.Append(CCFLAGS=["$(-fansi-escape-codes$)", "$(-fcolor-diagnostics$)"]) if get_is_ar_thin_supported(env): env.Append(ARFLAGS=["--thin"]) env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"]) env.Append( CPPDEFINES=[ ("WINVER", env["target_win_version"]), ("_WIN32_WINNT", env["target_win_version"]), ] ) env.Append( LIBS=[ "mingw32", "dsound", "ole32", "d3d9", "winmm", "gdi32", "iphlpapi", "shlwapi", "wsock32", "ws2_32", "kernel32", "oleaut32", "sapi", "dinput8", "dxguid", "ksuser", "imm32", "bcrypt", "crypt32", "avrt", "uuid", "dwmapi", "dwrite", "wbemuuid", "ntdll", ] ) if env.debug_features: env.Append(LIBS=["psapi", "dbghelp"]) if env["vulkan"]: env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if not env["use_volk"]: env.Append(LIBS=["vulkan"]) if env["d3d12"]: check_d3d12_installed(env) env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"]) env.Append(LIBS=["dxgi", "dxguid"]) # PIX if env["arch"] not in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): env["use_pix"] = False if env["use_pix"]: arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir]) env.Append(LIBS=["WinPixEventRuntime"]) env.Append(LIBPATH=[env["mesa_libs"] + "/bin"]) env.Append(LIBS=["libNIR.windows." + env["arch"]]) env.Append(LIBS=["version"]) # Mesa dependency. if env["opengl3"]: env.Append(CPPDEFINES=["GLES3_ENABLED"]) if env["angle_libs"] != "": env.AppendUnique(CPPDEFINES=["EGL_STATIC"]) env.Append(LIBPATH=[env["angle_libs"]]) env.Append( LIBS=[ "EGL.windows." + env["arch"], "GLES.windows." + env["arch"], "ANGLE.windows." + env["arch"], ] ) env.Append(LIBS=["dxgi", "d3d9", "d3d11"]) env.Prepend(CPPPATH=["#thirdparty/angle/include"]) env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)]) # resrc env.Append(BUILDERS={"RES": env.Builder(action=build_res_file, suffix=".o", src_suffix=".rc")}) def configure(env: "SConsEnvironment"): # Validate arch. supported_arches = ["x86_32", "x86_64", "arm32", "arm64"] if env["arch"] not in supported_arches: print_error( 'Unsupported CPU architecture "%s" for Windows. Supported architectures are: %s.' % (env["arch"], ", ".join(supported_arches)) ) sys.exit(255) # At this point the env has been set up with basic tools/compilers. env.Prepend(CPPPATH=["#platform/windows"]) if os.name == "nt": env["ENV"] = os.environ # this makes build less repeatable, but simplifies some things env["ENV"]["TMP"] = os.environ["TMP"] # First figure out which compiler, version, and target arch we're using if os.getenv("VCINSTALLDIR") and detect_build_env_arch() and not env["use_mingw"]: setup_msvc_manual(env) env.msvc = True vcvars_msvc_config = True elif env.get("MSVC_VERSION", "") and not env["use_mingw"]: setup_msvc_auto(env) env.msvc = True vcvars_msvc_config = False else: setup_mingw(env) env.msvc = False # Now set compiler/linker flags if env.msvc: configure_msvc(env, vcvars_msvc_config) else: # MinGW configure_mingw(env) def check_d3d12_installed(env): if not os.path.exists(env["mesa_libs"]): print_error( "The Direct3D 12 rendering driver requires dependencies to be installed.\n" "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n" "See the documentation for more information:\n\t" "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html" ) sys.exit(255) def validate_win_version(env): if int(env["target_win_version"], 16) < 0x0601: print_error("`target_win_version` should be 0x0601 or higher (Windows 7).") sys.exit(255)