1use std::borrow::ToOwned;
2use std::collections::BTreeSet;
3use std::env;
4use std::ffi::{OsStr, OsString};
5use std::{any, error, fmt};
6
7#[derive(Debug, Clone)]
13pub struct BuildEnv {
14 target: String,
18 host: String,
19
20 used_env_vars: BTreeSet<OsString>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum VarErrorKind {
27 NotString(OsString),
28 RequiredEnvMissing(env::VarError),
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct VarError<K: AsRef<OsStr>> {
34 key: K,
35 kind: VarErrorKind,
36}
37
38impl<K: AsRef<OsStr>> fmt::Display for VarError<K> {
39 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
40 match self.kind {
41 VarErrorKind::NotString(ref x) => write!(
42 fmt,
43 "Variable {:?} was found, but is not utf-8: {:?}",
44 self.key.as_ref(),
45 x
46 ),
47 VarErrorKind::RequiredEnvMissing(ref x) => write!(
48 fmt,
49 "Variable {:?} is required, but retrival failed: {}",
50 self.key.as_ref(),
51 x
52 ),
53 }
54 }
55}
56
57impl<K: AsRef<OsStr> + fmt::Debug + any::Any> error::Error for VarError<K> {
58 fn description(&self) -> &str {
59 match self.kind {
60 VarErrorKind::NotString(_) => "found but not utf-8",
61 VarErrorKind::RequiredEnvMissing(_) => "other required env var missing",
62 }
63 }
64}
65
66fn required_env_var(key: &str) -> Result<String, VarError<String>> {
67 env::var(key).map_err(|e| VarError {
68 key: key.to_owned(),
69 kind: VarErrorKind::RequiredEnvMissing(e),
70 })
71}
72
73impl BuildEnv {
74 pub fn from_env() -> Result<BuildEnv, VarError<String>> {
79 let target = required_env_var("TARGET")?;
82 let host = required_env_var("HOST")?;
83
84 Ok(BuildEnv {
85 target,
86 host,
87 used_env_vars: Default::default(),
88 })
89 }
90
91 pub fn new_cross(host: String, target: String) -> BuildEnv {
95 BuildEnv {
96 host,
97 target,
98 used_env_vars: Default::default(),
99 }
100 }
101
102 pub fn new(trip: String) -> BuildEnv {
106 BuildEnv {
107 host: trip.clone(),
108 target: trip,
109 used_env_vars: Default::default(),
110 }
111 }
112
113 pub fn target(&self) -> &str {
117 &self.target
118 }
119
120 pub fn host(&self) -> &str {
124 &self.host
125 }
126
127 pub fn used_env_vars(&self) -> impl Iterator<Item = &OsString> {
130 self.used_env_vars.iter()
131 }
132
133 pub fn cargo_print_used_env_vars(&self) {
135 for used in self.used_env_vars() {
136 println!("cargo:rerun-if-env-changed={}", used.to_str().unwrap());
138 }
139 }
140
141 pub fn mark_used(&mut self, var: OsString) {
142 println!(
143 "cargo:rerun-if-env-changed={}",
144 var.to_str().expect("tried to examine non-utf-8 variable")
145 );
146 self.used_env_vars.insert(var);
147 }
148
149 fn env_one(&mut self, var: OsString) -> Option<OsString> {
150 let v = env::var_os(&var);
151 self.mark_used(var);
152 v
153 }
154
155 pub fn var<K: AsRef<OsStr>>(&mut self, var_base: K) -> Option<OsString> {
163 let target = self.target();
165 let host = self.host();
166 let kind = if host == target { "HOST" } else { "TARGET" };
167 let target_u = target.replace("-", "_");
168 let mut a: OsString = var_base.as_ref().to_owned();
169 a.push("_");
170
171 let mut b = a.clone();
172
173 a.push(target);
174 b.push(target_u);
175
176 let mut c: OsString = AsRef::<OsStr>::as_ref(kind).to_owned();
177 c.push("_");
178 c.push(&var_base);
179
180 self.env_one(a)
181 .or_else(|| self.env_one(b))
182 .or_else(|| self.env_one(c))
183 .or_else(|| self.env_one(var_base.as_ref().to_owned()))
184 }
185
186 pub fn var_str<K: AsRef<OsStr> + fmt::Debug + any::Any>(
189 &mut self,
190 var_base: K,
191 ) -> Option<Result<String, VarError<K>>> {
192 self.var(&var_base).map(|v| {
193 v.into_string().map_err(|o| VarError {
194 key: var_base,
195 kind: VarErrorKind::NotString(o),
196 })
197 })
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::BuildEnv;
204 use std::env;
205
206 fn clear(trip: &str, var: &[&str]) {
207 for v in var {
208 env::remove_var(&format!("HOST_{}", v));
209 env::remove_var(&format!("TARGET_{}", v));
210 env::remove_var(&format!("{}_{}", v, trip));
211 env::remove_var(&format!("{}_{}", v, trip.replace("-", "_")));
212 env::remove_var(v);
213 }
214 }
215
216 fn most_general() {
217 let t = "this-is-a-target";
218 let cc = "a-cc-value";
219 clear(t, &["CC"]);
220 env::set_var("CC", cc);
221
222 let mut b = BuildEnv::new(t.to_owned());
223
224 assert_eq!(b.var_str("CC"), Some(Ok(cc.to_owned())));
225 let used_env_vars: Vec<_> = b.used_env_vars().collect();
226 assert_eq!(
227 &used_env_vars[..],
228 [
229 "CC",
230 "CC_this-is-a-target",
231 "CC_this_is_a_target",
232 "HOST_CC"
233 ]
234 );
235 clear(t, &["CC"]);
236 }
237
238 fn exact_target() {
239 let t = "this-is-a-target";
240 let cc = "a-cc-value";
241 clear(t, &["CC"]);
242
243 env::set_var("CC", "notThis");
244 env::set_var("HOST_CC", "not-this");
245 env::set_var(format!("CC_{}", t), cc);
246
247 let mut b = BuildEnv::new(t.to_owned());
248
249 assert_eq!(b.var_str("CC"), Some(Ok(cc.to_owned())));
250 let used_env_vars: Vec<_> = b.used_env_vars().collect();
251 assert_eq!(&used_env_vars[..], ["CC_this-is-a-target"]);
252 clear(t, &["CC"]);
253 }
254
255 fn underscore_target() {
256 let t = "this-is-a-target";
257 let cc = "a-cc-value";
258 clear(t, &["CC"]);
259
260 env::set_var("CC", "notThis");
261 env::set_var("HOST_CC", "not-this");
262 env::set_var("CC_this_is_a_target", cc);
263
264 let mut b = BuildEnv::new(t.to_owned());
265
266 assert_eq!(b.var_str("CC"), Some(Ok(cc.to_owned())));
267 let used_env_vars: Vec<_> = b.used_env_vars().collect();
268 assert_eq!(
269 &used_env_vars[..],
270 ["CC_this-is-a-target", "CC_this_is_a_target"]
271 );
272 clear(t, &["CC"]);
273 }
274
275 fn v_host() {
276 let t = "this-is-a-target";
277 let cc = "a-cc-value";
278 clear(t, &["CC"]);
279
280 env::set_var("CC", "not-this-value");
281 env::set_var("HOST_CC", cc);
282
283 let mut b = BuildEnv::new(t.to_owned());
284
285 assert_eq!(b.var_str("CC"), Some(Ok(cc.to_owned())));
286 let used_env_vars: Vec<_> = b.used_env_vars().collect();
287 assert_eq!(
288 &used_env_vars[..],
289 ["CC_this-is-a-target", "CC_this_is_a_target", "HOST_CC"]
290 );
291 clear(t, &["CC"]);
292 }
293
294 fn v_target() {
295 let t = "this-is-a-target";
296 let t2 = "some-target";
297 let cc = "a-cc-value";
298 clear(t, &["CC"]);
299 clear(t2, &["CC"]);
300
301 env::set_var("CC", "not-this-value");
302 env::set_var("HOST_CC", "not this!");
303 env::set_var("TARGET_CC", cc);
304 env::set_var(format!("CC_{}", t), "not this either");
305
306 let mut b = BuildEnv::new_cross(t.to_owned(), t2.to_owned());
307
308 assert_eq!(b.var_str("CC"), Some(Ok(cc.to_owned())));
309 let used_env_vars: Vec<_> = b.used_env_vars().collect();
310 assert_eq!(
311 &used_env_vars[..],
312 ["CC_some-target", "CC_some_target", "TARGET_CC"]
313 );
314 clear(t, &["CC"]);
315 }
316
317 #[test]
324 fn all() {
325 most_general();
326 exact_target();
327 underscore_target();
328 v_host();
329 v_target();
330 }
331}