From f1b7f52ab76034a4c884250adb256d0918c42afe Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 24 Jan 2024 22:16:49 -0500 Subject: [PATCH] When configuring fails in Github Actions, print folded logs A common, and challenging, issue in CI runners is debugging issues when you know the information you want to check, but it's in the log file which you don't have because remote CI machines. There are various edge cases where this is especially hard to solve, such as inside of `pip install` where the build directory with the log file is automatically cleaned up. But it's never really *easy* when you don't expect it, and the best case scenario is your iteration time gets cut in half as you hurriedly go add some `cat`s to your CI scripts. Meson can, at least sometimes, detect platforms where text can be emitted inside of "folds", which are auto-collapsed and don't obscure the general output, but when clicked will expand the logfile contents. Hook this up. We start off with a Github Actions implementation. We had some internal code used by our own project tests runner, which can be utilized. Also permit forcing it via an environment variable, in case autodetection fails and you just want to force *something*, especially when meson is called a couple layers deep inside some other tool. (cherry picked from commit 2b80d4cca174916a4b6d0c47c54ad56795741bb6) --- mesonbuild/mesonmain.py | 3 +++ mesonbuild/mlog.py | 31 ++++++++++++++++++++++++++++++- run_project_tests.py | 11 +++-------- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index c486dd846cb9..faa0f426d82a 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -27,6 +27,9 @@ def errorhandler(e: Exception, command: str) -> int: logfile = mlog.shutdown() if logfile is not None: mlog.log("\nA full log can be found at", mlog.bold(logfile)) + contents = mlog.ci_fold_file(logfile, f'CI platform detected, click here for {os.path.basename(logfile)} contents.') + if contents: + print(contents) if os.environ.get('MESON_FORCE_BACKTRACE'): raise e return 1 diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index a8b0185371d1..bc8faeba7d06 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -22,8 +22,9 @@ from pathlib import Path if T.TYPE_CHECKING: - from ._typing import StringProtocol, SizedStringProtocol + from typing_extensions import Literal + from ._typing import StringProtocol, SizedStringProtocol from .mparser import BaseNode TV_Loggable = T.Union[str, 'AnsiDecorator', StringProtocol] @@ -75,6 +76,7 @@ def setup_console() -> None: pass _in_ci = 'CI' in os.environ +_ci_is_github = 'GITHUB_ACTIONS' in os.environ class _Severity(enum.Enum): @@ -540,3 +542,30 @@ def code_line(text: str, line: str, colno: int) -> str: :return: A formatted string of the text, line, and a caret """ return f'{text}\n{line}\n{" " * colno}^' + +@T.overload +def ci_fold_file(fname: T.Union[str, os.PathLike], banner: str, force: Literal[True] = True) -> str: ... + +@T.overload +def ci_fold_file(fname: T.Union[str, os.PathLike], banner: str, force: Literal[False] = False) -> T.Optional[str]: ... + +def ci_fold_file(fname: T.Union[str, os.PathLike], banner: str, force: bool = False) -> T.Optional[str]: + if not _in_ci and not force: + return None + + if _ci_is_github: + header = f'::group::==== {banner} ====' + footer = '::endgroup::' + elif force: + header = banner + footer = '' + elif 'MESON_FORCE_SHOW_LOGS' in os.environ: + header = f'==== Forcing display of logs for {os.path.basename(fname)} ====' + footer = '' + else: + # only github is implemented + return None + + with open(fname, 'r', encoding='utf-8') as f: + data = f.read() + return f'{header}\n{data}\n{footer}\n' diff --git a/run_project_tests.py b/run_project_tests.py index 974273fc790e..c11410486e23 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -305,7 +305,6 @@ def __lt__(self, other: object) -> bool: failing_logs: T.List[str] = [] print_debug = 'MESON_PRINT_TEST_OUTPUT' in os.environ under_ci = 'CI' in os.environ -ci_is_github = 'GITHUB_ACTIONS' in os.environ raw_ci_jobname = os.environ.get('MESON_CI_JOBNAME', None) ci_jobname = raw_ci_jobname if raw_ci_jobname != 'thirdparty' else None do_debug = under_ci or print_debug @@ -437,16 +436,12 @@ def log_text_file(logfile: T.TextIO, testdir: Path, result: TestResult) -> None: def _run_ci_include(args: T.List[str]) -> str: - header = f'Included file {args[0]}:' - footer = '' - if ci_is_github: - header = f'::group::==== {header} ====' - footer = '::endgroup::' if not args: return 'At least one parameter required' + + header = f'Included file {args[0]}:' try: - data = Path(args[0]).read_text(errors='ignore', encoding='utf-8') - return f'{header}\n{data}\n{footer}\n' + return mlog.ci_fold_file(args[0], header, force=True) except Exception: return 'Failed to open {}\n'.format(args[0])