windows.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import os
  2. import sys
  3. import common_compiler_flags
  4. import my_spawn
  5. from SCons.Tool import mingw, msvc
  6. from SCons.Variables import BoolVariable
  7. def silence_msvc(env):
  8. import os
  9. import re
  10. import tempfile
  11. # Ensure we have a location to write captured output to, in case of false positives.
  12. capture_path = os.path.join(os.path.dirname(__file__), "..", "msvc_capture.log")
  13. with open(capture_path, "wt", encoding="utf-8"):
  14. pass
  15. old_spawn = env["SPAWN"]
  16. re_redirect_stream = re.compile(r"^[12]?>")
  17. re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE)
  18. re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)')
  19. def spawn_capture(sh, escape, cmd, args, env):
  20. # We only care about cl/link, process everything else as normal.
  21. if args[0] not in ["cl", "link"]:
  22. return old_spawn(sh, escape, cmd, args, env)
  23. # Process as normal if the user is manually rerouting output.
  24. for arg in args:
  25. if re_redirect_stream.match(arg):
  26. return old_spawn(sh, escape, cmd, args, env)
  27. tmp_stdout, tmp_stdout_name = tempfile.mkstemp()
  28. os.close(tmp_stdout)
  29. args.append(f">{tmp_stdout_name}")
  30. ret = old_spawn(sh, escape, cmd, args, env)
  31. try:
  32. with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout:
  33. lines = tmp_stdout.read().splitlines()
  34. os.remove(tmp_stdout_name)
  35. except OSError:
  36. pass
  37. # Early process no lines (OSError)
  38. if not lines:
  39. return ret
  40. is_cl = args[0] == "cl"
  41. content = ""
  42. caught = False
  43. for line in lines:
  44. # These conditions are far from all-encompassing, but are specialized
  45. # for what can be reasonably expected to show up in the repository.
  46. if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)):
  47. caught = True
  48. try:
  49. with open(capture_path, "a", encoding=sys.stdout.encoding) as log:
  50. log.write(line + "\n")
  51. except OSError:
  52. print(f'WARNING: Failed to log captured line: "{line}".')
  53. continue
  54. content += line + "\n"
  55. # Content remaining assumed to be an error/warning.
  56. if content:
  57. sys.stderr.write(content)
  58. return ret
  59. env["SPAWN"] = spawn_capture
  60. def options(opts):
  61. mingw = os.getenv("MINGW_PREFIX", "")
  62. opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False))
  63. opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True))
  64. opts.Add(BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting errors to stderr.", True))
  65. opts.Add(BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False))
  66. opts.Add(BoolVariable("use_llvm", "Use the LLVM compiler (MVSC or MinGW depending on the use_mingw flag)", False))
  67. opts.Add("mingw_prefix", "MinGW prefix", mingw)
  68. def exists(env):
  69. return True
  70. def generate(env):
  71. if not env["use_mingw"] and msvc.exists(env):
  72. if env["arch"] == "x86_64":
  73. env["TARGET_ARCH"] = "amd64"
  74. elif env["arch"] == "arm64":
  75. env["TARGET_ARCH"] = "arm64"
  76. elif env["arch"] == "arm32":
  77. env["TARGET_ARCH"] = "arm"
  78. elif env["arch"] == "x86_32":
  79. env["TARGET_ARCH"] = "x86"
  80. env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool
  81. env["MSVS_VERSION"] = None
  82. env["MSVC_VERSION"] = None
  83. env["is_msvc"] = True
  84. # MSVC, linker, and archiver.
  85. msvc.generate(env)
  86. env.Tool("msvc")
  87. env.Tool("mslib")
  88. env.Tool("mslink")
  89. env.Append(CPPDEFINES=["TYPED_METHOD_BIND", "NOMINMAX"])
  90. env.Append(CCFLAGS=["/utf-8"])
  91. env.Append(LINKFLAGS=["/WX"])
  92. if env["use_llvm"]:
  93. env["CC"] = "clang-cl"
  94. env["CXX"] = "clang-cl"
  95. if env["debug_crt"]:
  96. # Always use dynamic runtime, static debug CRT breaks thread_local.
  97. env.AppendUnique(CCFLAGS=["/MDd"])
  98. else:
  99. if env["use_static_cpp"]:
  100. env.AppendUnique(CCFLAGS=["/MT"])
  101. else:
  102. env.AppendUnique(CCFLAGS=["/MD"])
  103. if env["silence_msvc"] and not env.GetOption("clean"):
  104. silence_msvc(env)
  105. elif (sys.platform == "win32" or sys.platform == "msys") and not env["mingw_prefix"]:
  106. env["use_mingw"] = True
  107. mingw.generate(env)
  108. # Don't want lib prefixes
  109. env["IMPLIBPREFIX"] = ""
  110. env["SHLIBPREFIX"] = ""
  111. # Want dll suffix
  112. env["SHLIBSUFFIX"] = ".dll"
  113. env.Append(CCFLAGS=["-Wwrite-strings"])
  114. env.Append(LINKFLAGS=["-Wl,--no-undefined"])
  115. if env["use_static_cpp"]:
  116. env.Append(
  117. LINKFLAGS=[
  118. "-static",
  119. "-static-libgcc",
  120. "-static-libstdc++",
  121. ]
  122. )
  123. # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
  124. my_spawn.configure(env)
  125. else:
  126. env["use_mingw"] = True
  127. # Cross-compilation using MinGW
  128. prefix = ""
  129. if env["mingw_prefix"]:
  130. prefix = env["mingw_prefix"] + "/bin/"
  131. if env["arch"] == "x86_64":
  132. prefix += "x86_64"
  133. elif env["arch"] == "arm64":
  134. prefix += "aarch64"
  135. elif env["arch"] == "arm32":
  136. prefix += "armv7"
  137. elif env["arch"] == "x86_32":
  138. prefix += "i686"
  139. if env["use_llvm"]:
  140. env["CXX"] = prefix + "-w64-mingw32-clang++"
  141. env["CC"] = prefix + "-w64-mingw32-clang"
  142. env["AR"] = prefix + "-w64-mingw32-llvm-ar"
  143. env["RANLIB"] = prefix + "-w64-mingw32-ranlib"
  144. env["LINK"] = prefix + "-w64-mingw32-clang"
  145. else:
  146. env["CXX"] = prefix + "-w64-mingw32-g++"
  147. env["CC"] = prefix + "-w64-mingw32-gcc"
  148. env["AR"] = prefix + "-w64-mingw32-gcc-ar"
  149. env["RANLIB"] = prefix + "-w64-mingw32-ranlib"
  150. env["LINK"] = prefix + "-w64-mingw32-g++"
  151. # Want dll suffix
  152. env["SHLIBSUFFIX"] = ".dll"
  153. env.Append(CCFLAGS=["-Wwrite-strings"])
  154. env.Append(LINKFLAGS=["-Wl,--no-undefined"])
  155. if env["use_static_cpp"]:
  156. env.Append(
  157. LINKFLAGS=[
  158. "-static",
  159. "-static-libgcc",
  160. "-static-libstdc++",
  161. ]
  162. )
  163. if env["use_llvm"]:
  164. env.Append(LINKFLAGS=["-lstdc++"])
  165. if sys.platform == "win32" or sys.platform == "msys":
  166. my_spawn.configure(env)
  167. env.Append(CPPDEFINES=["WINDOWS_ENABLED"])
  168. # Refer to https://github.com/godotengine/godot/blob/master/platform/windows/detect.py
  169. if env["lto"] == "auto":
  170. if env.get("is_msvc", False):
  171. # No LTO by default for MSVC, doesn't help.
  172. env["lto"] = "none"
  173. else: # Release
  174. env["lto"] = "full"
  175. common_compiler_flags.generate(env)