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