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