diff --git a/ChangeLog.md b/ChangeLog.md index d22df7fda65b3..de4c9bf79556f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,8 @@ See docs/process.md for more on how version tagging works. 4.0.16 (in development) ----------------------- +- For windows users, colored console output for error messages and logging now + requires Windows 10 or above. (#25502) - A warning was added about usage of embind without C++17 or above. (#25424) - The minimum supported versions of Node, Chrome and Firefox were bumped enabling the removal of the `globalThis` polyfill and universally enabling diff --git a/test/test_other.py b/test/test_other.py index 8e01ea79763ce..8caf035a2f2b6 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -11607,21 +11607,12 @@ def test_color_diagnostics_disable(self, flag): }) def test_color_diagnostics_force(self, flag): create_file('src.c', 'int main() {') - # -fansi-escape-codes is needed here to make this test work on windows, which doesn't - # use ansi codes by default - output = self.expect_fail([EMCC, '-fansi-escape-codes', flag, 'src.c']) + # -fansi-escape-codes is needed on windows in order to get clang to emit ANSI colors + output = self.expect_fail([EMCC, flag, '-fansi-escape-codes', 'src.c']) self.assertIn("\x1b[1msrc.c:1:13: \x1b[0m\x1b[0;1;31merror: \x1b[0m\x1b[1mexpected '}'\x1b[0m", output) - # Verify that emcc errors show up as red and bold + # Verify that emcc errors show up as red and bold from emcc self.assertIn('emcc: \x1b[31m\x1b[1m', output) - if WINDOWS: - # Also test without -fansi-escape-codes on windows. - # In those mode the code will use kernel calls such as SetConsoleTextAttribute to - # change the output color. We cannot detect this in the output, but we can at least - # get coverage of the code path in the diagnositics.py. - output = self.expect_fail([EMCC, flag, 'src.c']) - self.assertNotIn('\x1b', output) - def test_sanitizer_color(self): create_file('src.c', ''' #include diff --git a/tools/cmdline.py b/tools/cmdline.py index c5d7021d78ce7..0f27b3c4bae8c 100644 --- a/tools/cmdline.py +++ b/tools/cmdline.py @@ -513,8 +513,6 @@ def consume_arg_file(): diagnostics.color_enabled = True elif arg in ('-fno-color-diagnostics', '-fdiagnostics-color=never'): diagnostics.color_enabled = False - elif arg == '-fansi-escape-codes': - diagnostics.force_ansi = True elif arg == '-fno-exceptions': settings.DISABLE_EXCEPTION_CATCHING = 1 settings.DISABLE_EXCEPTION_THROWING = 1 diff --git a/tools/colored_logger.py b/tools/colored_logger.py index 2802415a1ae55..b5e90ca2206e5 100644 --- a/tools/colored_logger.py +++ b/tools/colored_logger.py @@ -11,88 +11,25 @@ import logging -def add_coloring_to_emit_windows(fn): +def ansi_color_available(): + if not sys.platform.startswith('win'): + return sys.stderr.isatty() + # Constants from the Windows API STD_OUTPUT_HANDLE = -11 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - def _get_color(): - SHORT = ctypes.c_short - WORD = ctypes.c_ushort - - class COORD(ctypes.Structure): - _fields_ = [ - ("X", SHORT), - ("Y", SHORT)] - - class SMALL_RECT(ctypes.Structure): - _fields_ = [ - ("Left", SHORT), - ("Top", SHORT), - ("Right", SHORT), - ("Bottom", SHORT)] - - class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): - _fields_ = [ - ("dwSize", COORD), - ("dwCursorPosition", COORD), - ("wAttributes", WORD), - ("srWindow", SMALL_RECT), - ("dwMaximumWindowSize", COORD)] + kernel32 = ctypes.windll.kernel32 + stdout_handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - csbi = CONSOLE_SCREEN_BUFFER_INFO() - ctypes.windll.kernel32.GetConsoleScreenBufferInfo(hdl, ctypes.byref(csbi)) - return csbi.wAttributes + # Get the current console mode + console_mode = ctypes.c_uint() + if not kernel32.GetConsoleMode(stdout_handle, ctypes.byref(console_mode)): + # Handle error if GetConsoleMode fails + return False - def _set_color(code): - hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, code) - - def new(*args): - # wincon.h - FOREGROUND_BLACK = 0x0000 # noqa - FOREGROUND_BLUE = 0x0001 # noqa - FOREGROUND_GREEN = 0x0002 # noqa - FOREGROUND_CYAN = 0x0003 # noqa - FOREGROUND_RED = 0x0004 # noqa - FOREGROUND_MAGENTA = 0x0005 # noqa - FOREGROUND_YELLOW = 0x0006 # noqa - FOREGROUND_GREY = 0x0007 # noqa - FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified. - - FOREGROUND_WHITE = FOREGROUND_BLUE|FOREGROUND_GREEN |FOREGROUND_RED # noqa - - BACKGROUND_BLACK = 0x0000 # noqa - BACKGROUND_BLUE = 0x0010 # noqa - BACKGROUND_GREEN = 0x0020 # noqa - BACKGROUND_CYAN = 0x0030 # noqa - BACKGROUND_RED = 0x0040 # noqa - BACKGROUND_MAGENTA = 0x0050 # noqa - BACKGROUND_YELLOW = 0x0060 # noqa - BACKGROUND_GREY = 0x0070 # noqa - BACKGROUND_INTENSITY = 0x0080 # background color is intensified. - levelno = args[1].levelno - if (levelno >= 50): - color = BACKGROUND_YELLOW | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY - elif (levelno >= 40): - color = FOREGROUND_RED | FOREGROUND_INTENSITY - elif (levelno >= 30): - color = FOREGROUND_YELLOW | FOREGROUND_INTENSITY - elif (levelno >= 20): - color = FOREGROUND_GREEN - elif (levelno >= 10): - color = FOREGROUND_MAGENTA - else: - color = FOREGROUND_WHITE - - old_color = _get_color() - _set_color(color) - ret = fn(*args) - _set_color(old_color) - return ret - - new.orig_func = fn - return new + # Check if the flag is set in the current console mode + return (console_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 def add_coloring_to_emit_ansi(fn): @@ -117,11 +54,8 @@ def new(*args): def enable(): - if sys.stderr.isatty(): - if sys.platform.startswith('win'): - logging.StreamHandler.emit = add_coloring_to_emit_windows(logging.StreamHandler.emit) - else: - logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit) + if ansi_color_available(): + logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit) def disable(): diff --git a/tools/diagnostics.py b/tools/diagnostics.py index b75f206bc6349..3bf3a00357745 100644 --- a/tools/diagnostics.py +++ b/tools/diagnostics.py @@ -6,24 +6,22 @@ """Simple color-enabled diagnositics reporting functions. """ -import ctypes import logging import os import sys from typing import Dict -WINDOWS = sys.platform.startswith('win') +from . import colored_logger +color_enabled = colored_logger.ansi_color_available() logger = logging.getLogger('diagnostics') -color_enabled = sys.stderr.isatty() tool_name = os.path.splitext(os.path.basename(sys.argv[0]))[0] -force_ansi = False # diagnostic levels WARN = 1 ERROR = 2 -# available colors +# available (ANSI) colors RED = 1 GREEN = 2 YELLOW = 3 @@ -43,94 +41,19 @@ ERROR: 'error: ', } -# Constants from the Windows API -STD_OUTPUT_HANDLE = -11 - - -def output_color_windows(color): - assert not force_ansi - # TODO(sbc): This code is duplicated in colored_logger.py. Refactor. - # wincon.h - FOREGROUND_BLACK = 0x0000 # noqa - FOREGROUND_BLUE = 0x0001 # noqa - FOREGROUND_GREEN = 0x0002 # noqa - FOREGROUND_CYAN = 0x0003 # noqa - FOREGROUND_RED = 0x0004 # noqa - FOREGROUND_MAGENTA = 0x0005 # noqa - FOREGROUND_YELLOW = 0x0006 # noqa - FOREGROUND_GREY = 0x0007 # noqa - - color_map = { - RED: FOREGROUND_RED, - GREEN: FOREGROUND_GREEN, - YELLOW: FOREGROUND_YELLOW, - BLUE: FOREGROUND_BLUE, - MAGENTA: FOREGROUND_MAGENTA, - CYAN: FOREGROUND_CYAN, - WHITE: FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, - } - - sys.stderr.flush() - hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, color_map[color]) - - -def get_color_windows(): - assert not force_ansi - SHORT = ctypes.c_short - WORD = ctypes.c_ushort - - class COORD(ctypes.Structure): - _fields_ = [ - ("X", SHORT), - ("Y", SHORT)] - - class SMALL_RECT(ctypes.Structure): - _fields_ = [ - ("Left", SHORT), - ("Top", SHORT), - ("Right", SHORT), - ("Bottom", SHORT)] - - class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): - _fields_ = [ - ("dwSize", COORD), - ("dwCursorPosition", COORD), - ("wAttributes", WORD), - ("srWindow", SMALL_RECT), - ("dwMaximumWindowSize", COORD)] - - hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - csbi = CONSOLE_SCREEN_BUFFER_INFO() - ctypes.windll.kernel32.GetConsoleScreenBufferInfo(hdl, ctypes.byref(csbi)) - return csbi.wAttributes - - -def reset_color_windows(): - assert not force_ansi - sys.stderr.flush() - hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) - ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, default_color) - def output_color(color): - if WINDOWS and not force_ansi: - output_color_windows(color) - return '' + assert color_enabled return '\033[3%sm' % color def bold(): - if WINDOWS and not force_ansi: - # AFAICT there is no way to enable bold output on windows - return '' + assert color_enabled return '\033[1m' def reset_color(): - if WINDOWS and not force_ansi: - reset_color_windows() - return '' + assert color_enabled return '\033[0m' @@ -260,7 +183,4 @@ def capture_warnings(argv): return manager.capture_warnings(argv) -if WINDOWS: - default_color = get_color_windows() - manager = WarningManager()