1 #![allow(deprecated)] // for SipHasher
3 use std
::collections
::hash_map
::{Entry, HashMap}
;
5 use std
::hash
::{Hash, Hasher, SipHasher}
;
6 use std
::path
::{Path, PathBuf}
;
9 use log
::{debug, info, warn}
;
10 use serde
::{Deserialize, Serialize}
;
12 use crate::util
::paths
;
13 use crate::util
::{self, internal, profile, CargoResult, ProcessBuilder}
;
15 /// Information on the `rustc` executable
18 /// The location of the exe
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.
31 /// Runs the compiler at `path` to learn various pieces of information about
32 /// it, with an optional wrapper.
34 /// If successful this function returns a description of the compiler along
35 /// with a list of its capabilities.
38 wrapper
: Option
<PathBuf
>,
40 cache_location
: Option
<PathBuf
>,
41 ) -> CargoResult
<Rustc
> {
42 let _p
= profile
::start("Rustc::new");
44 let mut cache
= Cache
::load(&path
, rustup_rustc
, cache_location
);
46 let mut cmd
= util
::process(&path
);
48 let verbose_version
= cache
.cached_output(&cmd
)?
.0;
51 let triple
= verbose_version
53 .find(|l
| l
.starts_with("host: "))
57 "`rustc -vV` didn't have a line for `host:`, got:\n{}",
66 wrapper
: wrapper
.map(util
::process
),
69 cache
: Mutex
::new(cache
),
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
{
76 Some(ref wrapper
) if !wrapper
.get_program().is_empty() => {
77 let mut cmd
= wrapper
.clone();
78 cmd
.arg(path
.as_ref());
81 _
=> util
::process(path
.as_ref()),
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
)
90 pub fn process_no_wrapper(&self) -> ProcessBuilder
{
91 util
::process(&self.path
)
94 pub fn cached_output(&self, cmd
: &ProcessBuilder
) -> CargoResult
<(String
, String
)> {
95 self.cache
.lock().unwrap().cached_output(cmd
)
98 pub fn set_wrapper(&mut self, wrapper
: ProcessBuilder
) {
99 self.wrapper
= Some(wrapper
);
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.
109 /// https://github.com/rust-lang/cargo/issues/5315
110 /// https://github.com/rust-lang/rust/issues/49761
113 cache_location
: Option
<PathBuf
>,
118 #[derive(Serialize, Deserialize, Debug, Default)]
120 rustc_fingerprint
: u64,
121 outputs
: HashMap
<u64, (String
, String
)>,
122 successes
: HashMap
<u64, bool
>,
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
{
131 outputs
: HashMap
::new(),
132 successes
: HashMap
::new(),
134 let mut dirty
= true;
135 let data
= match read(&cache_location
) {
137 if data
.rustc_fingerprint
== rustc_fingerprint
{
138 debug
!("reusing existing rustc info cache");
142 debug
!("different compiler, creating new rustc info cache");
147 debug
!("failed to read rustc info cache: {}", e
);
152 cache_location
: Some(cache_location
),
157 fn read(path
: &Path
) -> CargoResult
<CacheData
> {
158 let json
= paths
::read(path
)?
;
159 Ok(serde_json
::from_str(&json
)?
)
162 (_
, fingerprint
) => {
163 if let Err(e
) = fingerprint
{
164 warn
!("failed to calculate rustc fingerprint: {}", e
);
166 debug
!("rustc info cache disabled");
168 cache_location
: None
,
170 data
: CacheData
::default(),
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())
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());
200 impl Drop
for Cache
{
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
),
215 fn rustc_fingerprint(path
: &Path
, rustup_rustc
: &Path
) -> CargoResult
<u64> {
216 let mut hasher
= SipHasher
::new_with_keys(0, 0);
218 let path
= paths
::resolve_executable(path
)?
;
219 path
.hash(&mut hasher
);
221 paths
::mtime(&path
)?
.hash(&mut hasher
);
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
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
;
237 env
::var("RUSTUP_HOME"),
238 env
::var("RUSTUP_TOOLCHAIN"),
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
)
246 .join(rustup_toolchain
)
249 .with_extension(env
::consts
::EXE_EXTENSION
);
250 paths
::mtime(&real_rustc
)?
.hash(&mut hasher
);
252 (true, _
, _
) => failure
::bail
!("probably rustup rustc, but without rustup's env vars"),
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
<_
>>();
264 env
.hash(&mut hasher
);