From fcdbc57ad135ad134f4f0cb9ea127a24098b7fdb Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 18 Aug 2026 09:26:08 +0800 Subject: refactor(ci-new): extract parallel cargo check runner Extract the duplicate parallel check logic from build-all and clippy-all tasks into a shared `run_parallel_checks` helper. Update renderers to set exit code instead of printing failure counts. --- mingling_ci/src/task.rs | 1 + mingling_ci/src/task/cmd_build.rs | 81 ++++++++--------------------------- mingling_ci/src/task/cmd_clippy.rs | 86 +++++++++----------------------------- mingling_ci/src/task/run.rs | 75 +++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 130 deletions(-) create mode 100644 mingling_ci/src/task/run.rs diff --git a/mingling_ci/src/task.rs b/mingling_ci/src/task.rs index 1d389b0..07ffb3f 100644 --- a/mingling_ci/src/task.rs +++ b/mingling_ci/src/task.rs @@ -1,2 +1,3 @@ pub(crate) mod cmd_build; pub(crate) mod cmd_clippy; +pub(crate) mod run; diff --git a/mingling_ci/src/task/cmd_build.rs b/mingling_ci/src/task/cmd_build.rs index a72ab0b..44be592 100644 --- a/mingling_ci/src/task/cmd_build.rs +++ b/mingling_ci/src/task/cmd_build.rs @@ -1,79 +1,29 @@ +use std::ffi::OsString; use std::path::Path; -use just_progress::progress::{self, ProgressInfo}; use mingling::{ Grouped, Routable, - macros::{buffer, command, r_println, renderer}, + macros::{buffer, command, renderer}, + res::ResExitCode, }; use crate::Next; -use crate::reporter::{self, ReportResult}; use crate::res::Manifests; +use crate::task::run::run_parallel_checks; #[command(node = "build-all")] pub async fn build_all(manifests: &Manifests) -> Next { - const TASK: &str = "Build-All"; - - reporter::set_task(TASK); - - let total = manifests.package_dirs.len(); - progress::update(TASK, 0.0, ProgressInfo::Info("Building")); - - // Run one `cargo build` per manifest in parallel. - let mut set = tokio::task::JoinSet::new(); - for (name, path) in &manifests.package_dirs { - let (name, path) = (name.clone(), path.clone()); - set.spawn(async move { (name, run_cargo_build(&path).await) }); - } - - // Collect all outcomes first, then dump the report files in one round. - let mut results: Vec<(String, ReportResult)> = Vec::new(); - let mut done = 0; - while let Some(joined) = set.join_next().await { - done += 1; - let Ok((name, (ok, output))) = joined else { - continue; - }; - // The count is small, so the `usize -> f32` cast cannot lose precision. - #[allow(clippy::cast_precision_loss)] - let overall = done as f32 / total as f32; - progress::update(TASK, overall, ProgressInfo::Info("Building")); - results.push(( - name, - if ok { - ReportResult::Ok - } else { - ReportResult::Error(output) - }, - )); - } - - let fail_count = results - .iter() - .filter(|(_, r)| matches!(r, ReportResult::Error(_))) - .count(); - for (name, result) in results { - reporter::export(&name, result); - } - + let fail_count = run_parallel_checks("Build-All", "Building", build_args, manifests).await; ResultBuildAll { fail_count }.to_chain() } -/// Runs `cargo build --manifest-path `, returning success and output. -async fn run_cargo_build(path: &Path) -> (bool, String) { - let output = tokio::process::Command::new("cargo") - .args(["build", "--manifest-path"]) - .arg(path) - .output() - .await; - match output { - Ok(output) => { - let mut log = String::from_utf8_lossy(&output.stdout).into_owned(); - log.push_str(&String::from_utf8_lossy(&output.stderr)); - (output.status.success(), log) - } - Err(e) => (false, format!("failed to run cargo: {e}")), - } +/// `cargo build --manifest-path ` +fn build_args(path: &Path) -> Vec { + vec![ + "build".into(), + "--manifest-path".into(), + path.as_os_str().to_os_string(), + ] } /// Number of packages that failed to build. @@ -82,7 +32,10 @@ pub struct ResultBuildAll { pub fail_count: usize, } +/// Silently sets a non-zero exit code when any build failed. #[renderer(buffer)] -pub fn render_build_all(r: ResultBuildAll) { - r_println!("Build-All: {} package(s) failed", r.fail_count); +pub fn render_build_all(r: ResultBuildAll, exit_code: &mut ResExitCode) { + if r.fail_count > 0 { + exit_code.exit_code = 1; + } } diff --git a/mingling_ci/src/task/cmd_clippy.rs b/mingling_ci/src/task/cmd_clippy.rs index 8ec4236..11227a2 100644 --- a/mingling_ci/src/task/cmd_clippy.rs +++ b/mingling_ci/src/task/cmd_clippy.rs @@ -1,81 +1,32 @@ +use std::ffi::OsString; use std::path::Path; -use just_progress::progress::{self, ProgressInfo}; use mingling::{ Grouped, Routable, - macros::{buffer, command, r_println, renderer}, + macros::{buffer, command, renderer}, + res::ResExitCode, }; use crate::Next; -use crate::reporter::{self, ReportResult}; use crate::res::Manifests; +use crate::task::run::run_parallel_checks; #[command(node = "clippy-all")] pub async fn clippy_all(manifests: &Manifests) -> Next { - const TASK: &str = "Clippy-All"; - - reporter::set_task(TASK); - - let total = manifests.package_dirs.len(); - progress::update(TASK, 0.0, ProgressInfo::Info("Clippy")); - - // Run one `cargo clippy` per manifest in parallel. - let mut set = tokio::task::JoinSet::new(); - for (name, path) in &manifests.package_dirs { - let (name, path) = (name.clone(), path.clone()); - set.spawn(async move { (name, run_cargo_clippy(&path).await) }); - } - - // Collect all outcomes first, then dump the report files in one round. - let mut results: Vec<(String, ReportResult)> = Vec::new(); - let mut done = 0; - while let Some(joined) = set.join_next().await { - done += 1; - let Ok((name, (ok, output))) = joined else { - continue; - }; - // The count is small, so the `usize -> f32` cast cannot lose precision. - #[allow(clippy::cast_precision_loss)] - let overall = done as f32 / total as f32; - progress::update(TASK, overall, ProgressInfo::Info("Clippy")); - results.push(( - name, - if ok { - ReportResult::Ok - } else { - ReportResult::Error(output) - }, - )); - } - - let fail_count = results - .iter() - .filter(|(_, r)| matches!(r, ReportResult::Error(_))) - .count(); - for (name, result) in results { - reporter::export(&name, result); - } - + let fail_count = run_parallel_checks("Clippy-All", "Clippy", clippy_args, manifests).await; ResultClippyAll { fail_count }.to_chain() } -/// Runs `cargo clippy --manifest-path -- -D warnings`, returning success -/// and output. -async fn run_cargo_clippy(path: &Path) -> (bool, String) { - let output = tokio::process::Command::new("cargo") - .args(["clippy", "--manifest-path"]) - .arg(path) - .args(["--", "-D", "warnings"]) - .output() - .await; - match output { - Ok(output) => { - let mut log = String::from_utf8_lossy(&output.stdout).into_owned(); - log.push_str(&String::from_utf8_lossy(&output.stderr)); - (output.status.success(), log) - } - Err(e) => (false, format!("failed to run cargo: {e}")), - } +/// `cargo clippy --manifest-path -- -D warnings` +fn clippy_args(path: &Path) -> Vec { + vec![ + "clippy".into(), + "--manifest-path".into(), + path.as_os_str().to_os_string(), + "--".into(), + "-D".into(), + "warnings".into(), + ] } /// Number of packages that failed clippy. @@ -84,7 +35,10 @@ pub struct ResultClippyAll { pub fail_count: usize, } +/// Silently sets a non-zero exit code when any clippy check failed. #[renderer(buffer)] -pub fn render_clippy_all(r: ResultClippyAll) { - r_println!("Clippy-All: {} package(s) failed", r.fail_count); +pub fn render_clippy_all(r: ResultClippyAll, exit_code: &mut ResExitCode) { + if r.fail_count > 0 { + exit_code.exit_code = 1; + } } diff --git a/mingling_ci/src/task/run.rs b/mingling_ci/src/task/run.rs new file mode 100644 index 0000000..1017677 --- /dev/null +++ b/mingling_ci/src/task/run.rs @@ -0,0 +1,75 @@ +use std::ffi::OsString; +use std::path::Path; + +use just_progress::progress::{self, ProgressInfo}; + +use crate::reporter::{self, ReportResult}; +use crate::res::Manifests; + +/// Runs one `cargo` subcommand per manifest in parallel, reporting each +/// outcome via `reporter` after the whole round finishes. +/// +/// Only output is the progress bar; returns the number of failing packages. +#[allow(clippy::cast_precision_loss)] // counts are small +pub(crate) async fn run_parallel_checks( + task: &str, + status: &'static str, + args_for: fn(&Path) -> Vec, + manifests: &Manifests, +) -> usize { + reporter::set_task(task); + let total = manifests.package_dirs.len(); + progress::update(task, 0.0, ProgressInfo::Info(status)); + + // Run one cargo invocation per manifest in parallel. + let mut set = tokio::task::JoinSet::new(); + for (name, path) in &manifests.package_dirs { + let (name, path) = (name.clone(), path.clone()); + let args = args_for(&path); + set.spawn(async move { (name, run_cargo(args).await) }); + } + + // Collect all outcomes first, then dump the report files in one round. + let mut results: Vec<(String, ReportResult)> = Vec::new(); + let mut done = 0; + while let Some(joined) = set.join_next().await { + done += 1; + let Ok((name, (ok, output))) = joined else { + continue; + }; + progress::update(task, done as f32 / total as f32, ProgressInfo::Info(status)); + results.push(( + name, + if ok { + ReportResult::Ok + } else { + ReportResult::Error(output) + }, + )); + } + + let fail_count = results + .iter() + .filter(|(_, r)| matches!(r, ReportResult::Error(_))) + .count(); + for (name, result) in results { + reporter::export(&name, result); + } + fail_count +} + +/// Runs a `cargo` subcommand, returning success and captured output. +async fn run_cargo(args: Vec) -> (bool, String) { + let output = tokio::process::Command::new("cargo") + .args(args) + .output() + .await; + match output { + Ok(output) => { + let mut log = String::from_utf8_lossy(&output.stdout).into_owned(); + log.push_str(&String::from_utf8_lossy(&output.stderr)); + (output.status.success(), log) + } + Err(e) => (false, format!("failed to run cargo: {e}")), + } +} -- cgit