Skip to main content

dolly_cli/
build.rs

1use std::io;
2use std::path::Path;
3use std::process::Command;
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum BuildError {
9    #[error("build step {step}/{total} failed: `{command}` (exit {code:?})")]
10    StepFailed {
11        step: usize,
12        total: usize,
13        command: String,
14        code: Option<i32>,
15    },
16
17    #[error(transparent)]
18    Io(#[from] io::Error),
19}
20
21pub fn run(steps: &[String], cwd: &Path) -> Result<(), BuildError> {
22    for (i, step) in steps.iter().enumerate() {
23        let status = Command::new("sh")
24            .arg("-c")
25            .arg(step)
26            .current_dir(cwd)
27            .status()?;
28
29        if !status.success() {
30            return Err(BuildError::StepFailed {
31                step: i + 1,
32                total: steps.len(),
33                command: step.clone(),
34                code: status.code(),
35            });
36        }
37    }
38
39    Ok(())
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn run_succeeds_for_all_passing_steps() {
48        let dir = tempfile::tempdir().unwrap();
49        let steps = vec!["true".to_string(), "echo hello > out.txt".to_string()];
50        run(&steps, dir.path()).unwrap();
51        assert_eq!(
52            std::fs::read_to_string(dir.path().join("out.txt"))
53                .unwrap()
54                .trim(),
55            "hello"
56        );
57    }
58
59    #[test]
60    fn run_runs_steps_in_provided_cwd() {
61        let dir = tempfile::tempdir().unwrap();
62        let steps = vec!["echo cwd > marker".to_string()];
63        run(&steps, dir.path()).unwrap();
64        assert!(dir.path().join("marker").exists());
65    }
66
67    #[test]
68    fn run_fails_on_first_failing_step_with_index() {
69        let dir = tempfile::tempdir().unwrap();
70        let steps = vec!["true".to_string(), "false".to_string(), "true".to_string()];
71        let err = run(&steps, dir.path()).unwrap_err();
72        match err {
73            BuildError::StepFailed {
74                step,
75                total,
76                command,
77                code,
78            } => {
79                assert_eq!(step, 2);
80                assert_eq!(total, 3);
81                assert_eq!(command, "false");
82                assert_eq!(code, Some(1));
83            }
84            _ => panic!("expected StepFailed, got {err:?}"),
85        }
86    }
87
88    #[test]
89    fn run_handles_shell_features() {
90        let dir = tempfile::tempdir().unwrap();
91        let steps = vec!["echo a > a && echo b > b".to_string()];
92        run(&steps, dir.path()).unwrap();
93        assert!(dir.path().join("a").exists());
94        assert!(dir.path().join("b").exists());
95    }
96
97    #[test]
98    fn run_with_no_steps_is_ok() {
99        let dir = tempfile::tempdir().unwrap();
100        let steps: Vec<String> = vec![];
101        run(&steps, dir.path()).unwrap();
102    }
103}