Skip to main content

cargo_bin_file/
lib.rs

1use std::{env, path::PathBuf};
2
3/// Returns the path stored in Cargo's `CARGO_BIN_FILE_*` environment
4/// variables for the given binary crate name.
5///
6/// Cargo sets these variables for integration tests and benchmarks so one
7/// package can locate another package's compiled binary by name.
8///
9/// This helper first checks the crate-scoped variable and then falls back to
10/// the bin-scoped form Cargo may expose for artifact dependencies:
11///
12/// - `CARGO_BIN_FILE_<CRATE_NAME>`
13/// - `CARGO_BIN_FILE_<CRATE_NAME>_<BIN_NAME>`
14///
15/// The input crate or bin name is normalized the same way Cargo does for the
16/// crate portion of these variables: hyphens become underscores and ASCII
17/// letters are uppercased.
18///
19/// Returns `None` when the expected environment variable is not set.
20///
21/// # Examples
22///
23/// ```
24/// use cargo_bin_file::bin_path;
25///
26/// let key = "CARGO_BIN_FILE_EXAMPLE_CRATE";
27///
28/// unsafe {
29///     std::env::set_var(key, "/tmp/example-crate");
30/// }
31///
32/// assert_eq!(
33///     bin_path("example-crate"),
34///     Some(std::path::PathBuf::from("/tmp/example-crate"))
35/// );
36///
37/// unsafe {
38///     std::env::remove_var(key);
39/// }
40/// ```
41pub 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}