|
7 | 7 | import subprocess |
8 | 8 | import sys |
9 | 9 | from collections import OrderedDict |
10 | | -from enum import Enum |
11 | 10 | from io import StringIO, TextIOWrapper |
12 | 11 | from pathlib import Path |
13 | | -from typing import Final, Generator, List, Optional, Union, cast |
| 12 | +from typing import Generator, List, Optional, Union, cast |
| 13 | + |
| 14 | +from misc.utility.color import print_error, print_info, print_warning |
14 | 15 |
|
15 | 16 | # Get the "Godot" folder name ahead of time |
16 | 17 | base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/" |
17 | 18 | base_folder_only = os.path.basename(os.path.normpath(base_folder_path)) |
18 | 19 |
|
19 | | -################################################################################ |
20 | | -# COLORIZE |
21 | | -################################################################################ |
22 | | - |
23 | | -IS_CI: Final[bool] = bool(os.environ.get("CI")) |
24 | | -IS_TTY: Final[bool] = bool(sys.stdout.isatty()) |
25 | | - |
26 | | - |
27 | | -def _color_supported() -> bool: |
28 | | - """ |
29 | | - Enables ANSI escape code support on Windows 10 and later (for colored console output). |
30 | | - See here: https://github.com/python/cpython/issues/73245 |
31 | | - """ |
32 | | - if sys.platform == "win32" and IS_TTY: |
33 | | - try: |
34 | | - from ctypes import WinError, byref, windll # type: ignore |
35 | | - from ctypes.wintypes import DWORD # type: ignore |
36 | | - |
37 | | - stdout_handle = windll.kernel32.GetStdHandle(DWORD(-11)) |
38 | | - mode = DWORD(0) |
39 | | - if not windll.kernel32.GetConsoleMode(stdout_handle, byref(mode)): |
40 | | - raise WinError() |
41 | | - mode = DWORD(mode.value | 4) |
42 | | - if not windll.kernel32.SetConsoleMode(stdout_handle, mode): |
43 | | - raise WinError() |
44 | | - except (TypeError, OSError) as e: |
45 | | - print(f"Failed to enable ANSI escape code support, disabling color output.\n{e}", file=sys.stderr) |
46 | | - return False |
47 | | - |
48 | | - return IS_TTY or IS_CI |
49 | | - |
50 | | - |
51 | | -# Colors are disabled in non-TTY environments such as pipes. This means |
52 | | -# that if output is redirected to a file, it won't contain color codes. |
53 | | -# Colors are always enabled on continuous integration. |
54 | | -COLOR_SUPPORTED: Final[bool] = _color_supported() |
55 | | -_can_color: bool = COLOR_SUPPORTED |
56 | | - |
57 | | - |
58 | | -def toggle_color(value: Optional[bool] = None) -> None: |
59 | | - """ |
60 | | - Explicitly toggle color codes, regardless of support. |
61 | | -
|
62 | | - - `value`: An optional boolean to explicitly set the color |
63 | | - state instead of toggling. |
64 | | - """ |
65 | | - global _can_color |
66 | | - _can_color = value if value is not None else not _can_color |
67 | | - |
68 | | - |
69 | | -class Ansi(Enum): |
70 | | - """ |
71 | | - Enum class for adding ansi colorcodes directly into strings. |
72 | | - Automatically converts values to strings representing their |
73 | | - internal value, or an empty string in a non-colorized scope. |
74 | | - """ |
75 | | - |
76 | | - RESET = "\x1b[0m" |
77 | | - |
78 | | - BOLD = "\x1b[1m" |
79 | | - DIM = "\x1b[2m" |
80 | | - ITALIC = "\x1b[3m" |
81 | | - UNDERLINE = "\x1b[4m" |
82 | | - STRIKETHROUGH = "\x1b[9m" |
83 | | - REGULAR = "\x1b[22;23;24;29m" |
84 | | - |
85 | | - BLACK = "\x1b[30m" |
86 | | - RED = "\x1b[31m" |
87 | | - GREEN = "\x1b[32m" |
88 | | - YELLOW = "\x1b[33m" |
89 | | - BLUE = "\x1b[34m" |
90 | | - MAGENTA = "\x1b[35m" |
91 | | - CYAN = "\x1b[36m" |
92 | | - WHITE = "\x1b[37m" |
93 | | - |
94 | | - LIGHT_BLACK = "\x1b[90m" |
95 | | - LIGHT_RED = "\x1b[91m" |
96 | | - LIGHT_GREEN = "\x1b[92m" |
97 | | - LIGHT_YELLOW = "\x1b[93m" |
98 | | - LIGHT_BLUE = "\x1b[94m" |
99 | | - LIGHT_MAGENTA = "\x1b[95m" |
100 | | - LIGHT_CYAN = "\x1b[96m" |
101 | | - LIGHT_WHITE = "\x1b[97m" |
102 | | - |
103 | | - GRAY = LIGHT_BLACK if IS_CI else BLACK |
104 | | - """ |
105 | | - Special case. GitHub Actions doesn't convert `BLACK` to gray as expected, but does convert `LIGHT_BLACK`. |
106 | | - By implementing `GRAY`, we handle both cases dynamically, while still allowing for explicit values if desired. |
107 | | - """ |
108 | | - |
109 | | - def __str__(self) -> str: |
110 | | - global _can_color |
111 | | - return str(self.value) if _can_color else "" |
112 | | - |
113 | | - |
114 | | -def print_info(*values: object) -> None: |
115 | | - """Prints a informational message with formatting.""" |
116 | | - print(f"{Ansi.GRAY}{Ansi.BOLD}INFO:{Ansi.REGULAR}", *values, Ansi.RESET) |
117 | | - |
118 | | - |
119 | | -def print_warning(*values: object) -> None: |
120 | | - """Prints a warning message with formatting.""" |
121 | | - print(f"{Ansi.YELLOW}{Ansi.BOLD}WARNING:{Ansi.REGULAR}", *values, Ansi.RESET, file=sys.stderr) |
122 | | - |
123 | | - |
124 | | -def print_error(*values: object) -> None: |
125 | | - """Prints an error message with formatting.""" |
126 | | - print(f"{Ansi.RED}{Ansi.BOLD}ERROR:{Ansi.REGULAR}", *values, Ansi.RESET, file=sys.stderr) |
127 | | - |
128 | | - |
129 | 20 | # Listing all the folders we have converted |
130 | 21 | # for SCU in scu_builders.py |
131 | 22 | _scu_folders = set() |
@@ -505,6 +396,8 @@ def mySpawn(sh, escape, cmd, args, env): |
505 | 396 |
|
506 | 397 |
|
507 | 398 | def no_verbose(env): |
| 399 | + from misc.utility.color import Ansi |
| 400 | + |
508 | 401 | colors = [Ansi.BLUE, Ansi.BOLD, Ansi.REGULAR, Ansi.RESET] |
509 | 402 |
|
510 | 403 | # There is a space before "..." to ensure that source file names can be |
@@ -875,7 +768,7 @@ def __init__(self): |
875 | 768 |
|
876 | 769 | # Progress reporting is not available in non-TTY environments since it |
877 | 770 | # messes with the output (for example, when writing to a file). |
878 | | - self.display = cast(bool, self.max and env["progress"] and IS_TTY) |
| 771 | + self.display = cast(bool, self.max and env["progress"] and sys.stdout.isatty()) |
879 | 772 | if self.display and not self.max: |
880 | 773 | print_info("Performing initial build, progress percentage unavailable!") |
881 | 774 |
|
@@ -1019,6 +912,31 @@ def prepare_cache(env) -> None: |
1019 | 912 | atexit.register(clean_cache, cache_path, cache_limit, env["verbose"]) |
1020 | 913 |
|
1021 | 914 |
|
| 915 | +def prepare_purge(env): |
| 916 | + from SCons.Script.Main import GetBuildFailures |
| 917 | + |
| 918 | + def purge_flaky_files(): |
| 919 | + paths_to_keep = [env["ninja_file"]] |
| 920 | + for build_failure in GetBuildFailures(): |
| 921 | + path = build_failure.node.path |
| 922 | + if os.path.isfile(path) and path not in paths_to_keep: |
| 923 | + os.remove(path) |
| 924 | + |
| 925 | + atexit.register(purge_flaky_files) |
| 926 | + |
| 927 | + |
| 928 | +def prepare_timer(): |
| 929 | + import time |
| 930 | + |
| 931 | + def print_elapsed_time(time_at_start: float): |
| 932 | + time_elapsed = time.time() - time_at_start |
| 933 | + time_formatted = time.strftime("%H:%M:%S", time.gmtime(time_elapsed)) |
| 934 | + time_centiseconds = round((time_elapsed % 1) * 100) |
| 935 | + print_info(f"Time elapsed: {time_formatted}.{time_centiseconds}") |
| 936 | + |
| 937 | + atexit.register(print_elapsed_time, time.time()) |
| 938 | + |
| 939 | + |
1022 | 940 | def dump(env): |
1023 | 941 | # Dumps latest build information for debugging purposes and external tools. |
1024 | 942 | from json import dump |
|
0 commit comments