envcache/lib.rs
1//! # 🏴☠️ envcache
2//! The envcache crate (**env**ironment **cache**) lets users cache
3//! environmental variables in a build.rs script, so that subsequent calls to
4//! cargo are not required to be ran with the same variable specified.
5//!
6//! For example, let's assume you have a build.rs that **requires** `SOME_VAR`
7//! to be specified for work. Maybe its a path to some library that lives
8//! outside of the Rust ecosystem (like LLVM):
9//!
10//! ```sh
11//! SOME_VAR=42 cargo test
12//! cargo clippy # Will fail because SOME_VAR is not set
13//! ```
14//!
15//! This would fail because the run to `cargo clippy` requires that it is run
16//! with `SOME_VAR=42`. With the envcache, we can use a `build.rs` that ensures
17//! this will run:
18//!
19//! ```
20//! use envcache;
21//! # std::env::set_var("OUT_DIR", std::env::temp_dir());
22//! let mut envcache = envcache::EnvCache::new();
23//! envcache.cache("SOME_VAR");
24//! ```
25//!
26//! Now if we run this again:
27//!
28//! ```sh
29//! SOME_VAR=42 cargo test
30//! cargo clippy # SOME_VAR will be 42
31//! ```
32//!
33//! You can change a previously set cached variable by simply re-specifying it
34//! on the command line:
35//!
36//! ```sh
37//! SOME_VAR=42 cargo test
38//! SOME_VAR=13 cargo test
39//! cargo test # SOME_VAR will be 13!
40//! ```
41//!
42//! Note that running `cargo clean` will remove any previously cached variables,
43//! so running:
44//!
45//! ```sh
46//! SOME_VAR=42 cargo test
47//! cargo clippy # Will work because we've cached SOME_VAR
48//! cargo clean
49//! cargo test # Will fail because SOME_VAR won't be set
50//! ```
51
52use std::collections::HashMap;
53use std::env;
54use std::fs::*;
55use std::io::{BufReader, Read, Write};
56use std::path::*;
57
58pub struct EnvCache {
59 entries: HashMap<String, String>,
60}
61
62fn cache_path() -> PathBuf {
63 Path::new(&env::var("OUT_DIR").unwrap()).join("envcache.config")
64}
65
66impl EnvCache {
67 /// Create a new `EnvCache`. This only works inside a `build.rs` with
68 /// `OUT_DIR` specified.
69 pub fn new() -> Self {
70 let cache_path = cache_path();
71
72 let mut entries = HashMap::new();
73
74 if let Ok(file) = File::open(cache_path) {
75 let mut f = BufReader::new(file);
76 let mut string = String::new();
77 f.read_to_string(&mut string).unwrap();
78
79 let mut index = 0;
80
81 while index < string.len() {
82 let env_len = usize::from_str_radix(&string[index..index + 8], 16).unwrap();
83 index += 8;
84
85 let env = &string[index..index + env_len];
86 index += env_len;
87
88 let val_len = usize::from_str_radix(&string[index..index + 8], 16).unwrap();
89 index += 8;
90
91 let val = &string[index..index + val_len];
92 index += val_len;
93
94 entries.insert(env.to_string(), val.to_string());
95 }
96 }
97
98 Self { entries }
99 }
100
101 /// Cache a variable `env` into the envcache.
102 ///
103 /// Returns the value of the environment variable.
104 pub fn cache<'a>(&'a mut self, env: &str) -> Option<&'a str> {
105 if let Ok(var) = env::var(env) {
106 self.entries.insert(env.to_string(), var);
107 }
108
109 if let Some(var) = self.entries.get(&env.to_string()) {
110 Some(var)
111 } else {
112 None
113 }
114 }
115}
116
117impl Default for EnvCache {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl Drop for EnvCache {
124 fn drop(&mut self) {
125 let cache_path = cache_path();
126
127 if let Ok(mut file) = File::create(cache_path) {
128 for (key, val) in self.entries.iter() {
129 println!("cargo:rerun-if-env-changed={}", key);
130 println!("cargo:rustc-env={}={}", key, val);
131 write!(file, "{:08x}{}{:08x}{}", key.len(), key, val.len(), val).unwrap();
132 }
133 }
134 }
135}