use app_dirs::{
AppDataType,
AppInfo,
app_dir,
};
use cache::disk::DiskCache;
use cache::s3::S3Cache;
use compiler::Compiler;
use futures;
use sha1;
use std::env;
use std::fmt;
use std::fs::File;
use std::io::{
self,
Cursor,
Error,
ErrorKind,
Read,
Seek,
Write,
};
use std::path::PathBuf;
use std::time::Duration;
use zip::{
CompressionMethod,
ZipArchive,
ZipWriter,
};
const APP_INFO: AppInfo = AppInfo {
name: "sccache",
author: "Mozilla",
};
pub enum Cache {
Error(Error),
Hit(CacheRead),
Miss,
Recache,
}
impl fmt::Debug for Cache {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Cache::Error(ref e) => write!(f, "Cache::Error({:?}", e),
Cache::Hit(_) => write!(f, "Cache::Hit(...)"),
Cache::Miss => write!(f, "Cache::Miss"),
Cache::Recache => write!(f, "Cache::Recache"),
}
}
}
pub trait ReadSeek : Read + Seek {}
impl<T: Read + Seek> ReadSeek for T {}
pub struct CacheRead {
zip: ZipArchive<Box<ReadSeek>>,
}
impl CacheRead {
pub fn from<R: ReadSeek + 'static>(reader: R) -> io::Result<CacheRead> {
let z = try!(ZipArchive::new(Box::new(reader) as Box<ReadSeek>).or(Err(Error::new(ErrorKind::Other, "Failed to parse cache entry"))));
Ok(CacheRead {
zip: z,
})
}
pub fn get_object<T: Write>(&mut self, name: &str, to: &mut T) -> io::Result<()> {
let mut file = try!(self.zip.by_name(name).or(Err(Error::new(ErrorKind::Other, "Failed to read object from cache entry"))));
try!(io::copy(&mut file, to));
Ok(())
}
}
pub enum ZipType {
File(ZipWriter<File>),
Cursor(ZipWriter<Cursor<Vec<u8>>>),
}
pub enum CacheWriteWriter {
File(File),
Cursor(Cursor<Vec<u8>>),
}
impl From<File> for ZipType {
fn from(f: File) -> ZipType {
ZipType::File(ZipWriter::new(f))
}
}
impl From<Cursor<Vec<u8>>> for ZipType {
fn from(c: Cursor<Vec<u8>>) -> ZipType {
ZipType::Cursor(ZipWriter::new(c))
}
}
pub type CacheWriteResult = Result<Duration, String>;
pub type CacheWriteFuture = futures::BoxFuture<CacheWriteResult, futures::Canceled>;
pub struct CacheWrite {
zip: ZipType,
}
impl CacheWrite {
pub fn new<W>(writer: W) -> CacheWrite where W: Into<ZipType> {
CacheWrite {
zip: writer.into(),
}
}
pub fn put_object<T: Read>(&mut self, name: &str, from: &mut T) -> io::Result<()> {
match &mut self.zip {
&mut ZipType::File(ref mut zf) => {
try!(zf.start_file(name, CompressionMethod::Deflated).or(Err(Error::new(ErrorKind::Other, "Failed to start cache entry object"))));
try!(io::copy(from, zf));
}
&mut ZipType::Cursor(ref mut zc) => {
try!(zc.start_file(name, CompressionMethod::Deflated).or(Err(Error::new(ErrorKind::Other, "Failed to start cache entry object"))));
try!(io::copy(from, zc));
}
};
Ok(())
}
pub fn finish(self) -> io::Result<CacheWriteWriter> {
let CacheWrite { zip } = self;
match zip {
ZipType::File(mut zf) => {
Ok(CacheWriteWriter::File(try!(zf.finish().or(Err(Error::new(ErrorKind::Other, "Failed to finish cache entry zip"))))))
}
ZipType::Cursor(mut zc) => {
Ok(CacheWriteWriter::Cursor(try!(zc.finish().or(Err(Error::new(ErrorKind::Other, "Failed to finish cache entry zip"))))))
}
}
}
}
pub trait Storage: Send + Sync {
fn get(&self, key: &str) -> Cache;
fn start_put(&self, key: &str) -> io::Result<CacheWrite>;
fn finish_put(&self, key: &str, entry: CacheWrite) -> CacheWriteFuture;
fn get_location(&self) -> String;
}
pub fn storage_from_environment() -> Box<Storage> {
if let Ok(bucket) = env::var("SCCACHE_BUCKET") {
trace!("Trying S3Cache({})", bucket);
match S3Cache::new(&bucket) {
Ok(s) => {
trace!("Using S3Cache");
return Box::new(s);
}
Err(e) => warn!("Failed to create S3Cache: {:?}", e),
}
}
let d = env::var_os("SCCACHE_DIR")
.map(|p| PathBuf::from(p))
.or_else(|| app_dir(AppDataType::UserCache, &APP_INFO, "").ok())
.unwrap_or(env::temp_dir().join("sccache_cache"));
trace!("Using DiskCache({:?})", d);
Box::new(DiskCache::new(&d))
}
pub const CACHE_VERSION : &'static [u8] = b"2";
pub const CACHED_ENV_VARS : &'static [&'static str] = &[
"MACOSX_DEPLOYMENT_TARGET",
"IPHONEOS_DEPLOYMENT_TARGET",
];
#[allow(dead_code)]
pub fn hash_key(compiler: &Compiler, args: &[String], preprocessor_output: &[u8]) -> String {
let mut m = sha1::Sha1::new();
m.update(compiler.digest.as_bytes());
m.update(compiler.executable.as_bytes());
m.update(CACHE_VERSION);
let last = args.len() - 1;
for (i, arg) in args.iter().enumerate() {
m.update(arg.as_bytes());
if i < last {
m.update(&b" "[..]);
}
}
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()
}
#[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_path_differs() {
let f = TestFixture::new();
let c1 = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
let c2 = Compiler::new(f.bins[1].to_str().unwrap(), CompilerKind::Gcc).unwrap();
let args = stringvec!["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_executable_contents_differs() {
let f = TestFixture::new();
let c1 = Compiler::new(f.bins[0].to_str().unwrap(), CompilerKind::Gcc).unwrap();
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 = stringvec!["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, &stringvec!["a", "b", "c"], &PREPROCESSED),
hash_key(&c, &stringvec!["x", "y", "z"], &PREPROCESSED));
assert_neq!(hash_key(&c, &stringvec!["a", "b", "c"], &PREPROCESSED),
hash_key(&c, &stringvec!["a", "b"], &PREPROCESSED));
assert_neq!(hash_key(&c, &stringvec!["a", "b", "c"], &PREPROCESSED),
hash_key(&c, &stringvec!["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 = stringvec!["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 = stringvec!["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);
}
}
}