oxdock_process/
serial_cargo_env.rs

1use oxdock_fs::GuardedPath;
2use std::sync::{Mutex, MutexGuard};
3
4static ENV_LOCK: Mutex<()> = Mutex::new(());
5
6/// RAII helper that serializes and scopes mutations of `CARGO_*` environment variables.
7///
8/// Tests that simulate different crate layouts frequently need to tweak `CARGO_MANIFEST_DIR`
9/// and `CARGO_PRIMARY_PACKAGE`. Those environment variables are global, so concurrent mutations
10/// can introduce racy failures. `SerialCargoEnv` acquires a process-wide mutex before mutating
11/// the variables, remembers the previous values, and restores them when dropped so each test can
12/// safely run in isolation.
13pub struct SerialCargoEnv<'a> {
14    _lock: MutexGuard<'a, ()>,
15    prev_manifest: Option<String>,
16    prev_primary: Option<String>,
17}
18
19impl<'a> SerialCargoEnv<'a> {
20    /// Acquire the guard and set the cargo environment to point at `manifest_dir`.
21    ///
22    /// `primary` controls whether `CARGO_PRIMARY_PACKAGE` is set to `"1"` or `"0"`.
23    /// The previous values are restored automatically when the guard drops.
24    pub fn new(manifest_dir: &GuardedPath, primary: bool) -> Self {
25        let lock = ENV_LOCK.lock().expect("environment lock");
26        let prev_manifest = std::env::var("CARGO_MANIFEST_DIR").ok();
27        let prev_primary = std::env::var("CARGO_PRIMARY_PACKAGE").ok();
28        // SAFETY: std::env setters are marked unsafe due to global mutation, but we serialize
29        // access via `ENV_LOCK` to keep mutations ordered and scoped by this guard.
30        unsafe {
31            std::env::set_var("CARGO_MANIFEST_DIR", manifest_dir.as_path());
32            std::env::set_var("CARGO_PRIMARY_PACKAGE", if primary { "1" } else { "0" });
33        }
34        Self {
35            _lock: lock,
36            prev_manifest,
37            prev_primary,
38        }
39    }
40}
41
42impl Drop for SerialCargoEnv<'_> {
43    fn drop(&mut self) {
44        unsafe {
45            if let Some(prev) = &self.prev_manifest {
46                std::env::set_var("CARGO_MANIFEST_DIR", prev);
47            } else {
48                std::env::remove_var("CARGO_MANIFEST_DIR");
49            }
50
51            if let Some(prev) = &self.prev_primary {
52                std::env::set_var("CARGO_PRIMARY_PACKAGE", prev);
53            } else {
54                std::env::remove_var("CARGO_PRIMARY_PACKAGE");
55            }
56        }
57    }
58}
59
60/// Convenience wrapper that constructs a [`SerialCargoEnv`].
61pub fn manifest_env_guard<'a>(manifest_dir: &'a GuardedPath, primary: bool) -> SerialCargoEnv<'a> {
62    SerialCargoEnv::new(manifest_dir, primary)
63}