]> git.proxmox.com Git - cargo.git/blob - src/cargo/util/rustc.rs
Move string interning to util
[cargo.git] / src / cargo / util / rustc.rs
1 use std::collections::hash_map::{Entry, HashMap};
2 use std::env;
3 use std::hash::{Hash, Hasher};
4 use std::path::{Path, PathBuf};
5 use std::sync::Mutex;
6
7 use log::{debug, info, warn};
8 use serde::{Deserialize, Serialize};
9
10 use crate::util::interning::InternedString;
11 use crate::util::paths;
12 use crate::util::{self, profile, CargoResult, CargoResultExt, ProcessBuilder, StableHasher};
13
14 /// Information on the `rustc` executable
15 #[derive(Debug)]
16 pub struct Rustc {
17 /// The location of the exe
18 pub path: PathBuf,
19 /// An optional program that will be passed the path of the rust exe as its first argument, and
20 /// rustc args following this.
21 pub wrapper: Option<PathBuf>,
22 /// An optional wrapper to be used in addition to `rustc.wrapper` for workspace crates
23 pub workspace_wrapper: Option<PathBuf>,
24 /// Verbose version information (the output of `rustc -vV`)
25 pub verbose_version: String,
26 /// The rustc version (`1.23.4-beta.2`), this comes from verbose_version.
27 pub version: semver::Version,
28 /// The host triple (arch-platform-OS), this comes from verbose_version.
29 pub host: InternedString,
30 cache: Mutex<Cache>,
31 }
32
33 impl Rustc {
34 /// Runs the compiler at `path` to learn various pieces of information about
35 /// it, with an optional wrapper.
36 ///
37 /// If successful this function returns a description of the compiler along
38 /// with a list of its capabilities.
39 pub fn new(
40 path: PathBuf,
41 wrapper: Option<PathBuf>,
42 workspace_wrapper: Option<PathBuf>,
43 rustup_rustc: &Path,
44 cache_location: Option<PathBuf>,
45 ) -> CargoResult<Rustc> {
46 let _p = profile::start("Rustc::new");
47
48 let mut cache = Cache::load(&path, rustup_rustc, cache_location);
49
50 let mut cmd = util::process(&path);
51 cmd.arg("-vV");
52 let verbose_version = cache.cached_output(&cmd)?.0;
53
54 let extract = |field: &str| -> CargoResult<&str> {
55 verbose_version
56 .lines()
57 .find(|l| l.starts_with(field))
58 .map(|l| &l[field.len()..])
59 .ok_or_else(|| {
60 anyhow::format_err!(
61 "`rustc -vV` didn't have a line for `{}`, got:\n{}",
62 field.trim(),
63 verbose_version
64 )
65 })
66 };
67
68 let host = InternedString::new(extract("host: ")?);
69 let version = semver::Version::parse(extract("release: ")?).chain_err(|| {
70 format!(
71 "rustc version does not appear to be a valid semver version, from:\n{}",
72 verbose_version
73 )
74 })?;
75
76 Ok(Rustc {
77 path,
78 wrapper,
79 workspace_wrapper,
80 verbose_version,
81 version,
82 host,
83 cache: Mutex::new(cache),
84 })
85 }
86
87 /// Gets a process builder set up to use the found rustc version, with a wrapper if `Some`.
88 pub fn process(&self) -> ProcessBuilder {
89 util::process(self.path.as_path()).wrapped(self.wrapper.as_ref())
90 }
91
92 /// Gets a process builder set up to use the found rustc version, with a wrapper if `Some`.
93 pub fn workspace_process(&self) -> ProcessBuilder {
94 util::process(self.path.as_path())
95 .wrapped(self.workspace_wrapper.as_ref())
96 .wrapped(self.wrapper.as_ref())
97 }
98
99 pub fn process_no_wrapper(&self) -> ProcessBuilder {
100 util::process(&self.path)
101 }
102
103 pub fn cached_output(&self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> {
104 self.cache.lock().unwrap().cached_output(cmd)
105 }
106 }
107
108 /// It is a well known fact that `rustc` is not the fastest compiler in the
109 /// world. What is less known is that even `rustc --version --verbose` takes
110 /// about a hundred milliseconds! Because we need compiler version info even
111 /// for no-op builds, we cache it here, based on compiler's mtime and rustup's
112 /// current toolchain.
113 ///
114 /// https://github.com/rust-lang/cargo/issues/5315
115 /// https://github.com/rust-lang/rust/issues/49761
116 #[derive(Debug)]
117 struct Cache {
118 cache_location: Option<PathBuf>,
119 dirty: bool,
120 data: CacheData,
121 }
122
123 #[derive(Serialize, Deserialize, Debug, Default)]
124 struct CacheData {
125 rustc_fingerprint: u64,
126 outputs: HashMap<u64, (String, String)>,
127 successes: HashMap<u64, bool>,
128 }
129
130 impl Cache {
131 fn load(rustc: &Path, rustup_rustc: &Path, cache_location: Option<PathBuf>) -> Cache {
132 match (cache_location, rustc_fingerprint(rustc, rustup_rustc)) {
133 (Some(cache_location), Ok(rustc_fingerprint)) => {
134 let empty = CacheData {
135 rustc_fingerprint,
136 outputs: HashMap::new(),
137 successes: HashMap::new(),
138 };
139 let mut dirty = true;
140 let data = match read(&cache_location) {
141 Ok(data) => {
142 if data.rustc_fingerprint == rustc_fingerprint {
143 debug!("reusing existing rustc info cache");
144 dirty = false;
145 data
146 } else {
147 debug!("different compiler, creating new rustc info cache");
148 empty
149 }
150 }
151 Err(e) => {
152 debug!("failed to read rustc info cache: {}", e);
153 empty
154 }
155 };
156 return Cache {
157 cache_location: Some(cache_location),
158 dirty,
159 data,
160 };
161
162 fn read(path: &Path) -> CargoResult<CacheData> {
163 let json = paths::read(path)?;
164 Ok(serde_json::from_str(&json)?)
165 }
166 }
167 (_, fingerprint) => {
168 if let Err(e) = fingerprint {
169 warn!("failed to calculate rustc fingerprint: {}", e);
170 }
171 debug!("rustc info cache disabled");
172 Cache {
173 cache_location: None,
174 dirty: false,
175 data: CacheData::default(),
176 }
177 }
178 }
179 }
180
181 fn cached_output(&mut self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> {
182 let key = process_fingerprint(cmd);
183 match self.data.outputs.entry(key) {
184 Entry::Occupied(entry) => {
185 debug!("rustc info cache hit");
186 Ok(entry.get().clone())
187 }
188 Entry::Vacant(entry) => {
189 debug!("rustc info cache miss");
190 debug!("running {}", cmd);
191 let output = cmd.exec_with_output()?;
192 let stdout = String::from_utf8(output.stdout)
193 .map_err(|e| anyhow::anyhow!("{}: {:?}", e, e.as_bytes()))
194 .chain_err(|| anyhow::anyhow!("`{}` didn't return utf8 output", cmd))?;
195 let stderr = String::from_utf8(output.stderr)
196 .map_err(|e| anyhow::anyhow!("{}: {:?}", e, e.as_bytes()))
197 .chain_err(|| anyhow::anyhow!("`{}` didn't return utf8 output", cmd))?;
198 let output = (stdout, stderr);
199 entry.insert(output.clone());
200 self.dirty = true;
201 Ok(output)
202 }
203 }
204 }
205 }
206
207 impl Drop for Cache {
208 fn drop(&mut self) {
209 if !self.dirty {
210 return;
211 }
212 if let Some(ref path) = self.cache_location {
213 let json = serde_json::to_string(&self.data).unwrap();
214 match paths::write(path, json.as_bytes()) {
215 Ok(()) => info!("updated rustc info cache"),
216 Err(e) => warn!("failed to update rustc info cache: {}", e),
217 }
218 }
219 }
220 }
221
222 fn rustc_fingerprint(path: &Path, rustup_rustc: &Path) -> CargoResult<u64> {
223 let mut hasher = StableHasher::new();
224
225 let path = paths::resolve_executable(path)?;
226 path.hash(&mut hasher);
227
228 paths::mtime(&path)?.hash(&mut hasher);
229
230 // Rustup can change the effective compiler without touching
231 // the `rustc` binary, so we try to account for this here.
232 // If we see rustup's env vars, we mix them into the fingerprint,
233 // but we also mix in the mtime of the actual compiler (and not
234 // the rustup shim at `~/.cargo/bin/rustup`), because `RUSTUP_TOOLCHAIN`
235 // could be just `stable-x86_64-unknown-linux-gnu`, i.e, it could
236 // not mention the version of Rust at all, which changes after
237 // `rustup update`.
238 //
239 // If we don't see rustup env vars, but it looks like the compiler
240 // is managed by rustup, we conservatively bail out.
241 let maybe_rustup = rustup_rustc == path;
242 match (
243 maybe_rustup,
244 env::var("RUSTUP_HOME"),
245 env::var("RUSTUP_TOOLCHAIN"),
246 ) {
247 (_, Ok(rustup_home), Ok(rustup_toolchain)) => {
248 debug!("adding rustup info to rustc fingerprint");
249 rustup_toolchain.hash(&mut hasher);
250 rustup_home.hash(&mut hasher);
251 let real_rustc = Path::new(&rustup_home)
252 .join("toolchains")
253 .join(rustup_toolchain)
254 .join("bin")
255 .join("rustc")
256 .with_extension(env::consts::EXE_EXTENSION);
257 paths::mtime(&real_rustc)?.hash(&mut hasher);
258 }
259 (true, _, _) => anyhow::bail!("probably rustup rustc, but without rustup's env vars"),
260 _ => (),
261 }
262
263 Ok(hasher.finish())
264 }
265
266 fn process_fingerprint(cmd: &ProcessBuilder) -> u64 {
267 let mut hasher = StableHasher::new();
268 cmd.get_args().hash(&mut hasher);
269 let mut env = cmd.get_envs().iter().collect::<Vec<_>>();
270 env.sort_unstable();
271 env.hash(&mut hasher);
272 hasher.finish()
273 }