| 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<Self> { |
| 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<T, E>(r: Result<Option<T>, E>) -> Option<Result<T, E>> { |
| 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<Item = Result<path::PathBuf, CargoError>> { |
| 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<path::PathBuf, CargoError> { |
| let bins: Result<Vec<_>, 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")) |
| } |