use std::path::{Path, PathBuf};
use eyre::bail;
pub fn find(binary_name: &str, crate_name: &str) -> eyre::Result<PathBuf> {
if let Some(path) = search(binary_name) {
return Ok(path);
}
eprintln!("{binary_name} not found, building...");
let build_ok = std::process::Command::new("cargo")
.args(["build", "-p", crate_name])
.status()
.is_ok_and(|s| s.success());
if build_ok && let Some(path) = search(binary_name) {
return Ok(path);
}
eprintln!("{binary_name} not found in workspace, installing via cargo install...");
let version = env!("CARGO_PKG_VERSION");
let status = std::process::Command::new("cargo")
.args(["install", crate_name, "--version", version])
.status();
match status {
Ok(s) if s.success() => {}
Ok(s) => bail!("failed to install {crate_name} (exit code: {s})"),
Err(e) => bail!(
"could not find `{binary_name}` and installation failed: {e}\n\
Install it manually with: cargo install {crate_name}"
),
}
search(binary_name).ok_or_else(|| {
eyre::eyre!(
"installed {crate_name} but could not find the binary.\n\
Try: cargo install {crate_name}"
)
})
}
fn probe_dir(dir: &Path, binary_name: &str) -> Option<PathBuf> {
let candidate = dir.join(binary_name);
if candidate.exists() {
return Some(candidate);
}
let suffix = std::env::consts::EXE_SUFFIX;
if !suffix.is_empty() {
let candidate = dir.join(format!("{binary_name}{suffix}"));
if candidate.exists() {
return Some(candidate);
}
}
None
}
fn search(binary_name: &str) -> Option<PathBuf> {
if let Ok(exe) = std::env::current_exe() {
let dir = exe.parent().unwrap_or(Path::new("."));
if let Some(candidate) = probe_dir(dir, binary_name) {
return Some(candidate);
}
}
if let Ok(path) = which::which(binary_name) {
return Some(path);
}
let cargo_target = std::env::var("CARGO_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("target"));
for profile in ["debug", "release"] {
if let Some(candidate) = probe_dir(&cargo_target.join(profile), binary_name) {
return dunce::canonicalize(candidate).ok();
}
}
None
}
#[cfg(test)]
mod tests {
use super::probe_dir;
#[test]
fn probe_dir_finds_binary_with_platform_suffix() {
let mut dir = std::env::temp_dir();
dir.push(format!("dora-node-binary-test-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let file_name = format!("foo{}", std::env::consts::EXE_SUFFIX);
let file_path = dir.join(&file_name);
std::fs::write(&file_path, b"binary").unwrap();
let found = probe_dir(&dir, "foo");
assert_eq!(found.as_deref(), Some(file_path.as_path()));
assert_eq!(probe_dir(&dir, "does-not-exist"), None);
std::fs::remove_dir_all(&dir).ok();
}
}