}
if let Some(ref code) = args.value_of("explain") {
- let mut procss = config.new_rustc()?.process();
+ let mut procss = config.rustc(None)?.process();
procss.arg("--explain").arg(code).exec()?;
return Ok(());
}
with_cfg.arg("--print=cfg");
let mut has_cfg_and_sysroot = true;
- let output = with_cfg
- .exec_with_output()
+ let (output, error) = build_config
+ .rustc
+ .cached_output(&with_cfg)
.or_else(|_| {
has_cfg_and_sysroot = false;
- process.exec_with_output()
+ build_config.rustc.cached_output(&process)
})
.chain_err(|| "failed to run `rustc` to learn about target-specific information")?;
- let error = str::from_utf8(&output.stderr).unwrap();
- let output = str::from_utf8(&output.stdout).unwrap();
let mut lines = output.lines();
let mut map = HashMap::new();
for crate_type in KNOWN_CRATE_TYPES {
- let out = parse_crate_type(crate_type, error, &mut lines)?;
+ let out = parse_crate_type(crate_type, &error, &mut lines)?;
map.insert(crate_type.to_string(), out);
}
config: &Config,
jobs: Option<u32>,
requested_target: &Option<String>,
+ rustc_info_cache: Option<PathBuf>,
) -> CargoResult<BuildConfig> {
if let &Some(ref s) = requested_target {
if s.trim().is_empty() {
None => None,
};
let jobs = jobs.or(cfg_jobs).unwrap_or(::num_cpus::get() as u32);
- let rustc = config.new_rustc()?;
+ let rustc = config.rustc(rustc_info_cache)?;
let host_config = TargetConfig::new(config, &rustc.host)?;
let target_config = match target.as_ref() {
Some(triple) => TargetConfig::new(config, triple)?,
}
}
- let mut build_config = BuildConfig::new(config, Some(1), &opts.target)?;
+ let mut build_config = BuildConfig::new(config, Some(1), &opts.target, None)?;
build_config.release = opts.release;
let mut cx = Context::new(ws, &resolve, &packages, opts.config, build_config, profiles)?;
cx.prepare_units(None, &units)?;
bail!("jobs must be at least 1")
}
- let mut build_config = BuildConfig::new(config, jobs, &target)?;
+ let rustc_info_cache = ws.target_dir().join("rustc_info.json").into_path_unlocked();
+ let mut build_config = BuildConfig::new(config, jobs, &target, Some(rustc_info_cache))?;
build_config.release = release;
build_config.test = mode == CompileMode::Test || mode == CompileMode::Bench;
build_config.json_messages = message_format == MessageFormat::Json;
}
/// Get the path to the `rustc` executable
- pub fn new_rustc(&self) -> CargoResult<Rustc> {
+ pub fn rustc(&self, cache_location: Option<PathBuf>) -> CargoResult<Rustc> {
Rustc::new(
self.get_tool("rustc")?,
self.maybe_get_tool("rustc_wrapper")?,
+ cache_location,
)
}
.map(PathBuf::from)
.next()
.ok_or(format_err!("no argv[0]"))?;
- if argv0.components().count() == 1 {
- probe_path(argv0)
- } else {
- Ok(argv0.canonicalize()?)
- }
- }
-
- fn probe_path(argv0: PathBuf) -> CargoResult<PathBuf> {
- let paths = env::var_os("PATH").ok_or(format_err!("no PATH"))?;
- for path in env::split_paths(&paths) {
- let candidate = PathBuf::from(path).join(&argv0);
- if candidate.is_file() {
- // PATH may have a component like "." in it, so we still need to
- // canonicalize.
- return Ok(candidate.canonicalize()?);
- }
- }
-
- bail!("no cargo executable candidate found in PATH")
+ paths::resolve_executable(&argv0)
}
let exe = from_current_exe()
}
}
+pub fn resolve_executable(exec: &Path) -> CargoResult<PathBuf> {
+ if exec.components().count() == 1 {
+ let paths = env::var_os("PATH").ok_or(format_err!("no PATH"))?;
+ for path in env::split_paths(&paths) {
+ let candidate = PathBuf::from(path).join(&exec);
+ if candidate.is_file() {
+ // PATH may have a component like "." in it, so we still need to
+ // canonicalize.
+ return Ok(candidate.canonicalize()?);
+ }
+ }
+
+ bail!("no executable for `{}` found in PATH", exec.display())
+ } else {
+ Ok(exec.canonicalize()?)
+ }
+}
+
pub fn read(path: &Path) -> CargoResult<String> {
match String::from_utf8(read_bytes(path)?) {
Ok(s) => Ok(s),
-use std::path::PathBuf;
+#![allow(deprecated)] // for SipHasher
+
+use std::path::{Path, PathBuf};
+use std::hash::{Hash, Hasher, SipHasher};
+use std::collections::hash_map::{Entry, HashMap};
+use std::sync::Mutex;
+use std::env;
+
+use serde_json;
use util::{self, internal, profile, CargoResult, ProcessBuilder};
+use util::paths;
/// Information on the `rustc` executable
#[derive(Debug)]
pub verbose_version: String,
/// The host triple (arch-platform-OS), this comes from verbose_version.
pub host: String,
+ cache: Mutex<Cache>,
}
impl Rustc {
///
/// If successful this function returns a description of the compiler along
/// with a list of its capabilities.
- pub fn new(path: PathBuf, wrapper: Option<PathBuf>) -> CargoResult<Rustc> {
+ pub fn new(
+ path: PathBuf,
+ wrapper: Option<PathBuf>,
+ cache_location: Option<PathBuf>,
+ ) -> CargoResult<Rustc> {
let _p = profile::start("Rustc::new");
+ let mut cache = Cache::load(&path, cache_location);
+
let mut cmd = util::process(&path);
cmd.arg("-vV");
-
- let output = cmd.exec_with_output()?;
-
- let verbose_version = String::from_utf8(output.stdout)
- .map_err(|_| internal("rustc -v didn't return utf8 output"))?;
+ let verbose_version = cache.cached_output(&cmd)?.0;
let host = {
let triple = verbose_version
wrapper,
verbose_version,
host,
+ cache: Mutex::new(cache),
})
}
util::process(&self.path)
}
}
+
+ pub fn cached_output(&self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> {
+ self.cache.lock().unwrap().cached_output(cmd)
+ }
+}
+
+/// It is a well known that `rustc` is not the fastest compiler in the world.
+/// What is less known is that even `rustc --version --verbose` takes about a
+/// hundred milliseconds! Because we need compiler version info even for no-op
+/// builds, we cache it here, based on compiler's mtime and rustup's current
+/// toolchain.
+#[derive(Debug)]
+struct Cache {
+ cache_location: Option<PathBuf>,
+ dirty: bool,
+ data: CacheData,
+}
+
+#[derive(Serialize, Deserialize, Debug, Default)]
+struct CacheData {
+ rustc_fingerprint: u64,
+ outputs: HashMap<u64, (String, String)>,
+}
+
+impl Cache {
+ fn load(rustc: &Path, cache_location: Option<PathBuf>) -> Cache {
+ match (cache_location, rustc_fingerprint(rustc)) {
+ (Some(cache_location), Ok(rustc_fingerprint)) => {
+ let empty = CacheData {
+ rustc_fingerprint,
+ outputs: HashMap::new(),
+ };
+ let mut dirty = true;
+ let data = match read(&cache_location) {
+ Ok(data) => {
+ if data.rustc_fingerprint == rustc_fingerprint {
+ info!("reusing existing rustc info cache");
+ dirty = false;
+ data
+ } else {
+ info!("different compiler, creating new rustc info cache");
+ empty
+ }
+ }
+ Err(e) => {
+ info!("failed to read rustc info cache: {}", e);
+ empty
+ }
+ };
+ return Cache {
+ cache_location: Some(cache_location),
+ dirty,
+ data,
+ };
+
+ fn read(path: &Path) -> CargoResult<CacheData> {
+ let json = paths::read(path)?;
+ Ok(serde_json::from_str(&json)?)
+ }
+ }
+ (_, fingerprint) => {
+ if let Err(e) = fingerprint {
+ warn!("failed to calculate rustc fingerprint: {}", e);
+ }
+ info!("rustc info cache disabled");
+ Cache {
+ cache_location: None,
+ dirty: false,
+ data: CacheData::default(),
+ }
+ }
+ }
+ }
+
+ fn cached_output(&mut self, cmd: &ProcessBuilder) -> CargoResult<(String, String)> {
+ let calculate = || {
+ let output = cmd.exec_with_output()?;
+ let stdout = String::from_utf8(output.stdout)
+ .map_err(|_| internal("rustc didn't return utf8 output"))?;
+ let stderr = String::from_utf8(output.stderr)
+ .map_err(|_| internal("rustc didn't return utf8 output"))?;
+ Ok((stdout, stderr))
+ };
+ if self.cache_location.is_none() {
+ info!("rustc info uncached");
+ return calculate();
+ }
+
+ let key = process_fingerprint(cmd);
+ match self.data.outputs.entry(key) {
+ Entry::Occupied(entry) => {
+ info!("rustc info cache hit");
+ Ok(entry.get().clone())
+ }
+ Entry::Vacant(entry) => {
+ info!("rustc info cache miss");
+ let output = calculate()?;
+ entry.insert(output.clone());
+ self.dirty = true;
+ Ok(output)
+ }
+ }
+ }
+}
+
+impl Drop for Cache {
+ fn drop(&mut self) {
+ match (&self.cache_location, self.dirty) {
+ (&Some(ref path), true) => {
+ let json = serde_json::to_string(&self.data).unwrap();
+ match paths::write(path, json.as_bytes()) {
+ Ok(()) => info!("updated rustc info cache"),
+ Err(e) => warn!("failed to update rustc info cache: {}", e),
+ }
+ }
+ _ => (),
+ }
+ }
+}
+
+fn rustc_fingerprint(path: &Path) -> CargoResult<u64> {
+ let mut hasher = SipHasher::new_with_keys(0, 0);
+
+ let path = paths::resolve_executable(path)?;
+ path.hash(&mut hasher);
+
+ paths::mtime(&path)?.hash(&mut hasher);
+
+ match (env::var("RUSTUP_HOME"), env::var("RUSTUP_TOOLCHAIN")) {
+ (Ok(rustup_home), Ok(rustup_toolchain)) => {
+ debug!("adding rustup info to rustc fingerprint");
+ rustup_toolchain.hash(&mut hasher);
+ rustup_home.hash(&mut hasher);
+ let rustup_rustc = Path::new(&rustup_home)
+ .join("toolchains")
+ .join(rustup_toolchain)
+ .join("bin")
+ .join("rustc");
+ paths::mtime(&rustup_rustc)?.hash(&mut hasher);
+ }
+ _ => (),
+ }
+
+ Ok(hasher.finish())
+}
+
+fn process_fingerprint(cmd: &ProcessBuilder) -> u64 {
+ let mut hasher = SipHasher::new_with_keys(0, 0);
+ cmd.get_args().hash(&mut hasher);
+ let mut env = cmd.get_envs().iter().collect::<Vec<_>>();
+ env.sort();
+ env.hash(&mut hasher);
+ hasher.finish()
}
pub mod install;
-thread_local!(pub static RUSTC: Rustc = Rustc::new(PathBuf::from("rustc"), None).unwrap());
+thread_local!(pub static RUSTC: Rustc = Rustc::new(PathBuf::from("rustc"), None, None).unwrap());
pub fn rustc_host() -> String {
RUSTC.with(|r| r.host.clone())
mod resolve;
mod run;
mod rustc;
+mod rustc_info_cache;
mod rustdocflags;
mod rustdoc;
mod rustflags;
--- /dev/null
+use cargotest::support::{execs, project};
+use cargotest::support::paths::CargoPathExt;
+use hamcrest::assert_that;
+use std::env;
+
+#[test]
+fn rustc_info_cache() {
+ let p = project("foo")
+ .file(
+ "Cargo.toml",
+ r#"
+ [project]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file("src/main.rs", r#"fn main() { println!("hello"); }"#)
+ .build();
+
+ let miss = "[..] rustc info cache miss[..]";
+ let hit = "[..] rustc info cache hit[..]";
+
+ assert_that(
+ p.cargo("build").env("RUST_LOG", "cargo::util::rustc=info"),
+ execs()
+ .with_status(0)
+ .with_stderr_contains("[..]failed to read rustc info cache[..]")
+ .with_stderr_contains(miss)
+ .with_stderr_does_not_contain(hit),
+ );
+
+ assert_that(
+ p.cargo("build").env("RUST_LOG", "cargo::util::rustc=info"),
+ execs()
+ .with_status(0)
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(hit)
+ .with_stderr_does_not_contain(miss),
+ );
+
+ let other_rustc = {
+ let p = project("compiler")
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "compiler"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ use std::process::Command;
+ use std::env;
+
+ fn main() {
+ let mut cmd = Command::new("rustc");
+ for arg in env::args_os().skip(1) {
+ cmd.arg(arg);
+ }
+ std::process::exit(cmd.status().unwrap().code().unwrap());
+ }
+ "#,
+ )
+ .build();
+ assert_that(p.cargo("build"), execs().with_status(0));
+
+ p.root()
+ .join("target/debug/compiler")
+ .with_extension(env::consts::EXE_EXTENSION)
+ };
+
+ assert_that(
+ p.cargo("build")
+ .env("RUST_LOG", "cargo::util::rustc=info")
+ .env("RUSTC", other_rustc.display().to_string()),
+ execs()
+ .with_status(0)
+ .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]")
+ .with_stderr_contains(miss)
+ .with_stderr_does_not_contain(hit),
+ );
+
+ assert_that(
+ p.cargo("build")
+ .env("RUST_LOG", "cargo::util::rustc=info")
+ .env("RUSTC", other_rustc.display().to_string()),
+ execs()
+ .with_status(0)
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(hit)
+ .with_stderr_does_not_contain(miss),
+ );
+
+ other_rustc.move_into_the_future();
+
+ assert_that(
+ p.cargo("build")
+ .env("RUST_LOG", "cargo::util::rustc=info")
+ .env("RUSTC", other_rustc.display().to_string()),
+ execs()
+ .with_status(0)
+ .with_stderr_contains("[..]different compiler, creating new rustc info cache[..]")
+ .with_stderr_contains(miss)
+ .with_stderr_does_not_contain(hit),
+ );
+
+ assert_that(
+ p.cargo("build")
+ .env("RUST_LOG", "cargo::util::rustc=info")
+ .env("RUSTC", other_rustc.display().to_string()),
+ execs()
+ .with_status(0)
+ .with_stderr_contains("[..]reusing existing rustc info cache[..]")
+ .with_stderr_contains(hit)
+ .with_stderr_does_not_contain(miss),
+ );
+}