aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/task
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-18 10:01:53 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-18 10:01:53 +0800
commit570b2bc1710c0ab8a68ad69a6121144e4f5e3aca (patch)
tree76995f8437ea0175724a1669a8f821736a20de60 /mingling_ci/src/task
parenta7adc172b3cc58e0555b1cddb45d6301c733be81 (diff)
feat(ci-new): add test-all task with per-crate config support
Add a new `test-all` command that runs tests across all crates with optional per-crate command overrides via `mingling-ci.toml` files.
Diffstat (limited to 'mingling_ci/src/task')
-rw-r--r--mingling_ci/src/task/cmd_build.rs8
-rw-r--r--mingling_ci/src/task/cmd_clippy.rs8
-rw-r--r--mingling_ci/src/task/cmd_test.rs54
-rw-r--r--mingling_ci/src/task/run.rs38
4 files changed, 90 insertions, 18 deletions
diff --git a/mingling_ci/src/task/cmd_build.rs b/mingling_ci/src/task/cmd_build.rs
index 44be592..a74c323 100644
--- a/mingling_ci/src/task/cmd_build.rs
+++ b/mingling_ci/src/task/cmd_build.rs
@@ -13,13 +13,19 @@ use crate::task::run::run_parallel_checks;
#[command(node = "build-all")]
pub async fn build_all(manifests: &Manifests) -> Next {
- let fail_count = run_parallel_checks("Build-All", "Building", build_args, manifests).await;
+ let tasks = manifests
+ .package_dirs
+ .iter()
+ .map(|(name, path)| (name.clone(), build_args(path)))
+ .collect();
+ let fail_count = run_parallel_checks("Build-All", "Building", tasks).await;
ResultBuildAll { fail_count }.to_chain()
}
/// `cargo build --manifest-path <path>`
fn build_args(path: &Path) -> Vec<OsString> {
vec![
+ "cargo".into(),
"build".into(),
"--manifest-path".into(),
path.as_os_str().to_os_string(),
diff --git a/mingling_ci/src/task/cmd_clippy.rs b/mingling_ci/src/task/cmd_clippy.rs
index 11227a2..0a4282b 100644
--- a/mingling_ci/src/task/cmd_clippy.rs
+++ b/mingling_ci/src/task/cmd_clippy.rs
@@ -13,13 +13,19 @@ use crate::task::run::run_parallel_checks;
#[command(node = "clippy-all")]
pub async fn clippy_all(manifests: &Manifests) -> Next {
- let fail_count = run_parallel_checks("Clippy-All", "Clippy", clippy_args, manifests).await;
+ let tasks = manifests
+ .package_dirs
+ .iter()
+ .map(|(name, path)| (name.clone(), clippy_args(path)))
+ .collect();
+ let fail_count = run_parallel_checks("Clippy-All", "Clippy", tasks).await;
ResultClippyAll { fail_count }.to_chain()
}
/// `cargo clippy --manifest-path <path> -- -D warnings`
fn clippy_args(path: &Path) -> Vec<OsString> {
vec![
+ "cargo".into(),
"clippy".into(),
"--manifest-path".into(),
path.as_os_str().to_os_string(),
diff --git a/mingling_ci/src/task/cmd_test.rs b/mingling_ci/src/task/cmd_test.rs
new file mode 100644
index 0000000..3ed193c
--- /dev/null
+++ b/mingling_ci/src/task/cmd_test.rs
@@ -0,0 +1,54 @@
+use std::ffi::OsString;
+use std::path::Path;
+
+use mingling::{
+ Grouped, Routable,
+ macros::{buffer, command, renderer},
+ res::ResExitCode,
+};
+
+use crate::Next;
+use crate::res::{Manifests, ResCrateConfig};
+use crate::task::run::run_parallel_checks;
+
+#[command(node = "test-all")]
+pub async fn test_all(manifests: &Manifests, config: &ResCrateConfig) -> Next {
+ let tasks = manifests
+ .package_dirs
+ .iter()
+ .map(|(name, path)| {
+ let args = config.test_command(name).map_or_else(
+ || test_args(path),
+ |cmd| cmd.iter().map(|s| OsString::from(s.as_str())).collect(),
+ );
+ (name.clone(), args)
+ })
+ .collect();
+ let fail_count = run_parallel_checks("Test-All", "Testing", tasks).await;
+ ResultTestAll { fail_count }.to_chain()
+}
+
+/// Default: `cargo test --manifest-path <path>` (crates without a
+/// `mingling-ci.toml` override).
+fn test_args(path: &Path) -> Vec<OsString> {
+ vec![
+ "cargo".into(),
+ "test".into(),
+ "--manifest-path".into(),
+ path.as_os_str().to_os_string(),
+ ]
+}
+
+/// Number of packages that failed tests.
+#[derive(Grouped)]
+pub struct ResultTestAll {
+ pub fail_count: usize,
+}
+
+/// Silently sets a non-zero exit code when any test failed.
+#[renderer(buffer)]
+pub fn render_test_all(r: ResultTestAll, 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
index eddc356..d00334d 100644
--- a/mingling_ci/src/task/run.rs
+++ b/mingling_ci/src/task/run.rs
@@ -1,11 +1,9 @@
use std::ffi::OsString;
-use std::path::Path;
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use crate::reporter::{self, ReportResult};
-use crate::res::Manifests;
/// Outcome of a `cargo` subcommand.
struct CargoResult {
@@ -14,20 +12,19 @@ struct CargoResult {
output: String,
}
-/// Runs one `cargo` subcommand per manifest in parallel.
+/// Runs the given cargo task list in parallel.
///
-/// Progress and failures go to stderr: a failing package prints its output
-/// immediately and writes its report entry at the same time. Returns the
-/// number of failing packages.
+/// Each task is a `(name, args)` pair; progress and failures go to stderr: a
+/// failing task prints its output immediately and writes its report entry at
+/// the same time. Returns the number of failing tasks.
pub(crate) async fn run_parallel_checks(
task: &str,
phase: &str,
- args_for: fn(&Path) -> Vec<OsString>,
- manifests: &Manifests,
+ tasks: Vec<(String, Vec<OsString>)>,
) -> usize {
reporter::set_task(task);
- let n = manifests.package_dirs.len();
+ let n = tasks.len();
let pb = ProgressBar::new(n as u64);
let padding = " ".repeat(12usize.saturating_sub(phase.len()));
let styled_prefix = format!("{}{}", padding, phase.bold().bright_cyan());
@@ -40,11 +37,9 @@ pub(crate) async fn run_parallel_checks(
.progress_chars("=> "),
);
- // Run one cargo invocation per manifest in parallel.
+ // Run each task 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);
+ for (name, args) in tasks {
set.spawn(async move { (name, run_cargo(args).await) });
}
@@ -85,11 +80,22 @@ pub(crate) async fn run_parallel_checks(
}
/// Runs a `cargo` subcommand, capturing its output.
-async fn run_cargo(args: Vec<OsString>) -> CargoResult {
- let output = tokio::process::Command::new("cargo")
- .args(args)
+/// Runs a cargo subcommand (`argv[0]` is the program), capturing its output.
+async fn run_cargo(argv: Vec<OsString>) -> CargoResult {
+ let mut argv = argv.into_iter();
+ let Some(program) = argv.next() else {
+ return CargoResult {
+ ok: false,
+ exit_code: None,
+ output: "empty command".to_string(),
+ };
+ };
+
+ let output = tokio::process::Command::new(program)
+ .args(argv)
.output()
.await;
+
match output {
Ok(output) => {
let mut log = String::from_utf8_lossy(&output.stdout).into_owned();