|
5 | 5 | from SCons.Variables import *
|
6 | 6 |
|
7 | 7 |
|
| 8 | +def silence_msvc(env): |
| 9 | + import os |
| 10 | + import re |
| 11 | + import tempfile |
| 12 | + |
| 13 | + # Ensure we have a location to write captured output to, in case of false positives. |
| 14 | + capture_path = os.path.join(os.path.dirname(__file__), "..", "msvc_capture.log") |
| 15 | + with open(capture_path, "wt", encoding="utf-8"): |
| 16 | + pass |
| 17 | + |
| 18 | + old_spawn = env["SPAWN"] |
| 19 | + re_redirect_stream = re.compile(r"^[12]?>") |
| 20 | + re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE) |
| 21 | + re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)') |
| 22 | + |
| 23 | + def spawn_capture(sh, escape, cmd, args, env): |
| 24 | + # We only care about cl/link, process everything else as normal. |
| 25 | + if args[0] not in ["cl", "link"]: |
| 26 | + return old_spawn(sh, escape, cmd, args, env) |
| 27 | + |
| 28 | + # Process as normal if the user is manually rerouting output. |
| 29 | + for arg in args: |
| 30 | + if re_redirect_stream.match(arg): |
| 31 | + return old_spawn(sh, escape, cmd, args, env) |
| 32 | + |
| 33 | + tmp_stdout, tmp_stdout_name = tempfile.mkstemp() |
| 34 | + os.close(tmp_stdout) |
| 35 | + args.append(f">{tmp_stdout_name}") |
| 36 | + ret = old_spawn(sh, escape, cmd, args, env) |
| 37 | + |
| 38 | + try: |
| 39 | + with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout: |
| 40 | + lines = tmp_stdout.read().splitlines() |
| 41 | + os.remove(tmp_stdout_name) |
| 42 | + except OSError: |
| 43 | + pass |
| 44 | + |
| 45 | + # Early process no lines (OSError) |
| 46 | + if not lines: |
| 47 | + return ret |
| 48 | + |
| 49 | + is_cl = args[0] == "cl" |
| 50 | + content = "" |
| 51 | + caught = False |
| 52 | + for line in lines: |
| 53 | + # These conditions are far from all-encompassing, but are specialized |
| 54 | + # for what can be reasonably expected to show up in the repository. |
| 55 | + if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)): |
| 56 | + caught = True |
| 57 | + try: |
| 58 | + with open(capture_path, "a", encoding=sys.stdout.encoding) as log: |
| 59 | + log.write(line + "\n") |
| 60 | + except OSError: |
| 61 | + print(f'WARNING: Failed to log captured line: "{line}".') |
| 62 | + continue |
| 63 | + content += line + "\n" |
| 64 | + # Content remaining assumed to be an error/warning. |
| 65 | + if content: |
| 66 | + sys.stderr.write(content) |
| 67 | + |
| 68 | + return ret |
| 69 | + |
| 70 | + env["SPAWN"] = spawn_capture |
| 71 | + |
| 72 | + |
8 | 73 | def options(opts):
|
9 | 74 | opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False))
|
10 | 75 | opts.Add(BoolVariable("use_clang_cl", "Use the clang driver instead of MSVC - only effective on Windows", False))
|
11 | 76 | opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True))
|
| 77 | + opts.Add(BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting errors to stderr.", True)) |
12 | 78 |
|
13 | 79 |
|
14 | 80 | def exists(env):
|
@@ -42,6 +108,9 @@ def generate(env):
|
42 | 108 | else:
|
43 | 109 | env.Append(CCFLAGS=["/MD"])
|
44 | 110 |
|
| 111 | + if env["silence_msvc"] and not env.GetOption("clean"): |
| 112 | + silence_msvc(env) |
| 113 | + |
45 | 114 | elif sys.platform == "win32" or sys.platform == "msys":
|
46 | 115 | env["use_mingw"] = True
|
47 | 116 | mingw.generate(env)
|
|
0 commit comments