aboutsummaryrefslogtreecommitdiff
path: root/dev/ci/src/res/crate_config.rs
blob: b20e83d4027457cee2d4766cda89d2181daf908b (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
use std::collections::HashMap;
use std::path::Path;

use mingling::{Program, macros::program_setup};

use crate::ThisProgram;
use crate::res::{Manifests, ResFeatureList};

/// Per-crate CI overrides from `mingling-ci.toml` (optional, crate root).
///
/// Currently only `[test] command` is read; `clippy.command` / `build.command`
/// will follow the same shape.
#[derive(Default, Clone)]
pub struct ResCrateConfig {
    /// Package name -> test command argv (with `<<<features>>>` expanded).
    test_commands: HashMap<String, Vec<String>>,
}

impl ResCrateConfig {
    /// The configured `[test] command` for a package, if any.
    #[must_use]
    pub fn test_command(&self, package: &str) -> Option<&[String]> {
        self.test_commands.get(package).map(Vec::as_slice)
    }
}

#[program_setup]
pub fn crate_config_setup(p: &mut Program<ThisProgram>) {
    let features = p
        .res::<ResFeatureList>()
        .map(|f| f.list.clone())
        .unwrap_or_default();
    let joined_features = features.join(",");

    let Some(manifests) = p.res::<Manifests>() else {
        return;
    };

    let mut test_commands = HashMap::new();
    for (name, manifest_path) in &manifests.package_dirs {
        let config_path = manifest_path
            .parent()
            .unwrap_or_else(|| Path::new("."))
            .join("mingling-ci.toml");

        let Ok(content) = std::fs::read_to_string(&config_path) else {
            continue;
        };

        let Ok(table) = content.parse::<toml::Value>() else {
            continue;
        };

        let Some(command) = table
            .get("test")
            .and_then(|t| t.get("command"))
            .and_then(|c| c.as_array())
        else {
            continue;
        };

        let argv: Vec<String> = command
            .iter()
            .filter_map(|v| v.as_str().map(str::to_string))
            .collect();

        if argv.is_empty() {
            continue;
        }

        let argv = argv
            .into_iter()
            .map(|arg| arg.replace("<<<features>>>", &joined_features))
            .collect();
        test_commands.insert(name.clone(), argv);
    }

    p.with_resource(ResCrateConfig { test_commands });
}