From ff3293f123a8768fc3b24715b6404ff3a0ab63da Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 18 Aug 2026 09:37:18 +0800 Subject: refactor(ci-new): aggregate successful package reports Replace per-package `.ok` files with a single `ok` file listing successful packages per platform, flushed after all checks complete. --- mingling_ci/src/reporter.rs | 84 ++++++++++++++++++++++++++++--------- mingling_ci/src/res/collect_logs.rs | 38 ++++++++--------- mingling_ci/src/task/run.rs | 1 + 3 files changed, 83 insertions(+), 40 deletions(-) diff --git a/mingling_ci/src/reporter.rs b/mingling_ci/src/reporter.rs index f5cef60..1cd5b2b 100644 --- a/mingling_ci/src/reporter.rs +++ b/mingling_ci/src/reporter.rs @@ -4,9 +4,10 @@ //! so that the [`crate::cmd::collect_results`] command can assemble the final //! report. The task name is set once per CI phase via [`set_task`]. +use std::collections::HashMap; use std::fs; use std::path::Path; -use std::sync::Mutex; +use std::sync::{LazyLock, Mutex}; /// Root of the collected CI logs (relative to the repo root). pub const COLLECT_DIR: &str = "./.temp/reports/collect"; @@ -15,7 +16,7 @@ pub const COLLECT_DIR: &str = "./.temp/reports/collect"; pub const REPORT_PATH: &str = "./.temp/reports/result.md"; /// The platform a package check ran on. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum ReportPlatform { Windows, Linux, @@ -45,6 +46,10 @@ pub enum ReportResult { /// Current task name (e.g. `Build-All`); set via [`set_task`]. static CURRENT_TASK: Mutex> = Mutex::new(None); +/// Successful package names pending a [`flush`], grouped by platform. +static OK_BUFFER: LazyLock>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + /// Sets the task that subsequent [`export`] calls write under. /// /// # Panics @@ -54,13 +59,11 @@ pub fn set_task(task: &str) { *CURRENT_TASK.lock().unwrap() = Some(task.to_string()); } -/// Exports one package result to `collect/{task}/{platform}/{package}.{ok|err}`. -/// -/// The platform is inferred from the current build target using `#[cfg]` -/// attributes, so callers don't need to pass it explicitly. +/// Exports one package result to `collect/{task}/{platform}/`. /// -/// Writes `{package}.ok` on success and `{package}.err` (with the output) on -/// failure. Errors are reported to stderr and otherwise ignored. +/// Successes are buffered and written to the `ok` file by [`flush`]; failures +/// write `{package}.err` (with the output) immediately. Errors are reported to +/// stderr and otherwise ignored. /// /// # Panics /// @@ -80,16 +83,59 @@ fn current_platform() -> ReportPlatform { } } -/// Exports one package result to `collect/{task}/{platform}/{package}.{ok|err}` -/// for a specific platform. +/// Exports one package result for a specific platform. /// -/// Writes `{package}.ok` on success and `{package}.err` (with the output) on -/// failure. Errors are reported to stderr and otherwise ignored. +/// Successes are buffered and written to the `ok` file by [`flush`]; failures +/// write `{package}.err` (with the output) immediately. Errors are reported to +/// stderr and otherwise ignored. /// /// # Panics /// /// Panics if the internal task mutex is poisoned. pub fn export_on(package: &str, platform: ReportPlatform, result: ReportResult) { + match result { + ReportResult::Ok => OK_BUFFER + .lock() + .unwrap() + .entry(platform) + .or_default() + .push(package.to_string()), + ReportResult::Error(output) => write_err(package, platform, output), + } +} + +/// Writes buffered successes to `{task}/{platform}/ok`, one package per line. +/// +/// # Panics +/// +/// Panics if the internal task mutex is poisoned. +pub fn flush() { + let Some(task) = CURRENT_TASK.lock().unwrap().clone() else { + eprintln!("reporter: no current task; call reporter::set_task first"); + return; + }; + + let buffered = std::mem::take(&mut *OK_BUFFER.lock().unwrap()); + for (platform, packages) in buffered { + let dir = Path::new(COLLECT_DIR).join(&task).join(platform.dir_name()); + if let Err(e) = fs::create_dir_all(&dir) { + eprintln!("reporter: failed to create {}: {e}", dir.display()); + continue; + } + let content = if packages.is_empty() { + String::new() + } else { + packages.join("\n") + "\n" + }; + let path = dir.join("ok"); + if let Err(e) = fs::write(&path, content) { + eprintln!("reporter: failed to write {}: {e}", path.display()); + } + } +} + +/// Writes a failure entry to `{task}/{platform}/{package}.err`. +fn write_err(package: &str, platform: ReportPlatform, output: String) { let Some(task) = CURRENT_TASK.lock().unwrap().clone() else { eprintln!("reporter: no current task; call reporter::set_task first"); return; @@ -101,12 +147,8 @@ pub fn export_on(package: &str, platform: ReportPlatform, result: ReportResult) return; } - let (file_name, content) = match result { - ReportResult::Ok => (format!("{package}.ok"), String::new()), - ReportResult::Error(output) => (format!("{package}.err"), output), - }; - let path = dir.join(file_name); - if let Err(e) = fs::write(&path, content) { + let path = dir.join(format!("{package}.err")); + if let Err(e) = fs::write(&path, output) { eprintln!("reporter: failed to write {}: {e}", path.display()); } } @@ -119,13 +161,15 @@ mod tests { fn export_writes_ok_and_err_files() { set_task("reporter-test"); let task_root = Path::new(COLLECT_DIR).join("reporter-test"); - let dir = task_root.join("Linux"); + let dir = task_root.join(current_platform().dir_name()); fs::remove_dir_all(&task_root).ok(); export("pkg-a", ReportResult::Ok); export("pkg-b", ReportResult::Error("boom".to_string())); + flush(); - assert!(dir.join("pkg-a.ok").is_file()); + assert!(dir.join("ok").is_file()); + assert_eq!(fs::read_to_string(dir.join("ok")).unwrap(), "pkg-a\n"); assert!(dir.join("pkg-b.err").is_file()); assert_eq!(fs::read_to_string(dir.join("pkg-b.err")).unwrap(), "boom"); diff --git a/mingling_ci/src/res/collect_logs.rs b/mingling_ci/src/res/collect_logs.rs index b764b49..df0af5b 100644 --- a/mingling_ci/src/res/collect_logs.rs +++ b/mingling_ci/src/res/collect_logs.rs @@ -26,7 +26,8 @@ pub struct ResCollectLogs { } impl ResCollectLogs { - /// Reads `collect/{task}/{os}/{package}.{ok|err}` and the git info. + /// Reads `collect/{task}/{os}/` — the aggregate `ok` file (one package per + /// line) and per-package `{package}.err` files — plus the git info. #[must_use] pub fn read() -> Self { let mut logs = Self::default(); @@ -52,16 +53,25 @@ impl ResCollectLogs { }; for file in files.flatten() { let file_name = file.file_name().to_string_lossy().into_owned(); - if let Some((package, ok)) = parse_log_name(&file_name) { + if file_name == "ok" { + // Aggregate success file: one package name per line. + if let Ok(content) = std::fs::read_to_string(file.path()) { + for package in content.lines().filter(|l| !l.is_empty()) { + logs.statuses + .entry((task.clone(), package.to_string())) + .or_default() + .insert(os.clone(), true); + } + } + } else if let Some(package) = file_name.strip_suffix(".err") { + let package = package.to_string(); logs.statuses .entry((task.clone(), package.clone())) .or_default() - .insert(os.clone(), ok); - if !ok { - let err = std::fs::read_to_string(file.path()).unwrap_or_default(); - logs.err_outputs - .insert((task.clone(), os.clone(), package), strip_ansi(&err)); - } + .insert(os.clone(), false); + let err = std::fs::read_to_string(file.path()).unwrap_or_default(); + logs.err_outputs + .insert((task.clone(), os.clone(), package), strip_ansi(&err)); } } } @@ -78,18 +88,6 @@ pub fn report_setup(p: &mut Program) { p.with_resource(ResCollectLogs::read()); } -/// Parses a `{package}.{ok|err}` file name into `(package, ok)`. -fn parse_log_name(file_name: &str) -> Option<(String, bool)> { - file_name - .strip_suffix(".ok") - .map(|n| (n.to_string(), true)) - .or_else(|| { - file_name - .strip_suffix(".err") - .map(|n| (n.to_string(), false)) - }) -} - /// Strips ANSI escape sequences from `input`. /// /// Handles CSI (`ESC [ ...`), OSC (`ESC ] ...` terminated by BEL or `ESC \`) diff --git a/mingling_ci/src/task/run.rs b/mingling_ci/src/task/run.rs index db34277..eddc356 100644 --- a/mingling_ci/src/task/run.rs +++ b/mingling_ci/src/task/run.rs @@ -80,6 +80,7 @@ pub(crate) async fn run_parallel_checks( } pb.finish_and_clear(); + reporter::flush(); fail_count } -- cgit