windows.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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("use_llvm", "Use the LLVM compiler (MVSC or MinGW depending on the use_mingw flag)", False))
  66. opts.Add("mingw_prefix", "MinGW prefix", mingw)
  67. def exists(env):
  68. return True
  69. def generate(env):
  70. if not env["use_mingw"] and msvc.exists(env):
  71. if env["arch"] == "x86_64":
  72. env["TARGET_ARCH"] = "amd64"
  73. elif env["arch"] == "arm64":
  74. env["TARGET_ARCH"] = "arm64"
  75. elif env["arch"] == "arm32":
  76. env["TARGET_ARCH"] = "arm"
  77. elif env["arch"] == "x86_32":
  78. env["TARGET_ARCH"] = "x86"
  79. env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool
  80. env["MSVS_VERSION"] = None
  81. env["MSVC_VERSION"] = None
  82. env["is_msvc"] = True
  83. # MSVC, linker, and archiver.
  84. msvc.generate(env)
  85. env.Tool("msvc")
  86. env.Tool("mslib")
  87. env.Tool("mslink")
  88. env.Append(CPPDEFINES=["TYPED_METHOD_BIND", "NOMINMAX"])
  89. env.Append(CCFLAGS=["/utf-8"])
  90. env.Append(LINKFLAGS=["/WX"])
  91. if env["use_llvm"]:
  92. env["CC"] = "clang-cl"
  93. env["CXX"] = "clang-cl"
  94. if env["use_static_cpp"]:
  95. env.Append(CCFLAGS=["/MT"])
  96. else:
  97. env.Append(CCFLAGS=["/MD"])
  98. if env["silence_msvc"] and not env.GetOption("clean"):
  99. silence_msvc(env)
  100. elif (sys.platform == "win32" or sys.platform == "msys") and not env["mingw_prefix"]:
  101. env["use_mingw"] = True
  102. mingw.generate(env)
  103. # Don't want lib prefixes
  104. env["IMPLIBPREFIX"] = ""
  105. env["SHLIBPREFIX"] = ""
  106. # Want dll suffix
  107. env["SHLIBSUFFIX"] = ".dll"
  108. env.Append(CCFLAGS=["-Wwrite-strings"])
  109. env.Append(LINKFLAGS=["-Wl,--no-undefined"])
  110. if env["use_static_cpp"]:
  111. env.Append(
  112. LINKFLAGS=[
  113. "-static",
  114. "-static-libgcc",
  115. "-static-libstdc++",
  116. ]
  117. )
  118. # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
  119. my_spawn.configure(env)
  120. else:
  121. env["use_mingw"] = True
  122. # Cross-compilation using MinGW
  123. prefix = ""
  124. if env["mingw_prefix"]:
  125. prefix = env["mingw_prefix"] + "/bin/"
  126. if env["arch"] == "x86_64":
  127. prefix += "x86_64"
  128. elif env["arch"] == "arm64":
  129. prefix += "aarch64"
  130. elif env["arch"] == "arm32":
  131. prefix += "armv7"
  132. elif env["arch"] == "x86_32":
  133. prefix += "i686"
  134. if env["use_llvm"]:
  135. env["CXX"] = prefix + "-w64-mingw32-clang++"
  136. env["CC"] = prefix + "-w64-mingw32-clang"
  137. env["AR"] = prefix + "-w64-mingw32-llvm-ar"
  138. env["RANLIB"] = prefix + "-w64-mingw32-ranlib"
  139. env["LINK"] = prefix + "-w64-mingw32-clang"
  140. else:
  141. env["CXX"] = prefix + "-w64-mingw32-g++"
  142. env["CC"] = prefix + "-w64-mingw32-gcc"
  143. env["AR"] = prefix + "-w64-mingw32-gcc-ar"
  144. env["RANLIB"] = prefix + "-w64-mingw32-ranlib"
  145. env["LINK"] = prefix + "-w64-mingw32-g++"
  146. # Want dll suffix
  147. env["SHLIBSUFFIX"] = ".dll"
  148. env.Append(CCFLAGS=["-Wwrite-strings"])
  149. env.Append(LINKFLAGS=["-Wl,--no-undefined"])
  150. if env["use_static_cpp"]:
  151. env.Append(
  152. LINKFLAGS=[
  153. "-static",
  154. "-static-libgcc",
  155. "-static-libstdc++",
  156. ]
  157. )
  158. if env["use_llvm"]:
  159. env.Append(LINKFLAGS=["-lstdc++"])
  160. if sys.platform == "win32" or sys.platform == "msys":
  161. my_spawn.configure(env)
  162. env.Append(CPPDEFINES=["WINDOWS_ENABLED"])
  163. # Refer to https://github.com/godotengine/godot/blob/master/platform/windows/detect.py
  164. if env["lto"] == "auto":
  165. if env.get("is_msvc", False):
  166. # No LTO by default for MSVC, doesn't help.
  167. env["lto"] = "none"
  168. else: # Release
  169. env["lto"] = "full"
  170. common_compiler_flags.generate(env)