1#![allow(deprecated)] use std::collections::hash_map::{Entry, HashMap};
4use std::env;
5use std::hash::{Hash, Hasher, SipHasher};
6use std::path::{Path, PathBuf};
7use std::sync::Mutex;
8
9use log::{debug, info, warn};
10use serde::{Deserialize, Serialize};
11
12use crate::core::InternedString;
13use crate::util::paths;
14use crate::util::{self, profile, CargoResult, CargoResultExt, ProcessBuilder};
15
16#[derive(Debug)]
18pub struct Rustc {
19 pub path: PathBuf,
21 pub wrapper: Option<PathBuf>,
24 pub workspace_wrapper: Option<PathBuf>,
26 pub verbose_version: String,
28 pub host: InternedString,
30 cache: Mutex<Cache>,
31}
32
33impl Rustc {
34 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 host = {
55 let triple = verbose_version
56 .lines()
57 .find(|l| l.starts_with("host: "))
58 .map(|l| &l[6..])
59 .ok_or_else(|| {
60 anyhow::format_err!(
61 "`rustc -vV` didn't have a line for `host:`, got:\n{}",
62 verbose_version
63 )
64 })?;
65 InternedString::new(triple)
66 };
67
68 Ok(Rustc {
69 path,
70 wrapper,
71 workspace_wrapper,
72 verbose_version,
73 host,
74 cache: Mutex::new(cache),
75 })
76 }
77
78 pub fn process(&self) -> ProcessBuilder {
80 util::process(self.path.as_path()).wrapped(self.wrapper.as_ref())
81 }
82
83 pub fn workspace_process(&self) -> ProcessBuilder {
85 util::process(self.path.as_path())
86 .wrapped(self.workspace_wrapper.as_ref())
87 .wrapped(self.wrapper.as_ref())
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
99#[derive(Debug)]
108struct Cache {
109 cache_location: Option<PathBuf>,
110 dirty: bool,
111 data: CacheData,
112}
113
114#[derive(Serialize, Deserialize, Debug, Default)]
115struct CacheData {
116 rustc_fingerprint: u64,
117 outputs: HashMap<u64, (String, String)>,
118 successes: HashMap<u64, bool>,
119}
120
121impl Cache {
122 fn load(rustc: &Path, rustup_rustc: &Path, cache_location: Option<PathBuf>) -> Cache {
123 match (cache_location, rustc_fingerprint(rustc, rustup_rustc)) {
124 (Some(cache_location), Ok(rustc_fingerprint)) => {
125 let empty = CacheData {
126 rustc_fingerprint,
127 outputs: HashMap::new(),
128 successes: HashMap::new(),
129 };
130 let mut dirty = true;
131 let data = match read(&cache_location) {
132 Ok(data) => {
133 if data.rustc_fingerprint == rustc_fingerprint {
134 debug!("reusing existing rustc info cache");
135 dirty = false;
136 data
137 } else {
138 debug!("different compiler, creating new rustc info cache");
139 empty
140 }
141 }
142 Err(e) => {
143 debug!("failed to read rustc info cache: {}", e);
144 empty
145 }
146 };
147 return Cache {
148 cache_location: Some(cache_location),
149 dirty,
150 data,
151 };
152
153 fn read(path: &Path) -> CargoResult<CacheData> {
154 let json = paths::read(path)?;
155 Ok(serde_json::from_str(&json)?)
156 }
157 }
158 (_, fingerprint) => {
159 if let Err(e) = fingerprint {
160 warn!("failed to calculate rustc fingerprint: {}", e);
161 }
162 debug!("rustc info cache disabled");
163 Cache {
164 cache_location: None,
165 dirty: false,
166 data: CacheData::default(),
167 }
168 }
169 }
170 }
171
172 fn cached_output(&mut self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> {
173 let key = process_fingerprint(cmd);
174 match self.data.outputs.entry(key) {
175 Entry::Occupied(entry) => {
176 debug!("rustc info cache hit");
177 Ok(entry.get().clone())
178 }
179 Entry::Vacant(entry) => {
180 debug!("rustc info cache miss");
181 debug!("running {}", cmd);
182 let output = cmd.exec_with_output()?;
183 let stdout = String::from_utf8(output.stdout)
184 .map_err(|e| anyhow::anyhow!("{}: {:?}", e, e.as_bytes()))
185 .chain_err(|| anyhow::anyhow!("`{}` didn't return utf8 output", cmd))?;
186 let stderr = String::from_utf8(output.stderr)
187 .map_err(|e| anyhow::anyhow!("{}: {:?}", e, e.as_bytes()))
188 .chain_err(|| anyhow::anyhow!("`{}` didn't return utf8 output", cmd))?;
189 let output = (stdout, stderr);
190 entry.insert(output.clone());
191 self.dirty = true;
192 Ok(output)
193 }
194 }
195 }
196}
197
198impl Drop for Cache {
199 fn drop(&mut self) {
200 if !self.dirty {
201 return;
202 }
203 if let Some(ref path) = self.cache_location {
204 let json = serde_json::to_string(&self.data).unwrap();
205 match paths::write(path, json.as_bytes()) {
206 Ok(()) => info!("updated rustc info cache"),
207 Err(e) => warn!("failed to update rustc info cache: {}", e),
208 }
209 }
210 }
211}
212
213fn rustc_fingerprint(path: &Path, rustup_rustc: &Path) -> CargoResult<u64> {
214 let mut hasher = SipHasher::new();
215
216 let path = paths::resolve_executable(path)?;
217 path.hash(&mut hasher);
218
219 paths::mtime(&path)?.hash(&mut hasher);
220
221 let maybe_rustup = rustup_rustc == path;
233 match (
234 maybe_rustup,
235 env::var("RUSTUP_HOME"),
236 env::var("RUSTUP_TOOLCHAIN"),
237 ) {
238 (_, Ok(rustup_home), Ok(rustup_toolchain)) => {
239 debug!("adding rustup info to rustc fingerprint");
240 rustup_toolchain.hash(&mut hasher);
241 rustup_home.hash(&mut hasher);
242 let real_rustc = Path::new(&rustup_home)
243 .join("toolchains")
244 .join(rustup_toolchain)
245 .join("bin")
246 .join("rustc")
247 .with_extension(env::consts::EXE_EXTENSION);
248 paths::mtime(&real_rustc)?.hash(&mut hasher);
249 }
250 (true, _, _) => anyhow::bail!("probably rustup rustc, but without rustup's env vars"),
251 _ => (),
252 }
253
254 Ok(hasher.finish())
255}
256
257fn process_fingerprint(cmd: &ProcessBuilder) -> u64 {
258 let mut hasher = SipHasher::new();
259 cmd.get_args().hash(&mut hasher);
260 let mut env = cmd.get_envs().iter().collect::<Vec<_>>();
261 env.sort_unstable();
262 env.hash(&mut hasher);
263 hasher.finish()
264}