Skip to content

Add support for caching Rust compilations. Fixes #42 #77

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 24, 2017
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ sccache - Shared Compilation Cache

Sccache is a [ccache](https://ccache.samba.org/)-like tool. It is used as a compiler wrapper and avoids compilation when possible, storing a cache in a remote storage using the S3 API.

Sccache now includes [experimental Rust support](docs/Rust.md).

It works as a client-server. The client spawns a server if one is not running already, and sends the wrapped command line as a request to the server, which then does the work and returns stdout/stderr for the job. The client-server model allows the server to be more efficient in its handling of the remote storage.

Sccache can also be used with local storage instead of remote.
Expand Down
10 changes: 10 additions & 0 deletions docs/Rust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sccache now includes experimental support for caching Rust compilation. This includes many caveats, and is primarily focused on caching rustc invocations as produced by cargo. A (possibly-incomplete) list follows:
* `--emit` is required.
* Only `link` and `dep-info` are supported as `--emit` values, and `link` must be present.
* `--out-dir` is required.
* `-o file` is not supported.
* Compilation from stdin is not supported, a source file must be provided.
* Values from `env!` will not be tracked in caching.
* Procedural macros that read files from the filesystem may not be cached properly
* The system linker is not factored in as a cache input, so changing the linker may produce incorrect cached results.
* Target specs aren't hashed (e.g. custom target specs)
103 changes: 0 additions & 103 deletions src/cache/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ use app_dirs::{
};
use cache::disk::DiskCache;
use cache::s3::S3Cache;
use compiler::Compiler;
use futures_cpupool::CpuPool;
use regex::Regex;
use sha1;
use std::env;
use std::fmt;
use std::io::{
Expand Down Expand Up @@ -221,37 +219,6 @@ pub fn storage_from_environment(pool: &CpuPool, handle: &Handle) -> Arc<Storage>
Arc::new(DiskCache::new(&d, cache_size, pool))
}

/// The cache is versioned by the inputs to `hash_key`.
pub const CACHE_VERSION : &'static [u8] = b"3";

/// Environment variables that are factored into the cache key.
pub const CACHED_ENV_VARS : &'static [&'static str] = &[
"MACOSX_DEPLOYMENT_TARGET",
"IPHONEOS_DEPLOYMENT_TARGET",
];

/// Compute the hash key of `compiler` compiling `preprocessor_output` with `args`.
#[allow(dead_code)]
pub fn hash_key(compiler: &Compiler, arguments: &str, preprocessor_output: &[u8]) -> String {
// If you change any of the inputs to the hash, you should change `CACHE_VERSION`.
let mut m = sha1::Sha1::new();
m.update(compiler.digest.as_bytes());
m.update(CACHE_VERSION);
m.update(arguments.as_bytes());
//TODO: should propogate these over from the client.
// https://github.com/glandium/sccache/issues/5
for var in CACHED_ENV_VARS.iter() {
if let Ok(val) = env::var(var) {
m.update(var.as_bytes());
m.update(&b"="[..]);
m.update(val.as_bytes());
}
}
m.update(preprocessor_output);
m.digest().to_string()
}


#[test]
fn test_parse_size() {
assert_eq!(None, parse_size(""));
Expand All @@ -261,73 +228,3 @@ fn test_parse_size() {
assert_eq!(Some(TEN_GIGS), parse_size("10G"));
assert_eq!(Some(1024 * TEN_GIGS), parse_size("10T"));
}

#[cfg(test)]
mod test {
use super::*;
use compiler::{Compiler,CompilerKind};
use std::env;
use std::io::Write;
use test::utils::*;

#[test]
fn test_hash_key_executable_contents_differs() {
let f = TestFixture::new();
// Try to avoid testing exact hashes.
let c1 = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
// Overwrite the contents of the binary.
mk_bin_contents(f.tempdir.path(), "a/bin", |mut f| f.write_all(b"hello")).unwrap();
let c2 = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
let args = "a b c";
const PREPROCESSED : &'static [u8] = b"hello world";
assert_neq!(hash_key(&c1, &args, &PREPROCESSED),
hash_key(&c2, &args, &PREPROCESSED));
}

#[test]
fn test_hash_key_args_differs() {
let f = TestFixture::new();
let c = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
const PREPROCESSED : &'static [u8] = b"hello world";
assert_neq!(hash_key(&c, "a b c", &PREPROCESSED),
hash_key(&c, "x y z", &PREPROCESSED));

assert_neq!(hash_key(&c, "a b c", &PREPROCESSED),
hash_key(&c, "a b", &PREPROCESSED));

assert_neq!(hash_key(&c, "a b c", &PREPROCESSED),
hash_key(&c, "a", &PREPROCESSED));
}

#[test]
fn test_hash_key_preprocessed_content_differs() {
let f = TestFixture::new();
let c = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
let args = "a b c";
assert_neq!(hash_key(&c, &args, &b"hello world"[..]),
hash_key(&c, &args, &b"goodbye"[..]));
}

#[test]
fn test_hash_key_env_var_differs() {
let f = TestFixture::new();
let c = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
let args = "a b c";
const PREPROCESSED : &'static [u8] = b"hello world";
for var in CACHED_ENV_VARS.iter() {
let old = env::var_os(var);
env::remove_var(var);
let h1 = hash_key(&c, &args, &PREPROCESSED);
env::set_var(var, "something");
let h2 = hash_key(&c, &args, &PREPROCESSED);
env::set_var(var, "something else");
let h3 = hash_key(&c, &args, &PREPROCESSED);
match old {
Some(val) => env::set_var(var, val),
None => env::remove_var(var),
}
assert_neq!(h1, h2);
assert_neq!(h2, h3);
}
}
}
11 changes: 9 additions & 2 deletions src/cache/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,19 @@ impl Storage for DiskCache {
trace!("DiskCache::get({})", key);
let path = make_key_path(key);
let lru = self.lru.clone();
let key = key.to_owned();
self.pool.spawn_fn(move || {
let mut lru = lru.lock().unwrap();
let f = match lru.get(&path) {
Ok(f) => f,
Err(LruError::FileNotInCache) => return Ok(Cache::Miss),
Err(LruError::Io(e)) => return Err(e.into()),
Err(LruError::FileNotInCache) => {
trace!("DiskCache::get({}): FileNotInCache", key);
return Ok(Cache::Miss);
}
Err(LruError::Io(e)) => {
trace!("DiskCache::get({}): IoError: {:?}", key, e);
return Err(e.into());
}
Err(_) => panic!("Unexpected error!"),
};
let hit = CacheRead::from(f)?;
Expand Down
Loading