use std::path; use std::process; use crate::error::*; use crate::format; use crate::msg::*; /// The `run` subcommand (emulated). /// /// Created via [`CargoBuild::run`][crate::CargoBuild::run]. /// /// Benefits over spawning `cargo run`: /// - Able to cache binary path, avoiding cargo overhead. /// - Independent of CWD. /// - stdout/stderr are clean of `cargo run` output. /// /// Relevant features /// - `print` for logged output to be printed instead, generally for test writing. /// /// # Example /// /// To create a [`CargoRun`]: /// ```rust /// let temp = assert_fs::TempDir::new().unwrap(); /// let run = escargot::CargoBuild::new() /// .bin("bin") /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/bin/Cargo.toml") /// .target_dir(temp.path()) /// .run() /// .unwrap(); /// println!("artifact={}", run.path().display()); /// ``` /// See [`CargoRun::path`] for how to then run the newly compiled /// program. pub struct CargoRun { bin_path: path::PathBuf, } impl CargoRun { pub(crate) fn from_message( msgs: CommandMessages, is_bin: bool, is_example: bool, ) -> CargoResult { let kind = match (is_bin, is_example) { (true, true) => { return Err(CargoError::new(ErrorKind::CommandFailed) .set_context("Ambiguous which binary is intended, multiple selected")); } (false, true) => "example", _ => "bin", }; let bin_path = extract_binary_path(msgs, kind)?; Ok(Self { bin_path }) } /// Path to the specified binary. /// /// This is to support alternative ways of launching the binary besides [`Command`]. /// /// # Example /// /// ```rust /// let temp = assert_fs::TempDir::new().unwrap(); /// let run = escargot::CargoBuild::new() /// .bin("bin") /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/bin/Cargo.toml") /// .target_dir(temp.path()) /// .run() /// .unwrap(); /// println!("artifact={}", run.path().display()); /// ``` /// or /// ```rust,no_run /// let temp = assert_fs::TempDir::new().unwrap(); /// let run = escargot::CargoBuild::new() /// .example("example_fixture") /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/example/Cargo.toml") /// .target_dir(temp.path()) /// .run() /// .unwrap(); /// println!("artifact={}", run.path().display()); /// ``` /// /// [`Command`]: std::process::Command pub fn path(&self) -> &path::Path { &self.bin_path } /// Run the build artifact. /// /// # Example /// /// ```rust,no_run /// let temp = assert_fs::TempDir::new().unwrap(); /// let run = escargot::CargoBuild::new() /// .bin("bin") /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/bin/Cargo.toml") /// .target_dir(temp.path()) /// .run() /// .unwrap() /// .command() /// .arg("--help") /// .status() /// .unwrap(); /// ``` /// or /// ```rust /// extern crate escargot; /// extern crate assert_fs; /// /// let temp = assert_fs::TempDir::new().unwrap(); /// let run = escargot::CargoBuild::new() /// .example("example_fixture") /// .current_release() /// .current_target() /// .manifest_path("tests/fixtures/example/Cargo.toml") /// .target_dir(temp.path()) /// .run() /// .unwrap() /// .command() /// .arg("--help") /// .status() /// .unwrap(); /// ``` pub fn command(&self) -> process::Command { process::Command::new(self.path()) } } fn extract_bin<'a>(msg: &'a format::Message<'_>, desired_kind: &str) -> Option<&'a path::Path> { match msg { format::Message::CompilerArtifact(art) => { if !art.profile.test && art.target.crate_types == ["bin"] && art.target.kind == [desired_kind] { Some(art.filenames.get(0).expect("files must exist")) } else { None } } _ => None, } } fn transpose(r: Result, E>) -> Option> { match r { Ok(Some(x)) => Some(Ok(x)), Ok(None) => None, Err(e) => Some(Err(e)), } } fn extract_binary_paths( msgs: CommandMessages, kind: &'static str, ) -> impl Iterator> { msgs.filter_map(move |m| { let m = m.and_then(|m| { let m = m.decode()?; format::log_message(&m); let p = extract_bin(&m, kind).map(|p| p.to_path_buf()); Ok(p) }); transpose(m) }) } fn extract_binary_path( msgs: CommandMessages, kind: &'static str, ) -> Result { let bins: Result, CargoError> = extract_binary_paths(msgs, kind).collect(); let bins = bins?; if bins.is_empty() { return Err(CargoError::new(ErrorKind::CommandFailed).set_context("No binaries in crate")); } else if bins.len() != 1 { return Err( CargoError::new(ErrorKind::CommandFailed).set_context(std::format!( "Ambiguous which binary is intended: {:?}", bins )), ); } Ok(bins.into_iter().next().expect("already validated")) }