use anyhow::{Context, Result}; use std::{ io::Read, path::Path, process::{Command, Stdio}, }; /// Run a command with a description for a possible error and append the merged stdout and stderr. /// The boolean in the returned `Result` is true if the command's exit status is success. pub fn run_cmd(mut cmd: Command, description: &str, output: Option<&mut Vec>) -> Result { let spawn = |mut cmd: Command| { // NOTE: The closure drops `cmd` which prevents a pipe deadlock. cmd.spawn() .with_context(|| format!("Failed to run the command `{description}`")) }; let mut handle = if let Some(output) = output { let (mut reader, writer) = os_pipe::pipe().with_context(|| { format!("Failed to create a pipe to run the command `{description}``") })?; let writer_clone = writer.try_clone().with_context(|| { format!("Failed to clone the pipe writer for the command `{description}`") })?; cmd.stdout(writer_clone).stderr(writer); let handle = spawn(cmd)?; reader .read_to_end(output) .with_context(|| format!("Failed to read the output of the command `{description}`"))?; output.push(b'\n'); handle } else { cmd.stdout(Stdio::null()).stderr(Stdio::null()); spawn(cmd)? }; handle .wait() .with_context(|| format!("Failed to wait on the command `{description}` to exit")) .map(|status| status.success()) } pub struct CargoCmd<'a> { pub subcommand: &'a str, pub args: &'a [&'a str], pub bin_name: &'a str, pub description: &'a str, /// RUSTFLAGS="-A warnings" pub hide_warnings: bool, /// Added as `--target-dir` if `Self::dev` is true. pub target_dir: &'a Path, /// The output buffer to append the merged stdout and stderr. pub output: Option<&'a mut Vec>, /// true while developing Rustlings. pub dev: bool, } impl<'a> CargoCmd<'a> { /// Run `cargo SUBCOMMAND --bin EXERCISE_NAME … ARGS`. pub fn run(self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg(self.subcommand); // A hack to make `cargo run` work when developing Rustlings. if self.dev { cmd.arg("--manifest-path") .arg("dev/Cargo.toml") .arg("--target-dir") .arg(self.target_dir); } cmd.arg("--color") .arg("always") .arg("-q") .arg("--bin") .arg(self.bin_name) .args(self.args); if self.hide_warnings { cmd.env("RUSTFLAGS", "-A warnings"); } run_cmd(cmd, self.description, self.output) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_run_cmd() { let mut cmd = Command::new("echo"); cmd.arg("Hello"); let mut output = Vec::with_capacity(8); run_cmd(cmd, "echo …", Some(&mut output)).unwrap(); assert_eq!(output, b"Hello\n\n"); } }