You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
SCons: Refactor color output implementation
This commit is contained in:
144
methods.py
144
methods.py
@@ -7,125 +7,16 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
from enum import Enum
|
||||
from io import StringIO, TextIOWrapper
|
||||
from pathlib import Path
|
||||
from typing import Final, Generator, List, Optional, Union, cast
|
||||
from typing import Generator, List, Optional, Union, cast
|
||||
|
||||
from misc.utility.color import print_error, print_info, print_warning
|
||||
|
||||
# Get the "Godot" folder name ahead of time
|
||||
base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
|
||||
base_folder_only = os.path.basename(os.path.normpath(base_folder_path))
|
||||
|
||||
################################################################################
|
||||
# COLORIZE
|
||||
################################################################################
|
||||
|
||||
IS_CI: Final[bool] = bool(os.environ.get("CI"))
|
||||
IS_TTY: Final[bool] = bool(sys.stdout.isatty())
|
||||
|
||||
|
||||
def _color_supported() -> bool:
|
||||
"""
|
||||
Enables ANSI escape code support on Windows 10 and later (for colored console output).
|
||||
See here: https://github.com/python/cpython/issues/73245
|
||||
"""
|
||||
if sys.platform == "win32" and IS_TTY:
|
||||
try:
|
||||
from ctypes import WinError, byref, windll # type: ignore
|
||||
from ctypes.wintypes import DWORD # type: ignore
|
||||
|
||||
stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11))
|
||||
mode = DWORD(0)
|
||||
if not windll.kernel32.GetConsoleMode(stdout_handle, byref(mode)):
|
||||
raise WinError()
|
||||
mode = DWORD(mode.value | 4)
|
||||
if not windll.kernel32.SetConsoleMode(stdout_handle, mode):
|
||||
raise WinError()
|
||||
except (TypeError, OSError) as e:
|
||||
print(f"Failed to enable ANSI escape code support, disabling color output.\n{e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
return IS_TTY or IS_CI
|
||||
|
||||
|
||||
# Colors are disabled in non-TTY environments such as pipes. This means
|
||||
# that if output is redirected to a file, it won't contain color codes.
|
||||
# Colors are always enabled on continuous integration.
|
||||
COLOR_SUPPORTED: Final[bool] = _color_supported()
|
||||
_can_color: bool = COLOR_SUPPORTED
|
||||
|
||||
|
||||
def toggle_color(value: Optional[bool] = None) -> None:
|
||||
"""
|
||||
Explicitly toggle color codes, regardless of support.
|
||||
|
||||
- `value`: An optional boolean to explicitly set the color
|
||||
state instead of toggling.
|
||||
"""
|
||||
global _can_color
|
||||
_can_color = value if value is not None else not _can_color
|
||||
|
||||
|
||||
class Ansi(Enum):
|
||||
"""
|
||||
Enum class for adding ansi colorcodes directly into strings.
|
||||
Automatically converts values to strings representing their
|
||||
internal value, or an empty string in a non-colorized scope.
|
||||
"""
|
||||
|
||||
RESET = "\x1b[0m"
|
||||
|
||||
BOLD = "\x1b[1m"
|
||||
DIM = "\x1b[2m"
|
||||
ITALIC = "\x1b[3m"
|
||||
UNDERLINE = "\x1b[4m"
|
||||
STRIKETHROUGH = "\x1b[9m"
|
||||
REGULAR = "\x1b[22;23;24;29m"
|
||||
|
||||
BLACK = "\x1b[30m"
|
||||
RED = "\x1b[31m"
|
||||
GREEN = "\x1b[32m"
|
||||
YELLOW = "\x1b[33m"
|
||||
BLUE = "\x1b[34m"
|
||||
MAGENTA = "\x1b[35m"
|
||||
CYAN = "\x1b[36m"
|
||||
WHITE = "\x1b[37m"
|
||||
|
||||
LIGHT_BLACK = "\x1b[90m"
|
||||
LIGHT_RED = "\x1b[91m"
|
||||
LIGHT_GREEN = "\x1b[92m"
|
||||
LIGHT_YELLOW = "\x1b[93m"
|
||||
LIGHT_BLUE = "\x1b[94m"
|
||||
LIGHT_MAGENTA = "\x1b[95m"
|
||||
LIGHT_CYAN = "\x1b[96m"
|
||||
LIGHT_WHITE = "\x1b[97m"
|
||||
|
||||
GRAY = LIGHT_BLACK if IS_CI else BLACK
|
||||
"""
|
||||
Special case. GitHub Actions doesn't convert `BLACK` to gray as expected, but does convert `LIGHT_BLACK`.
|
||||
By implementing `GRAY`, we handle both cases dynamically, while still allowing for explicit values if desired.
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
global _can_color
|
||||
return str(self.value) if _can_color else ""
|
||||
|
||||
|
||||
def print_info(*values: object) -> None:
|
||||
"""Prints a informational message with formatting."""
|
||||
print(f"{Ansi.GRAY}{Ansi.BOLD}INFO:{Ansi.REGULAR}", *values, Ansi.RESET)
|
||||
|
||||
|
||||
def print_warning(*values: object) -> None:
|
||||
"""Prints a warning message with formatting."""
|
||||
print(f"{Ansi.YELLOW}{Ansi.BOLD}WARNING:{Ansi.REGULAR}", *values, Ansi.RESET, file=sys.stderr)
|
||||
|
||||
|
||||
def print_error(*values: object) -> None:
|
||||
"""Prints an error message with formatting."""
|
||||
print(f"{Ansi.RED}{Ansi.BOLD}ERROR:{Ansi.REGULAR}", *values, Ansi.RESET, file=sys.stderr)
|
||||
|
||||
|
||||
# Listing all the folders we have converted
|
||||
# for SCU in scu_builders.py
|
||||
_scu_folders = set()
|
||||
@@ -505,6 +396,8 @@ def use_windows_spawn_fix(self, platform=None):
|
||||
|
||||
|
||||
def no_verbose(env):
|
||||
from misc.utility.color import Ansi
|
||||
|
||||
colors = [Ansi.BLUE, Ansi.BOLD, Ansi.REGULAR, Ansi.RESET]
|
||||
|
||||
# There is a space before "..." to ensure that source file names can be
|
||||
@@ -875,7 +768,7 @@ def show_progress(env):
|
||||
|
||||
# Progress reporting is not available in non-TTY environments since it
|
||||
# messes with the output (for example, when writing to a file).
|
||||
self.display = cast(bool, self.max and env["progress"] and IS_TTY)
|
||||
self.display = cast(bool, self.max and env["progress"] and sys.stdout.isatty())
|
||||
if self.display and not self.max:
|
||||
print_info("Performing initial build, progress percentage unavailable!")
|
||||
|
||||
@@ -1019,6 +912,31 @@ def prepare_cache(env) -> None:
|
||||
atexit.register(clean_cache, cache_path, cache_limit, env["verbose"])
|
||||
|
||||
|
||||
def prepare_purge(env):
|
||||
from SCons.Script.Main import GetBuildFailures
|
||||
|
||||
def purge_flaky_files():
|
||||
paths_to_keep = [env["ninja_file"]]
|
||||
for build_failure in GetBuildFailures():
|
||||
path = build_failure.node.path
|
||||
if os.path.isfile(path) and path not in paths_to_keep:
|
||||
os.remove(path)
|
||||
|
||||
atexit.register(purge_flaky_files)
|
||||
|
||||
|
||||
def prepare_timer():
|
||||
import time
|
||||
|
||||
def print_elapsed_time(time_at_start: float):
|
||||
time_elapsed = time.time() - time_at_start
|
||||
time_formatted = time.strftime("%H:%M:%S", time.gmtime(time_elapsed))
|
||||
time_centiseconds = round((time_elapsed % 1) * 100)
|
||||
print_info(f"Time elapsed: {time_formatted}.{time_centiseconds}")
|
||||
|
||||
atexit.register(print_elapsed_time, time.time())
|
||||
|
||||
|
||||
def dump(env):
|
||||
# Dumps latest build information for debugging purposes and external tools.
|
||||
from json import dump
|
||||
|
||||
Reference in New Issue
Block a user