use std::env;
use std::error::Error;
use std::fmt;
use std::path;
use std::process;
#[doc(inline)]
pub use crate::cargo_bin;
#[doc(inline)]
pub use crate::cargo_bin_cmd;
pub trait CommandCargoExt
where
Self: Sized,
{
#[deprecated(
since = "2.1.0",
note = "incompatible with a custom cargo build-dir, see instead `cargo::cargo_bin!`"
)]
fn cargo_bin<S: AsRef<str>>(name: S) -> Result<Self, CargoError>;
}
impl CommandCargoExt for crate::cmd::Command {
fn cargo_bin<S: AsRef<str>>(name: S) -> Result<Self, CargoError> {
#[allow(deprecated)]
crate::cmd::Command::cargo_bin(name)
}
}
impl CommandCargoExt for process::Command {
fn cargo_bin<S: AsRef<str>>(name: S) -> Result<Self, CargoError> {
cargo_bin_cmd(name)
}
}
pub(crate) fn cargo_bin_cmd<S: AsRef<str>>(name: S) -> Result<process::Command, CargoError> {
#[allow(deprecated)]
let path = cargo_bin(name);
if path.is_file() {
if let Some(runner) = cargo_runner() {
let mut cmd = process::Command::new(&runner[0]);
cmd.args(&runner[1..]).arg(path);
Ok(cmd)
} else {
Ok(process::Command::new(path))
}
} else {
Err(CargoError::with_cause(NotFoundError { path }))
}
}
pub(crate) fn cargo_runner() -> Option<Vec<String>> {
let runner_env = format!(
"CARGO_TARGET_{}_RUNNER",
CURRENT_TARGET.replace('-', "_").to_uppercase()
);
let runner = env::var(runner_env).ok()?;
Some(runner.split(' ').map(str::to_string).collect())
}
#[derive(Debug)]
pub struct CargoError {
cause: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl CargoError {
pub fn with_cause<E>(cause: E) -> Self
where
E: Error + Send + Sync + 'static,
{
let cause = Box::new(cause);
Self { cause: Some(cause) }
}
}
impl Error for CargoError {}
impl fmt::Display for CargoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref cause) = self.cause {
writeln!(f, "Cause: {cause}")?;
}
Ok(())
}
}
#[derive(Debug)]
struct NotFoundError {
path: path::PathBuf,
}
impl Error for NotFoundError {}
impl fmt::Display for NotFoundError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Cargo command not found: {}", self.path.display())
}
}
fn target_dir() -> path::PathBuf {
env::current_exe()
.ok()
.map(|mut path| {
path.pop();
if path.ends_with("deps") {
path.pop();
}
path
})
.expect("this should only be used where a `current_exe` can be set")
}
#[deprecated(
since = "2.1.0",
note = "incompatible with a custom cargo build-dir, see instead `cargo::cargo_bin!`"
)]
pub fn cargo_bin<S: AsRef<str>>(name: S) -> path::PathBuf {
cargo_bin_str(name.as_ref())
}
fn cargo_bin_str(name: &str) -> path::PathBuf {
let env_var = format!("CARGO_BIN_EXE_{name}");
env::var_os(env_var)
.map(|p| p.into())
.unwrap_or_else(|| target_dir().join(format!("{}{}", name, env::consts::EXE_SUFFIX)))
}
const CURRENT_TARGET: &str = include_str!(concat!(env!("OUT_DIR"), "/current_target.txt"));