diff --git a/lib/spack/llnl/util/tty/color.py b/lib/spack/llnl/util/tty/color.py index 83e8316914..a468d19634 100644 --- a/lib/spack/llnl/util/tty/color.py +++ b/lib/spack/llnl/util/tty/color.py @@ -107,6 +107,64 @@ def __init__(self, message): _force_color = None +def try_enable_terminal_color_on_windows(): + """Turns coloring in Windows terminal by enabling VTP in Windows consoles (CMD/PWSH/CONHOST) + Method based on the link below + https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing + + Note: No-op on non windows platforms + """ + if sys.platform == "win32": + import ctypes + import msvcrt + from ctypes import wintypes + + try: + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + kernel32 = ctypes.WinDLL("kernel32") + + def _err_check(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + kernel32.GetConsoleMode.errcheck = _err_check + kernel32.GetConsoleMode.argtypes = ( + wintypes.HANDLE, # hConsoleHandle, i.e. GetStdHandle output type + ctypes.POINTER(wintypes.DWORD), # result of GetConsoleHandle + ) + kernel32.SetConsoleMode.errcheck = _err_check + kernel32.SetConsoleMode.argtypes = ( + wintypes.HANDLE, # hConsoleHandle, i.e. GetStdHandle output type + wintypes.DWORD, # result of GetConsoleHandle + ) + # Use conout$ here to handle a redirectired stdout/get active console associated + # with spack + with open(r"\\.\CONOUT$", "w") as conout: + # Link above would use kernel32.GetStdHandle(-11) however this would not handle + # a redirected stdout appropriately, so we always refer to the current CONSOLE out + # which is defined as conout$ on Windows. + # linked example is follow more or less to the letter beyond this point + con_handle = msvcrt.get_osfhandle(conout.fileno()) + dw_orig_mode = wintypes.DWORD() + kernel32.GetConsoleMode(con_handle, ctypes.byref(dw_orig_mode)) + dw_new_mode_request = ( + ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN + ) + dw_new_mode = dw_new_mode_request | dw_orig_mode.value + kernel32.SetConsoleMode(con_handle, wintypes.DWORD(dw_new_mode)) + except OSError: + # We failed to enable color support for associated console + # report and move on but spack will no longer attempt to + # color + global _force_color + _force_color = False + from . import debug + + debug("Unable to support color on Windows terminal") + + def _color_when_value(when): """Raise a ValueError for an invalid color setting. diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py index b95a8562eb..8153839abc 100644 --- a/lib/spack/spack/main.py +++ b/lib/spack/spack/main.py @@ -605,6 +605,10 @@ def setup_main_options(args): for config_var in args.config_vars or []: spack.config.add(fullpath=config_var, scope="command_line") + # On Windows10 console handling for ASCI/VT100 sequences is not + # on by default. Turn on before we try to write to console + # with color + color.try_enable_terminal_color_on_windows() # when to use color (takes always, auto, or never) color.set_color_when(args.color)