1use std::{env, path::PathBuf};
2
3pub fn bin_path(name: &str) -> Option<PathBuf> {
42 bin_file_env_vars(name)
43 .into_iter()
44 .find_map(|key| env::var(key).ok().map(Into::into))
45}
46
47fn bin_file_env_var(name: &str) -> String {
48 format!("CARGO_BIN_FILE_{}", crate_env_name(name))
49}
50
51fn bin_file_env_var_with_bin_name(name: &str) -> String {
52 format!("{}_{}", bin_file_env_var(name), name)
53}
54
55fn bin_file_env_vars(name: &str) -> [String; 2] {
56 [bin_file_env_var(name), bin_file_env_var_with_bin_name(name)]
57}
58
59fn crate_env_name(name: &str) -> String {
60 name.replace("-", "_").to_ascii_uppercase()
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use std::sync::{Mutex, OnceLock};
67
68 fn env_lock() -> &'static Mutex<()> {
69 static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
70 LOCK.get_or_init(|| Mutex::new(()))
71 }
72
73 #[test]
74 fn crate_name_is_normalized_for_env_var_suffix() {
75 assert_eq!(crate_env_name("my-crate"), "MY_CRATE");
76 assert_eq!(crate_env_name("already_UPPER"), "ALREADY_UPPER");
77 }
78
79 #[test]
80 fn crate_name_maps_to_cargo_bin_file_env_var() {
81 assert_eq!(
82 bin_file_env_var("my-crate"),
83 "CARGO_BIN_FILE_MY_CRATE".to_string()
84 );
85 }
86
87 #[test]
88 fn crate_name_maps_to_bin_scoped_cargo_bin_file_env_var() {
89 assert_eq!(
90 bin_file_env_var_with_bin_name("my-crate"),
91 "CARGO_BIN_FILE_MY_CRATE_my-crate".to_string()
92 );
93 }
94
95 #[test]
96 fn returns_none_when_env_var_is_missing() {
97 let _guard = env_lock().lock().unwrap();
98 let [crate_key, bin_key] = bin_file_env_vars("missing-crate");
99
100 unsafe {
101 env::remove_var(&crate_key);
102 env::remove_var(&bin_key);
103 }
104
105 assert_eq!(bin_path("missing-crate"), None);
106 }
107
108 #[test]
109 fn returns_path_from_matching_env_var() {
110 let _guard = env_lock().lock().unwrap();
111 let key = bin_file_env_var("example-crate");
112 let value = "/tmp/example-artifact";
113
114 unsafe {
115 env::set_var(&key, value);
116 }
117
118 assert_eq!(bin_path("example-crate"), Some(PathBuf::from(value)));
119
120 unsafe {
121 env::remove_var(&key);
122 }
123 }
124
125 #[test]
126 fn falls_back_to_bin_scoped_env_var() {
127 let _guard = env_lock().lock().unwrap();
128 let [crate_key, bin_key] = bin_file_env_vars("example-crate");
129 let value = "/tmp/example-bin-artifact";
130
131 unsafe {
132 env::remove_var(&crate_key);
133 env::set_var(&bin_key, value);
134 }
135
136 assert_eq!(bin_path("example-crate"), Some(PathBuf::from(value)));
137
138 unsafe {
139 env::remove_var(&bin_key);
140 }
141 }
142
143 #[test]
144 fn prefers_crate_scoped_env_var_when_both_exist() {
145 let _guard = env_lock().lock().unwrap();
146 let [crate_key, bin_key] = bin_file_env_vars("example-crate");
147 let crate_value = "/tmp/example-crate-artifact";
148 let bin_value = "/tmp/example-bin-artifact";
149
150 unsafe {
151 env::set_var(&crate_key, crate_value);
152 env::set_var(&bin_key, bin_value);
153 }
154
155 assert_eq!(bin_path("example-crate"), Some(PathBuf::from(crate_value)));
156
157 unsafe {
158 env::remove_var(&crate_key);
159 env::remove_var(&bin_key);
160 }
161 }
162}