From 8cf9489072a1de9b1673800d6de9748e7a24e4d6 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Tue, 29 Sep 2026 16:35:30 +0200 Subject: [PATCH] logfmt: teach the make classifier kbuild status tags and warnings Kernel builds abbreviate every invocation to an uppercase status tag (CC, LD, HOSTCC, MODPOST, ...) instead of printing the full command line, so the substring rules that catch classic make output never fired and the rolling pane went static for the whole kbuild stream. Recognize the common tags after leading whitespace and show those lines. Also surface warnings instead of hiding them: gcc's lowercase 'file.c:12:3: warning:' diagnostics and kbuild's line-leading 'WARNING:' banners (modpost, etc.) now map to Action::Warning. --- src/logfmt.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/src/logfmt.rs b/src/logfmt.rs index cd5da90..70f14ac 100644 --- a/src/logfmt.rs +++ b/src/logfmt.rs @@ -5,7 +5,7 @@ //! errors) and whether the line carries countable progress. Classifiers are //! pure with respect to the UI: they only return [`Action`]s. -use std::sync::OnceLock; +use std::sync::{LazyLock, OnceLock}; use crate::context::Stream; use regex::Regex; @@ -226,7 +226,8 @@ impl Classifier for QuiltClassifier { /// Classifier for make/cmake-based builds (`debian/rules build`, dh helpers) /// /// Detects `[ 42%]`-style progress markers, hides directory enter/leave -/// noise, and shows compile/link/dh lines. +/// noise, and shows compile/link/dh lines as well as kbuild status-tag +/// lines and warnings. #[derive(Default)] pub struct MakeClassifier {} @@ -242,6 +243,18 @@ impl Classifier for MakeClassifier { static PERCENT_RE: OnceLock = OnceLock::new(); let percent_re = PERCENT_RE.get_or_init(|| Regex::new(r"\[\s*(\d+)%\]").unwrap()); + // kbuild abbreviates each invocation to an uppercase status tag + // (` CC [M] drivers/x.o`, ` HOSTCC scripts/foo`) instead of + // printing the full command line, so the classic-make substring + // rules below never fire for kernel builds; this tag list is the + // kbuild counterpart of those substrings. + static KBUILD_TAG_RE: LazyLock = LazyLock::new(|| { + Regex::new( + r"^\s*(CC|HOSTCC|HOSTLD|HOSTAR|LD|AR|AS|OBJCOPY|STRIP|GEN|UPD|DESCEND|MODPOST|MODORDER|BTF|SHIPPED|INSTALL|INSTALLMOD|DEPMOD|SIGN|WRAP|SYSHDR|SYSTBL|KSYMS)\b", + ) + .unwrap() + }); + if let Some(caps) = percent_re.captures(line) { let pct: u64 = caps[1].parse().unwrap_or(0); return vec![ @@ -253,12 +266,6 @@ impl Classifier for MakeClassifier { ]; } - if line.contains("make[") - && (line.contains("Entering directory") || line.contains("Leaving directory")) - { - return vec![Action::Hidden]; - } - if line.contains("error:") || line.contains("Error ") || line.contains("*** [") @@ -267,6 +274,19 @@ impl Classifier for MakeClassifier { return vec![Action::Error(truncate(line))]; } + // gcc-style diagnostics (`file.c:12:3: warning: ...`) and kbuild's + // uppercase banners (`WARNING: modpost: ...`) must not be drowned + // out by the activity checks below. + if line.contains("warning:") || line.trim_start().starts_with("WARNING:") { + return vec![Action::Warning(truncate(line))]; + } + + if line.contains("make[") + && (line.contains("Entering directory") || line.contains("Leaving directory")) + { + return vec![Action::Hidden]; + } + if line.starts_with("dh_") || line.contains("gcc ") || line.contains("g++ ") @@ -274,6 +294,7 @@ impl Classifier for MakeClassifier { || line.contains("clang") || line.contains("ld ") || line.contains("ar ") + || KBUILD_TAG_RE.is_match(line) { return vec![Action::Shown(truncate(line))]; } @@ -517,6 +538,73 @@ mod tests { ); } + #[test] + fn test_make_shows_kbuild_status_tags() { + let mut c = MakeClassifier::new(); + assert_eq!( + feed_one(&mut c, " CC [M] drivers/net/foo.o"), + vec![Action::Shown(" CC [M] drivers/net/foo.o".to_string())] + ); + assert_eq!( + feed_one(&mut c, " LD [M] drivers/bar.ko"), + vec![Action::Shown(" LD [M] drivers/bar.ko".to_string())] + ); + assert_eq!( + feed_one(&mut c, " HOSTCC scripts/tool"), + vec![Action::Shown(" HOSTCC scripts/tool".to_string())] + ); + assert_eq!( + feed_one(&mut c, " MODPOST 1 modules"), + vec![Action::Shown(" MODPOST 1 modules".to_string())] + ); + // kbuild tag lines still roll the pane while make progress markers + // update the progress bar + assert_eq!( + feed_one(&mut c, "[ 45%] Building C object x.o"), + vec![ + Action::Shown("[ 45%] Building C object x.o".to_string()), + Action::Progress { + pos: 45, + total: 100 + } + ] + ); + // plain noise stays hidden + assert_eq!( + feed_one(&mut c, "test -x debian/scripts/foo"), + vec![Action::Hidden] + ); + } + + #[test] + fn test_make_shows_warnings_and_keeps_regressions() { + let mut c = MakeClassifier::new(); + assert_eq!( + feed_one(&mut c, "drivers/x.c:12:3: warning: unused variable 'y'"), + vec![Action::Warning( + "drivers/x.c:12:3: warning: unused variable 'y'".to_string() + )] + ); + assert_eq!( + feed_one(&mut c, "WARNING: modpost: missing MODULE_LICENSE"), + vec![Action::Warning( + "WARNING: modpost: missing MODULE_LICENSE".to_string() + )] + ); + // configure-stage probe lines embed the toolchain name and must + // stay visible + assert_eq!( + feed_one( + &mut c, + "checking whether riscv64-linux-gnu-gcc understands -c and -o together... (cached) yes" + ), + vec![Action::Shown( + "checking whether riscv64-linux-gnu-gcc understands -c and -o together... (cached) yes" + .to_string() + )] + ); + } + #[test] fn test_mmdebstrap_prefixes() { let mut c = MmdebstrapClassifier::new();