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
mod config;
mod execution;
pub mod features;

pub use self::{config::Config, execution::TaskKind};
use self::{execution::Task, features::FeatureMatrix};
use cargo_metadata::{Metadata, MetadataCommand, Package};
use figment::{
    providers::{Format, Json},
    Figment,
};
use std::path::PathBuf;
use thiserror::Error;

pub fn run(
    command: String,
    args: Vec<String>,
    task: TaskKind,
    manifest_path: Option<PathBuf>,
    figment: Figment,
) -> Result<(), Error> {
    let mut cmd = MetadataCommand::new();
    if let Some(manifest_path) = &manifest_path {
        cmd.manifest_path(manifest_path);
    }
    let metadata = cmd.exec()?;

    let mut error = None;
    for package in get_workspace_members(&metadata) {
        let figment = if let Some(package_config) =
            package.metadata.get("feature-matrix")
        {
            figment
                .clone()
                .merge(Figment::from(Json::string(&package_config.to_string())))
        } else {
            figment.clone()
        };

        let config = Config::from(figment)?;

        let matrix = FeatureMatrix::new(package, &config);
        let task = Task::new(
            task,
            &command,
            manifest_path.as_deref(),
            &package.name,
            &args,
            matrix,
        );

        if let Err(err) = task.run() {
            error = Some(err);
        }
    }

    match error {
        Some(err) => Err(err),
        None => Ok(()),
    }
}

/// Gets a list of packages that are members of the workspace
fn get_workspace_members(
    metadata: &Metadata,
) -> impl Iterator<Item = &Package> + '_ {
    metadata
        .packages
        .iter()
        .filter(|package| metadata.workspace_members.contains(&package.id))
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("failed to get cargo metadata")]
    Metadata(#[from] cargo_metadata::Error),
    #[error("{}", message)]
    Io {
        message: &'static str,
        #[source]
        source: std::io::Error,
    },
    #[error("child process exited with {}", _0)]
    Fail(std::process::ExitStatus),
    #[error("failed to get config from metadata")]
    Config(#[from] figment::Error),
}

#[cfg(test)]
mod tests {}