use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Mutex, OnceLock};
fn cache() -> &'static Mutex<HashMap<String, PathBuf>> {
static CACHE: OnceLock<Mutex<HashMap<String, PathBuf>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn tools_bin(name: &str) -> PathBuf {
let mut cache = cache().lock().expect("tools_bin cache poisoned");
if let Some(p) = cache.get(name) {
return p.clone();
}
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../Cargo.toml");
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let out = Command::new(cargo)
.arg("build")
.arg("--manifest-path")
.arg(&manifest)
.args(["-p", "tools", "--bin", name])
.args(if cfg!(debug_assertions) {
&[][..]
} else {
&["--release"][..]
})
.arg("--message-format=json-render-diagnostics")
.output()
.expect("failed to spawn cargo to build the tools crate");
assert!(
out.status.success(),
"cargo build -p tools --bin {name} failed:\n{}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
let path = stdout
.lines()
.find_map(|line| {
let rest = line.split("\"executable\":\"").nth(1)?;
let path = rest.split('"').next()?;
(!path.is_empty()).then(|| PathBuf::from(path))
})
.unwrap_or_else(|| {
panic!("no executable path in `cargo build -p tools --bin {name}` output")
});
assert!(
path.exists(),
"tools binary {name} reported at {} but missing",
path.display()
);
cache.insert(name.to_string(), path.clone());
path
}