1use std::collections::HashMap;
25use std::ffi::OsString;
26use std::str::FromStr;
27use std::sync::{OnceLock, RwLock};
28
29static OVERRIDES: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
30
31fn map() -> &'static RwLock<HashMap<String, String>> {
32 OVERRIDES.get_or_init(|| RwLock::new(HashMap::new()))
33}
34
35pub fn normalize_key(key: &str) -> String {
37 if key.starts_with("RLX_") {
38 key.to_string()
39 } else {
40 format!("RLX_{key}")
41 }
42}
43
44pub fn set(key: impl AsRef<str>, value: impl Into<String>) {
47 let key = normalize_key(key.as_ref());
48 if let Ok(mut g) = map().write() {
49 g.insert(key, value.into());
50 }
51}
52
53pub fn unset(key: impl AsRef<str>) {
55 let key = normalize_key(key.as_ref());
56 if let Ok(mut g) = map().write() {
57 g.remove(&key);
58 }
59}
60
61pub fn clear_overrides() {
63 if let Ok(mut g) = map().write() {
64 g.clear();
65 }
66}
67
68pub fn var(key: &str) -> Option<String> {
70 let key = normalize_key(key);
71 if let Ok(g) = map().read() {
72 if let Some(v) = g.get(&key) {
73 return Some(v.clone());
74 }
75 }
76 std::env::var(&key).ok()
77}
78
79pub fn var_os(key: &str) -> Option<OsString> {
89 var(key).map(Into::into)
90}
91
92pub fn flag(key: &str) -> bool {
95 match var(key) {
96 Some(v) => truthy(&v),
97 None => false,
98 }
99}
100
101pub fn flag_or(key: &str, default: bool) -> bool {
104 match var(key) {
105 Some(v) => truthy(&v),
106 None => default,
107 }
108}
109
110pub fn is_unset(key: &str) -> bool {
112 var(key).is_none()
113}
114
115#[must_use = "the caller must actually return when this is true, or the test runs without a device"]
142pub fn skip_unless_device(label: &str, compiled: bool, available: bool) -> bool {
143 if available {
144 return false;
145 }
146 assert!(
147 !flag("RLX_REQUIRE_DEVICE") || !compiled,
148 "RLX_REQUIRE_DEVICE=1, the `{label}` backend is compiled in, and it still \
149 could not be instantiated — this run would have reported `ok` without \
150 executing anything on it"
151 );
152 if compiled {
153 eprintln!("{label} unavailable on this host — skipping");
154 }
155 true
156}
157
158pub fn parse_or<T: FromStr>(key: &str, default: T) -> T {
160 var(key).and_then(|s| s.parse().ok()).unwrap_or(default)
161}
162
163fn truthy(v: &str) -> bool {
164 let s = v.trim();
165 if s.is_empty() {
166 return false;
167 }
168 match s.to_ascii_lowercase().as_str() {
169 "0" | "false" | "off" | "no" => false,
170 "1" | "true" | "yes" | "on" => true,
171 _ if s.chars().all(|c| c.is_ascii_digit()) => s != "0",
172 _ => true, }
174}
175
176#[derive(Debug, Clone, Default)]
178pub struct RlxEnv {
179 pairs: Vec<(String, String)>,
180}
181
182impl RlxEnv {
183 pub fn new() -> Self {
184 Self::default()
185 }
186
187 pub fn set(mut self, key: impl AsRef<str>, value: impl Into<String>) -> Self {
188 self.pairs.push((normalize_key(key.as_ref()), value.into()));
189 self
190 }
191
192 pub fn flag(mut self, key: impl AsRef<str>, on: bool) -> Self {
193 self.pairs.push((
194 normalize_key(key.as_ref()),
195 if on { "1" } else { "0" }.into(),
196 ));
197 self
198 }
199
200 pub fn apply(self) {
202 for (k, v) in self.pairs {
203 set(&k, v);
204 }
205 }
206}
207
208pub struct RuntimeOverrides {
210 saved: Vec<(String, Option<String>)>,
211}
212
213impl RuntimeOverrides {
214 pub fn install(pairs: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>) -> Self {
216 let mut saved = Vec::new();
217 for (key, value) in pairs {
218 let key = normalize_key(key.as_ref());
219 let prev = map().read().ok().and_then(|g| g.get(&key).cloned());
220 saved.push((key.clone(), prev));
221 set(&key, value);
222 }
223 Self { saved }
224 }
225}
226
227impl Drop for RuntimeOverrides {
228 fn drop(&mut self) {
229 for (key, prev) in self.saved.drain(..) {
230 match prev {
231 Some(v) => set(&key, v),
232 None => unset(&key),
233 }
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use std::sync::Mutex;
242
243 static ENV_TEST_LOCK: Mutex<()> = Mutex::new(());
245
246 fn with_clean_overrides(f: impl FnOnce()) {
247 let _guard = ENV_TEST_LOCK.lock().expect("env test lock poisoned");
248 clear_overrides();
249 f();
250 clear_overrides();
251 }
252
253 #[test]
254 fn code_override_wins_over_process_env() {
255 with_clean_overrides(|| {
256 let _g = RuntimeOverrides::install([("VERBOSE", "2")]);
257 assert_eq!(var("RLX_VERBOSE"), Some("2".into()));
258 assert!(flag("RLX_VERBOSE"));
259 });
260 }
261
262 #[test]
263 fn flag_parses_falsy_override() {
264 with_clean_overrides(|| {
265 set("RLX_DISABLE_MPSGRAPH", "0");
266 assert!(!flag("RLX_DISABLE_MPSGRAPH"));
267 });
268 }
269
270 #[test]
271 fn rlx_env_bulk_apply() {
272 with_clean_overrides(|| {
273 RlxEnv::new()
274 .set("MPSGRAPH_MIN_FLOPS", "42")
275 .flag("USE_ICB", true)
276 .apply();
277 assert_eq!(parse_or("RLX_MPSGRAPH_MIN_FLOPS", 0u64), 42);
278 assert!(flag("RLX_USE_ICB"));
279 });
280 }
281}