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();