turbo_tasks_env/
dotenv.rs

1use std::{env, sync::MutexGuard};
2
3use anyhow::{Context, Result, anyhow};
4use turbo_rcstr::RcStr;
5use turbo_tasks::{FxIndexMap, ReadRef, ResolvedVc, ValueToString, Vc};
6use turbo_tasks_fs::{FileContent, FileSystemPath};
7
8use crate::{EnvMap, GLOBAL_ENV_LOCK, ProcessEnv, sorted_env_vars};
9
10/// Load the environment variables defined via a dotenv file, with an
11/// optional prior state that we can lookup already defined variables
12/// from.
13#[turbo_tasks::value]
14pub struct DotenvProcessEnv {
15    prior: Option<ResolvedVc<Box<dyn ProcessEnv>>>,
16    path: ResolvedVc<FileSystemPath>,
17}
18
19#[turbo_tasks::value_impl]
20impl DotenvProcessEnv {
21    #[turbo_tasks::function]
22    pub fn new(
23        prior: Option<ResolvedVc<Box<dyn ProcessEnv>>>,
24        path: ResolvedVc<FileSystemPath>,
25    ) -> Vc<Self> {
26        DotenvProcessEnv { prior, path }.cell()
27    }
28
29    #[turbo_tasks::function]
30    pub fn read_prior(&self) -> Vc<EnvMap> {
31        match self.prior {
32            None => EnvMap::empty(),
33            Some(p) => p.read_all(),
34        }
35    }
36
37    #[turbo_tasks::function]
38    pub async fn read_all_with_prior(self: Vc<Self>, prior: Vc<EnvMap>) -> Result<Vc<EnvMap>> {
39        let this = self.await?;
40        let prior = prior.await?;
41
42        let file = this.path.read().await?;
43        if let FileContent::Content(f) = &*file {
44            let res;
45            let vars;
46            {
47                let lock = GLOBAL_ENV_LOCK.lock().unwrap();
48
49                // Unfortunately, dotenvy only looks up variable references from the global env.
50                // So we must mutate while we process. Afterwards, we can restore the initial
51                // state.
52                let initial = sorted_env_vars();
53
54                restore_env(&initial, &prior, &lock);
55
56                // from_read will load parse and evalute the Read, and set variables
57                // into the global env. If a later dotenv defines an already defined
58                // var, it'll be ignored.
59                res = dotenv::from_read(f.read()).map(|e| e.load());
60
61                vars = sorted_env_vars();
62                restore_env(&vars, &initial, &lock);
63            }
64
65            if let Err(e) = res {
66                return Err(e).context(anyhow!(
67                    "unable to read {} for env vars",
68                    this.path.to_string().await?
69                ));
70            }
71
72            Ok(Vc::cell(vars))
73        } else {
74            // We want to cell the value here and not just return the Vc.
75            // This is important to avoid Vc changes when adding/removing the env file.
76            Ok(ReadRef::cell(prior))
77        }
78    }
79}
80
81#[turbo_tasks::value_impl]
82impl ProcessEnv for DotenvProcessEnv {
83    #[turbo_tasks::function]
84    fn read_all(self: Vc<Self>) -> Vc<EnvMap> {
85        let prior = self.read_prior();
86        self.read_all_with_prior(prior)
87    }
88}
89
90/// Restores the global env variables to mirror `to`.
91fn restore_env(
92    from: &FxIndexMap<RcStr, RcStr>,
93    to: &FxIndexMap<RcStr, RcStr>,
94    _lock: &MutexGuard<()>,
95) {
96    for key in from.keys() {
97        if !to.contains_key(key) {
98            unsafe { env::remove_var(key) };
99        }
100    }
101    for (key, value) in to {
102        match from.get(key) {
103            Some(v) if v == value => {}
104            _ => unsafe { env::set_var(key, value) },
105        }
106    }
107}