aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/task/cmd_clippy.rs
blob: 8ec4236408c676f4c0c7575a5c30678155c887c5 (plain) (blame)
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
use std::path::Path;

use just_progress::progress::{self, ProgressInfo};
use mingling::{
    Grouped, Routable,
    macros::{buffer, command, r_println, renderer},
};

use crate::Next;
use crate::reporter::{self, ReportResult};
use crate::res::Manifests;

#[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);
    }

    ResultClippyAll { fail_count }.to_chain()
}

/// Runs `cargo clippy --manifest-path <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}")),
    }
}

/// Number of packages that failed clippy.
#[derive(Grouped)]
pub struct ResultClippyAll {
    pub fail_count: usize,
}

#[renderer(buffer)]
pub fn render_clippy_all(r: ResultClippyAll) {
    r_println!("Clippy-All: {} package(s) failed", r.fail_count);
}