use std::{env, path::PathBuf};
pub fn bin_path(name: &str) -> Option<PathBuf> {
bin_file_env_vars(name)
.into_iter()
.find_map(|key| env::var(key).ok().map(Into::into))
}
fn bin_file_env_var(name: &str) -> String {
format!("CARGO_BIN_FILE_{}", crate_env_name(name))
}
fn bin_file_env_var_with_bin_name(name: &str) -> String {
format!("{}_{}", bin_file_env_var(name), name)
}
fn bin_file_env_vars(name: &str) -> [String; 2] {
[bin_file_env_var(name), bin_file_env_var_with_bin_name(name)]
}
fn crate_env_name(name: &str) -> String {
name.replace("-", "_").to_ascii_uppercase()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Mutex, OnceLock};
fn env_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
#[test]
fn crate_name_is_normalized_for_env_var_suffix() {
assert_eq!(crate_env_name("my-crate"), "MY_CRATE");
assert_eq!(crate_env_name("already_UPPER"), "ALREADY_UPPER");
}
#[test]
fn crate_name_maps_to_cargo_bin_file_env_var() {
assert_eq!(
bin_file_env_var("my-crate"),
"CARGO_BIN_FILE_MY_CRATE".to_string()
);
}
#[test]
fn crate_name_maps_to_bin_scoped_cargo_bin_file_env_var() {
assert_eq!(
bin_file_env_var_with_bin_name("my-crate"),
"CARGO_BIN_FILE_MY_CRATE_my-crate".to_string()
);
}
#[test]
fn returns_none_when_env_var_is_missing() {
let _guard = env_lock().lock().unwrap();
let [crate_key, bin_key] = bin_file_env_vars("missing-crate");
unsafe {
env::remove_var(&crate_key);
env::remove_var(&bin_key);
}
assert_eq!(bin_path("missing-crate"), None);
}
#[test]
fn returns_path_from_matching_env_var() {
let _guard = env_lock().lock().unwrap();
let key = bin_file_env_var("example-crate");
let value = "/tmp/example-artifact";
unsafe {
env::set_var(&key, value);
}
assert_eq!(bin_path("example-crate"), Some(PathBuf::from(value)));
unsafe {
env::remove_var(&key);
}
}
#[test]
fn falls_back_to_bin_scoped_env_var() {
let _guard = env_lock().lock().unwrap();
let [crate_key, bin_key] = bin_file_env_vars("example-crate");
let value = "/tmp/example-bin-artifact";
unsafe {
env::remove_var(&crate_key);
env::set_var(&bin_key, value);
}
assert_eq!(bin_path("example-crate"), Some(PathBuf::from(value)));
unsafe {
env::remove_var(&bin_key);
}
}
#[test]
fn prefers_crate_scoped_env_var_when_both_exist() {
let _guard = env_lock().lock().unwrap();
let [crate_key, bin_key] = bin_file_env_vars("example-crate");
let crate_value = "/tmp/example-crate-artifact";
let bin_value = "/tmp/example-bin-artifact";
unsafe {
env::set_var(&crate_key, crate_value);
env::set_var(&bin_key, bin_value);
}
assert_eq!(bin_path("example-crate"), Some(PathBuf::from(crate_value)));
unsafe {
env::remove_var(&crate_key);
env::remove_var(&bin_key);
}
}
}