cargo_feature_matrix/
execution.rs

1use crate::{features::FeatureMatrix, Error};
2use lazy_static::lazy_static;
3use std::{
4  env, ffi::OsString, io::Write, path::Path, process::Command, process::Stdio,
5};
6use yansi::Paint;
7
8pub(crate) struct Task<'t> {
9  matrix: FeatureMatrix<'t>,
10  manifest_path: Option<&'t Path>,
11  package_name: &'t str,
12  args: &'t [String],
13  command: &'t str,
14  kind: TaskKind,
15}
16
17#[derive(Copy, Clone)]
18pub enum TaskKind {
19  DryRun,
20  PrintJobs,
21  Execute,
22}
23
24impl<'t> Task<'t> {
25  pub(crate) fn new(
26    kind: TaskKind,
27    command: &'t str,
28    manifest_path: Option<&'t Path>,
29    package_name: &'t str,
30    args: &'t [String],
31    matrix: FeatureMatrix<'t>,
32  ) -> Self {
33    Self {
34      matrix,
35      manifest_path,
36      package_name,
37      args,
38      command,
39      kind,
40    }
41  }
42
43  pub(crate) fn run(self) -> Result<(), Error> {
44    match self.kind {
45      TaskKind::DryRun => self.execute(true),
46      TaskKind::PrintJobs => {
47        self.print_jobs();
48        Ok(())
49      }
50      TaskKind::Execute => self.execute(false),
51    }
52  }
53
54  fn execute(self, dry_run: bool) -> Result<(), Error> {
55    let mut exit_status = None;
56    for feature_set in self.matrix {
57      let feature_set = feature_set.to_string();
58
59      let cmd = if dry_run {
60        None
61      } else {
62        let mut cmd = Command::new(CARGO.as_os_str());
63        cmd
64          .arg(self.command)
65          .args(self.args.iter())
66          .stderr(Stdio::piped())
67          .stdout(Stdio::piped())
68          .arg("--no-default-features")
69          .arg("--package")
70          .arg(self.package_name);
71
72        if let Some(manifest_path) = self.manifest_path {
73          cmd.arg("--manifest-path").arg(manifest_path);
74        }
75
76        if !feature_set.is_empty() {
77          cmd.arg("--features").arg(&feature_set);
78        }
79
80        Some(cmd)
81      };
82
83      print!(
84        "{}{} {}{} {}{}{}......",
85        "running: cmd=".cyan(),
86        self.command,
87        "package=".cyan(),
88        self.package_name,
89        "features=[".cyan(),
90        feature_set,
91        "]".cyan(),
92      );
93      std::io::stdout().flush().expect("failed to flush stdout");
94
95      // let on_success = || println!("{}", Black.style().bg(Green).paint("OK"));
96      let on_success = || println!("{}", "OK".black().on_green());
97
98      match cmd {
99        None => on_success(),
100        Some(mut cmd) => {
101          let output = cmd.output().map_err(|err| Error::Io {
102            message: "failed to get output of cargo command",
103            source: err,
104          })?;
105          if output.status.success() {
106            on_success();
107          } else {
108            // println!("{}", Black.style().bg(Red).paint("Fail"));
109            println!("{}", "Fail".black().on_red());
110            exit_status = Some(output.status);
111            std::io::stderr()
112              .write_all(&output.stderr)
113              .expect("failed to write to stderr");
114            std::io::stdout()
115              .write_all(&output.stdout)
116              .expect("failed to write to stdout");
117          }
118        }
119      };
120    }
121
122    match exit_status {
123      Some(exit_status) => Err(Error::Fail(exit_status)),
124      None => Ok(()),
125    }
126  }
127
128  fn print_jobs(self) {
129    let prefix = format!(
130      "{} {} --package {} {}",
131      CARGO.to_string_lossy(),
132      self.command,
133      self.package_name,
134      self.args.join(" ")
135    );
136    for feature_set in self.matrix {
137      if feature_set.is_empty() {
138        println!("{}", prefix);
139      } else {
140        println!("{} --features {}", prefix, feature_set);
141      }
142    }
143  }
144}
145
146lazy_static! {
147  static ref CARGO: OsString =
148    env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
149}