# Copyright 2013-2014 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import io import sys import time import platform from contextlib import contextmanager """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 from ctypes.wintypes import DWORD kernel = windll.kernel32 stdout = kernel.GetStdHandle(-11) mode = DWORD() if not kernel.GetConsoleMode(stdout, byref(mode)): return False # 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') if platform.system().lower() == 'windows': colorize_console = os.isatty(sys.stdout.fileno()) and _windows_ansi() 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 def initialize(logdir, fatal_warnings=False): 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): global log_timestamp_start log_timestamp_start = start def shutdown(): global log_file if log_file is not None: path = log_file.name exception_around_goer = log_file log_file = None exception_around_goer.close() return path return None class AnsiDecorator: plain_code = "\033[0m" def __init__(self, text, code, quoted=False): self.text = text self.code = code self.quoted = quoted def get_text(self, with_codes): text = self.text if with_codes: text = self.code + self.text + AnsiDecorator.plain_code if self.quoted: text = '"{}"'.format(text) return text def bold(text, quoted=False): return AnsiDecorator(text, "\033[1m", quoted=quoted) def red(text): return AnsiDecorator(text, "\033[1;31m") def green(text): return AnsiDecorator(text, "\033[1;32m") def yellow(text): return AnsiDecorator(text, "\033[1;33m") def blue(text): return AnsiDecorator(text, "\033[1;34m") def cyan(text): return AnsiDecorator(text, "\033[1;36m") def process_markup(args, keep): arr = [] if log_timestamp_start is not None: arr = ['[{:.3f}]'.format(time.monotonic() - log_timestamp_start)] for arg in args: if arg is None: continue if isinstance(arg, str): arr.append(arg) elif isinstance(arg, AnsiDecorator): arr.append(arg.get_text(keep)) else: arr.append(str(arg)) return arr def force_print(*args, **kwargs): iostr = io.StringIO() kwargs['file'] = iostr print(*args, **kwargs) raw = iostr.getvalue() if log_depth > 0: prepend = '|' * log_depth raw = prepend + raw.replace('\n', '\n' + prepend, raw.count('\n') - 1) # _Something_ is going to get printed. try: print(raw, end='') except UnicodeEncodeError: cleaned = raw.encode('ascii', 'replace').decode('ascii') print(cleaned, end='') def debug(*args, **kwargs): arr = process_markup(args, False) if log_file is not None: print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. log_file.flush() def log(*args, **kwargs): arr = process_markup(args, False) if log_file is not None: print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. log_file.flush() if colorize_console: arr = process_markup(args, True) force_print(*arr, **kwargs) def _log_error(severity, *args, **kwargs): from .mesonlib import get_error_location_string from .environment import build_filename from .mesonlib import MesonException if severity == 'warning': args = (yellow('WARNING:'),) + args elif severity == 'error': args = (red('ERROR:'),) + args elif severity == 'deprecation': args = (red('DEPRECATION:'),) + args else: assert False, 'Invalid severity ' + severity 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 log(*args, **kwargs) global log_fatal_warnings if log_fatal_warnings: raise MesonException("Fatal warnings enabled, aborting") def error(*args, **kwargs): return _log_error('error', *args, **kwargs) def warning(*args, **kwargs): return _log_error('warning', *args, **kwargs) def deprecation(*args, **kwargs): return _log_error('deprecation', *args, **kwargs) def exception(e): log() if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'): log('%s:%d:%d:' % (e.file, e.lineno, e.colno), red('ERROR: '), e) else: log(red('ERROR:'), e) # 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) if l > 2: return ' and '.join([', '.join(list[:-1]), list[-1]]) elif l == 2: return ' and '.join(list) elif l == 1: return list[0] else: return '' @contextmanager def nested(): global log_depth log_depth += 1 try: yield finally: log_depth -= 1