detect.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  1. import os
  2. import re
  3. import subprocess
  4. import sys
  5. from typing import TYPE_CHECKING
  6. import methods
  7. from methods import print_error, print_info, print_warning
  8. from platform_methods import detect_arch, validate_arch
  9. if TYPE_CHECKING:
  10. from SCons.Script.SConscript import SConsEnvironment
  11. # To match other platforms
  12. STACK_SIZE = 8388608
  13. STACK_SIZE_SANITIZERS = 30 * 1024 * 1024
  14. def get_name():
  15. return "Windows"
  16. def try_cmd(test, prefix, arch, check_clang=False):
  17. archs = ["x86_64", "x86_32", "arm64", "arm32"]
  18. if arch:
  19. archs = [arch]
  20. for a in archs:
  21. try:
  22. out = subprocess.Popen(
  23. get_mingw_bin_prefix(prefix, a) + test,
  24. shell=True,
  25. stderr=subprocess.PIPE,
  26. stdout=subprocess.PIPE,
  27. )
  28. outs, errs = out.communicate()
  29. if out.returncode == 0:
  30. if check_clang and not outs.startswith(b"clang"):
  31. return False
  32. return True
  33. except Exception:
  34. pass
  35. return False
  36. def can_build():
  37. if os.name == "nt":
  38. # Building natively on Windows
  39. return True
  40. if os.name == "posix":
  41. # Cross-compiling with MinGW-w64 (old MinGW32 is not supported)
  42. prefix = os.getenv("MINGW_PREFIX", "")
  43. if try_cmd("gcc --version", prefix, "") or try_cmd("clang --version", prefix, ""):
  44. return True
  45. return False
  46. def get_mingw_bin_prefix(prefix, arch):
  47. bin_prefix = (os.path.normpath(os.path.join(prefix, "bin")) + os.sep) if prefix else ""
  48. ARCH_PREFIXES = {
  49. "x86_64": "x86_64-w64-mingw32-",
  50. "x86_32": "i686-w64-mingw32-",
  51. "arm32": "armv7-w64-mingw32-",
  52. "arm64": "aarch64-w64-mingw32-",
  53. }
  54. arch_prefix = ARCH_PREFIXES[arch] if arch else ""
  55. return bin_prefix + arch_prefix
  56. def get_detected(env: "SConsEnvironment", tool: str) -> str:
  57. checks = [
  58. get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + tool,
  59. get_mingw_bin_prefix(env["mingw_prefix"], "") + tool,
  60. ]
  61. return str(env.Detect(checks))
  62. def detect_build_env_arch():
  63. msvc_target_aliases = {
  64. "amd64": "x86_64",
  65. "i386": "x86_32",
  66. "i486": "x86_32",
  67. "i586": "x86_32",
  68. "i686": "x86_32",
  69. "x86": "x86_32",
  70. "x64": "x86_64",
  71. "x86_64": "x86_64",
  72. "arm": "arm32",
  73. "arm64": "arm64",
  74. "aarch64": "arm64",
  75. }
  76. if os.getenv("VCINSTALLDIR") or os.getenv("VCTOOLSINSTALLDIR"):
  77. if os.getenv("Platform"):
  78. msvc_arch = os.getenv("Platform").lower()
  79. if msvc_arch in msvc_target_aliases.keys():
  80. return msvc_target_aliases[msvc_arch]
  81. if os.getenv("VSCMD_ARG_TGT_ARCH"):
  82. msvc_arch = os.getenv("VSCMD_ARG_TGT_ARCH").lower()
  83. if msvc_arch in msvc_target_aliases.keys():
  84. return msvc_target_aliases[msvc_arch]
  85. # Pre VS 2017 checks.
  86. if os.getenv("VCINSTALLDIR"):
  87. PATH = os.getenv("PATH").upper()
  88. VCINSTALLDIR = os.getenv("VCINSTALLDIR").upper()
  89. path_arch = {
  90. "BIN\\x86_ARM;": "arm32",
  91. "BIN\\amd64_ARM;": "arm32",
  92. "BIN\\x86_ARM64;": "arm64",
  93. "BIN\\amd64_ARM64;": "arm64",
  94. "BIN\\x86_amd64;": "a86_64",
  95. "BIN\\amd64;": "x86_64",
  96. "BIN\\amd64_x86;": "x86_32",
  97. "BIN;": "x86_32",
  98. }
  99. for path, arch in path_arch.items():
  100. final_path = VCINSTALLDIR + path
  101. if final_path in PATH:
  102. return arch
  103. # VS 2017 and newer.
  104. if os.getenv("VCTOOLSINSTALLDIR"):
  105. host_path_index = os.getenv("PATH").upper().find(os.getenv("VCTOOLSINSTALLDIR").upper() + "BIN\\HOST")
  106. if host_path_index > -1:
  107. first_path_arch = os.getenv("PATH")[host_path_index:].split(";")[0].rsplit("\\", 1)[-1].lower()
  108. if first_path_arch in msvc_target_aliases.keys():
  109. return msvc_target_aliases[first_path_arch]
  110. msys_target_aliases = {
  111. "mingw32": "x86_32",
  112. "mingw64": "x86_64",
  113. "ucrt64": "x86_64",
  114. "clang64": "x86_64",
  115. "clang32": "x86_32",
  116. "clangarm64": "arm64",
  117. }
  118. if os.getenv("MSYSTEM"):
  119. msys_arch = os.getenv("MSYSTEM").lower()
  120. if msys_arch in msys_target_aliases.keys():
  121. return msys_target_aliases[msys_arch]
  122. return ""
  123. def get_tools(env: "SConsEnvironment"):
  124. from SCons.Tool.MSCommon import msvc_exists
  125. if os.name != "nt" or env.get("use_mingw") or not msvc_exists():
  126. return ["mingw"]
  127. else:
  128. msvc_arch_aliases = {"x86_32": "x86", "arm32": "arm"}
  129. env["TARGET_ARCH"] = msvc_arch_aliases.get(env["arch"], env["arch"])
  130. env["MSVC_VERSION"] = env["MSVS_VERSION"] = env.get("msvc_version")
  131. return ["msvc", "mslink", "mslib"]
  132. def get_opts():
  133. from SCons.Variables import BoolVariable, EnumVariable
  134. mingw = os.getenv("MINGW_PREFIX", "")
  135. # Direct3D 12 SDK dependencies folder.
  136. d3d12_deps_folder = os.getenv("LOCALAPPDATA")
  137. if d3d12_deps_folder:
  138. d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps")
  139. else:
  140. # Cross-compiling, the deps install script puts things in `bin`.
  141. # Getting an absolute path to it is a bit hacky in Python.
  142. try:
  143. import inspect
  144. caller_frame = inspect.stack()[1]
  145. caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1]))
  146. d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps")
  147. except Exception: # Give up.
  148. d3d12_deps_folder = ""
  149. return [
  150. ("mingw_prefix", "MinGW prefix", mingw),
  151. # Targeted Windows version: 7 (and later), minimum supported version
  152. # XP support dropped after EOL due to missing API for IPv6 and other issues
  153. # Vista support dropped after EOL due to GH-10243
  154. (
  155. "target_win_version",
  156. "Targeted Windows version, >= 0x0601 (Windows 7)",
  157. "0x0601",
  158. ),
  159. EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("gui", "console")),
  160. ("msvc_version", "MSVC version to use. Handled automatically by SCons if omitted.", ""),
  161. BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed.", False),
  162. BoolVariable("use_llvm", "Use the LLVM compiler", False),
  163. BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True),
  164. BoolVariable("use_asan", "Use address sanitizer (ASAN)", False),
  165. BoolVariable("use_ubsan", "Use LLVM compiler undefined behavior sanitizer (UBSAN)", False),
  166. BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False),
  167. BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False),
  168. BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting any errors to stderr.", True),
  169. ("angle_libs", "Path to the ANGLE static libraries", ""),
  170. # Direct3D 12 support.
  171. (
  172. "mesa_libs",
  173. "Path to the MESA/NIR static libraries (required for D3D12)",
  174. os.path.join(d3d12_deps_folder, "mesa"),
  175. ),
  176. (
  177. "agility_sdk_path",
  178. "Path to the Agility SDK distribution (optional for D3D12)",
  179. os.path.join(d3d12_deps_folder, "agility_sdk"),
  180. ),
  181. BoolVariable(
  182. "agility_sdk_multiarch",
  183. "Whether the Agility SDK DLLs will be stored in arch-specific subdirectories",
  184. False,
  185. ),
  186. BoolVariable("use_pix", "Use PIX (Performance tuning and debugging for DirectX 12) runtime", False),
  187. (
  188. "pix_path",
  189. "Path to the PIX runtime distribution (optional for D3D12)",
  190. os.path.join(d3d12_deps_folder, "pix"),
  191. ),
  192. ]
  193. def get_doc_classes():
  194. return [
  195. "EditorExportPlatformWindows",
  196. ]
  197. def get_doc_path():
  198. return "doc_classes"
  199. def get_flags():
  200. arch = detect_build_env_arch() or detect_arch()
  201. return {
  202. "arch": arch,
  203. "supported": ["d3d12", "mono", "xaudio2"],
  204. }
  205. def build_def_file(target, source, env: "SConsEnvironment"):
  206. arch_aliases = {
  207. "x86_32": "i386",
  208. "x86_64": "i386:x86-64",
  209. "arm32": "arm",
  210. "arm64": "arm64",
  211. }
  212. cmdbase = "dlltool -m " + arch_aliases[env["arch"]]
  213. if env["arch"] != "x86_32":
  214. cmdbase += " --no-leading-underscore"
  215. mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
  216. for x in range(len(source)):
  217. ok = True
  218. # Try prefixed executable (MinGW on Linux).
  219. cmd = mingw_bin_prefix + cmdbase + " -d " + str(source[x]) + " -l " + str(target[x])
  220. try:
  221. out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
  222. if len(out[1]):
  223. ok = False
  224. except Exception:
  225. ok = False
  226. # Try generic executable (MSYS2).
  227. if not ok:
  228. cmd = cmdbase + " -d " + str(source[x]) + " -l " + str(target[x])
  229. try:
  230. out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
  231. if len(out[1]):
  232. return -1
  233. except Exception:
  234. return -1
  235. return 0
  236. def configure_msvc(env: "SConsEnvironment"):
  237. """Configure env to work with MSVC"""
  238. ## Build type
  239. # TODO: Re-evaluate the need for this / streamline with common config.
  240. if env["target"] == "template_release":
  241. env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"])
  242. if env["windows_subsystem"] == "gui":
  243. env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"])
  244. else:
  245. env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])
  246. env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])
  247. ## Compile/link flags
  248. if env["use_llvm"]:
  249. env["CC"] = "clang-cl"
  250. env["CXX"] = "clang-cl"
  251. env["LINK"] = "lld-link"
  252. env["AR"] = "llvm-lib"
  253. env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"])
  254. env.extra_suffix = ".llvm" + env.extra_suffix
  255. # Ensure intellisense tools like `compile_commands.json` play nice with MSVC syntax.
  256. env["CPPDEFPREFIX"] = "-D"
  257. env["INCPREFIX"] = "-I"
  258. env.AppendUnique(CPPDEFINES=[("alloca", "_alloca")])
  259. if env["silence_msvc"] and not env.GetOption("clean"):
  260. from tempfile import mkstemp
  261. # Ensure we have a location to write captured output to, in case of false positives.
  262. capture_path = methods.base_folder / "platform" / "windows" / "msvc_capture.log"
  263. with open(capture_path, "wt", encoding="utf-8"):
  264. pass
  265. old_spawn = env["SPAWN"]
  266. re_redirect_stream = re.compile(r"^[12]?>")
  267. re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE)
  268. re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)')
  269. def spawn_capture(sh, escape, cmd, args, env):
  270. # We only care about cl/link, process everything else as normal.
  271. if args[0] not in ["cl", "link"]:
  272. return old_spawn(sh, escape, cmd, args, env)
  273. # Process as normal if the user is manually rerouting output.
  274. for arg in args:
  275. if re_redirect_stream.match(arg):
  276. return old_spawn(sh, escape, cmd, args, env)
  277. tmp_stdout, tmp_stdout_name = mkstemp()
  278. os.close(tmp_stdout)
  279. args.append(f">{tmp_stdout_name}")
  280. ret = old_spawn(sh, escape, cmd, args, env)
  281. try:
  282. with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout:
  283. lines = tmp_stdout.read().splitlines()
  284. os.remove(tmp_stdout_name)
  285. except OSError:
  286. pass
  287. # Early process no lines (OSError)
  288. if not lines:
  289. return ret
  290. is_cl = args[0] == "cl"
  291. content = ""
  292. caught = False
  293. for line in lines:
  294. # These conditions are far from all-encompassing, but are specialized
  295. # for what can be reasonably expected to show up in the repository.
  296. if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)):
  297. caught = True
  298. try:
  299. with open(capture_path, "a", encoding=sys.stdout.encoding) as log:
  300. log.write(line + "\n")
  301. except OSError:
  302. print_warning(f'Failed to log captured line: "{line}".')
  303. continue
  304. content += line + "\n"
  305. # Content remaining assumed to be an error/warning.
  306. if content:
  307. sys.stderr.write(content)
  308. return ret
  309. env["SPAWN"] = spawn_capture
  310. if env["debug_crt"]:
  311. # Always use dynamic runtime, static debug CRT breaks thread_local.
  312. env.AppendUnique(CCFLAGS=["/MDd"])
  313. else:
  314. if env["use_static_cpp"]:
  315. env.AppendUnique(CCFLAGS=["/MT"])
  316. else:
  317. env.AppendUnique(CCFLAGS=["/MD"])
  318. # MSVC incremental linking is broken and may _increase_ link time (GH-77968).
  319. if not env["incremental_link"]:
  320. env.Append(LINKFLAGS=["/INCREMENTAL:NO"])
  321. if env["arch"] == "x86_32":
  322. env["x86_libtheora_opt_vc"] = True
  323. env.Append(CCFLAGS=["/fp:strict"])
  324. env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"])
  325. env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
  326. # Once it was thought that only debug builds would be too large,
  327. # but this has recently stopped being true. See the mingw function
  328. # for notes on why this shouldn't be enabled for gcc
  329. env.AppendUnique(CCFLAGS=["/bigobj"])
  330. validate_win_version(env)
  331. if env["accesskit"]:
  332. if int(env["target_win_version"], 16) < 0x0602:
  333. print_info("AccessKit enabled, targeted Windows version changed to Windows 8 (0x602).")
  334. env["target_win_version"] = "0x0602" # Accessibility API require Windows 8+
  335. env.AppendUnique(
  336. CPPDEFINES=[
  337. "WINDOWS_ENABLED",
  338. "WASAPI_ENABLED",
  339. "WINMIDI_ENABLED",
  340. "TYPED_METHOD_BIND",
  341. "WIN32",
  342. "WINVER=%s" % env["target_win_version"],
  343. "_WIN32_WINNT=%s" % env["target_win_version"],
  344. ]
  345. )
  346. env.AppendUnique(CPPDEFINES=["NOMINMAX"]) # disable bogus min/max WinDef.h macros
  347. if env["arch"] == "x86_64":
  348. env.AppendUnique(CPPDEFINES=["_WIN64"])
  349. # Sanitizers
  350. prebuilt_lib_extra_suffix = ""
  351. if env["use_asan"]:
  352. env.extra_suffix += ".san"
  353. prebuilt_lib_extra_suffix = ".san"
  354. env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"])
  355. env.Append(CCFLAGS=["/fsanitize=address"])
  356. env.Append(LINKFLAGS=["/INFERASANLIBS"])
  357. ## Libs
  358. LIBS = [
  359. "winmm",
  360. "dsound",
  361. "kernel32",
  362. "ole32",
  363. "oleaut32",
  364. "sapi",
  365. "user32",
  366. "gdi32",
  367. "IPHLPAPI",
  368. "Shlwapi",
  369. "wsock32",
  370. "Ws2_32",
  371. "shell32",
  372. "advapi32",
  373. "dinput8",
  374. "dxguid",
  375. "imm32",
  376. "bcrypt",
  377. "Crypt32",
  378. "Avrt",
  379. "dwmapi",
  380. "dwrite",
  381. "wbemuuid",
  382. "ntdll",
  383. ]
  384. if env.debug_features:
  385. LIBS += ["psapi", "dbghelp"]
  386. if env["accesskit"]:
  387. if env["accesskit_sdk_path"] != "":
  388. env.Prepend(CPPPATH=[env["accesskit_sdk_path"] + "/include"])
  389. if env["arch"] == "arm64":
  390. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/arm64/msvc/static"])
  391. elif env["arch"] == "x86_64":
  392. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/x86_64/msvc/static"])
  393. elif env["arch"] == "x86_32":
  394. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/x86/msvc/static"])
  395. LIBS += [
  396. "accesskit",
  397. "uiautomationcore",
  398. "runtimeobject",
  399. "propsys",
  400. "oleaut32",
  401. "user32",
  402. "userenv",
  403. "ntdll",
  404. ]
  405. else:
  406. env.Append(CPPDEFINES=["ACCESSKIT_DYNAMIC"])
  407. env.Append(CPPDEFINES=["ACCESSKIT_ENABLED"])
  408. if env["vulkan"]:
  409. env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
  410. if not env["use_volk"]:
  411. LIBS += ["vulkan"]
  412. if env["d3d12"]:
  413. check_d3d12_installed(env)
  414. env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
  415. LIBS += ["dxgi", "dxguid"]
  416. LIBS += ["version"] # Mesa dependency.
  417. # Needed for avoiding C1128.
  418. if env["target"] == "release_debug":
  419. env.Append(CXXFLAGS=["/bigobj"])
  420. # PIX
  421. if env["arch"] not in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]):
  422. env["use_pix"] = False
  423. if env["use_pix"]:
  424. arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
  425. env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir])
  426. LIBS += ["WinPixEventRuntime"]
  427. env.Append(LIBPATH=[env["mesa_libs"] + "/bin"])
  428. LIBS += ["libNIR.windows." + env["arch"] + prebuilt_lib_extra_suffix]
  429. if env["opengl3"]:
  430. env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
  431. if env["angle_libs"] != "":
  432. env.AppendUnique(CPPDEFINES=["EGL_STATIC"])
  433. env.Append(LIBPATH=[env["angle_libs"]])
  434. LIBS += [
  435. "libANGLE.windows." + env["arch"] + prebuilt_lib_extra_suffix,
  436. "libEGL.windows." + env["arch"] + prebuilt_lib_extra_suffix,
  437. "libGLES.windows." + env["arch"] + prebuilt_lib_extra_suffix,
  438. ]
  439. LIBS += ["dxgi", "d3d9", "d3d11"]
  440. env.Prepend(CPPEXTPATH=["#thirdparty/angle/include"])
  441. if env["target"] in ["editor", "template_debug"]:
  442. LIBS += ["psapi", "dbghelp"]
  443. if env["use_llvm"]:
  444. LIBS += [f"clang_rt.builtins-{env['arch']}"]
  445. env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
  446. ## LTO
  447. if env["lto"] == "auto": # No LTO by default for MSVC, doesn't help.
  448. env["lto"] = "none"
  449. if env["lto"] != "none":
  450. if env["lto"] == "thin":
  451. if not env["use_llvm"]:
  452. print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
  453. sys.exit(255)
  454. env.AppendUnique(CCFLAGS=["-flto=thin"])
  455. elif env["use_llvm"]:
  456. env.AppendUnique(CCFLAGS=["-flto"])
  457. else:
  458. env.AppendUnique(CCFLAGS=["/GL"])
  459. if env["progress"]:
  460. env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
  461. else:
  462. env.AppendUnique(LINKFLAGS=["/LTCG"])
  463. env.AppendUnique(ARFLAGS=["/LTCG"])
  464. env.Append(LINKFLAGS=["/NATVIS:platform\\windows\\godot.natvis"])
  465. if env["use_asan"]:
  466. env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE_SANITIZERS)])
  467. else:
  468. env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
  469. def get_ar_version(env):
  470. ret = {
  471. "major": -1,
  472. "minor": -1,
  473. "patch": -1,
  474. "is_llvm": False,
  475. }
  476. try:
  477. output = (
  478. subprocess.check_output([env.subst(env["AR"]), "--version"], shell=(os.name == "nt"))
  479. .strip()
  480. .decode("utf-8")
  481. )
  482. except (subprocess.CalledProcessError, OSError):
  483. print_warning("Couldn't check version of `ar`.")
  484. return ret
  485. match = re.search(r"GNU ar(?: \(GNU Binutils\)| version) (\d+)\.(\d+)(?:\.(\d+))?", output)
  486. if match:
  487. ret["major"] = int(match[1])
  488. ret["minor"] = int(match[2])
  489. if match[3]:
  490. ret["patch"] = int(match[3])
  491. else:
  492. ret["patch"] = 0
  493. return ret
  494. match = re.search(r"LLVM version (\d+)\.(\d+)\.(\d+)", output)
  495. if match:
  496. ret["major"] = int(match[1])
  497. ret["minor"] = int(match[2])
  498. ret["patch"] = int(match[3])
  499. ret["is_llvm"] = True
  500. return ret
  501. print_warning("Couldn't parse version of `ar`.")
  502. return ret
  503. def get_is_ar_thin_supported(env):
  504. """Check whether `ar --thin` is supported. It is only supported since Binutils 2.38 or LLVM 14."""
  505. ar_version = get_ar_version(env)
  506. if ar_version["major"] == -1:
  507. return False
  508. if ar_version["is_llvm"]:
  509. return ar_version["major"] >= 14
  510. if ar_version["major"] == 2:
  511. return ar_version["minor"] >= 38
  512. print_warning("Unknown Binutils `ar` version.")
  513. return False
  514. WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)")
  515. def tempfile_arg_esc_func(arg):
  516. from SCons.Subst import quote_spaces
  517. arg = quote_spaces(arg)
  518. # GCC requires double Windows slashes, let's use UNIX separator
  519. return WINPATHSEP_RE.sub(r"/\1", arg)
  520. def configure_mingw(env: "SConsEnvironment"):
  521. if os.getenv("MSYSTEM") == "MSYS":
  522. print_error(
  523. "Running from base MSYS2 console/environment, use target specific environment instead (e.g., mingw32, mingw64, clang32, clang64)."
  524. )
  525. sys.exit(255)
  526. if (env_arch := detect_build_env_arch()) and env["arch"] != env_arch:
  527. print_error(
  528. f"Arch argument ({env['arch']}) is not matching MSYS2 console/environment that is being used to run SCons ({env_arch}).\n"
  529. "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."
  530. )
  531. sys.exit(255)
  532. if not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]) and not try_cmd(
  533. "clang --version", env["mingw_prefix"], env["arch"]
  534. ):
  535. print_error("No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path.")
  536. sys.exit(255)
  537. # Workaround for MinGW. See:
  538. # https://www.scons.org/wiki/LongCmdLinesOnWin32
  539. env.use_windows_spawn_fix()
  540. # HACK: For some reason, Windows-native shells have their MinGW tools
  541. # frequently fail as a result of parsing path separators incorrectly.
  542. # For some other reason, this issue is circumvented entirely if the
  543. # `mingw_prefix` bin is prepended to PATH.
  544. if os.sep == "\\":
  545. env.PrependENVPath("PATH", os.path.join(env["mingw_prefix"], "bin"))
  546. # In case the command line to AR is too long, use a response file.
  547. env["ARCOM_ORIG"] = env["ARCOM"]
  548. env["ARCOM"] = "${TEMPFILE('$ARCOM_ORIG', '$ARCOMSTR')}"
  549. env["TEMPFILESUFFIX"] = ".rsp"
  550. if os.name == "nt":
  551. env["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func
  552. ## Build type
  553. if not env["use_llvm"] and not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]):
  554. env["use_llvm"] = True
  555. if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]):
  556. env["use_llvm"] = False
  557. if not env["use_llvm"] and try_cmd("gcc --version", env["mingw_prefix"], env["arch"], True):
  558. print("Detected GCC to be a wrapper for Clang.")
  559. env["use_llvm"] = True
  560. if env.dev_build:
  561. # Allow big objects. It's supposed not to have drawbacks but seems to break
  562. # GCC LTO, so enabling for debug builds only (which are not built with LTO
  563. # and are the only ones with too big objects).
  564. env.Append(CCFLAGS=["-Wa,-mbig-obj"])
  565. if env["windows_subsystem"] == "gui":
  566. env.Append(LINKFLAGS=["-Wl,--subsystem,windows"])
  567. else:
  568. env.Append(LINKFLAGS=["-Wl,--subsystem,console"])
  569. env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])
  570. ## Compiler configuration
  571. if env["arch"] == "x86_32":
  572. if env["use_static_cpp"]:
  573. env.Append(LINKFLAGS=["-static"])
  574. env.Append(LINKFLAGS=["-static-libgcc"])
  575. env.Append(LINKFLAGS=["-static-libstdc++"])
  576. else:
  577. if env["use_static_cpp"]:
  578. env.Append(LINKFLAGS=["-static"])
  579. if env["arch"] == "x86_32":
  580. env["x86_libtheora_opt_gcc"] = True
  581. env.Append(CCFLAGS=["-ffp-contract=off"])
  582. if env["use_llvm"]:
  583. env["CC"] = get_detected(env, "clang")
  584. env["CXX"] = get_detected(env, "clang++")
  585. env["AR"] = get_detected(env, "ar")
  586. env["RANLIB"] = get_detected(env, "ranlib")
  587. env.Append(ASFLAGS=["-c"])
  588. env.extra_suffix = ".llvm" + env.extra_suffix
  589. else:
  590. env["CC"] = get_detected(env, "gcc")
  591. env["CXX"] = get_detected(env, "g++")
  592. env["AR"] = get_detected(env, "gcc-ar" if os.name != "nt" else "ar")
  593. env["RANLIB"] = get_detected(env, "gcc-ranlib")
  594. env["RC"] = get_detected(env, "windres")
  595. ARCH_TARGETS = {
  596. "x86_32": "pe-i386",
  597. "x86_64": "pe-x86-64",
  598. "arm32": "armv7-w64-mingw32",
  599. "arm64": "aarch64-w64-mingw32",
  600. }
  601. env.AppendUnique(RCFLAGS=f"--target={ARCH_TARGETS[env['arch']]}")
  602. env["AS"] = get_detected(env, "as")
  603. env["OBJCOPY"] = get_detected(env, "objcopy")
  604. env["STRIP"] = get_detected(env, "strip")
  605. ## LTO
  606. if env["lto"] == "auto": # Enable LTO for production with MinGW.
  607. env["lto"] = "thin" if env["use_llvm"] else "full"
  608. if env["lto"] != "none":
  609. if env["lto"] == "thin":
  610. if not env["use_llvm"]:
  611. print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
  612. sys.exit(255)
  613. env.Append(CCFLAGS=["-flto=thin"])
  614. env.Append(LINKFLAGS=["-flto=thin"])
  615. elif not env["use_llvm"] and env.GetOption("num_jobs") > 1:
  616. env.Append(CCFLAGS=["-flto"])
  617. env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))])
  618. else:
  619. env.Append(CCFLAGS=["-flto"])
  620. env.Append(LINKFLAGS=["-flto"])
  621. if not env["use_llvm"]:
  622. # For mingw-gcc LTO, disable linker plugin and enable whole program to work around GH-102867.
  623. env.Append(CCFLAGS=["-fno-use-linker-plugin", "-fwhole-program"])
  624. env.Append(LINKFLAGS=["-fno-use-linker-plugin", "-fwhole-program"])
  625. if env["use_asan"]:
  626. env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE_SANITIZERS)])
  627. else:
  628. env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)])
  629. ## Compile flags
  630. validate_win_version(env)
  631. if env["accesskit"]:
  632. if int(env["target_win_version"], 16) < 0x0602:
  633. print_info("AccessKit enabled, targeted Windows version changed to Windows 8 (0x602).")
  634. env["target_win_version"] = "0x0602" # Accessibility API require Windows 8+
  635. if not env["use_llvm"]:
  636. env.Append(CCFLAGS=["-mwindows"])
  637. if env["use_asan"] or env["use_ubsan"]:
  638. if not env["use_llvm"]:
  639. print("GCC does not support sanitizers on Windows.")
  640. sys.exit(255)
  641. if env["arch"] not in ["x86_32", "x86_64"]:
  642. print("Sanitizers are only supported for x86_32 and x86_64.")
  643. sys.exit(255)
  644. env.extra_suffix += ".san"
  645. env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"])
  646. san_flags = []
  647. if env["use_asan"]:
  648. san_flags.append("-fsanitize=address")
  649. if env["use_ubsan"]:
  650. san_flags.append("-fsanitize=undefined")
  651. # Disable the vptr check since it gets triggered on any COM interface calls.
  652. san_flags.append("-fno-sanitize=vptr")
  653. env.Append(CFLAGS=san_flags)
  654. env.Append(CCFLAGS=san_flags)
  655. env.Append(LINKFLAGS=san_flags)
  656. if get_is_ar_thin_supported(env):
  657. env.Append(ARFLAGS=["--thin"])
  658. env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"])
  659. env.Append(
  660. CPPDEFINES=[
  661. ("WINVER", env["target_win_version"]),
  662. ("_WIN32_WINNT", env["target_win_version"]),
  663. ]
  664. )
  665. env.Append(
  666. LIBS=[
  667. "mingw32",
  668. "dsound",
  669. "ole32",
  670. "d3d9",
  671. "winmm",
  672. "gdi32",
  673. "iphlpapi",
  674. "shlwapi",
  675. "wsock32",
  676. "ws2_32",
  677. "kernel32",
  678. "oleaut32",
  679. "sapi",
  680. "dinput8",
  681. "dxguid",
  682. "ksuser",
  683. "imm32",
  684. "bcrypt",
  685. "crypt32",
  686. "avrt",
  687. "uuid",
  688. "dwmapi",
  689. "dwrite",
  690. "wbemuuid",
  691. "ntdll",
  692. ]
  693. )
  694. if env["accesskit"]:
  695. if env["accesskit_sdk_path"] != "":
  696. env.Prepend(CPPPATH=[env["accesskit_sdk_path"] + "/include"])
  697. if env["use_llvm"]:
  698. if env["arch"] == "arm64":
  699. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/arm64/mingw-llvm/static/"])
  700. elif env["arch"] == "x86_64":
  701. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/x86_64/mingw-llvm/static/"])
  702. elif env["arch"] == "x86_32":
  703. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/x86/mingw-llvm/static/"])
  704. else:
  705. if env["arch"] == "x86_64":
  706. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/x86_64/mingw/static/"])
  707. elif env["arch"] == "x86_32":
  708. env.Append(LIBPATH=[env["accesskit_sdk_path"] + "/lib/windows/x86/mingw/static/"])
  709. env.Append(
  710. LIBS=[
  711. "accesskit",
  712. "uiautomationcore." + env["arch"],
  713. "runtimeobject",
  714. "propsys",
  715. "oleaut32",
  716. "user32",
  717. "userenv",
  718. "ntdll",
  719. ]
  720. )
  721. else:
  722. env.Append(CPPDEFINES=["ACCESSKIT_DYNAMIC"])
  723. env.Append(LIBPATH=["#platform/windows"])
  724. env.Append(CPPDEFINES=["ACCESSKIT_ENABLED"])
  725. if env.debug_features:
  726. env.Append(LIBS=["psapi", "dbghelp"])
  727. if env["vulkan"]:
  728. env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
  729. if not env["use_volk"]:
  730. env.Append(LIBS=["vulkan"])
  731. if env["d3d12"]:
  732. check_d3d12_installed(env)
  733. env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
  734. env.Append(LIBS=["dxgi", "dxguid"])
  735. # PIX
  736. if env["arch"] not in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]):
  737. env["use_pix"] = False
  738. if env["use_pix"]:
  739. arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
  740. env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir])
  741. env.Append(LIBS=["WinPixEventRuntime"])
  742. env.Append(LIBPATH=[env["mesa_libs"] + "/bin"])
  743. env.Append(LIBS=["libNIR.windows." + env["arch"]])
  744. env.Append(LIBS=["version"]) # Mesa dependency.
  745. if env["opengl3"]:
  746. env.Append(CPPDEFINES=["GLES3_ENABLED"])
  747. if env["angle_libs"] != "":
  748. env.AppendUnique(CPPDEFINES=["EGL_STATIC"])
  749. env.Append(LIBPATH=[env["angle_libs"]])
  750. env.Append(
  751. LIBS=[
  752. "EGL.windows." + env["arch"],
  753. "GLES.windows." + env["arch"],
  754. "ANGLE.windows." + env["arch"],
  755. ]
  756. )
  757. env.Append(LIBS=["dxgi", "d3d9", "d3d11"])
  758. env.Prepend(CPPEXTPATH=["#thirdparty/angle/include"])
  759. env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)])
  760. # dlltool
  761. env.Append(BUILDERS={"DEF": env.Builder(action=build_def_file, suffix=".a", src_suffix=".def")})
  762. def configure(env: "SConsEnvironment"):
  763. # Validate arch.
  764. supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
  765. validate_arch(env["arch"], get_name(), supported_arches)
  766. # At this point the env has been set up with basic tools/compilers.
  767. env.Prepend(CPPPATH=["#platform/windows"])
  768. env.msvc = "mingw" not in env["TOOLS"]
  769. if env.msvc:
  770. configure_msvc(env)
  771. else:
  772. configure_mingw(env)
  773. def check_d3d12_installed(env):
  774. if not os.path.exists(env["mesa_libs"]):
  775. print_error(
  776. "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
  777. "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
  778. "See the documentation for more information:\n\t"
  779. "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
  780. )
  781. sys.exit(255)
  782. def validate_win_version(env):
  783. if int(env["target_win_version"], 16) < 0x0601:
  784. print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
  785. sys.exit(255)