diff options
Diffstat (limited to 'mesonbuild/mlog.py')
-rw-r--r-- | mesonbuild/mlog.py | 124 |
1 files changed, 70 insertions, 54 deletions
diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index 0434274..e8ee6c8 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -18,13 +18,15 @@ import sys import time import platform from contextlib import contextmanager +import typing """This is (mostly) a standalone module used to write logging information about Meson runs. Some output goes to screen, some to logging dir and some goes to both.""" -def _windows_ansi(): - from ctypes import windll, byref +def _windows_ansi() -> bool: + # windll only exists on windows, so mypy will get mad + from ctypes import windll, byref # type: ignore from ctypes.wintypes import DWORD kernel = windll.kernel32 @@ -35,48 +37,48 @@ def _windows_ansi(): # ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0x4 # If the call to enable VT processing fails (returns 0), we fallback to # original behavior - return kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON') + return bool(kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON')) if platform.system().lower() == 'windows': - colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi() + colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi() # type: bool else: colorize_console = os.isatty(sys.stdout.fileno()) and os.environ.get('TERM') != 'dumb' -log_dir = None -log_file = None -log_fname = 'meson-log.txt' -log_depth = 0 -log_timestamp_start = None -log_fatal_warnings = False -log_disable_stdout = False -log_errors_only = False - -def disable(): +log_dir = None # type: typing.Optional[str] +log_file = None # type: typing.Optional[typing.TextIO] +log_fname = 'meson-log.txt' # type: str +log_depth = 0 # type: int +log_timestamp_start = None # type: typing.Optional[float] +log_fatal_warnings = False # type: bool +log_disable_stdout = False # type: bool +log_errors_only = False # type: bool + +def disable() -> None: global log_disable_stdout log_disable_stdout = True -def enable(): +def enable() -> None: global log_disable_stdout log_disable_stdout = False -def set_quiet(): +def set_quiet() -> None: global log_errors_only log_errors_only = True -def set_verbose(): +def set_verbose() -> None: global log_errors_only log_errors_only = False -def initialize(logdir, fatal_warnings=False): +def initialize(logdir: str, fatal_warnings: bool = False) -> None: global log_dir, log_file, log_fatal_warnings log_dir = logdir log_file = open(os.path.join(logdir, log_fname), 'w', encoding='utf8') log_fatal_warnings = fatal_warnings -def set_timestamp_start(start): +def set_timestamp_start(start: float) -> None: global log_timestamp_start log_timestamp_start = start -def shutdown(): +def shutdown() -> typing.Optional[str]: global log_file if log_file is not None: path = log_file.name @@ -89,12 +91,12 @@ def shutdown(): class AnsiDecorator: plain_code = "\033[0m" - def __init__(self, text, code, quoted=False): + def __init__(self, text: str, code: str, quoted: bool = False): self.text = text self.code = code self.quoted = quoted - def get_text(self, with_codes): + def get_text(self, with_codes: bool) -> str: text = self.text if with_codes: text = self.code + self.text + AnsiDecorator.plain_code @@ -102,26 +104,28 @@ class AnsiDecorator: text = '"{}"'.format(text) return text -def bold(text, quoted=False): +def bold(text: str, quoted: bool = False) -> AnsiDecorator: return AnsiDecorator(text, "\033[1m", quoted=quoted) -def red(text): +def red(text: str) -> AnsiDecorator: return AnsiDecorator(text, "\033[1;31m") -def green(text): +def green(text: str) -> AnsiDecorator: return AnsiDecorator(text, "\033[1;32m") -def yellow(text): +def yellow(text: str) -> AnsiDecorator: return AnsiDecorator(text, "\033[1;33m") -def blue(text): +def blue(text: str) -> AnsiDecorator: return AnsiDecorator(text, "\033[1;34m") -def cyan(text): +def cyan(text: str) -> AnsiDecorator: return AnsiDecorator(text, "\033[1;36m") -def process_markup(args, keep): - arr = [] +# This really should be AnsiDecorator or anything that implements +# __str__(), but that requires protocols from typing_extensions +def process_markup(args: typing.Sequence[typing.Union[AnsiDecorator, str]], keep: bool) -> typing.List[str]: + arr = [] # type: typing.List[str] if log_timestamp_start is not None: arr = ['[{:.3f}]'.format(time.monotonic() - log_timestamp_start)] for arg in args: @@ -135,7 +139,7 @@ def process_markup(args, keep): arr.append(str(arg)) return arr -def force_print(*args, **kwargs): +def force_print(*args: str, **kwargs: typing.Any) -> None: global log_disable_stdout if log_disable_stdout: return @@ -155,41 +159,51 @@ def force_print(*args, **kwargs): cleaned = raw.encode('ascii', 'replace').decode('ascii') print(cleaned, end='') -def debug(*args, **kwargs): +# We really want a heterogenous dict for this, but that's in typing_extensions +def debug(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None: arr = process_markup(args, False) if log_file is not None: - print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. + print(*arr, file=log_file, **kwargs) log_file.flush() -def log(*args, is_error=False, **kwargs): +def log(*args: typing.Union[str, AnsiDecorator], is_error: bool = False, + **kwargs: typing.Any) -> None: global log_errors_only arr = process_markup(args, False) if log_file is not None: - print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. + print(*arr, file=log_file, **kwargs) log_file.flush() if colorize_console: arr = process_markup(args, True) if not log_errors_only or is_error: force_print(*arr, **kwargs) -def _log_error(severity, *args, **kwargs): +def _log_error(severity: str, *rargs: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None: from .mesonlib import get_error_location_string from .environment import build_filename from .mesonlib import MesonException + + # The tping requirements here are non-obvious. Lists are invariant, + # therefore List[A] and List[Union[A, B]] are not able to be joined if severity == 'warning': - args = (yellow('WARNING:'),) + args + label = [yellow('WARNING:')] # type: typing.List[typing.Union[str, AnsiDecorator]] elif severity == 'error': - args = (red('ERROR:'),) + args + label = [red('ERROR:')] elif severity == 'deprecation': - args = (red('DEPRECATION:'),) + args + label = [red('DEPRECATION:')] else: - assert False, 'Invalid severity ' + severity + raise MesonException('Invalid severity ' + severity) + # rargs is a tuple, not a list + args = label + list(rargs) location = kwargs.pop('location', None) if location is not None: location_file = os.path.join(location.subdir, build_filename) location_str = get_error_location_string(location_file, location.lineno) - args = (location_str,) + args + # Unions are frankly awful, and we have to cast here to get mypy + # to understand that the list concatenation is safe + location_list = typing.cast(typing.List[typing.Union[str, AnsiDecorator]], [location_str]) + args = location_list + args log(*args, **kwargs) @@ -197,40 +211,42 @@ def _log_error(severity, *args, **kwargs): if log_fatal_warnings: raise MesonException("Fatal warnings enabled, aborting") -def error(*args, **kwargs): +def error(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None: return _log_error('error', *args, **kwargs, is_error=True) -def warning(*args, **kwargs): +def warning(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None: return _log_error('warning', *args, **kwargs, is_error=True) -def deprecation(*args, **kwargs): +def deprecation(*args: typing.Union[str, AnsiDecorator], **kwargs: typing.Any) -> None: return _log_error('deprecation', *args, **kwargs, is_error=True) -def exception(e, prefix=red('ERROR:')): +def exception(e: Exception, prefix: AnsiDecorator = red('ERROR:')) -> None: log() - args = [] + args = [] # type: typing.List[typing.Union[AnsiDecorator, str]] if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'): - args.append('%s:%d:%d:' % (e.file, e.lineno, e.colno)) + # Mypy can't figure this out, and it's pretty easy to vidual inspect + # that this is correct, so we'll just ignore it. + args.append('%s:%d:%d:' % (e.file, e.lineno, e.colno)) # type: ignore if prefix: args.append(prefix) - args.append(e) + args.append(str(e)) log(*args) # Format a list for logging purposes as a string. It separates # all but the last item with commas, and the last with 'and'. -def format_list(list): - l = len(list) +def format_list(list_: typing.List[str]) -> str: + l = len(list_) if l > 2: - return ' and '.join([', '.join(list[:-1]), list[-1]]) + return ' and '.join([', '.join(list_[:-1]), list_[-1]]) elif l == 2: - return ' and '.join(list) + return ' and '.join(list_) elif l == 1: - return list[0] + return list_[0] else: return '' @contextmanager -def nested(): +def nested() -> typing.Generator[None, None, None]: global log_depth log_depth += 1 try: |