use std::io;
use std::path::Path;
use std::process::Command;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BuildError {
#[error("build step {step}/{total} failed: `{command}` (exit {code:?})")]
StepFailed {
step: usize,
total: usize,
command: String,
code: Option<i32>,
},
#[error(transparent)]
Io(#[from] io::Error),
}
pub fn run(steps: &[String], cwd: &Path) -> Result<(), BuildError> {
for (i, step) in steps.iter().enumerate() {
let status = Command::new("sh")
.arg("-c")
.arg(step)
.current_dir(cwd)
.status()?;
if !status.success() {
return Err(BuildError::StepFailed {
step: i + 1,
total: steps.len(),
command: step.clone(),
code: status.code(),
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_succeeds_for_all_passing_steps() {
let dir = tempfile::tempdir().unwrap();
let steps = vec!["true".to_string(), "echo hello > out.txt".to_string()];
run(&steps, dir.path()).unwrap();
assert_eq!(
std::fs::read_to_string(dir.path().join("out.txt"))
.unwrap()
.trim(),
"hello"
);
}
#[test]
fn run_runs_steps_in_provided_cwd() {
let dir = tempfile::tempdir().unwrap();
let steps = vec!["echo cwd > marker".to_string()];
run(&steps, dir.path()).unwrap();
assert!(dir.path().join("marker").exists());
}
#[test]
fn run_fails_on_first_failing_step_with_index() {
let dir = tempfile::tempdir().unwrap();
let steps = vec!["true".to_string(), "false".to_string(), "true".to_string()];
let err = run(&steps, dir.path()).unwrap_err();
match err {
BuildError::StepFailed {
step,
total,
command,
code,
} => {
assert_eq!(step, 2);
assert_eq!(total, 3);
assert_eq!(command, "false");
assert_eq!(code, Some(1));
}
_ => panic!("expected StepFailed, got {err:?}"),
}
}
#[test]
fn run_handles_shell_features() {
let dir = tempfile::tempdir().unwrap();
let steps = vec!["echo a > a && echo b > b".to_string()];
run(&steps, dir.path()).unwrap();
assert!(dir.path().join("a").exists());
assert!(dir.path().join("b").exists());
}
#[test]
fn run_with_no_steps_is_ok() {
let dir = tempfile::tempdir().unwrap();
let steps: Vec<String> = vec![];
run(&steps, dir.path()).unwrap();
}
}