aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-18 09:37:18 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-18 09:37:18 +0800
commitff3293f123a8768fc3b24715b6404ff3a0ab63da (patch)
tree77a2a634c6e6e1d93ae8824cdeb58ad86f2990e2
parent3b56d50c16cf86429de9bf82b717ad4c13d7f968 (diff)
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.
-rw-r--r--mingling_ci/src/reporter.rs84
-rw-r--r--mingling_ci/src/res/collect_logs.rs38
-rw-r--r--mingling_ci/src/task/run.rs1
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<Option<String>> = Mutex::new(None);
+/// Successful package names pending a [`flush`], grouped by platform.
+static OK_BUFFER: LazyLock<Mutex<HashMap<ReportPlatform, Vec<String>>>> =
+ 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<ThisProgram>) {
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
}