ソースを参照

SCons: Improve colored output

Thaddeus Crews 1 年間 前
コミット
d8761f2c79

+ 1 - 1
.pre-commit-config.yaml

@@ -98,7 +98,7 @@ repos:
         name: doc-status
         language: python
         entry: python doc/tools/doc_status.py
-        args: [doc/classes, modules/*/doc_classes, platform/*/doc_classes]
+        args: [doc/classes, modules/*/doc_classes, platform/*/doc_classes, -c]
         pass_filenames: false
         files: ^(doc/classes|.*/doc_classes)/.*\.xml$
 

+ 4 - 22
SConstruct

@@ -58,31 +58,13 @@ import gles3_builders
 import glsl_builders
 import methods
 import scu_builders
-from methods import print_error, print_warning
+from methods import Ansi, print_error, print_info, print_warning
 from platform_methods import architecture_aliases, architectures, compatibility_platform_aliases
 
 if ARGUMENTS.get("target", "editor") == "editor":
     _helper_module("editor.editor_builders", "editor/editor_builders.py")
     _helper_module("editor.template_builders", "editor/template_builders.py")
 
-# Enable ANSI escape code support on Windows 10 and later (for colored console output).
-# <https://github.com/python/cpython/issues/73245>
-if sys.stdout.isatty() and sys.platform == "win32":
-    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 Exception as e:
-        methods._colorize = False
-        print_error(f"Failed to enable ANSI escape code support, disabling color output.\n{e}")
-
 # Scan possible build platforms
 
 platform_list = []  # list of platforms
@@ -630,7 +612,7 @@ detect.configure(env)
 
 print(f'Building for platform "{env["platform"]}", architecture "{env["arch"]}", target "{env["target"]}".')
 if env.dev_build:
-    print("NOTE: Developer build, with debug optimization level and debug symbols (unless overridden).")
+    print_info("Developer build, with debug optimization level and debug symbols (unless overridden).")
 
 # Enforce our minimal compiler version requirements
 cc_version = methods.get_compiler_version(env)
@@ -1095,10 +1077,10 @@ def print_elapsed_time():
     time_centiseconds = round((elapsed_time_sec % 1) * 100)
     print(
         "{}[Time elapsed: {}.{:02}]{}".format(
-            methods.ANSI.GRAY,
+            Ansi.GRAY,
             time.strftime("%H:%M:%S", time.gmtime(elapsed_time_sec)),
             time_centiseconds,
-            methods.ANSI.RESET,
+            Ansi.RESET,
         )
     )
 

+ 18 - 29
doc/tools/doc_status.py

@@ -3,18 +3,21 @@
 import fnmatch
 import math
 import os
-import platform
 import re
 import sys
 import xml.etree.ElementTree as ET
 from typing import Dict, List, Set
 
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
+
+from methods import COLOR_SUPPORTED, Ansi, toggle_color
+
 ################################################################################
 #                                    Config                                    #
 ################################################################################
 
 flags = {
-    "c": platform.platform() != "Windows",  # Disable by default on windows, since we use ANSI escape codes
+    "c": COLOR_SUPPORTED,
     "b": False,
     "g": False,
     "s": False,
@@ -85,16 +88,16 @@ table_column_names = [
     "Constructors",
 ]
 colors = {
-    "name": [36],  # cyan
-    "part_big_problem": [4, 31],  # underline, red
-    "part_problem": [31],  # red
-    "part_mostly_good": [33],  # yellow
-    "part_good": [32],  # green
-    "url": [4, 34],  # underline, blue
-    "section": [1, 4],  # bold, underline
-    "state_off": [36],  # cyan
-    "state_on": [1, 35],  # bold, magenta/plum
-    "bold": [1],  # bold
+    "name": [Ansi.CYAN],  # cyan
+    "part_big_problem": [Ansi.RED, Ansi.UNDERLINE],  # underline, red
+    "part_problem": [Ansi.RED],  # red
+    "part_mostly_good": [Ansi.YELLOW],  # yellow
+    "part_good": [Ansi.GREEN],  # green
+    "url": [Ansi.BLUE, Ansi.UNDERLINE],  # underline, blue
+    "section": [Ansi.BOLD, Ansi.UNDERLINE],  # bold, underline
+    "state_off": [Ansi.CYAN],  # cyan
+    "state_on": [Ansi.BOLD, Ansi.MAGENTA],  # bold, magenta/plum
+    "bold": [Ansi.BOLD],  # bold
 }
 overall_progress_description_weight = 10
 
@@ -111,13 +114,8 @@ def validate_tag(elem: ET.Element, tag: str) -> None:
 
 
 def color(color: str, string: str) -> str:
-    if flags["c"] and terminal_supports_color():
-        color_format = ""
-        for code in colors[color]:
-            color_format += "\033[" + str(code) + "m"
-        return color_format + string + "\033[0m"
-    else:
-        return string
+    color_format = "".join([str(x) for x in colors[color]])
+    return f"{color_format}{string}{Ansi.RESET}"
 
 
 ansi_escape = re.compile(r"\x1b[^m]*m")
@@ -127,16 +125,6 @@ def nonescape_len(s: str) -> int:
     return len(ansi_escape.sub("", s))
 
 
-def terminal_supports_color():
-    p = sys.platform
-    supported_platform = p != "Pocket PC" and (p != "win32" or "ANSICON" in os.environ)
-
-    is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
-    if not supported_platform or not is_a_tty:
-        return False
-    return True
-
-
 ################################################################################
 #                                   Classes                                    #
 ################################################################################
@@ -342,6 +330,7 @@ if flags["u"]:
     table_column_names.append("Docs URL")
     table_columns.append("url")
 
+toggle_color(flags["c"])
 
 ################################################################################
 #                                     Help                                     #

+ 15 - 43
doc/tools/make_rst.py

@@ -10,10 +10,10 @@ import xml.etree.ElementTree as ET
 from collections import OrderedDict
 from typing import Any, Dict, List, Optional, TextIO, Tuple, Union
 
-# Import hardcoded version information from version.py
-root_directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
-sys.path.append(root_directory)  # Include the root directory
-import version  # noqa: E402
+sys.path.insert(0, root_directory := os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
+
+import version
+from methods import Ansi, toggle_color
 
 # $DOCS_URL/path/to/page.html(#fragment-tag)
 GODOT_DOCS_PATTERN = re.compile(r"^\$DOCS_URL/(.*)\.html(#.*)?$")
@@ -90,8 +90,6 @@ BASE_STRINGS = [
 ]
 strings_l10n: Dict[str, str] = {}
 
-STYLES: Dict[str, str] = {}
-
 CLASS_GROUPS: Dict[str, str] = {
     "global": "Globals",
     "node": "Nodes",
@@ -699,31 +697,7 @@ def main() -> None:
     )
     args = parser.parse_args()
 
-    should_color = bool(args.color or sys.stdout.isatty() or os.environ.get("CI"))
-
-    # Enable ANSI escape code support on Windows 10 and later (for colored console output).
-    # <https://github.com/python/cpython/issues/73245>
-    if should_color and sys.stdout.isatty() and sys.platform == "win32":
-        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 Exception:
-            should_color = False
-
-    STYLES["red"] = "\x1b[91m" if should_color else ""
-    STYLES["green"] = "\x1b[92m" if should_color else ""
-    STYLES["yellow"] = "\x1b[93m" if should_color else ""
-    STYLES["bold"] = "\x1b[1m" if should_color else ""
-    STYLES["regular"] = "\x1b[22m" if should_color else ""
-    STYLES["reset"] = "\x1b[0m" if should_color else ""
+    toggle_color(args.color)
 
     # Retrieve heading translations for the given language.
     if not args.dry_run and args.lang != "en":
@@ -834,16 +808,16 @@ def main() -> None:
     if state.script_language_parity_check.hit_count > 0:
         if not args.verbose:
             print(
-                f'{STYLES["yellow"]}{state.script_language_parity_check.hit_count} code samples failed parity check. Use --verbose to get more information.{STYLES["reset"]}'
+                f"{Ansi.YELLOW}{state.script_language_parity_check.hit_count} code samples failed parity check. Use --verbose to get more information.{Ansi.RESET}"
             )
         else:
             print(
-                f'{STYLES["yellow"]}{state.script_language_parity_check.hit_count} code samples failed parity check:{STYLES["reset"]}'
+                f"{Ansi.YELLOW}{state.script_language_parity_check.hit_count} code samples failed parity check:{Ansi.RESET}"
             )
 
             for class_name in state.script_language_parity_check.hit_map.keys():
                 class_hits = state.script_language_parity_check.hit_map[class_name]
-                print(f'{STYLES["yellow"]}- {len(class_hits)} hits in class "{class_name}"{STYLES["reset"]}')
+                print(f'{Ansi.YELLOW}- {len(class_hits)} hits in class "{class_name}"{Ansi.RESET}')
 
                 for context, error in class_hits:
                     print(f"  - {error} in {format_context_name(context)}")
@@ -853,24 +827,22 @@ def main() -> None:
 
     if state.num_warnings >= 2:
         print(
-            f'{STYLES["yellow"]}{state.num_warnings} warnings were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+            f"{Ansi.YELLOW}{state.num_warnings} warnings were found in the class reference XML. Please check the messages above.{Ansi.RESET}"
         )
     elif state.num_warnings == 1:
         print(
-            f'{STYLES["yellow"]}1 warning was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+            f"{Ansi.YELLOW}1 warning was found in the class reference XML. Please check the messages above.{Ansi.RESET}"
         )
 
     if state.num_errors >= 2:
         print(
-            f'{STYLES["red"]}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
+            f"{Ansi.RED}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{Ansi.RESET}"
         )
     elif state.num_errors == 1:
-        print(
-            f'{STYLES["red"]}1 error was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
-        )
+        print(f"{Ansi.RED}1 error was found in the class reference XML. Please check the messages above.{Ansi.RESET}")
 
     if state.num_warnings == 0 and state.num_errors == 0:
-        print(f'{STYLES["green"]}No warnings or errors found in the class reference XML.{STYLES["reset"]}')
+        print(f"{Ansi.GREEN}No warnings or errors found in the class reference XML.{Ansi.RESET}")
         if not args.dry_run:
             print(f"Wrote reStructuredText files for each class to: {args.output}")
     else:
@@ -881,12 +853,12 @@ def main() -> None:
 
 
 def print_error(error: str, state: State) -> None:
-    print(f'{STYLES["red"]}{STYLES["bold"]}ERROR:{STYLES["regular"]} {error}{STYLES["reset"]}')
+    print(f"{Ansi.RED}{Ansi.BOLD}ERROR:{Ansi.REGULAR} {error}{Ansi.RESET}")
     state.num_errors += 1
 
 
 def print_warning(warning: str, state: State) -> None:
-    print(f'{STYLES["yellow"]}{STYLES["bold"]}WARNING:{STYLES["regular"]} {warning}{STYLES["reset"]}')
+    print(f"{Ansi.YELLOW}{Ansi.BOLD}WARNING:{Ansi.REGULAR} {warning}{Ansi.RESET}")
     state.num_warnings += 1
 
 

+ 85 - 22
methods.py

@@ -10,26 +10,63 @@ from collections import OrderedDict
 from enum import Enum
 from io import StringIO, TextIOWrapper
 from pathlib import Path
-from typing import Generator, List, Optional, Union, cast
+from typing import Final, Generator, List, Optional, Union, cast
 
 # 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))
-# Listing all the folders we have converted
-# for SCU in scu_builders.py
-_scu_folders = set()
+
+################################################################################
+# 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.
-_colorize = bool(sys.stdout.isatty() or os.environ.get("CI"))
+COLOR_SUPPORTED: Final[bool] = _color_supported()
+_can_color: bool = COLOR_SUPPORTED
 
 
-def set_scu_folders(scu_folders):
-    global _scu_folders
-    _scu_folders = scu_folders
+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):
+class Ansi(Enum):
     """
     Enum class for adding ansi colorcodes directly into strings.
     Automatically converts values to strings representing their
@@ -39,6 +76,7 @@ class ANSI(Enum):
     RESET = "\x1b[0m"
 
     BOLD = "\x1b[1m"
+    DIM = "\x1b[2m"
     ITALIC = "\x1b[3m"
     UNDERLINE = "\x1b[4m"
     STRIKETHROUGH = "\x1b[9m"
@@ -53,24 +91,49 @@ class ANSI(Enum):
     CYAN = "\x1b[36m"
     WHITE = "\x1b[37m"
 
-    PURPLE = "\x1b[38;5;93m"
-    PINK = "\x1b[38;5;206m"
-    ORANGE = "\x1b[38;5;214m"
-    GRAY = "\x1b[38;5;244m"
+    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 _colorize
-        return str(self.value) if _colorize else ""
+        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)
+    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)
+    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()
+
+
+def set_scu_folders(scu_folders):
+    global _scu_folders
+    _scu_folders = scu_folders
 
 
 def add_source_files_orig(self, sources, files, allow_gen=False):
@@ -163,7 +226,7 @@ def get_version_info(module_version_string="", silent=False):
     if os.getenv("BUILD_NAME") is not None:
         build_name = str(os.getenv("BUILD_NAME"))
         if not silent:
-            print(f"Using custom build name: '{build_name}'.")
+            print_info(f"Using custom build name: '{build_name}'.")
 
     import version
 
@@ -185,7 +248,7 @@ def get_version_info(module_version_string="", silent=False):
     if os.getenv("GODOT_VERSION_STATUS") is not None:
         version_info["status"] = str(os.getenv("GODOT_VERSION_STATUS"))
         if not silent:
-            print(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.")
+            print_info(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.")
 
     # Parse Git hash if we're in a Git repo.
     githash = ""
@@ -441,7 +504,7 @@ def use_windows_spawn_fix(self, platform=None):
 
 
 def no_verbose(env):
-    colors = [ANSI.BLUE, ANSI.BOLD, ANSI.REGULAR, ANSI.RESET]
+    colors = [Ansi.BLUE, Ansi.BOLD, Ansi.REGULAR, Ansi.RESET]
 
     # There is a space before "..." to ensure that source file names can be
     # Ctrl + clicked in the VS Code terminal.
@@ -792,7 +855,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). Ninja has its own progress/tracking tool that clashes
     # with ours.
-    if not env["progress"] or not sys.stdout.isatty() or env["ninja"]:
+    if not env["progress"] or not IS_TTY or env["ninja"]:
         return
 
     NODE_COUNT_FILENAME = f"{base_folder_path}.scons_node_count"
@@ -1480,7 +1543,7 @@ def generate_copyright_header(filename: str) -> str:
 """
     filename = filename.split("/")[-1].ljust(MARGIN)
     if len(filename) > MARGIN:
-        print(f'WARNING: Filename "{filename}" too large for copyright header.')
+        print_warning(f'Filename "{filename}" too large for copyright header.')
     return TEMPLATE % filename
 
 

+ 7 - 14
misc/scripts/install_d3d12_sdk_windows.py

@@ -6,16 +6,9 @@ import subprocess
 import sys
 import urllib.request
 
-# Enable ANSI escape code support on Windows 10 and later (for colored console output).
-# <https://github.com/python/cpython/issues/73245>
-if sys.platform == "win32":
-    from ctypes import byref, c_int, windll
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
 
-    stdout_handle = windll.kernel32.GetStdHandle(c_int(-11))
-    mode = c_int(0)
-    windll.kernel32.GetConsoleMode(c_int(stdout_handle), byref(mode))
-    mode = c_int(mode.value | 4)
-    windll.kernel32.SetConsoleMode(c_int(stdout_handle), mode)
+from methods import Ansi
 
 # Base Godot dependencies path
 # If cross-compiling (no LOCALAPPDATA), we install in `bin`
@@ -49,7 +42,7 @@ if not os.path.exists(deps_folder):
     os.makedirs(deps_folder)
 
 # Mesa NIR
-print("\x1b[1m[1/3] Mesa NIR\x1b[0m")
+print(f"{Ansi.BOLD}[1/3] Mesa NIR{Ansi.RESET}")
 if os.path.isfile(mesa_archive):
     os.remove(mesa_archive)
 print(f"Downloading Mesa NIR {mesa_filename} ...")
@@ -76,7 +69,7 @@ if dlltool == "":
     dlltool = shutil.which("x86_64-w64-mingw32-dlltool") or ""
 has_mingw = gendef != "" and dlltool != ""
 
-print("\x1b[1m[2/3] WinPixEventRuntime\x1b[0m")
+print(f"{Ansi.BOLD}[2/3] WinPixEventRuntime{Ansi.RESET}")
 if os.path.isfile(pix_archive):
     os.remove(pix_archive)
 print(f"Downloading WinPixEventRuntime {pix_version} ...")
@@ -107,7 +100,7 @@ else:
 print(f"WinPixEventRuntime {pix_version} installed successfully.\n")
 
 # DirectX 12 Agility SDK
-print("\x1b[1m[3/3] DirectX 12 Agility SDK\x1b[0m")
+print(f"{Ansi.BOLD}[3/3] DirectX 12 Agility SDK{Ansi.RESET}")
 if os.path.isfile(agility_sdk_archive):
     os.remove(agility_sdk_archive)
 print(f"Downloading DirectX 12 Agility SDK {agility_sdk_version} ...")
@@ -123,5 +116,5 @@ os.remove(agility_sdk_archive)
 print(f"DirectX 12 Agility SDK {agility_sdk_version} installed successfully.\n")
 
 # Complete message
-print(f'\x1b[92mAll Direct3D 12 SDK components were installed to "{deps_folder}" successfully!\x1b[0m')
-print('\x1b[92mYou can now build Godot with Direct3D 12 support enabled by running "scons d3d12=yes".\x1b[0m')
+print(f'{Ansi.GREEN}All Direct3D 12 SDK components were installed to "{deps_folder}" successfully!{Ansi.RESET}')
+print(f'{Ansi.GREEN}You can now build Godot with Direct3D 12 support enabled by running "scons d3d12=yes".{Ansi.RESET}')

+ 5 - 21
modules/text_server_adv/gdextension_build/SConstruct

@@ -1,29 +1,13 @@
 #!/usr/bin/env python
-from misc.utility.scons_hints import *
 
 import atexit
-import sys
 import time
+from typing import TYPE_CHECKING
 
 import methods
 
-# Enable ANSI escape code support on Windows 10 and later (for colored console output).
-# <https://github.com/python/cpython/issues/73245>
-if sys.stdout.isatty() and sys.platform == "win32":
-    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 Exception as e:
-        methods._colorize = False
-        methods.print_error(f"Failed to enable ANSI escape code support, disabling color output.\n{e}")
+if TYPE_CHECKING:
+    from misc.utility.scons_hints import *
 
 # For the reference:
 # - CCFLAGS are compilation flags shared between C and C++
@@ -793,10 +777,10 @@ def print_elapsed_time():
     time_centiseconds = round((elapsed_time_sec % 1) * 100)
     print(
         "{}[Time elapsed: {}.{:02}]{}".format(
-            methods.ANSI.GRAY,
+            methods.Ansi.GRAY,
             time.strftime("%H:%M:%S", time.gmtime(elapsed_time_sec)),
             time_centiseconds,
-            methods.ANSI.RESET,
+            methods.Ansi.RESET,
         )
     )
 

+ 3 - 39
modules/text_server_adv/gdextension_build/methods.py

@@ -1,49 +1,13 @@
 import os
 import sys
-from enum import Enum
 
-# 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.
-_colorize = bool(sys.stdout.isatty() or os.environ.get("CI"))
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../"))
 
-
-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"
-    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"
-
-    PURPLE = "\x1b[38;5;93m"
-    PINK = "\x1b[38;5;206m"
-    ORANGE = "\x1b[38;5;214m"
-    GRAY = "\x1b[38;5;244m"
-
-    def __str__(self) -> str:
-        global _colorize
-        return str(self.value) if _colorize else ""
+from methods import Ansi
 
 
 def no_verbose(env):
-    colors = [ANSI.BLUE, ANSI.BOLD, ANSI.REGULAR, ANSI.RESET]
+    colors = [Ansi.BLUE, Ansi.BOLD, Ansi.REGULAR, Ansi.RESET]
 
     # There is a space before "..." to ensure that source file names can be
     # Ctrl + clicked in the VS Code terminal.

+ 5 - 21
modules/text_server_fb/gdextension_build/SConstruct

@@ -1,29 +1,13 @@
 #!/usr/bin/env python
-from misc.utility.scons_hints import *
 
 import atexit
-import sys
 import time
+from typing import TYPE_CHECKING
 
 import methods
 
-# Enable ANSI escape code support on Windows 10 and later (for colored console output).
-# <https://github.com/python/cpython/issues/73245>
-if sys.stdout.isatty() and sys.platform == "win32":
-    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 Exception as e:
-        methods._colorize = False
-        methods.print_error(f"Failed to enable ANSI escape code support, disabling color output.\n{e}")
+if TYPE_CHECKING:
+    from misc.utility.scons_hints import *
 
 # For the reference:
 # - CCFLAGS are compilation flags shared between C and C++
@@ -335,10 +319,10 @@ def print_elapsed_time():
     time_centiseconds = round((elapsed_time_sec % 1) * 100)
     print(
         "{}[Time elapsed: {}.{:02}]{}".format(
-            methods.ANSI.GRAY,
+            methods.Ansi.GRAY,
             time.strftime("%H:%M:%S", time.gmtime(elapsed_time_sec)),
             time_centiseconds,
-            methods.ANSI.RESET,
+            methods.Ansi.RESET,
         )
     )
 

+ 3 - 39
modules/text_server_fb/gdextension_build/methods.py

@@ -1,49 +1,13 @@
 import os
 import sys
-from enum import Enum
 
-# 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.
-_colorize = bool(sys.stdout.isatty() or os.environ.get("CI"))
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../"))
 
-
-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"
-    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"
-
-    PURPLE = "\x1b[38;5;93m"
-    PINK = "\x1b[38;5;206m"
-    ORANGE = "\x1b[38;5;214m"
-    GRAY = "\x1b[38;5;244m"
-
-    def __str__(self) -> str:
-        global _colorize
-        return str(self.value) if _colorize else ""
+from methods import Ansi
 
 
 def no_verbose(env):
-    colors = [ANSI.BLUE, ANSI.BOLD, ANSI.REGULAR, ANSI.RESET]
+    colors = [Ansi.BLUE, Ansi.BOLD, Ansi.REGULAR, Ansi.RESET]
 
     # There is a space before "..." to ensure that source file names can be
     # Ctrl + clicked in the VS Code terminal.

+ 2 - 2
platform/linuxbsd/detect.py

@@ -3,7 +3,7 @@ import platform
 import sys
 from typing import TYPE_CHECKING
 
-from methods import get_compiler_version, print_error, print_warning, using_gcc
+from methods import get_compiler_version, print_error, print_info, print_warning, using_gcc
 from platform_methods import detect_arch, validate_arch
 
 if TYPE_CHECKING:
@@ -492,7 +492,7 @@ def configure(env: "SConsEnvironment"):
         else:
             # The default crash handler depends on glibc, so if the host uses
             # a different libc (BSD libc, musl), libexecinfo is required.
-            print("Note: Using `execinfo=no` disables the crash handler on platforms where glibc is missing.")
+            print_info("Using `execinfo=no` disables the crash handler on platforms where glibc is missing.")
     else:
         env.Append(CPPDEFINES=["CRASH_HANDLER_ENABLED"])
 

+ 2 - 2
platform/web/detect.py

@@ -13,7 +13,7 @@ from emscripten_helpers import (
 )
 from SCons.Util import WhereIs
 
-from methods import get_compiler_version, print_error, print_warning
+from methods import get_compiler_version, print_error, print_info, print_warning
 from platform_methods import validate_arch
 
 if TYPE_CHECKING:
@@ -107,7 +107,7 @@ def configure(env: "SConsEnvironment"):
         env.Append(LINKFLAGS=["-sASSERTIONS=1"])
 
     if env.editor_build and env["initial_memory"] < 64:
-        print("Note: Forcing `initial_memory=64` as it is required for the web editor.")
+        print_info("Forcing `initial_memory=64` as it is required for the web editor.")
         env["initial_memory"] = 64
 
     env.Append(LINKFLAGS=["-sINITIAL_MEMORY=%sMB" % env["initial_memory"]])