windows.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import sys
  2. import common_compiler_flags
  3. import my_spawn
  4. from SCons.Tool import mingw, msvc
  5. from SCons.Variables import BoolVariable
  6. def silence_msvc(env):
  7. import os
  8. import re
  9. import tempfile
  10. # Ensure we have a location to write captured output to, in case of false positives.
  11. capture_path = os.path.join(os.path.dirname(__file__), "..", "msvc_capture.log")
  12. with open(capture_path, "wt", encoding="utf-8"):
  13. pass
  14. old_spawn = env["SPAWN"]
  15. re_redirect_stream = re.compile(r"^[12]?>")
  16. re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE)
  17. re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)')
  18. def spawn_capture(sh, escape, cmd, args, env):
  19. # We only care about cl/link, process everything else as normal.
  20. if args[0] not in ["cl", "link"]:
  21. return old_spawn(sh, escape, cmd, args, env)
  22. # Process as normal if the user is manually rerouting output.
  23. for arg in args:
  24. if re_redirect_stream.match(arg):
  25. return old_spawn(sh, escape, cmd, args, env)
  26. tmp_stdout, tmp_stdout_name = tempfile.mkstemp()
  27. os.close(tmp_stdout)
  28. args.append(f">{tmp_stdout_name}")
  29. ret = old_spawn(sh, escape, cmd, args, env)
  30. try:
  31. with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout:
  32. lines = tmp_stdout.read().splitlines()
  33. os.remove(tmp_stdout_name)
  34. except OSError:
  35. pass
  36. # Early process no lines (OSError)
  37. if not lines:
  38. return ret
  39. is_cl = args[0] == "cl"
  40. content = ""
  41. caught = False
  42. for line in lines:
  43. # These conditions are far from all-encompassing, but are specialized
  44. # for what can be reasonably expected to show up in the repository.
  45. if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)):
  46. caught = True
  47. try:
  48. with open(capture_path, "a", encoding=sys.stdout.encoding) as log:
  49. log.write(line + "\n")
  50. except OSError:
  51. print(f'WARNING: Failed to log captured line: "{line}".')
  52. continue
  53. content += line + "\n"
  54. # Content remaining assumed to be an error/warning.
  55. if content:
  56. sys.stderr.write(content)
  57. return ret
  58. env["SPAWN"] = spawn_capture
  59. def options(opts):
  60. opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False))
  61. opts.Add(BoolVariable("use_clang_cl", "Use the clang driver instead of MSVC - only effective on Windows", False))
  62. opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True))
  63. opts.Add(BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting errors to stderr.", True))
  64. def exists(env):
  65. return True
  66. def generate(env):
  67. if not env["use_mingw"] and msvc.exists(env):
  68. if env["arch"] == "x86_64":
  69. env["TARGET_ARCH"] = "amd64"
  70. elif env["arch"] == "x86_32":
  71. env["TARGET_ARCH"] = "x86"
  72. env["is_msvc"] = True
  73. # MSVC, linker, and archiver.
  74. msvc.generate(env)
  75. env.Tool("mslib")
  76. env.Tool("mslink")
  77. env.Append(CPPDEFINES=["TYPED_METHOD_BIND", "NOMINMAX"])
  78. env.Append(CCFLAGS=["/utf-8"])
  79. env.Append(LINKFLAGS=["/WX"])
  80. if env["use_clang_cl"]:
  81. env["CC"] = "clang-cl"
  82. env["CXX"] = "clang-cl"
  83. if env["use_static_cpp"]:
  84. env.Append(CCFLAGS=["/MT"])
  85. else:
  86. env.Append(CCFLAGS=["/MD"])
  87. if env["silence_msvc"] and not env.GetOption("clean"):
  88. silence_msvc(env)
  89. elif sys.platform == "win32" or sys.platform == "msys":
  90. env["use_mingw"] = True
  91. mingw.generate(env)
  92. # Don't want lib prefixes
  93. env["IMPLIBPREFIX"] = ""
  94. env["SHLIBPREFIX"] = ""
  95. # Want dll suffix
  96. env["SHLIBSUFFIX"] = ".dll"
  97. env.Append(CCFLAGS=["-Wwrite-strings"])
  98. env.Append(LINKFLAGS=["-Wl,--no-undefined"])
  99. if env["use_static_cpp"]:
  100. env.Append(
  101. LINKFLAGS=[
  102. "-static",
  103. "-static-libgcc",
  104. "-static-libstdc++",
  105. ]
  106. )
  107. # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
  108. my_spawn.configure(env)
  109. else:
  110. env["use_mingw"] = True
  111. # Cross-compilation using MinGW
  112. prefix = "i686" if env["arch"] == "x86_32" else env["arch"]
  113. env["CXX"] = prefix + "-w64-mingw32-g++"
  114. env["CC"] = prefix + "-w64-mingw32-gcc"
  115. env["AR"] = prefix + "-w64-mingw32-ar"
  116. env["RANLIB"] = prefix + "-w64-mingw32-ranlib"
  117. env["LINK"] = prefix + "-w64-mingw32-g++"
  118. # Want dll suffix
  119. env["SHLIBSUFFIX"] = ".dll"
  120. env.Append(CCFLAGS=["-Wwrite-strings"])
  121. env.Append(LINKFLAGS=["-Wl,--no-undefined"])
  122. if env["use_static_cpp"]:
  123. env.Append(
  124. LINKFLAGS=[
  125. "-static",
  126. "-static-libgcc",
  127. "-static-libstdc++",
  128. ]
  129. )
  130. env.Append(CPPDEFINES=["WINDOWS_ENABLED"])
  131. common_compiler_flags.generate(env)