aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/task
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-19 05:54:00 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-19 06:15:20 +0800
commitec9edc294fd5e7e29977fc7b0e6fb953422bc0e2 (patch)
treebbf89ef4457b8de8a8a9bca5668045d612d27572 /mingling_ci/src/task
parent06dfc27194c11e1d1033c292c759a1c5d82e780b (diff)
chore: reorganize dev tools into dev/ directory and consolidate configsHEADmain
Move CI, dev tools, and configs from root-level scattered locations into a unified `dev/` directory structure. Update all references across build scripts, documentation, and editor configurations. Also consolidate editor config generation into build.rs for automated synchronization of rust-analyzer settings across VS Code and Zed.
Diffstat (limited to 'mingling_ci/src/task')
-rw-r--r--mingling_ci/src/task/cmd_build_check.rs47
-rw-r--r--mingling_ci/src/task/cmd_clippy_check.rs50
-rw-r--r--mingling_ci/src/task/cmd_docs_check.rs44
-rw-r--r--mingling_ci/src/task/cmd_example_check.rs69
-rw-r--r--mingling_ci/src/task/cmd_markdown_check.rs192
-rw-r--r--mingling_ci/src/task/cmd_markdown_compare.rs221
-rw-r--r--mingling_ci/src/task/cmd_test.rs54
-rw-r--r--mingling_ci/src/task/run.rs114
8 files changed, 0 insertions, 791 deletions
diff --git a/mingling_ci/src/task/cmd_build_check.rs b/mingling_ci/src/task/cmd_build_check.rs
deleted file mode 100644
index f67fe2e..0000000
--- a/mingling_ci/src/task/cmd_build_check.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-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;
-use crate::task::run::{location, run_parallel_checks};
-
-#[command(node = "build-check")]
-pub async fn build_check(manifests: &Manifests) -> Next {
- let tasks = manifests
- .package_dirs
- .iter()
- .map(|(name, path)| (name.clone(), location(path), build_args(path)))
- .collect();
- let fail_count = run_parallel_checks("Build-Check", "Building", tasks).await;
- ResultBuildCheck { 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(),
- ]
-}
-
-/// Number of packages that failed to build.
-#[derive(Grouped)]
-pub struct ResultBuildCheck {
- pub fail_count: usize,
-}
-
-/// Silently sets a non-zero exit code when any build failed.
-#[renderer(buffer)]
-pub fn render_build_check(r: ResultBuildCheck, exit_code: &mut ResExitCode) {
- if r.fail_count > 0 {
- exit_code.exit_code = 1;
- }
-}
diff --git a/mingling_ci/src/task/cmd_clippy_check.rs b/mingling_ci/src/task/cmd_clippy_check.rs
deleted file mode 100644
index a0dd46e..0000000
--- a/mingling_ci/src/task/cmd_clippy_check.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-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;
-use crate::task::run::{location, run_parallel_checks};
-
-#[command(node = "clippy-check")]
-pub async fn clippy_check(manifests: &Manifests) -> Next {
- let tasks = manifests
- .package_dirs
- .iter()
- .map(|(name, path)| (name.clone(), location(path), clippy_args(path)))
- .collect();
- let fail_count = run_parallel_checks("Clippy-Check", "Clippy", tasks).await;
- ResultClippyCheck { 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(),
- "--".into(),
- "-D".into(),
- "warnings".into(),
- ]
-}
-
-/// Number of packages that failed clippy.
-#[derive(Grouped)]
-pub struct ResultClippyCheck {
- pub fail_count: usize,
-}
-
-/// Silently sets a non-zero exit code when any clippy check failed.
-#[renderer(buffer)]
-pub fn render_clippy_check(r: ResultClippyCheck, exit_code: &mut ResExitCode) {
- if r.fail_count > 0 {
- exit_code.exit_code = 1;
- }
-}
diff --git a/mingling_ci/src/task/cmd_docs_check.rs b/mingling_ci/src/task/cmd_docs_check.rs
deleted file mode 100644
index 3a77d4d..0000000
--- a/mingling_ci/src/task/cmd_docs_check.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-use std::ffi::OsString;
-
-use mingling::{
- Grouped, Routable,
- macros::{buffer, command, renderer},
- res::ResExitCode,
-};
-
-use crate::Next;
-use crate::res::ResFeatureList;
-use crate::task::run::run_parallel_checks;
-
-#[command(node = "docs-check")]
-pub async fn docs_check(features: &ResFeatureList) -> Next {
- let args = vec![
- OsString::from("cargo"),
- OsString::from("rustdoc"),
- OsString::from("--features"),
- OsString::from(features.list.join(",")),
- OsString::from("-p"),
- OsString::from("mingling"),
- OsString::from("--"),
- OsString::from("-D"),
- OsString::from("warnings"),
- ];
- let tasks = vec![("mingling".to_string(), "./mingling".to_string(), args)];
- let fail_count = run_parallel_checks("Docs-Check", "Docs", tasks).await;
-
- ResultDocsCheck { fail_count }.to_chain()
-}
-
-/// Number of failed doc builds (0 or 1).
-#[derive(Grouped)]
-pub struct ResultDocsCheck {
- pub fail_count: usize,
-}
-
-/// Silently sets a non-zero exit code when the doc build failed.
-#[renderer(buffer)]
-pub fn render_docs_check(r: ResultDocsCheck, exit_code: &mut ResExitCode) {
- if r.fail_count > 0 {
- exit_code.exit_code = 1;
- }
-}
diff --git a/mingling_ci/src/task/cmd_example_check.rs b/mingling_ci/src/task/cmd_example_check.rs
deleted file mode 100644
index 1b9f440..0000000
--- a/mingling_ci/src/task/cmd_example_check.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use colored::Colorize;
-use mingling::{
- Grouped, Routable,
- macros::{buffer, command, renderer},
- res::ResExitCode,
-};
-
-use crate::Next;
-use crate::examples::{check_example, load_test_configs};
-use crate::progress::task_progress_bar;
-use crate::reporter::{self, ReportResult};
-
-#[command(node = "example-check")]
-pub async fn example_check() -> Next {
- reporter::set_task("Example-Check");
-
- let configs = load_test_configs();
- let total = configs.len();
- let pb = task_progress_bar(total, "Testing");
- pb.set_message("examples");
-
- // One blocking task per example: build + run its test cases.
- let mut handles = Vec::new();
- for example in configs {
- handles.push(tokio::task::spawn_blocking(move || check_example(example)));
- }
-
- let mut fail_count = 0;
- for handle in handles {
- let Ok(outcome) = handle.await else {
- continue;
- };
- pb.set_message(outcome.name.clone());
- pb.inc(1);
-
- if outcome.ok {
- reporter::export(&outcome.name, &outcome.location, ReportResult::Ok);
- } else {
- fail_count += 1;
- // Plain stderr: `pb.println` is swallowed on non-TTY (CI).
- eprintln!(" {} {}", "failed".bright_red(), outcome.name);
- eprintln!(" {}", outcome.output);
- reporter::export(
- &outcome.name,
- &outcome.location,
- ReportResult::Error(outcome.output),
- );
- }
- }
-
- pb.finish_and_clear();
- reporter::flush();
-
- ResultExampleCheck { fail_count }.to_chain()
-}
-
-/// Number of examples that failed to build or pass their tests.
-#[derive(Grouped)]
-pub struct ResultExampleCheck {
- pub fail_count: usize,
-}
-
-/// Silently sets a non-zero exit code when any example failed.
-#[renderer(buffer)]
-pub fn render_example_check(r: ResultExampleCheck, exit_code: &mut ResExitCode) {
- if r.fail_count > 0 {
- exit_code.exit_code = 1;
- }
-}
diff --git a/mingling_ci/src/task/cmd_markdown_check.rs b/mingling_ci/src/task/cmd_markdown_check.rs
deleted file mode 100644
index 2408636..0000000
--- a/mingling_ci/src/task/cmd_markdown_check.rs
+++ /dev/null
@@ -1,192 +0,0 @@
-use std::collections::HashMap;
-use std::path::{Path, PathBuf};
-
-use just_fmt::snake_case;
-use mingling::{
- Grouped, RenderResult, Routable,
- macros::{buffer, command, renderer},
- res::ResExitCode,
-};
-
-use crate::Next;
-use crate::markdown::project::parse_markdown;
-use crate::markdown::test::{MarkdownBlockOutcome, try_test_markdown_project};
-use crate::reporter::{self, ReportResult};
-use crate::res::{CargoError, MessagePrinter};
-
-const VERIFIED_DOCS: &str = ".config/verified-docs.toml";
-
-#[command(node = "markdown-check")]
-pub async fn markdown_check(args: Vec<String>) -> Next {
- let Some(path_str) = args.first() else {
- return ErrorMarkdownArgs("missing <path> argument".to_string()).to_chain();
- };
- let path =
- std::env::current_dir().map_or_else(|_| PathBuf::from(path_str), |cwd| cwd.join(path_str));
- if !path.is_file() {
- return ErrorMarkdownArgs(format!("{} is not a file", path.display())).to_chain();
- }
- let Ok(content) = std::fs::read_to_string(&path) else {
- return ErrorMarkdownArgs(format!("failed to read {}", path.display())).to_chain();
- };
-
- let location = path.to_string_lossy().into_owned();
- let item = format!("doc-{}", snake_case!(&stem_of(&path)));
- reporter::set_task("Markdown-Check");
-
- let projects = parse_markdown(&content, &location);
- let outcomes = try_test_markdown_project(projects).await;
- let file_info = HashMap::from([(location.clone(), (item, location))]);
- let fail_count = report_files(&outcomes, &file_info);
- reporter::flush();
-
- ResultMarkdownCheck { fail_count }.to_chain()
-}
-
-#[command(node = "markdown-check-all")]
-pub async fn markdown_check_all() -> Next {
- let Some(files) = verified_md_files() else {
- return ErrorMarkdownConfig.to_chain();
- };
- reporter::set_task("Markdown-Check-All");
-
- // Collect all projects; remember each file's report identity
- // (`{key}-{snake_case(file_stem)}` -> location).
- let mut projects = Vec::new();
- let mut file_info: HashMap<String, (String, String)> = HashMap::new();
- for (label, path) in files {
- let Ok(content) = std::fs::read_to_string(&path) else {
- continue;
- };
- let file_name = path.file_name().unwrap().to_string_lossy();
- let source_file = format!("{label}/{file_name}");
- let item = format!("{label}-{}", snake_case!(&stem_of(&path)));
- let location = path.to_string_lossy().into_owned();
- file_info.insert(source_file.clone(), (item, location));
- projects.extend(parse_markdown(&content, &source_file));
- }
-
- let outcomes = try_test_markdown_project(projects).await;
- let fail_count = report_files(&outcomes, &file_info);
- reporter::flush();
-
- ResultMarkdownCheck { fail_count }.to_chain()
-}
-
-/// The file name without extension, e.g. `README.md` → `README`.
-pub(crate) fn stem_of(path: &Path) -> String {
- path.file_stem()
- .unwrap_or_default()
- .to_string_lossy()
- .into_owned()
-}
-
-/// Exports one report entry per source file: `ok` when every block passed,
-/// otherwise an error carrying the failed blocks' details.
-fn report_files(
- outcomes: &[MarkdownBlockOutcome],
- file_info: &HashMap<String, (String, String)>,
-) -> usize {
- let mut by_file: HashMap<&str, (bool, Vec<String>)> = HashMap::new();
- for outcome in outcomes {
- let (ok, outputs) = by_file
- .entry(outcome.source_file.as_str())
- .or_insert((true, Vec::new()));
- if !outcome.ok {
- *ok = false;
- outputs.push(format!(
- "{}:{}:\n{}",
- outcome.source_file, outcome.line, outcome.output
- ));
- }
- }
-
- let mut fail_count = 0;
- for (source_file, (ok, outputs)) in by_file {
- let Some((item, location)) = file_info.get(source_file) else {
- continue;
- };
- if ok {
- reporter::export(item, location, ReportResult::Ok);
- } else {
- fail_count += outputs.len();
- reporter::export(item, location, ReportResult::Error(outputs.join("\n\n")));
- }
- }
- fail_count
-}
-
-/// Reads `verified-docs.toml` and collects all `.md` files: single files,
-/// directories, or `**` globs (walked from the base directory).
-fn verified_md_files() -> Option<Vec<(String, PathBuf)>> {
- let content = std::fs::read_to_string(VERIFIED_DOCS).ok()?;
- let table: toml::Table = content.parse().ok()?;
-
- let mut files: Vec<(String, PathBuf)> = Vec::new();
- for (label, value) in table.get("verified")?.as_table()? {
- let value_str = value.as_str()?;
- let candidate = PathBuf::from(value_str);
- if candidate.is_dir() {
- collect_md_files(&candidate, &mut files, label);
- } else if candidate.is_file() {
- files.push((label.clone(), candidate));
- } else if candidate.extension().is_none() {
- // Glob like "docs/pages/**": walk the base directory.
- let base = PathBuf::from(value_str.trim_end_matches("/**").trim_end_matches('*'));
- if base.is_dir() {
- collect_md_files(&base, &mut files, label);
- }
- }
- }
-
- files.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
- Some(files)
-}
-
-/// Recursively collects all `.md` files under a directory.
-fn collect_md_files(dir: &Path, files: &mut Vec<(String, PathBuf)>, label: &str) {
- if let Ok(entries) = std::fs::read_dir(dir) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.is_dir() {
- collect_md_files(&path, files, label);
- } else if path.extension().is_some_and(|ext| ext == "md") {
- files.push((label.to_string(), path));
- }
- }
- }
-}
-
-/// Number of code blocks that failed to build.
-#[derive(Grouped)]
-pub struct ResultMarkdownCheck {
- pub fail_count: usize,
-}
-
-#[derive(Grouped, Default)]
-pub struct ErrorMarkdownArgs(pub String);
-
-#[derive(Grouped, Default)]
-pub struct ErrorMarkdownConfig;
-
-/// Silently sets a non-zero exit code when any block failed.
-#[renderer(buffer)]
-pub fn render_markdown_check(r: ResultMarkdownCheck, exit_code: &mut ResExitCode) {
- if r.fail_count > 0 {
- exit_code.exit_code = 1;
- }
-}
-
-#[renderer]
-pub fn render_error_markdown_args(e: ErrorMarkdownArgs, error: &CargoError) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![e.0]);
- render_result
-}
-
-#[renderer]
-pub fn render_error_markdown_config(_: ErrorMarkdownConfig, error: &CargoError) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![format!("failed to read {VERIFIED_DOCS}")]);
- render_result
-}
diff --git a/mingling_ci/src/task/cmd_markdown_compare.rs b/mingling_ci/src/task/cmd_markdown_compare.rs
deleted file mode 100644
index b014f1e..0000000
--- a/mingling_ci/src/task/cmd_markdown_compare.rs
+++ /dev/null
@@ -1,221 +0,0 @@
-use std::collections::BTreeSet;
-use std::path::{Path, PathBuf};
-
-use colored::Colorize;
-use just_fmt::snake_case;
-use mingling::{
- Grouped, Routable,
- macros::{buffer, command, renderer},
- res::ResExitCode,
-};
-
-use crate::Next;
-use crate::markdown::compare::{collect_md_files, compare_signature};
-use crate::reporter::{self, ReportResult};
-use crate::task::cmd_markdown_check::{ErrorMarkdownArgs, ErrorMarkdownConfig, stem_of};
-
-const DOCS_DIR: &str = "./docs";
-const LANG_CONFIG: &str = ".config/docs-lang.txt";
-
-/// One file-pair outcome of a structure comparison.
-struct CompareOutcome {
- item: String,
- location: String,
- ok: bool,
- output: String,
-}
-
-#[command(node = "markdown-compare")]
-// `#[command]` rewrites an owned first param into the entry type, so the args
-// must be passed by value even though the body only reads them.
-#[allow(clippy::needless_pass_by_value)]
-pub fn markdown_compare(args: Vec<String>) -> Next {
- let [ref_arg, trans_arg] = args.as_slice() else {
- return ErrorMarkdownArgs("missing <reference> and <translation> arguments".to_string())
- .to_chain();
- };
- let ref_path = cwd().join(ref_arg);
- let trans_path = cwd().join(trans_arg);
-
- reporter::set_task("Markdown-Compare");
- let outcomes = if ref_path.is_dir() && trans_path.is_dir() {
- compare_dirs(&ref_path, &trans_path, "doc")
- } else if ref_path.is_file() && trans_path.is_file() {
- compare_files(&ref_path, &trans_path, "doc")
- } else {
- return ErrorMarkdownArgs(
- "both arguments must be files or both must be directories".to_string(),
- )
- .to_chain();
- };
- let fail_count = export_outcomes(&outcomes);
- reporter::flush();
-
- ResultMarkdownCompare { fail_count }.to_chain()
-}
-
-#[command(node = "markdown-compare-all")]
-pub fn markdown_compare_all() -> Next {
- let Some(langs) = lang_config() else {
- return ErrorMarkdownConfig.to_chain();
- };
- let Some(reference) = langs.first() else {
- return ErrorMarkdownConfig.to_chain();
- };
- let ref_dir = PathBuf::from(DOCS_DIR).join(reference);
- if !ref_dir.is_dir() {
- return ErrorMarkdownArgs(format!(
- "reference docs directory `{}` does not exist",
- ref_dir.display()
- ))
- .to_chain();
- }
-
- reporter::set_task("Markdown-Compare-All");
- let mut fail_count = 0;
- for lang in &langs[1..] {
- let lang_dir = PathBuf::from(DOCS_DIR).join(lang);
- if !lang_dir.is_dir() {
- eprintln!(
- " {}: `{}` does not exist",
- "ERROR".bright_red(),
- lang_dir.display()
- );
- fail_count += 1;
- continue;
- }
- let outcomes = compare_dirs(&ref_dir, &lang_dir, &lang_key(lang));
- fail_count += export_outcomes(&outcomes);
- }
- reporter::flush();
-
- ResultMarkdownCompare { fail_count }.to_chain()
-}
-
-/// Compares one file pair (reference vs translation).
-fn compare_files(ref_path: &Path, trans_path: &Path, prefix: &str) -> Vec<CompareOutcome> {
- let item = format!("{prefix}-{}", snake_case!(&stem_of(ref_path)));
- let location = trans_path.to_string_lossy().into_owned();
- match compare_signature(ref_path, trans_path) {
- Ok(()) => vec![CompareOutcome {
- item,
- location,
- ok: true,
- output: String::new(),
- }],
- Err(diffs) => vec![CompareOutcome {
- item,
- location,
- ok: false,
- output: diffs.join("\n"),
- }],
- }
-}
-
-/// Compares two directories: every `.md` file in the reference must exist in
-/// the translation with the same structural signature; extra files are errors.
-fn compare_dirs(ref_dir: &Path, trans_dir: &Path, prefix: &str) -> Vec<CompareOutcome> {
- let ref_files = collect_md_files(ref_dir);
- let ref_set: BTreeSet<PathBuf> = ref_files.iter().cloned().collect();
- let trans_set: BTreeSet<PathBuf> = collect_md_files(trans_dir).into_iter().collect();
-
- let mut outcomes = Vec::new();
- for file in ref_files {
- let item = format!("{prefix}-{}", snake_case!(&stem_of(&file)));
- let trans_path = trans_dir.join(&file);
- let location = trans_path.to_string_lossy().into_owned();
- if !trans_set.contains(&file) {
- outcomes.push(CompareOutcome {
- item,
- location,
- ok: false,
- output: "missing in translation".to_string(),
- });
- continue;
- }
- outcomes.push(match compare_signature(&ref_dir.join(&file), &trans_path) {
- Ok(()) => CompareOutcome {
- item,
- location,
- ok: true,
- output: String::new(),
- },
- Err(diffs) => CompareOutcome {
- item,
- location,
- ok: false,
- output: diffs.join("\n"),
- },
- });
- }
-
- for file in trans_set.difference(&ref_set) {
- let item = format!("{prefix}-{}", snake_case!(&stem_of(file)));
- let trans_path = trans_dir.join(file);
- outcomes.push(CompareOutcome {
- item,
- location: trans_path.to_string_lossy().into_owned(),
- ok: false,
- output: "extra file, not in reference".to_string(),
- });
- }
- outcomes
-}
-
-/// Exports the outcomes via `reporter`; failures also print to stderr.
-fn export_outcomes(outcomes: &[CompareOutcome]) -> usize {
- let mut fail_count = 0;
- for outcome in outcomes {
- if outcome.ok {
- reporter::export(&outcome.item, &outcome.location, ReportResult::Ok);
- } else {
- fail_count += 1;
- eprintln!(" {} {}", "failed".bright_red(), outcome.item);
- eprintln!(" {}\n{}", outcome.location, outcome.output);
- reporter::export(
- &outcome.item,
- &outcome.location,
- ReportResult::Error(outcome.output.clone()),
- );
- }
- }
- fail_count
-}
-
-/// Reads `.config/docs-lang.txt`: the first line is the reference directory
-/// (relative to `./docs/`), the rest are translations that must mirror it.
-fn lang_config() -> Option<Vec<String>> {
- let content = std::fs::read_to_string(LANG_CONFIG).ok()?;
- Some(
- content
- .lines()
- .map(str::trim)
- .filter(|l| !l.is_empty() && !l.starts_with('#'))
- .map(|l| l.trim_start_matches("./").to_string())
- .collect(),
- )
-}
-
-/// Turns a lang directory path into a report-item key, e.g.
-/// `./_zh_CN/pages/` → `_zh_CN_pages`.
-fn lang_key(lang: &str) -> String {
- lang.trim_matches('/').replace('/', "_")
-}
-
-fn cwd() -> PathBuf {
- std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
-}
-
-/// Number of files that failed the structure comparison.
-#[derive(Grouped)]
-pub struct ResultMarkdownCompare {
- pub fail_count: usize,
-}
-
-/// Silently sets a non-zero exit code when any comparison failed.
-#[renderer(buffer)]
-pub fn render_markdown_compare(r: ResultMarkdownCompare, exit_code: &mut ResExitCode) {
- if r.fail_count > 0 {
- exit_code.exit_code = 1;
- }
-}
diff --git a/mingling_ci/src/task/cmd_test.rs b/mingling_ci/src/task/cmd_test.rs
deleted file mode 100644
index 5b9f55a..0000000
--- a/mingling_ci/src/task/cmd_test.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-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::{location, 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(), location(path), 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
deleted file mode 100644
index ba752dd..0000000
--- a/mingling_ci/src/task/run.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-use std::ffi::OsString;
-use std::path::Path;
-
-use colored::Colorize;
-
-use crate::progress::task_progress_bar;
-use crate::reporter::{self, ReportResult};
-
-/// The manifest's parent directory, e.g. `./mingling` — the report location
-/// for a crate-based item.
-pub(crate) fn location(path: &Path) -> String {
- path.parent()
- .map_or_else(|| ".".to_string(), |d| d.to_string_lossy().into_owned())
-}
-
-/// Outcome of a `cargo` subcommand.
-struct CargoResult {
- ok: bool,
- exit_code: Option<i32>,
- output: String,
-}
-
-/// Runs the given cargo task list in parallel.
-///
-/// Each task is an `(item, location, argv)` triple; 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,
- tasks: Vec<(String, String, Vec<OsString>)>,
-) -> usize {
- reporter::set_task(task);
-
- let n = tasks.len();
- let pb = task_progress_bar(n, phase);
- pb.set_message("tasks");
-
- // Run each task in parallel.
- let mut set = tokio::task::JoinSet::new();
- for (item, location, args) in tasks {
- set.spawn(async move { (item, location, run_cargo(args).await) });
- }
-
- let mut fail_count = 0;
- while let Some(joined) = set.join_next().await {
- let Ok((item, location, result)) = joined else {
- continue;
- };
- pb.inc(1);
- pb.set_message(item.clone());
-
- if result.ok {
- reporter::export(&item, &location, ReportResult::Ok);
- } else {
- fail_count += 1;
- // Failures print to stderr immediately (bar suspended to avoid
- // interleaving) and write their report entry at the same time.
- pb.suspend(|| {
- eprintln!(
- "{}: {} failed{}",
- phase.bold().bright_cyan(),
- item,
- result
- .exit_code
- .map_or_else(String::new, |c| format!(" (exit code {c})"))
- );
- for line in result.output.lines() {
- eprintln!(" {line}");
- }
- });
- reporter::export(&item, &location, ReportResult::Error(result.output));
- }
- }
-
- pb.finish_and_clear();
- reporter::flush();
- fail_count
-}
-
-/// Runs a `cargo` subcommand, capturing its output.
-/// 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();
- log.push_str(&String::from_utf8_lossy(&output.stderr));
- CargoResult {
- ok: output.status.success(),
- exit_code: output.status.code(),
- output: log,
- }
- }
- Err(e) => CargoResult {
- ok: false,
- exit_code: None,
- output: format!("failed to run cargo: {e}"),
- },
- }
-}