1use self::EnvError as E;
19use crate::utils::files;
20use color_eyre::eyre::bail;
21use color_eyre::eyre::eyre;
22use color_eyre::eyre::Result;
23use displaydoc::Display;
24use std::collections::HashMap;
25use thiserror::Error;
26use tracing::debug;
27use tracing::trace;
28
29pub const UP_HARDWARE_UUID: &str = "UP_HARDWARE_UUID";
32
33#[allow(clippy::implicit_hasher)]
37pub fn get_env(
38 inherit_env: Option<&Vec<String>>,
39 input_env: Option<&HashMap<String, String>>,
40) -> Result<HashMap<String, String>> {
41 let mut env: HashMap<String, String> = HashMap::new();
42 if let Some(inherited_env) = inherit_env {
43 for inherited_var in inherited_env {
44 if let Ok(value) = std::env::var(inherited_var) {
45 env.insert(inherited_var.clone(), value);
46 }
47 }
48 }
49
50 add_builtin_env_vars(&mut env)?;
51
52 let mut unresolved_env = Vec::new();
53
54 if let Some(config_env) = input_env {
55 trace!("Provided env: {config_env:#?}");
56 let mut calculated_env = HashMap::new();
57 let home_dir = files::home_dir()?;
58 for (key, val) in config_env {
59 calculated_env.insert(
60 key.clone(),
61 shellexpand::full_with_context(
62 val,
63 || Some(&home_dir),
64 |k| {
65 env.get(k).map_or_else(
66 || {
67 if config_env.contains_key(k) {
68 unresolved_env.push(key.clone());
69 Ok(None)
70 } else {
71 Err(eyre!("Value {k} not found in inherited_env or env vars."))
72 }
73 },
74 |val| Ok(Some(val)),
75 )
76 },
77 )
78 .map_err(|e| E::EnvLookup {
79 var: e.var_name,
80 source: e.cause,
81 })?
82 .into_owned(),
83 );
84 }
85 for (k, v) in calculated_env.drain() {
86 env.insert(k, v);
87 }
88 }
89
90 debug!("Unresolved env: {unresolved_env:?}");
92 while !unresolved_env.is_empty() {
93 trace!("Env so far: {env:#?}");
94 trace!("Still unresolved env: {unresolved_env:#?}");
95 let mut resolved_indices = Vec::new();
96 for (index, key) in unresolved_env.iter().enumerate() {
97 let val = env.get(key).ok_or_else(|| eyre!("How did we get here?"))?;
98 let resolved_val = shellexpand::env_with_context(val, |k| {
99 if unresolved_env.iter().any(|s| s == k) {
100 Ok(None)
101 } else if let Some(v) = env.get(k) {
102 resolved_indices.push(index);
103 Ok(Some(v))
104 } else {
105 Err(eyre!("Shouldn't be possible to hit this."))
106 }
107 })
108 .map_err(|e| E::EnvLookup {
109 var: e.var_name,
110 source: e.cause,
111 })?
112 .into_owned();
113 let val_ref = env
114 .get_mut(key)
115 .ok_or_else(|| eyre!("How did we get here?"))?;
116 *val_ref = resolved_val;
117 }
118 trace!("resolved indices: {resolved_indices:?}");
119 if resolved_indices.is_empty() {
120 bail!("Errors resolving env, do you have cycles? Unresolved env: {unresolved_env:#?}",);
121 }
122 unresolved_env = unresolved_env
123 .into_iter()
124 .enumerate()
125 .filter_map(|(index, value)| {
126 if resolved_indices.contains(&index) {
127 None
128 } else {
129 Some(value)
130 }
131 })
132 .collect();
133 }
134
135 debug!("Expanded config env: {env:#?}");
136 Ok(env)
137}
138
139fn add_builtin_env_vars(env: &mut HashMap<String, String>) -> Result<()> {
141 env.insert(
142 UP_HARDWARE_UUID.to_owned(),
143 if cfg!(target_os = "macos") {
144 crate::utils::mac::get_hardware_uuid()?
145 } else {
146 String::new()
147 },
148 );
149 Ok(())
150}
151
152#[derive(Error, Debug, Display)]
153pub enum EnvError {
155 EnvLookup {
157 var: String,
159 source: color_eyre::eyre::Error,
161 },
162}