[llvm] [lit] cross platform progress bar (PR #189970)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 1 07:45:02 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-testing-tools
Author: Charles Zablit (charles-zablit)
<details>
<summary>Changes</summary>
This patch enables the lit progress bar on Windows.
lit currently uses the `curses` module which is not available on all platforms. The module was mostly used to lookup AINSI control characters which are identical on POSIX and Windows. This patch hardcodes the control sequences' values and sets up the Windows Terminal to be able to handle VT sequences with `ENABLE_VIRTUAL_TERMINAL_PROCESSING`.
---
Full diff: https://github.com/llvm/llvm-project/pull/189970.diff
1 Files Affected:
- (modified) llvm/utils/lit/lit/ProgressBar.py (+65-80)
``````````diff
diff --git a/llvm/utils/lit/lit/ProgressBar.py b/llvm/utils/lit/lit/ProgressBar.py
index 382b8f2e52540..af6c4dfcad0ae 100644
--- a/llvm/utils/lit/lit/ProgressBar.py
+++ b/llvm/utils/lit/lit/ProgressBar.py
@@ -3,7 +3,10 @@
# Source: http://code.activestate.com/recipes/475116/, with
# modifications by Daniel Dunbar.
-import sys, re, time
+import os
+import re
+import sys
+import time
def to_bytes(str):
@@ -47,28 +50,28 @@ class TerminalController:
"""
# Cursor movement:
- BOL = "" #: Move the cursor to the beginning of the line
- UP = "" #: Move the cursor up one line
- DOWN = "" #: Move the cursor down one line
- LEFT = "" #: Move the cursor left one char
- RIGHT = "" #: Move the cursor right one char
+ BOL = "\r" #: Move the cursor to the beginning of the line
+ UP = "\033[A" #: Move the cursor up one line
+ DOWN = "\033[B" #: Move the cursor down one line
+ LEFT = "\033[D" #: Move the cursor left one char
+ RIGHT = "\033[C" #: Move the cursor right one char
# Deletion:
- CLEAR_SCREEN = "" #: Clear the screen and move to home position
- CLEAR_EOL = "" #: Clear to the end of the line.
- CLEAR_BOL = "" #: Clear to the beginning of the line.
- CLEAR_EOS = "" #: Clear to the end of the screen
+ CLEAR_SCREEN = "\033[2J\033[H" #: Clear the screen and move to home position
+ CLEAR_EOL = "\033[K" #: Clear to the end of the line.
+ CLEAR_BOL = "\033[1K" #: Clear to the beginning of the line.
+ CLEAR_EOS = "\033[J" #: Clear to the end of the screen
# Output modes:
- BOLD = "" #: Turn on bold mode
- BLINK = "" #: Turn on blink mode
- DIM = "" #: Turn on half-bright mode
- REVERSE = "" #: Turn on reverse-video mode
- NORMAL = "" #: Turn off all modes
+ BOLD = "\033[1m" #: Turn on bold mode
+ BLINK = "\033[5m" #: Turn on blink mode
+ DIM = "\033[2m" #: Turn on half-bright mode
+ REVERSE = "\033[7m" #: Turn on reverse-video mode
+ NORMAL = "\033[0m" #: Turn off all modes
# Cursor display:
- HIDE_CURSOR = "" #: Make the cursor invisible
- SHOW_CURSOR = "" #: Make the cursor visible
+ HIDE_CURSOR = "\033[?25l" #: Make the cursor invisible
+ SHOW_CURSOR = "\033[?25h" #: Make the cursor visible
# Terminal size:
COLS = None #: Width of the terminal (None for unknown)
@@ -81,12 +84,7 @@ class TerminalController:
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ""
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ""
- _STRING_CAPABILITIES = """
- BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
- CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
- BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
- HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
- _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
+ # ANSI color order: index maps to escape code offset from 30 (fg) / 40 (bg)
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
def __init__(self, term_stream=sys.stdout):
@@ -97,68 +95,55 @@ def __init__(self, term_stream=sys.stdout):
output; if this stream is not a tty, then the terminal is
assumed to be a dumb terminal (i.e., have no capabilities).
"""
- # Curses isn't available on all platforms
- try:
- import curses
- except:
- return
-
# If the stream isn't a tty, then assume it has no capabilities.
if not term_stream.isatty():
return
- # Check the terminal type. If we fail, then assume that the
- # terminal has no capabilities.
- try:
- curses.setupterm()
- except:
- return
-
- # Look up numeric capabilities.
- self.COLS = curses.tigetnum("cols")
- self.LINES = curses.tigetnum("lines")
- self.XN = curses.tigetflag("xenl")
-
- # Look up string capabilities.
- for capability in self._STRING_CAPABILITIES:
- (attrib, cap_name) = capability.split("=")
- setattr(self, attrib, self._tigetstr(cap_name) or "")
-
- # Colors
- set_fg = self._tigetstr("setf")
- if set_fg:
- for i, color in zip(range(len(self._COLORS)), self._COLORS):
- setattr(self, color, self._tparm(set_fg, i))
- set_fg_ansi = self._tigetstr("setaf")
- if set_fg_ansi:
- for i, color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
- setattr(self, color, self._tparm(set_fg_ansi, i))
- set_bg = self._tigetstr("setb")
- if set_bg:
- for i, color in zip(range(len(self._COLORS)), self._COLORS):
- setattr(self, "BG_" + color, self._tparm(set_bg, i))
- set_bg_ansi = self._tigetstr("setab")
- if set_bg_ansi:
- for i, color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
- setattr(self, "BG_" + color, self._tparm(set_bg_ansi, i))
-
- def _tparm(self, arg, index):
- import curses
-
- return curses.tparm(to_bytes(arg), index).decode("utf-8") or ""
-
- def _tigetstr(self, cap_name):
- # String capabilities can include "delays" of the form "$<2>".
- # For any modern terminal, we should be able to just ignore
- # these, so strip them out.
- import curses
-
- cap = curses.tigetstr(cap_name)
- if cap is None:
- cap = ""
+ # On Windows, ANSI/VT processing must be explicitly enabled.
+ if sys.platform == "win32":
+ try:
+ import ctypes
+ import ctypes.wintypes
+ import msvcrt
+
+ kernel32 = ctypes.windll.kernel32
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+ handle = msvcrt.get_osfhandle(term_stream.fileno())
+ mode = ctypes.wintypes.DWORD()
+ if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
+ return
+ if not kernel32.SetConsoleMode(
+ handle, mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
+ ):
+ return
+ # xenl flag: the Windows Terminal handle the right-margin newline correctly.
+ self.XN = True
+ except Exception:
+ return
else:
- cap = cap.decode("utf-8")
- return re.sub(r"\$<\d+>[/*]?", "", cap)
+ try:
+ # Curses isn't available on all platforms
+ import curses
+
+ # Check the terminal type. If we fail, then assume that the
+ # terminal has no capabilities.
+ curses.setupterm()
+ self.XN = curses.tigetflag("xenl")
+ except:
+ return
+
+ # Foreground and background colors
+ for i, color in enumerate(self._ANSICOLORS):
+ setattr(self, color, "\033[%dm" % (30 + i))
+ setattr(self, "BG_" + color, "\033[%dm" % (40 + i))
+
+ # Terminal dimensions
+ try:
+ size = os.get_terminal_size(term_stream.fileno())
+ self.COLS = size.columns
+ self.LINES = size.lines
+ except (AttributeError, ValueError, OSError):
+ pass
def render(self, template):
"""
``````````
</details>
https://github.com/llvm/llvm-project/pull/189970
More information about the llvm-commits
mailing list