use std::path::{Path, PathBuf};
pub const ENV_WORKER_BIN: &str = "NEWT_WORKER_BIN";
fn worker_exe_name() -> String {
format!("newt{}", std::env::consts::EXE_SUFFIX)
}
pub fn resolve_worker_bin_with<F, G>(
cli_override: Option<PathBuf>,
lookup_env: F,
current_exe: G,
) -> Resolution
where
F: Fn(&str) -> Option<String>,
G: Fn() -> Option<PathBuf>,
{
let mut candidates: Vec<PathBuf> = Vec::new();
if let Some(p) = cli_override {
candidates.push(p.clone());
let found = p.exists();
return Resolution {
path: p,
found,
candidates,
source: ResolutionSource::CliFlag,
};
}
if let Some(raw) = lookup_env(ENV_WORKER_BIN) {
let p = PathBuf::from(raw);
candidates.push(p.clone());
let found = p.exists();
return Resolution {
path: p,
found,
candidates,
source: ResolutionSource::EnvVar,
};
}
if let Some(exe) = current_exe() {
if let Some(parent) = exe.parent() {
let sibling = parent.join(worker_exe_name());
candidates.push(sibling.clone());
if sibling.exists() {
return Resolution {
path: sibling,
found: true,
candidates,
source: ResolutionSource::ArgvSibling,
};
}
}
}
if let Some(td) = lookup_env("CARGO_TARGET_DIR") {
for profile in ["release", "debug"] {
let p = Path::new(&td).join(profile).join(worker_exe_name());
candidates.push(p.clone());
if p.exists() {
return Resolution {
path: p,
found: true,
candidates,
source: ResolutionSource::CargoTargetDir,
};
}
}
}
for p in [
PathBuf::from("target")
.join("release")
.join(worker_exe_name()),
PathBuf::from("target")
.join("debug")
.join(worker_exe_name()),
PathBuf::from("..")
.join("target")
.join("release")
.join(worker_exe_name()),
PathBuf::from("..")
.join("target")
.join("debug")
.join(worker_exe_name()),
] {
candidates.push(p.clone());
if p.exists() {
return Resolution {
path: p,
found: true,
candidates,
source: ResolutionSource::CwdRelative,
};
}
}
let fallback = PathBuf::from("target")
.join("release")
.join(worker_exe_name());
Resolution {
path: fallback,
found: false,
candidates,
source: ResolutionSource::NotFound,
}
}
pub fn resolve_worker_bin(cli_override: Option<PathBuf>) -> Resolution {
resolve_worker_bin_with(
cli_override,
|k| std::env::var(k).ok(),
|| std::env::current_exe().ok(),
)
}
#[derive(Debug, Clone)]
pub struct Resolution {
pub path: PathBuf,
pub found: bool,
pub candidates: Vec<PathBuf>,
pub source: ResolutionSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolutionSource {
CliFlag,
EnvVar,
ArgvSibling,
CargoTargetDir,
CwdRelative,
NotFound,
}
impl Resolution {
pub fn render_candidates(&self) -> String {
if self.candidates.is_empty() {
return " (no candidates were tried)".to_string();
}
self.candidates
.iter()
.map(|p| format!(" - {}", p.display()))
.collect::<Vec<_>>()
.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn env_lookup<'a>(
map: &'a HashMap<&'static str, String>,
) -> impl Fn(&str) -> Option<String> + 'a {
move |k| map.get(k).cloned()
}
#[test]
fn cli_flag_wins_even_if_missing() {
let env = HashMap::new();
let r =
resolve_worker_bin_with(Some(PathBuf::from("/nope/newt")), env_lookup(&env), || {
Some(PathBuf::from("/usr/bin/newt-eval"))
});
assert_eq!(r.source, ResolutionSource::CliFlag);
assert_eq!(r.path, PathBuf::from("/nope/newt"));
assert!(!r.found);
assert_eq!(r.candidates, vec![PathBuf::from("/nope/newt")]);
}
#[test]
fn cli_flag_wins_when_present() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join(worker_exe_name());
std::fs::write(&bin, "").unwrap();
let env = HashMap::new();
let r = resolve_worker_bin_with(Some(bin.clone()), env_lookup(&env), || None);
assert_eq!(r.source, ResolutionSource::CliFlag);
assert!(r.found);
assert_eq!(r.path, bin);
}
#[test]
fn env_var_used_when_no_cli_flag() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join(worker_exe_name());
std::fs::write(&bin, "").unwrap();
let mut env = HashMap::new();
env.insert("NEWT_WORKER_BIN", bin.to_string_lossy().into_owned());
let r = resolve_worker_bin_with(None, env_lookup(&env), || None);
assert_eq!(r.source, ResolutionSource::EnvVar);
assert!(r.found);
assert_eq!(r.path, bin);
}
#[test]
fn argv_sibling_wins_over_cargo_target_dir() {
let exe_dir = tempfile::tempdir().unwrap();
let target_dir = tempfile::tempdir().unwrap();
let sibling = exe_dir.path().join(worker_exe_name());
let in_target = target_dir.path().join("release").join(worker_exe_name());
std::fs::create_dir_all(in_target.parent().unwrap()).unwrap();
std::fs::write(&sibling, "").unwrap();
std::fs::write(&in_target, "").unwrap();
let mut env = HashMap::new();
env.insert(
"CARGO_TARGET_DIR",
target_dir.path().to_string_lossy().into_owned(),
);
let r = resolve_worker_bin_with(None, env_lookup(&env), || {
Some(exe_dir.path().join("newt-eval"))
});
assert_eq!(r.source, ResolutionSource::ArgvSibling);
assert_eq!(r.path, sibling);
}
#[test]
fn cargo_target_dir_release_preferred_over_debug() {
let target_dir = tempfile::tempdir().unwrap();
let release = target_dir.path().join("release").join(worker_exe_name());
let debug = target_dir.path().join("debug").join(worker_exe_name());
std::fs::create_dir_all(release.parent().unwrap()).unwrap();
std::fs::create_dir_all(debug.parent().unwrap()).unwrap();
std::fs::write(&release, "").unwrap();
std::fs::write(&debug, "").unwrap();
let mut env = HashMap::new();
env.insert(
"CARGO_TARGET_DIR",
target_dir.path().to_string_lossy().into_owned(),
);
let unrelated = tempfile::tempdir().unwrap();
let r = resolve_worker_bin_with(None, env_lookup(&env), || {
Some(unrelated.path().join("newt-eval"))
});
assert_eq!(r.source, ResolutionSource::CargoTargetDir);
assert_eq!(r.path, release);
}
#[test]
fn cargo_target_dir_debug_when_release_missing() {
let target_dir = tempfile::tempdir().unwrap();
let debug = target_dir.path().join("debug").join(worker_exe_name());
std::fs::create_dir_all(debug.parent().unwrap()).unwrap();
std::fs::write(&debug, "").unwrap();
let mut env = HashMap::new();
env.insert(
"CARGO_TARGET_DIR",
target_dir.path().to_string_lossy().into_owned(),
);
let unrelated = tempfile::tempdir().unwrap();
let r = resolve_worker_bin_with(None, env_lookup(&env), || {
Some(unrelated.path().join("newt-eval"))
});
assert_eq!(r.source, ResolutionSource::CargoTargetDir);
assert_eq!(r.path, debug);
}
#[test]
fn candidates_list_starts_with_argv_sibling() {
let unrelated = tempfile::tempdir().unwrap();
let env = HashMap::new();
let r = resolve_worker_bin_with(None, env_lookup(&env), || {
Some(unrelated.path().join("newt-eval"))
});
assert_eq!(r.candidates[0], unrelated.path().join(worker_exe_name()));
}
#[test]
fn not_found_state_is_reachable_with_isolated_probes() {
let exe_dir = tempfile::tempdir().unwrap();
let target_dir = tempfile::tempdir().unwrap();
let mut env = HashMap::new();
env.insert(
"CARGO_TARGET_DIR",
target_dir.path().to_string_lossy().into_owned(),
);
let r = resolve_worker_bin_with(None, env_lookup(&env), || {
Some(exe_dir.path().join("newt-eval"))
});
assert!(!r.candidates.is_empty());
match r.source {
ResolutionSource::NotFound => {
assert!(!r.found);
assert_eq!(
r.path,
PathBuf::from("target")
.join("release")
.join(worker_exe_name())
);
}
ResolutionSource::CwdRelative => {
assert!(r.found);
}
other => panic!("unexpected source {other:?}"),
}
}
#[test]
fn render_candidates_lists_each_path() {
let r = Resolution {
path: PathBuf::from("x"),
found: false,
candidates: vec![PathBuf::from("/a"), PathBuf::from("/b")],
source: ResolutionSource::NotFound,
};
let rendered = r.render_candidates();
assert!(rendered.contains("/a"));
assert!(rendered.contains("/b"));
}
}