From 73fefaf874c9596c9380aab455a56c87d113450b Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko Date: Sat, 18 Jul 2026 13:55:07 +0300 Subject: [PATCH 1/2] kbuild: parse device tree compiler errors Recognize DTC diagnostics before .dtb Make failures so reports include the real source location and fatal excerpt. Use the underlying diagnostic for signatures instead of whichever parallel DTB target fails first. Link: https://lore.kernel.org/all/178426434363.11836.10489080558310367907@kernelci.org/ --- logspec/errors/kbuild.py | 77 ++++++++++++++++++++++++++++++-- tests/logs/index.txt | 4 ++ tests/logs/kbuild/kbuild_020.log | 7 +++ tests/test_kbuild.py | 36 +++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 tests/logs/kbuild/kbuild_020.log diff --git a/logspec/errors/kbuild.py b/logspec/errors/kbuild.py index b6bd5d9..57ed524 100644 --- a/logspec/errors/kbuild.py +++ b/logspec/errors/kbuild.py @@ -255,6 +255,64 @@ def _parse(self, text): return parse_end_pos +class KbuildDtcError(Error): + """Models an error emitted by the Device Tree Compiler (DTC).""" + + _diagnostic_re = re.compile( + rf'^{TIMESTAMP}(?PLexical error|Error): ' + r'(?P.*?):' + r'(?P\d+\.\d+(?:-\d+(?:\.\d+)?)?) ' + r'(?P.*)$', + flags=re.MULTILINE, + ) + + def __init__(self, script=None, target=None): + super().__init__() + self.script = script + self.target = target + self.src_file = "" + self.location = "" + self._signature_fields.extend([ + 'src_file', + 'location', + ]) + + @classmethod + def has_diagnostic(cls, text): + """Return whether *text* contains a structured DTC error.""" + return cls._diagnostic_re.search(text) is not None + + def _parse(self, text): + """Extract the last DTC diagnostic preceding the Make failure.""" + matches = list(self._diagnostic_re.finditer(text)) + if not matches: + return 0 + + match = matches[-1] + kind = match.group('kind') + self.error_type = ( + "kbuild.dtc.lexical_error" + if kind == "Lexical error" + else "kbuild.dtc.error" + ) + self.src_file = match.group('src_file') + self.location = match.group('location') + self.error_summary = f"{kind}: {match.group('message')}" + + # Include the primary diagnostic and a following DTC fatal line, but + # avoid unrelated parallel-build output. + report_end = match.end() + following_text = text[report_end:] + fatal_match = re.match( + rf'\n{TIMESTAMP}FATAL ERROR: .*', + following_text, + ) + if fatal_match: + report_end += fatal_match.end() + self._report = text[match.start():report_end] + "\n" + return report_end + + class KbuildProcessError(Error): """Models the information extracted from a kbuild error caused by a script, configuration or other runtime error. @@ -458,6 +516,14 @@ def _is_kbuild_target(target): return False +def _is_dtc_target(script, target): + """Return whether a Make failure belongs to a DTC build target.""" + return ( + target.endswith(('.dtb', '.dtbo')) + or os.path.basename(script.split(':', maxsplit=1)[0]) == 'Makefile.dtbs' + ) + + def _find_script_target(error_str, text): match = re.search(r'\[(?P