1 use std
::collections
::hash_map
::{Entry, HashMap}
;
3 use std
::hash
::{Hash, Hasher}
;
4 use std
::path
::{Path, PathBuf}
;
7 use log
::{debug, info, warn}
;
8 use serde
::{Deserialize, Serialize}
;
10 use crate::util
::interning
::InternedString
;
11 use crate::util
::paths
;
12 use crate::util
::{self, profile, CargoResult, CargoResultExt, ProcessBuilder, StableHasher}
;
14 /// Information on the `rustc` executable
17 /// The location of the exe
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
,
34 /// Runs the compiler at `path` to learn various pieces of information about
35 /// it, with an optional wrapper.
37 /// If successful this function returns a description of the compiler along
38 /// with a list of its capabilities.
41 wrapper
: Option
<PathBuf
>,
42 workspace_wrapper
: Option
<PathBuf
>,
44 cache_location
: Option
<PathBuf
>,
45 ) -> CargoResult
<Rustc
> {
46 let _p
= profile
::start("Rustc::new");
48 let mut cache
= Cache
::load(&path
, rustup_rustc
, cache_location
);
50 let mut cmd
= util
::process(&path
);
52 let verbose_version
= cache
.cached_output(&cmd
)?
.0;
54 let extract
= |field
: &str| -> CargoResult
<&str> {
57 .find(|l
| l
.starts_with(field
))
58 .map(|l
| &l
[field
.len()..])
61 "`rustc -vV` didn't have a line for `{}`, got:\n{}",
68 let host
= InternedString
::new(extract("host: ")?
);
69 let version
= semver
::Version
::parse(extract("release: ")?
).chain_err(|| {
71 "rustc version does not appear to be a valid semver version, from:\n{}",
83 cache
: Mutex
::new(cache
),
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())
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())
99 pub fn process_no_wrapper(&self) -> ProcessBuilder
{
100 util
::process(&self.path
)
103 pub fn cached_output(&self, cmd
: &ProcessBuilder
) -> CargoResult
<(String
, String
)> {
104 self.cache
.lock().unwrap().cached_output(cmd
)
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.
114 /// https://github.com/rust-lang/cargo/issues/5315
115 /// https://github.com/rust-lang/rust/issues/49761
118 cache_location
: Option
<PathBuf
>,
123 #[derive(Serialize, Deserialize, Debug, Default)]
125 rustc_fingerprint
: u64,
126 outputs
: HashMap
<u64, (String
, String
)>,
127 successes
: HashMap
<u64, bool
>,
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
{
136 outputs
: HashMap
::new(),
137 successes
: HashMap
::new(),
139 let mut dirty
= true;
140 let data
= match read(&cache_location
) {
142 if data
.rustc_fingerprint
== rustc_fingerprint
{
143 debug
!("reusing existing rustc info cache");
147 debug
!("different compiler, creating new rustc info cache");
152 debug
!("failed to read rustc info cache: {}", e
);
157 cache_location
: Some(cache_location
),
162 fn read(path
: &Path
) -> CargoResult
<CacheData
> {
163 let json
= paths
::read(path
)?
;
164 Ok(serde_json
::from_str(&json
)?
)
167 (_
, fingerprint
) => {
168 if let Err(e
) = fingerprint
{
169 warn
!("failed to calculate rustc fingerprint: {}", e
);
171 debug
!("rustc info cache disabled");
173 cache_location
: None
,
175 data
: CacheData
::default(),
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())
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());
207 impl Drop
for Cache
{
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
),
222 fn rustc_fingerprint(path
: &Path
, rustup_rustc
: &Path
) -> CargoResult
<u64> {
223 let mut hasher
= StableHasher
::new();
225 let path
= paths
::resolve_executable(path
)?
;
226 path
.hash(&mut hasher
);
228 paths
::mtime(&path
)?
.hash(&mut hasher
);
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
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
;
244 env
::var("RUSTUP_HOME"),
245 env
::var("RUSTUP_TOOLCHAIN"),
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
)
253 .join(rustup_toolchain
)
256 .with_extension(env
::consts
::EXE_EXTENSION
);
257 paths
::mtime(&real_rustc
)?
.hash(&mut hasher
);
259 (true, _
, _
) => anyhow
::bail
!("probably rustup rustc, but without rustup's env vars"),
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
<_
>>();
271 env
.hash(&mut hasher
);