Skip to content

Commit 981ab76

Browse files
committed
Remove windows-specific console coloring
Windows 10 and above support ANSI coloring and there should be very few emscripten users on older version of windows at this point (if any?). The downside is that windows 7 will no longer have nice colored error messages, but the upside is that we can cleanup and refactor this code now (see emscripten-core#25495).
1 parent d9e0690 commit 981ab76

File tree

5 files changed

+26
-180
lines changed

5 files changed

+26
-180
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.16 (in development)
2222
-----------------------
23+
- Colored console output for error messages and logging is not longer supported
24+
on Windows 8 and older. (#25502)
2325
- A warning was added about usage of embind without C++17 or above. (#25424)
2426
- The minimum supported versions of Node, Chrome and Firefox were bumped
2527
enabling the removal of the `globalThis` polyfill and universally enabling

test/test_other.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11607,21 +11607,11 @@ def test_color_diagnostics_disable(self, flag):
1160711607
})
1160811608
def test_color_diagnostics_force(self, flag):
1160911609
create_file('src.c', 'int main() {')
11610-
# -fansi-escape-codes is needed here to make this test work on windows, which doesn't
11611-
# use ansi codes by default
11612-
output = self.expect_fail([EMCC, '-fansi-escape-codes', flag, 'src.c'])
11610+
output = self.expect_fail([EMCC, flag, 'src.c'])
1161311611
self.assertIn("\x1b[1msrc.c:1:13: \x1b[0m\x1b[0;1;31merror: \x1b[0m\x1b[1mexpected '}'\x1b[0m", output)
1161411612
# Verify that emcc errors show up as red and bold
1161511613
self.assertIn('emcc: \x1b[31m\x1b[1m', output)
1161611614

11617-
if WINDOWS:
11618-
# Also test without -fansi-escape-codes on windows.
11619-
# In those mode the code will use kernel calls such as SetConsoleTextAttribute to
11620-
# change the output color. We cannot detect this in the output, but we can at least
11621-
# get coverage of the code path in the diagnositics.py.
11622-
output = self.expect_fail([EMCC, flag, 'src.c'])
11623-
self.assertNotIn('\x1b', output)
11624-
1162511615
def test_sanitizer_color(self):
1162611616
create_file('src.c', '''
1162711617
#include <emscripten.h>

tools/cmdline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ def consume_arg_file():
514514
elif arg in ('-fno-color-diagnostics', '-fdiagnostics-color=never'):
515515
diagnostics.color_enabled = False
516516
elif arg == '-fansi-escape-codes':
517-
diagnostics.force_ansi = True
517+
diagnostics.color_enabled = True
518518
elif arg == '-fno-exceptions':
519519
settings.DISABLE_EXCEPTION_CATCHING = 1
520520
settings.DISABLE_EXCEPTION_THROWING = 1

tools/colored_logger.py

Lines changed: 16 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,88 +11,25 @@
1111
import logging
1212

1313

14-
def add_coloring_to_emit_windows(fn):
14+
def ansi_color_available():
15+
if not sys.platform.startswith('win'):
16+
return sys.stderr.isatty()
17+
1518
# Constants from the Windows API
1619
STD_OUTPUT_HANDLE = -11
20+
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
1721

18-
def _get_color():
19-
SHORT = ctypes.c_short
20-
WORD = ctypes.c_ushort
21-
22-
class COORD(ctypes.Structure):
23-
_fields_ = [
24-
("X", SHORT),
25-
("Y", SHORT)]
26-
27-
class SMALL_RECT(ctypes.Structure):
28-
_fields_ = [
29-
("Left", SHORT),
30-
("Top", SHORT),
31-
("Right", SHORT),
32-
("Bottom", SHORT)]
33-
34-
class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
35-
_fields_ = [
36-
("dwSize", COORD),
37-
("dwCursorPosition", COORD),
38-
("wAttributes", WORD),
39-
("srWindow", SMALL_RECT),
40-
("dwMaximumWindowSize", COORD)]
22+
kernel32 = ctypes.windll.kernel32
23+
stdout_handle = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
4124

42-
hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
43-
csbi = CONSOLE_SCREEN_BUFFER_INFO()
44-
ctypes.windll.kernel32.GetConsoleScreenBufferInfo(hdl, ctypes.byref(csbi))
45-
return csbi.wAttributes
25+
# Get the current console mode
26+
console_mode = ctypes.c_uint()
27+
if not kernel32.GetConsoleMode(stdout_handle, ctypes.byref(console_mode)):
28+
# Handle error if GetConsoleMode fails
29+
return False
4630

47-
def _set_color(code):
48-
hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
49-
ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, code)
50-
51-
def new(*args):
52-
# wincon.h
53-
FOREGROUND_BLACK = 0x0000 # noqa
54-
FOREGROUND_BLUE = 0x0001 # noqa
55-
FOREGROUND_GREEN = 0x0002 # noqa
56-
FOREGROUND_CYAN = 0x0003 # noqa
57-
FOREGROUND_RED = 0x0004 # noqa
58-
FOREGROUND_MAGENTA = 0x0005 # noqa
59-
FOREGROUND_YELLOW = 0x0006 # noqa
60-
FOREGROUND_GREY = 0x0007 # noqa
61-
FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.
62-
63-
FOREGROUND_WHITE = FOREGROUND_BLUE|FOREGROUND_GREEN |FOREGROUND_RED # noqa
64-
65-
BACKGROUND_BLACK = 0x0000 # noqa
66-
BACKGROUND_BLUE = 0x0010 # noqa
67-
BACKGROUND_GREEN = 0x0020 # noqa
68-
BACKGROUND_CYAN = 0x0030 # noqa
69-
BACKGROUND_RED = 0x0040 # noqa
70-
BACKGROUND_MAGENTA = 0x0050 # noqa
71-
BACKGROUND_YELLOW = 0x0060 # noqa
72-
BACKGROUND_GREY = 0x0070 # noqa
73-
BACKGROUND_INTENSITY = 0x0080 # background color is intensified.
74-
levelno = args[1].levelno
75-
if (levelno >= 50):
76-
color = BACKGROUND_YELLOW | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY
77-
elif (levelno >= 40):
78-
color = FOREGROUND_RED | FOREGROUND_INTENSITY
79-
elif (levelno >= 30):
80-
color = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
81-
elif (levelno >= 20):
82-
color = FOREGROUND_GREEN
83-
elif (levelno >= 10):
84-
color = FOREGROUND_MAGENTA
85-
else:
86-
color = FOREGROUND_WHITE
87-
88-
old_color = _get_color()
89-
_set_color(color)
90-
ret = fn(*args)
91-
_set_color(old_color)
92-
return ret
93-
94-
new.orig_func = fn
95-
return new
31+
# Check if the flag is set in the current console mode
32+
return (console_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
9633

9734

9835
def add_coloring_to_emit_ansi(fn):
@@ -117,11 +54,8 @@ def new(*args):
11754

11855

11956
def enable():
120-
if sys.stderr.isatty():
121-
if sys.platform.startswith('win'):
122-
logging.StreamHandler.emit = add_coloring_to_emit_windows(logging.StreamHandler.emit)
123-
else:
124-
logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit)
57+
if ansi_color_available():
58+
logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit)
12559

12660

12761
def disable():

tools/diagnostics.py

Lines changed: 6 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,22 @@
66
"""Simple color-enabled diagnositics reporting functions.
77
"""
88

9-
import ctypes
109
import logging
1110
import os
1211
import sys
1312
from typing import Dict
1413

15-
WINDOWS = sys.platform.startswith('win')
14+
from . import colored_logger
1615

16+
color_enabled = colored_logger.ansi_color_available()
1717
logger = logging.getLogger('diagnostics')
18-
color_enabled = sys.stderr.isatty()
1918
tool_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
20-
force_ansi = False
2119

2220
# diagnostic levels
2321
WARN = 1
2422
ERROR = 2
2523

26-
# available colors
24+
# available (ANSI) colors
2725
RED = 1
2826
GREEN = 2
2927
YELLOW = 3
@@ -43,94 +41,19 @@
4341
ERROR: 'error: ',
4442
}
4543

46-
# Constants from the Windows API
47-
STD_OUTPUT_HANDLE = -11
48-
49-
50-
def output_color_windows(color):
51-
assert not force_ansi
52-
# TODO(sbc): This code is duplicated in colored_logger.py. Refactor.
53-
# wincon.h
54-
FOREGROUND_BLACK = 0x0000 # noqa
55-
FOREGROUND_BLUE = 0x0001 # noqa
56-
FOREGROUND_GREEN = 0x0002 # noqa
57-
FOREGROUND_CYAN = 0x0003 # noqa
58-
FOREGROUND_RED = 0x0004 # noqa
59-
FOREGROUND_MAGENTA = 0x0005 # noqa
60-
FOREGROUND_YELLOW = 0x0006 # noqa
61-
FOREGROUND_GREY = 0x0007 # noqa
62-
63-
color_map = {
64-
RED: FOREGROUND_RED,
65-
GREEN: FOREGROUND_GREEN,
66-
YELLOW: FOREGROUND_YELLOW,
67-
BLUE: FOREGROUND_BLUE,
68-
MAGENTA: FOREGROUND_MAGENTA,
69-
CYAN: FOREGROUND_CYAN,
70-
WHITE: FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
71-
}
72-
73-
sys.stderr.flush()
74-
hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
75-
ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, color_map[color])
76-
77-
78-
def get_color_windows():
79-
assert not force_ansi
80-
SHORT = ctypes.c_short
81-
WORD = ctypes.c_ushort
82-
83-
class COORD(ctypes.Structure):
84-
_fields_ = [
85-
("X", SHORT),
86-
("Y", SHORT)]
87-
88-
class SMALL_RECT(ctypes.Structure):
89-
_fields_ = [
90-
("Left", SHORT),
91-
("Top", SHORT),
92-
("Right", SHORT),
93-
("Bottom", SHORT)]
94-
95-
class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
96-
_fields_ = [
97-
("dwSize", COORD),
98-
("dwCursorPosition", COORD),
99-
("wAttributes", WORD),
100-
("srWindow", SMALL_RECT),
101-
("dwMaximumWindowSize", COORD)]
102-
103-
hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
104-
csbi = CONSOLE_SCREEN_BUFFER_INFO()
105-
ctypes.windll.kernel32.GetConsoleScreenBufferInfo(hdl, ctypes.byref(csbi))
106-
return csbi.wAttributes
107-
108-
109-
def reset_color_windows():
110-
assert not force_ansi
111-
sys.stderr.flush()
112-
hdl = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
113-
ctypes.windll.kernel32.SetConsoleTextAttribute(hdl, default_color)
114-
11544

11645
def output_color(color):
117-
if WINDOWS and not force_ansi:
118-
output_color_windows(color)
119-
return ''
46+
assert color_enabled
12047
return '\033[3%sm' % color
12148

12249

12350
def bold():
124-
if WINDOWS and not force_ansi:
125-
# AFAICT there is no way to enable bold output on windows
126-
return ''
51+
assert color_enabled
12752
return '\033[1m'
12853

12954

13055
def reset_color():
131-
if WINDOWS and not force_ansi:
132-
reset_color_windows()
133-
return ''
56+
assert color_enabled
13457
return '\033[0m'
13558

13659

@@ -260,7 +183,4 @@ def capture_warnings(argv):
260183
return manager.capture_warnings(argv)
261184

262185

263-
if WINDOWS:
264-
default_color = get_color_windows()
265-
266186
manager = WarningManager()

0 commit comments

Comments
 (0)