mlog: Add a log_once function

There are a number of cases where we end up spamming users with the same
message over and over again, which is really annoying. This solves that.
pull/4325/head
Dylan Baker 5 years ago committed by Xavier Claessens
parent eb30c493f6
commit 614372aa55
  1. 48
      mesonbuild/mlog.py
  2. 29
      run_unittests.py

@ -18,7 +18,18 @@ import sys
import time import time
import platform import platform
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Generator, List, Optional, Sequence, TextIO, Union, cast from typing import (
Any,
Generator,
List,
Optional,
Sequence,
Set,
TextIO,
Tuple,
Union,
cast,
)
from pathlib import Path from pathlib import Path
"""This is (mostly) a standalone module used to write logging """This is (mostly) a standalone module used to write logging
@ -56,6 +67,7 @@ log_fatal_warnings = False # type: bool
log_disable_stdout = False # type: bool log_disable_stdout = False # type: bool
log_errors_only = False # type: bool log_errors_only = False # type: bool
_in_ci = 'CI' in os.environ # type: bool _in_ci = 'CI' in os.environ # type: bool
_logged_once = set() # type: Set[Tuple[str, ...]]
def disable() -> None: def disable() -> None:
global log_disable_stdout global log_disable_stdout
@ -205,7 +217,21 @@ def log(*args: Union[str, AnsiDecorator], is_error: bool = False,
if not log_errors_only or is_error: if not log_errors_only or is_error:
force_print(*arr, **kwargs) force_print(*arr, **kwargs)
def _log_error(severity: str, *rargs: Union[str, AnsiDecorator], **kwargs: Any) -> None: def log_once(*args: Union[str, AnsiDecorator], is_error: bool = False,
**kwargs: Any) -> None:
"""Log variant that only prints a given message one time per meson invocation.
This considers nasi decorated values by the values they wrap without
regard for the AnsiDecorator itself.
"""
t = tuple(a.text if isinstance(a, AnsiDecorator) else a for a in args)
if t in _logged_once:
return
_logged_once.add(t)
log(*args, is_error=is_error, **kwargs)
def _log_error(severity: str, *rargs: Union[str, AnsiDecorator],
once: bool = False, **kwargs: Any) -> None:
from .mesonlib import get_error_location_string from .mesonlib import get_error_location_string
from .environment import build_filename from .environment import build_filename
from .mesonlib import MesonException from .mesonlib import MesonException
@ -232,20 +258,22 @@ def _log_error(severity: str, *rargs: Union[str, AnsiDecorator], **kwargs: Any)
location_list = cast(List[Union[str, AnsiDecorator]], [location_str]) location_list = cast(List[Union[str, AnsiDecorator]], [location_str])
args = location_list + args args = location_list + args
log(*args, **kwargs) if once:
log_once(*args, **kwargs)
else:
log(*args, **kwargs)
global log_fatal_warnings
if log_fatal_warnings: if log_fatal_warnings:
raise MesonException("Fatal warnings enabled, aborting") raise MesonException("Fatal warnings enabled, aborting")
def error(*args: Union[str, AnsiDecorator], **kwargs: Any) -> None: def error(*args: Union[str, AnsiDecorator], once: bool = False, **kwargs: Any) -> None:
return _log_error('error', *args, **kwargs, is_error=True) return _log_error('error', *args, **kwargs, is_error=True, once=once)
def warning(*args: Union[str, AnsiDecorator], **kwargs: Any) -> None: def warning(*args: Union[str, AnsiDecorator], once: bool = False, **kwargs: Any) -> None:
return _log_error('warning', *args, **kwargs, is_error=True) return _log_error('warning', *args, **kwargs, is_error=True, once=once)
def deprecation(*args: Union[str, AnsiDecorator], **kwargs: Any) -> None: def deprecation(*args: Union[str, AnsiDecorator], once: bool = False, **kwargs: Any) -> None:
return _log_error('deprecation', *args, **kwargs, is_error=True) return _log_error('deprecation', *args, **kwargs, is_error=True, once=once)
def get_relative_path(target: Path, current: Path) -> Path: def get_relative_path(target: Path, current: Path) -> Path:
"""Get the path to target from current""" """Get the path to target from current"""

@ -1141,6 +1141,35 @@ class InternalTests(unittest.TestCase):
deps = d.get_all_dependencies(target) deps = d.get_all_dependencies(target)
self.assertEqual(deps, expdeps) self.assertEqual(deps, expdeps)
def test_log_once(self):
f = io.StringIO()
with mock.patch('mesonbuild.mlog.log_file', f), \
mock.patch('mesonbuild.mlog._logged_once', set()):
mesonbuild.mlog.log_once('foo')
mesonbuild.mlog.log_once('foo')
actual = f.getvalue().strip()
self.assertEqual(actual, 'foo', actual)
def test_log_once_ansi(self):
f = io.StringIO()
with mock.patch('mesonbuild.mlog.log_file', f), \
mock.patch('mesonbuild.mlog._logged_once', set()):
mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo'))
mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo'))
actual = f.getvalue().strip()
self.assertEqual(actual.count('foo'), 1, actual)
mesonbuild.mlog.log_once('foo')
actual = f.getvalue().strip()
self.assertEqual(actual.count('foo'), 1, actual)
f.truncate()
mesonbuild.mlog.warning('bar', once=True)
mesonbuild.mlog.warning('bar', once=True)
actual = f.getvalue().strip()
self.assertEqual(actual.count('bar'), 1, actual)
@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release') @unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
class DataTests(unittest.TestCase): class DataTests(unittest.TestCase):

Loading…
Cancel
Save