1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
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}"),
},
}
}
|