detect.py 33 KB

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