detect.py 32 KB

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