From de0ad0a9799eabe1fe8bf5b084f3644da0f726b8 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Thu, 20 Aug 2026 23:11:13 +0300 Subject: [PATCH] kbuild: preserve generic error excerpts Keep complete diagnostics when the failed target appears within the diagnostic line, and fall back to the Make failure when no preceding diagnostic is available. Consume timestamp prefixes as part of Make error lines so they do not become spurious report content. Reported-by: kernelci.org bot Link: https://lore.kernel.org/all/178711194407.380.4108589457572337694@kernelci.org/ --- logspec/errors/kbuild.py | 45 +++++++++++++++++++++++----- tests/logs/index.txt | 7 +++++ tests/logs/kbuild/kbuild_025.log | 3 ++ tests/logs/kbuild/kbuild_026.log | 5 ++++ tests/test_kbuild.py | 51 ++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 tests/logs/kbuild/kbuild_025.log create mode 100644 tests/logs/kbuild/kbuild_026.log diff --git a/logspec/errors/kbuild.py b/logspec/errors/kbuild.py index 60e2481..c1f66d5 100644 --- a/logspec/errors/kbuild.py +++ b/logspec/errors/kbuild.py @@ -492,9 +492,11 @@ def _parse(self, text): """Parses a log fragment looking for a generic Kbuild error and updates the object with the extracted information. - Strategy: if a target was specified, search for errors _after_ - the first appearance of the `target' string in the log. To - search for these errors, look for unindented lines. + Strategy: if a target was specified, search for errors at or + after the first appearance of the `target' string in the log. + When text follows the target on the same line, keep that entire + line so diagnostics containing the target are not truncated. + To search for these errors, look for unindented lines. Parameters: text (str): the text log containing the modpost error @@ -505,11 +507,28 @@ def _parse(self, text): self.error_type = "kbuild.other" end = 0 if self.target: - match = re.search(self.target, text) - if not match: + target_match = re.search(re.escape(self.target), text) + if not target_match: return end + + line_start = text.rfind('\n', 0, target_match.start()) + 1 + line_end = text.find('\n', target_match.end()) + if line_end == -1: + line_end = len(text) + + # A diagnostic can contain the target followed by the actual + # failure, for example: "cp: cannot create '': File + # exists". Start at the beginning of that line instead of + # returning only the text after the target. If the target ends + # the line (as it commonly does in a command), start at the next + # line and retain the existing command-exclusion behaviour. + trailing_text = text[target_match.end():line_end].strip() + search_start = ( + line_start if trailing_text else min(line_end + 1, len(text)) + ) + summary_strings = [] - match = re.finditer(r'^[^\s]+.*$', text[match.end():], flags=re.MULTILINE) + match = re.finditer(r'^[^\s]+.*$', text[search_start:], flags=re.MULTILINE) for m in match: current_match = m.group() self._report += f"{current_match}\n" @@ -523,7 +542,7 @@ def _parse(self, text): generic_error_match = re.search(fr'{TIMESTAMP}(.*error:.*)', current_match) if generic_error_match: summary_strings.append(generic_error_match.group(1)) - end = m.end() + end = search_start + m.end() if summary_strings: self.error_summary = " ".join([string for string in summary_strings if string]) return end @@ -629,7 +648,11 @@ def find_kbuild_error(text): None if no error report was found. """ end = 0 - match = re.search(r'make.*?: \*\*\* (?P.*)', text) + match = re.search( + fr'^{TIMESTAMP}make.*?: \*\*\* (?P.*)', + text, + flags=re.MULTILINE, + ) if not match: return None error_str = match.group('error_str') @@ -658,6 +681,12 @@ def find_kbuild_error(text): # Catch-all condition for non-specific errors error = KbuildGenericError(script=script, target=target) error.parse(error_text) + if isinstance(error, KbuildGenericError) and not error._report: + # Some generic failures have no preceding diagnostic or target + # occurrence. The Make failure is still useful context and must + # not result in an empty report body. + line_start = text.rfind('\n', 0, start) + 1 + error._report = f"{text[line_start:end]}\n" else: # Unrecognized error, these are marked as unknown and not parsed error = KbuildUnknownError(error_str) diff --git a/tests/logs/index.txt b/tests/logs/index.txt index c86684e..38cace9 100644 --- a/tests/logs/index.txt +++ b/tests/logs/index.txt @@ -133,6 +133,13 @@ - kbuild_024.log: Device Tree Compiler fatal file error FATAL ERROR: Couldn't open "arch/arm64/boot/dts/qcom/missing.dtsi": No such file or directory + - kbuild_025.log: Generic generated-header failure without a diagnostic + make[5]: *** [/tmp/kci/linux/arch/x86/Makefile:269: arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1 + + - kbuild_026.log: Generic diagnostic containing the target path + cp: cannot create regular file '/tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic': File exists + + ./linux_boot diff --git a/tests/logs/kbuild/kbuild_025.log b/tests/logs/kbuild/kbuild_025.log new file mode 100644 index 0000000..cefbaf3 --- /dev/null +++ b/tests/logs/kbuild/kbuild_025.log @@ -0,0 +1,3 @@ +00:00:15 make --silent --keep-going --jobs=8 O=/tmp/kci/artifacts/build ARCH=x86_64 HOSTCC=clang CC=clang +00:13:13 make[5]: *** [/tmp/kci/linux/arch/x86/Makefile:269: arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1 +00:13:13 make[5]: Target 'prepare' not remade because of errors. diff --git a/tests/logs/kbuild/kbuild_026.log b/tests/logs/kbuild/kbuild_026.log new file mode 100644 index 0000000..707d98c --- /dev/null +++ b/tests/logs/kbuild/kbuild_026.log @@ -0,0 +1,5 @@ +04:41:39 CC acct_syscall +04:41:39 CC tags_test +04:41:43 cp: cannot create regular file '/tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic': File exists +04:41:43 make[2]: *** [Makefile:21: /tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic] Error 1 +04:41:43 make[2]: Target 'all' not remade because of errors. diff --git a/tests/test_kbuild.py b/tests/test_kbuild.py index 30d94b4..1ef15d4 100644 --- a/tests/test_kbuild.py +++ b/tests/test_kbuild.py @@ -567,6 +567,32 @@ "target": "arch/arm64/boot/dts/qcom/example.dtb" } ] + }), + + # Generic generated-header failure without an earlier target occurrence. + ('kbuild_025.log', + 'kbuild', + { + "errors": [ + { + "error_type": "kbuild.other", + "script": "/tmp/kci/linux/arch/x86/Makefile:269", + "target": "arch/x86/include/generated/asm/cpufeaturemasks.h" + } + ] + }), + + # Generic diagnostic containing the complete target path. + ('kbuild_026.log', + 'kbuild', + { + "errors": [ + { + "error_type": "kbuild.other", + "script": "Makefile:21", + "target": "/tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic" + } + ] }) ]) def test_kbuild(log_file, parser_id, expected): @@ -604,3 +630,28 @@ def test_kbuild_dtc_check_report(): "288.8-296.3\n" "ERROR: Input tree has errors, aborting (use -f to force output)\n" ) + + +def test_kbuild_generic_make_fallback_report(): + log_file = os.path.join(LOG_DIR, 'kbuild_025.log') + parsed_data = load_parser_and_parse_log( + log_file, 'kbuild', tests.setup.PARSER_DEFS_FILE + ) + + assert parsed_data['errors'][0]._report == ( + "00:13:13 make[5]: *** [/tmp/kci/linux/arch/x86/Makefile:269: " + "arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1\n" + ) + + +def test_kbuild_generic_preserves_diagnostic_line(): + log_file = os.path.join(LOG_DIR, 'kbuild_026.log') + parsed_data = load_parser_and_parse_log( + log_file, 'kbuild', tests.setup.PARSER_DEFS_FILE + ) + + assert parsed_data['errors'][0]._report == ( + "04:41:43 cp: cannot create regular file " + "'/tmp/kci/artifacts/build/kselftest/arm64/signal/" + "fake_sigreturn_bad_magic': File exists\n" + )