logfmt: collapse the rootless chown EPERM flood into one digest line

A rules chown failing for the unprivileged build user is expected
and harmless (see the revert of the build-step fakeroot wrap), but a
sysroot tree can print thousands of 'chown: changing ownership of
...: Operation not permitted' lines. MakeClassifier now shows one
warning digest for the flood and hides the rest; every other line,
make errors included, keeps its classification. The full tee in the
record's build.log is untouched.
This commit is contained in:
2026-10-02 18:42:36 +02:00
parent 6050da7dbb
commit 4535255844
+73 -2
View File
@@ -227,9 +227,17 @@ impl Classifier for QuiltClassifier {
///
/// Detects `[ 42%]`-style progress markers, hides directory enter/leave
/// noise, and shows compile/link/dh lines as well as kbuild status-tag
/// lines and warnings.
/// lines and warnings. Collapses the per-file `chown: ... Operation not
/// permitted` flood of a rootless build into one digest line: a rules
/// chown failing for the unprivileged build user is expected and harmless
/// (the .deb is assembled in the binary step's fakeroot session, which
/// records root ownership anyway), but a sysroot can emit thousands of
/// lines.
#[derive(Default)]
pub struct MakeClassifier {}
pub struct MakeClassifier {
/// How many chown-EPERM lines were collapsed so far.
suppressed_chown: usize,
}
impl MakeClassifier {
/// Create a new classifier
@@ -255,6 +263,11 @@ impl Classifier for MakeClassifier {
.unwrap()
});
static CHOWN_EPERM_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^chown: (changing ownership|cannot read directory|cannot operate on)")
.unwrap()
});
if let Some(caps) = percent_re.captures(line) {
let pct: u64 = caps[1].parse().unwrap_or(0);
return vec![
@@ -266,6 +279,18 @@ impl Classifier for MakeClassifier {
];
}
if CHOWN_EPERM_RE.is_match(line) {
self.suppressed_chown += 1;
return if self.suppressed_chown == 1 {
vec![Action::Warning(truncate(
"chown: ownership changes skipped by the unprivileged build user \
(harmless for the .deb; further messages collapsed)",
))]
} else {
vec![Action::Hidden]
};
}
if line.contains("error:")
|| line.contains("Error ")
|| line.contains("*** [")
@@ -538,6 +563,52 @@ mod tests {
);
}
/// A rootless build's per-file chown failures collapse into one
/// digest warning; every other line keeps its normal classification.
#[test]
fn test_make_collapses_chown_eperm_flood() {
let mut c = MakeClassifier::new();
let first = "chown: changing ownership of \
'/tmp/pkh-build/k3/demos/sysroot/usr/share/zoneinfo/Asia/Sao_Paulo': \
Operation not permitted";
assert_eq!(
feed_one(&mut c, first),
vec![Action::Warning(
"chown: ownership changes skipped by the unprivileged build user \
(harmless for the .deb; further messages collapsed)"
.to_string()
)]
);
assert_eq!(
feed_one(
&mut c,
"chown: changing ownership of \
'/tmp/pkh-build/k3/demos/sysroot/usr/share/zoneinfo/Asia/Tokyo': \
Operation not permitted"
),
vec![Action::Hidden]
);
assert_eq!(
feed_one(
&mut c,
"chown: cannot read directory '/tmp/pkh-build/k3/demos': Permission denied"
),
vec![Action::Hidden]
);
// A non-chown line keeps its own classification after the flood.
assert_eq!(
feed_one(&mut c, " CC [M] drivers/net/foo.o"),
vec![Action::Shown(" CC [M] drivers/net/foo.o".to_string())]
);
// The digest must not swallow real make errors.
assert_eq!(
feed_one(&mut c, "make[1]: *** [Makefile:531: all] Error 2"),
vec![Action::Error(
"make[1]: *** [Makefile:531: all] Error 2".to_string()
)]
);
}
#[test]
fn test_make_shows_kbuild_status_tags() {
let mut c = MakeClassifier::new();