Skip to main content

mk_lib/schema/
precondition.rs

1use anyhow::Context as _;
2use serde::Deserialize;
3use std::io::{
4  BufRead as _,
5  BufReader,
6};
7use std::thread;
8
9use super::{
10  Shell,
11  TaskContext,
12};
13use crate::defaults::default_verbose;
14use crate::handle_output;
15use crate::schema::get_output_handler;
16
17/// This struct represents a precondition that must be met before a task can be
18/// executed.
19#[derive(Debug, Default, Deserialize)]
20pub struct Precondition {
21  /// The command to run
22  pub command: String,
23
24  /// The message to display if the command fails
25  #[serde(default)]
26  pub message: Option<String>,
27
28  /// The shell to use to run the command
29  #[serde(default)]
30  pub shell: Option<Shell>,
31
32  /// The working directory to run the command in
33  #[serde(default)]
34  pub work_dir: Option<String>,
35
36  /// Show verbose output
37  #[serde(default)]
38  pub verbose: Option<bool>,
39}
40
41impl Precondition {
42  pub fn execute(&self, context: &TaskContext) -> anyhow::Result<()> {
43    assert!(!self.command.is_empty());
44
45    let verbose = self.verbose(context);
46
47    let stdout = get_output_handler(verbose);
48    let stderr = get_output_handler(verbose);
49
50    let mut cmd = self
51      .shell
52      .as_ref()
53      .map(|shell| shell.proc())
54      .unwrap_or_else(|| context.shell().proc());
55
56    cmd.arg(self.command.clone()).stdout(stdout).stderr(stderr);
57
58    if let Some(work_dir) = self.resolved_work_dir(context) {
59      cmd.current_dir(work_dir);
60    }
61
62    // Inject environment variables
63    for (key, value) in context.env_vars.iter() {
64      cmd.env(key, value);
65    }
66
67    let mut cmd = cmd.spawn()?;
68
69    if verbose {
70      handle_output!(cmd.stdout, context);
71      handle_output!(cmd.stderr, context);
72    }
73
74    let status = cmd.wait()?;
75    if !status.success() {
76      if let Some(message) = &self.message {
77        anyhow::bail!("Precondition failed - {}", message);
78      } else {
79        anyhow::bail!("Precondition failed - {}", self.command);
80      }
81    }
82
83    Ok(())
84  }
85
86  fn verbose(&self, context: &TaskContext) -> bool {
87    self.verbose.or(context.verbose).unwrap_or(default_verbose())
88  }
89
90  fn resolved_work_dir(&self, context: &TaskContext) -> Option<std::path::PathBuf> {
91    self
92      .work_dir
93      .as_ref()
94      .map(|work_dir| context.resolve_from_config(work_dir))
95  }
96}
97
98#[cfg(test)]
99mod test {
100  use super::*;
101
102  #[test]
103  fn test_precondition_1() -> anyhow::Result<()> {
104    {
105      let yaml = "
106        command: 'echo \"Hello, World!\"'
107        message: 'This is a message'
108      ";
109      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
110
111      assert_eq!(precondition.command, "echo \"Hello, World!\"");
112      assert_eq!(precondition.message, Some("This is a message".into()));
113      assert_eq!(precondition.work_dir, None);
114      assert_eq!(precondition.verbose, None);
115
116      Ok(())
117    }
118  }
119
120  #[test]
121  fn test_precondition_2() -> anyhow::Result<()> {
122    {
123      let yaml = "
124        command: 'echo \"Hello, World!\"'
125      ";
126      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
127
128      assert_eq!(precondition.command, "echo \"Hello, World!\"");
129      assert_eq!(precondition.message, None);
130      assert_eq!(precondition.work_dir, None);
131      assert_eq!(precondition.verbose, None);
132
133      Ok(())
134    }
135  }
136
137  #[test]
138  fn test_precondition_3() -> anyhow::Result<()> {
139    {
140      let yaml = "
141        command: 'echo \"Hello, World!\"'
142        message: null
143      ";
144      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
145
146      assert_eq!(precondition.command, "echo \"Hello, World!\"");
147      assert_eq!(precondition.message, None);
148      assert_eq!(precondition.work_dir, None);
149      assert_eq!(precondition.verbose, None);
150
151      Ok(())
152    }
153  }
154
155  #[test]
156  fn test_precondition_4() -> anyhow::Result<()> {
157    {
158      let yaml = "
159        command: 'echo \"Hello, World!\"'
160        work_dir: /tmp
161      ";
162      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
163
164      assert_eq!(precondition.command, "echo \"Hello, World!\"");
165      assert_eq!(precondition.message, None);
166      assert_eq!(precondition.work_dir, Some("/tmp".into()));
167      assert_eq!(precondition.verbose, None);
168
169      Ok(())
170    }
171  }
172
173  #[test]
174  fn test_precondition_5() -> anyhow::Result<()> {
175    {
176      let yaml = "
177        command: 'echo \"Hello, World!\"'
178        verbose: true
179      ";
180      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
181
182      assert_eq!(precondition.command, "echo \"Hello, World!\"");
183      assert_eq!(precondition.message, None);
184      assert_eq!(precondition.work_dir, None);
185      assert_eq!(precondition.verbose, Some(true));
186
187      Ok(())
188    }
189  }
190
191  #[test]
192  fn test_precondition_6() -> anyhow::Result<()> {
193    {
194      let yaml = "
195        command: ls -la
196        message: Listing directory contents
197        shell: bash
198        work_dir: /tmp
199        verbose: false
200      ";
201      let precondition = serde_yaml::from_str::<Precondition>(yaml)?;
202
203      assert_eq!(precondition.command, "ls -la");
204      assert_eq!(
205        precondition.message,
206        Some("Listing directory contents".to_string())
207      );
208      if let Some(shell) = precondition.shell {
209        assert_eq!(shell.cmd(), "bash".to_string());
210      } else {
211        panic!("Expected shell to be Some");
212      }
213      assert_eq!(precondition.work_dir, Some("/tmp".into()));
214      assert_eq!(precondition.verbose, Some(false));
215
216      Ok(())
217    }
218  }
219}