use cache::{
Cache,
CacheWrite,
Storage,
};
use compiler::msvc;
use compiler::c::{CCompiler, CCompilerKind};
use compiler::clang::Clang;
use compiler::gcc::GCC;
use compiler::msvc::MSVC;
use compiler::rust::Rust;
use futures::{Future, IntoFuture};
use futures_cpupool::CpuPool;
use mock_command::{
CommandChild,
CommandCreatorSync,
RunCommand,
exit_status,
};
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fmt;
#[cfg(unix)]
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::{self,Stdio};
use std::str;
use std::sync::Arc;
use std::time::{
Duration,
Instant,
};
use tempdir::TempDir;
use tempfile::NamedTempFile;
use util::fmt_duration_as_secs;
use tokio_core::reactor::{Handle, Timeout};
use errors::*;
#[derive(Debug, PartialEq, Clone)]
pub enum CompilerKind {
C(CCompilerKind),
Rust,
}
pub trait Compiler<T>: Send + 'static
where T: CommandCreatorSync,
{
fn kind(&self) -> CompilerKind;
fn parse_arguments(&self,
arguments: &[OsString],
cwd: &Path) -> CompilerArguments<Box<CompilerHasher<T> + 'static>>;
fn box_clone(&self) -> Box<Compiler<T>>;
}
impl<T: CommandCreatorSync> Clone for Box<Compiler<T>> {
fn clone(&self) -> Box<Compiler<T>> { self.box_clone() }
}
pub trait CompilerHasher<T>: fmt::Debug + Send + 'static
where T: CommandCreatorSync,
{
fn generate_hash_key(self: Box<Self>,
creator: &T,
cwd: &Path,
env_vars: &[(OsString, OsString)],
pool: &CpuPool)
-> SFuture<HashResult<T>>;
fn get_cached_or_compile(self: Box<Self>,
creator: T,
storage: Arc<Storage>,
arguments: Vec<OsString>,
cwd: PathBuf,
env_vars: Vec<(OsString, OsString)>,
cache_control: CacheControl,
pool: CpuPool,
handle: Handle)
-> SFuture<(CompileResult, process::Output)>
{
let out_pretty = self.output_pretty().into_owned();
debug!("[{}]: get_cached_or_compile: {:?}", out_pretty, arguments);
let start = Instant::now();
let result = self.generate_hash_key(&creator, &cwd, &env_vars, &pool);
Box::new(result.then(move |res| -> SFuture<_> {
debug!("[{}]: generate_hash_key took {}", out_pretty, fmt_duration_as_secs(&start.elapsed()));
let (key, compilation) = match res {
Err(Error(ErrorKind::ProcessError(output), _)) => {
return f_ok((CompileResult::Error, output));
}
Err(e) => return f_err(e),
Ok(HashResult { key, compilation }) => (key, compilation),
};
trace!("[{}]: Hash key: {}", out_pretty, key);
let start = Instant::now();
let cache_status = if cache_control == CacheControl::ForceRecache {
f_ok(Cache::Recache)
} else {
storage.get(&key)
};
let timeout = Duration::new(60, 0);
let timeout = Timeout::new(timeout, &handle).into_future().flatten();
let cache_status = cache_status.map(Some);
let timeout = timeout.map(|_| None).chain_err(|| "timeout error");
let cache_status = cache_status.select(timeout).then(|r| {
match r {
Ok((e, _other)) => Ok(e),
Err((e, _other)) => Err(e),
}
});
Box::new(cache_status.then(move |result| {
let duration = start.elapsed();
let pwd = Path::new(&cwd);
let outputs = compilation.outputs()
.map(|(key, path)| (key.to_string(), pwd.join(path)))
.collect::<HashMap<_, _>>();
let miss_type = match result {
Ok(Some(Cache::Hit(mut entry))) => {
debug!("[{}]: Cache hit in {}", out_pretty, fmt_duration_as_secs(&duration));
let mut stdout = Vec::new();
let mut stderr = Vec::new();
drop(entry.get_object("stdout", &mut stdout));
drop(entry.get_object("stderr", &mut stderr));
let write = pool.spawn_fn(move ||{
for (key, path) in &outputs {
let dir = match path.parent() {
Some(d) => d,
None => bail!("Output file without a parent directory!"),
};
let mut tmp = NamedTempFile::new_in(dir)?;
let mode = entry.get_object(&key, &mut tmp)?;
tmp.persist(path)?;
if let Some(mode) = mode {
set_file_mode(&path, mode)?;
}
}
Ok(())
});
let output = process::Output {
status: exit_status(0),
stdout: stdout,
stderr: stderr,
};
let result = CompileResult::CacheHit(duration);
return Box::new(write.map(|_| {
(result, output)
})) as SFuture<_>
}
Ok(Some(Cache::Miss)) => {
debug!("[{}]: Cache miss in {}", out_pretty, fmt_duration_as_secs(&duration));
MissType::Normal
}
Ok(Some(Cache::Recache)) => {
debug!("[{}]: Cache recache in {}", out_pretty, fmt_duration_as_secs(&duration));
MissType::ForcedRecache
}
Ok(None) => {
debug!("[{}]: Cache timed out {}", out_pretty, fmt_duration_as_secs(&duration));
MissType::TimedOut
}
Err(err) => {
error!("[{}]: Cache read error: {}", out_pretty, err);
for e in err.iter().skip(1) {
error!("[{}] \t{}", out_pretty, e);
}
MissType::CacheReadError
}
};
let start = Instant::now();
let out_pretty = out_pretty.clone();
let compile = compilation.compile(&creator, &cwd, &env_vars);
Box::new(compile.and_then(move |(cacheable, compiler_result)| {
let duration = start.elapsed();
if !compiler_result.status.success() {
debug!("[{}]: Compiled but failed, not storing in cache",
out_pretty);
return f_ok((CompileResult::CompileFailed, compiler_result))
as SFuture<_>
}
if cacheable != Cacheable::Yes {
debug!("[{}]: Compiled but not cacheable",
out_pretty);
return f_ok((CompileResult::NotCacheable, compiler_result))
}
debug!("[{}]: Compiled in {}, storing in cache", out_pretty, fmt_duration_as_secs(&duration));
let write = pool.spawn_fn(move || -> Result<_> {
let mut entry = CacheWrite::new();
for (key, path) in &outputs {
let mut f = File::open(&path)?;
let mode = get_file_mode(&path)?;
entry.put_object(key, &mut f, mode).chain_err(|| {
format!("failed to put object `{:?}` in zip", path)
})?;
}
Ok(entry)
});
let write = write.chain_err(|| "failed to zip up compiler outputs");
let o = out_pretty.clone();
Box::new(write.and_then(move |mut entry| {
if !compiler_result.stdout.is_empty() {
let mut stdout = &compiler_result.stdout[..];
entry.put_object("stdout", &mut stdout, None)?;
}
if !compiler_result.stderr.is_empty() {
let mut stderr = &compiler_result.stderr[..];
entry.put_object("stderr", &mut stderr, None)?;
}
let out_pretty = out_pretty.clone();
let future = storage.put(&key, entry)
.then(move |res| {
match res {
Ok(_) => debug!("[{}]: Stored in cache successfully!", out_pretty),
Err(ref e) => debug!("[{}]: Cache write error: {:?}", out_pretty, e),
}
res.map(|duration| CacheWriteInfo {
object_file_pretty: out_pretty,
duration: duration,
})
});
let future = Box::new(future);
Ok((CompileResult::CacheMiss(miss_type, duration, future), compiler_result))
}).chain_err(move || {
format!("failed to store `{}` to cache", o)
}))
}))
}))
}))
}
fn output_pretty(&self) -> Cow<str>;
fn box_clone(&self) -> Box<CompilerHasher<T>>;
}
impl<T: CommandCreatorSync> Clone for Box<CompilerHasher<T>> {
fn clone(&self) -> Box<CompilerHasher<T>> { self.box_clone() }
}
pub trait Compilation<T>
where T: CommandCreatorSync,
{
fn compile(self: Box<Self>,
creator: &T,
cwd: &Path,
env_vars: &[(OsString, OsString)])
-> SFuture<(Cacheable, process::Output)>;
fn outputs<'a>(&'a self) -> Box<Iterator<Item=(&'a str, &'a Path)> + 'a>;
}
pub struct HashResult<T: CommandCreatorSync> {
pub key: String,
pub compilation: Box<Compilation<T> + 'static>,
}
#[derive(Debug, PartialEq)]
pub enum CompilerArguments<T>
{
Ok(T),
CannotCache(&'static str),
NotCompilation,
}
#[derive(Debug, PartialEq)]
pub enum MissType {
Normal,
ForcedRecache,
TimedOut,
CacheReadError,
}
pub struct CacheWriteInfo {
pub object_file_pretty: String,
pub duration: Duration,
}
pub enum CompileResult {
Error,
CacheHit(Duration),
CacheMiss(MissType, Duration, SFuture<CacheWriteInfo>),
NotCacheable,
CompileFailed,
}
impl fmt::Debug for CompileResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&CompileResult::Error => write!(f, "CompileResult::Error"),
&CompileResult::CacheHit(ref d) => write!(f, "CompileResult::CacheHit({:?})", d),
&CompileResult::CacheMiss(ref m, ref d, _) => write!(f, "CompileResult::CacheMiss({:?}, {:?}, _)", d, m),
&CompileResult::NotCacheable => write!(f, "CompileResult::NotCacheable"),
&CompileResult::CompileFailed => write!(f, "CompileResult::CompileFailed"),
}
}
}
impl PartialEq<CompileResult> for CompileResult {
fn eq(&self, other: &CompileResult) -> bool {
match (self, other) {
(&CompileResult::Error, &CompileResult::Error) => true,
(&CompileResult::CacheHit(_), &CompileResult::CacheHit(_)) => true,
(&CompileResult::CacheMiss(ref m, _, _), &CompileResult::CacheMiss(ref n, _, _)) => m == n,
(&CompileResult::NotCacheable, &CompileResult::NotCacheable) => true,
(&CompileResult::CompileFailed, &CompileResult::CompileFailed) => true,
_ => false,
}
}
}
#[cfg(unix)]
fn get_file_mode(path: &Path) -> Result<Option<u32>>
{
use std::os::unix::fs::MetadataExt;
Ok(Some(fs::metadata(path)?.mode()))
}
#[cfg(windows)]
fn get_file_mode(_path: &Path) -> Result<Option<u32>>
{
Ok(None)
}
#[cfg(unix)]
fn set_file_mode(path: &Path, mode: u32) -> Result<()>
{
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
let p = Permissions::from_mode(mode);
fs::set_permissions(path, p)?;
Ok(())
}
#[cfg(windows)]
fn set_file_mode(_path: &Path, _mode: u32) -> Result<()>
{
Ok(())
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Cacheable {
Yes,
No,
}
#[derive(Debug, PartialEq)]
pub enum CacheControl {
Default,
ForceRecache,
}
pub fn write_temp_file(pool: &CpuPool, path: &Path, contents: Vec<u8>)
-> SFuture<(TempDir, PathBuf)> {
let path = path.to_owned();
pool.spawn_fn(move || -> Result<_> {
let dir = TempDir::new("sccache")?;
let src = dir.path().join(path);
let mut file = File::create(&src)?;
file.write_all(&contents)?;
Ok((dir, src))
}).chain_err(|| {
"failed to write temporary file"
})
}
fn detect_compiler<T>(creator: &T, executable: &Path, pool: &CpuPool)
-> SFuture<Option<Box<Compiler<T>>>>
where T: CommandCreatorSync
{
trace!("detect_compiler");
let filename = match executable.file_stem() {
None => return f_err("could not determine compiler kind"),
Some(f) => f,
};
let is_rustc = if filename.to_string_lossy().to_lowercase() == "rustc" {
let child = creator.clone().new_command_sync(&executable)
.stdout(Stdio::piped())
.stderr(Stdio::null())
.args(&["--version"])
.spawn().chain_err(|| {
format!("failed to execute {:?}", executable)
});
let output = child.into_future().and_then(move |child| {
child.wait_with_output()
.chain_err(|| "failed to read child output")
});
Box::new(output.map(|output| {
if output.status.success() {
if let Ok(stdout) = String::from_utf8(output.stdout) {
if stdout.starts_with("rustc ") {
return true;
}
}
}
false
}))
} else {
f_ok(false)
};
let creator = creator.clone();
let executable = executable.to_owned();
let pool = pool.clone();
Box::new(is_rustc.and_then(move |is_rustc| {
if is_rustc {
debug!("Found rustc");
Box::new(Rust::new(creator, executable, pool).map(|c| Some(Box::new(c) as Box<Compiler<T>>)))
} else {
detect_c_compiler(creator, executable, pool)
}
}))
}
fn detect_c_compiler<T>(creator: T, executable: PathBuf, pool: CpuPool)
-> SFuture<Option<Box<Compiler<T>>>>
where T: CommandCreatorSync
{
trace!("detect_c_compiler");
let test = b"#if defined(_MSC_VER)
msvc
#elif defined(__clang__)
clang
#elif defined(__GNUC__)
gcc
#endif
".to_vec();
let write = write_temp_file(&pool, "testfile.c".as_ref(), test);
let mut cmd = creator.clone().new_command_sync(&executable);
cmd.stdout(Stdio::piped())
.stderr(Stdio::null());
let output = write.and_then(move |(tempdir, src)| {
cmd.arg("-E").arg(src);
trace!("compiler {:?}", cmd);
let child = cmd.spawn().chain_err(|| {
format!("failed to execute {:?}", cmd)
});
child.into_future().and_then(|child| {
child.wait_with_output().chain_err(|| "failed to read child output")
}).map(|e| {
drop(tempdir);
e
})
});
Box::new(output.and_then(move |output| -> SFuture<_> {
let stdout = match str::from_utf8(&output.stdout) {
Ok(s) => s,
Err(_) => return f_err("Failed to parse output"),
};
for line in stdout.lines() {
if line == "gcc" {
debug!("Found GCC");
return Box::new(CCompiler::new(GCC, executable, &pool)
.map(|c| Some(Box::new(c) as Box<Compiler<T>>)));
} else if line == "clang" {
debug!("Found clang");
return Box::new(CCompiler::new(Clang, executable, &pool)
.map(|c| Some(Box::new(c) as Box<Compiler<T>>)));
} else if line == "msvc" {
debug!("Found MSVC");
let prefix = msvc::detect_showincludes_prefix(&creator,
executable.as_ref(),
&pool);
return Box::new(prefix.and_then(move |prefix| {
trace!("showIncludes prefix: '{}'", prefix);
CCompiler::new(MSVC {
includes_prefix: prefix,
}, executable, &pool)
.map(|c| Some(Box::new(c) as Box<Compiler<T>>))
}))
}
}
debug!("nothing useful in detection output {:?}", stdout);
f_ok(None)
}))
}
pub fn get_compiler_info<T>(creator: &T, executable: &Path, pool: &CpuPool)
-> SFuture<Box<Compiler<T>>>
where T: CommandCreatorSync
{
let pool = pool.clone();
let detect = detect_compiler(creator, executable, &pool);
Box::new(detect.and_then(move |compiler| -> Result<_> {
match compiler {
Some(compiler) => Ok(compiler),
None => bail!("could not determine compiler kind"),
}
}))
}
#[cfg(test)]
mod test {
use super::*;
use cache::Storage;
use cache::disk::DiskCache;
use futures::Future;
use futures_cpupool::CpuPool;
use mock_command::*;
use std::fs::{self,File};
use std::io::Write;
use std::sync::Arc;
use std::time::Duration;
use std::usize;
use test::mock_storage::MockStorage;
use test::utils::*;
use tokio_core::reactor::Core;
#[test]
fn test_detect_compiler_kind_gcc() {
let f = TestFixture::new();
let creator = new_creator();
let pool = CpuPool::new(1);
next_command(&creator, Ok(MockChild::new(exit_status(0), "foo\nbar\ngcc", "")));
let c = detect_compiler(&creator, &f.bins[0], &pool).wait().unwrap().unwrap();
assert_eq!(CompilerKind::C(CCompilerKind::GCC), c.kind());
}
#[test]
fn test_detect_compiler_kind_clang() {
let f = TestFixture::new();
let creator = new_creator();
let pool = CpuPool::new(1);
next_command(&creator, Ok(MockChild::new(exit_status(0), "clang\nfoo", "")));
let c = detect_compiler(&creator, &f.bins[0], &pool).wait().unwrap().unwrap();
assert_eq!(CompilerKind::C(CCompilerKind::Clang), c.kind());
}
#[test]
fn test_detect_compiler_kind_msvc() {
use env_logger;
drop(env_logger::init());
let creator = new_creator();
let pool = CpuPool::new(1);
let f = TestFixture::new();
let srcfile = f.touch("test.h").unwrap();
let mut s = srcfile.to_str().unwrap();
if s.starts_with("\\\\?\\") {
s = &s[4..];
}
let prefix = String::from("blah: ");
let stdout = format!("{}{}\r\n", prefix, s);
next_command(&creator, Ok(MockChild::new(exit_status(0), "foo\nmsvc\nbar", "")));
next_command(&creator, Ok(MockChild::new(exit_status(0), &stdout, &String::new())));
let c = detect_compiler(&creator, &f.bins[0], &pool).wait().unwrap().unwrap();
assert_eq!(CompilerKind::C(CCompilerKind::MSVC), c.kind());
}
#[test]
fn test_detect_compiler_kind_rustc() {
let f = TestFixture::new();
fs::create_dir(f.tempdir.path().join("lib")).unwrap();
fs::create_dir(f.tempdir.path().join("bin")).unwrap();
let rustc = f.mk_bin("rustc").unwrap();
let creator = new_creator();
let pool = CpuPool::new(1);
next_command(&creator, Ok(MockChild::new(exit_status(0), "rustc 1.15 (blah 2017-01-01)", "")));
let sysroot = f.tempdir.path().to_str().unwrap();
next_command(&creator, Ok(MockChild::new(exit_status(0), &sysroot, "")));
let c = detect_compiler(&creator, &rustc, &pool).wait().unwrap().unwrap();
assert_eq!(CompilerKind::Rust, c.kind());
}
#[test]
fn test_detect_compiler_kind_unknown() {
let creator = new_creator();
let pool = CpuPool::new(1);
next_command(&creator, Ok(MockChild::new(exit_status(0), "something", "")));
assert!(detect_compiler(&creator, "/foo/bar".as_ref(), &pool).wait().unwrap().is_none());
}
#[test]
fn test_detect_compiler_kind_process_fail() {
let creator = new_creator();
let pool = CpuPool::new(1);
next_command(&creator, Ok(MockChild::new(exit_status(1), "", "")));
assert!(detect_compiler(&creator, "/foo/bar".as_ref(), &pool).wait().unwrap().is_none());
}
#[test]
fn test_get_compiler_info() {
let creator = new_creator();
let pool = CpuPool::new(1);
let f = TestFixture::new();
next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", "")));
let c = get_compiler_info(&creator,
&f.bins[0],
&pool).wait().unwrap();
assert_eq!(CompilerKind::C(CCompilerKind::GCC), c.kind());
}
#[test]
fn test_compiler_get_cached_or_compile_uncached() {
use env_logger;
drop(env_logger::init());
let creator = new_creator();
let f = TestFixture::new();
let pool = CpuPool::new(1);
let core = Core::new().unwrap();
let handle = core.handle();
let storage = DiskCache::new(&f.tempdir.path().join("cache"),
usize::MAX,
&pool);
let storage: Arc<Storage> = Arc::new(storage);
next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", "")));
let c = get_compiler_info(&creator,
&f.bins[0],
&pool).wait().unwrap();
next_command(&creator, Ok(MockChild::new(exit_status(0), "preprocessor output", "")));
const COMPILER_STDOUT : &'static [u8] = b"compiler stdout";
const COMPILER_STDERR : &'static [u8] = b"compiler stderr";
let obj = f.tempdir.path().join("foo.o");
let o = obj.clone();
next_command_calls(&creator, move |_| {
match File::create(&o)
.and_then(|mut f| f.write_all(b"file contents")) {
Ok(_) => Ok(MockChild::new(exit_status(0), COMPILER_STDOUT, COMPILER_STDERR)),
Err(e) => Err(e),
}
});
let cwd = f.tempdir.path();
let arguments = ovec!["-c", "foo.c", "-o", "foo.o"];
let hasher = match c.parse_arguments(&arguments, ".".as_ref()) {
CompilerArguments::Ok(h) => h,
o @ _ => panic!("Bad result from parse_arguments: {:?}", o),
};
let hasher2 = hasher.clone();
let (cached, res) = hasher.get_cached_or_compile(creator.clone(),
storage.clone(),
arguments.clone(),
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool.clone(),
handle.clone()).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
match cached {
CompileResult::CacheMiss(MissType::Normal, _, f) => {
f.wait().unwrap();
}
_ => assert!(false, "Unexpected compile result: {:?}", cached),
}
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
fs::remove_file(&obj).unwrap();
next_command(&creator, Ok(MockChild::new(exit_status(0), "preprocessor output", "")));
let (cached, res) = hasher2.get_cached_or_compile(creator.clone(),
storage.clone(),
arguments,
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool.clone(),
handle).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
assert_eq!(CompileResult::CacheHit(Duration::new(0, 0)), cached);
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
}
#[test]
fn test_compiler_get_cached_or_compile_cached() {
use env_logger;
drop(env_logger::init());
let creator = new_creator();
let f = TestFixture::new();
let pool = CpuPool::new(1);
let core = Core::new().unwrap();
let handle = core.handle();
let storage = DiskCache::new(&f.tempdir.path().join("cache"),
usize::MAX,
&pool);
let storage: Arc<Storage> = Arc::new(storage);
next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", "")));
let c = get_compiler_info(&creator,
&f.bins[0],
&pool).wait().unwrap();
next_command(&creator, Ok(MockChild::new(exit_status(0), "preprocessor output", "")));
const COMPILER_STDOUT : &'static [u8] = b"compiler stdout";
const COMPILER_STDERR : &'static [u8] = b"compiler stderr";
let obj = f.tempdir.path().join("foo.o");
let o = obj.clone();
next_command_calls(&creator, move |_| {
match File::create(&o)
.and_then(|mut f| f.write_all(b"file contents")) {
Ok(_) => Ok(MockChild::new(exit_status(0), COMPILER_STDOUT, COMPILER_STDERR)),
Err(e) => Err(e),
}
});
let cwd = f.tempdir.path();
let arguments = ovec!["-c", "foo.c", "-o", "foo.o"];
let hasher = match c.parse_arguments(&arguments, ".".as_ref()) {
CompilerArguments::Ok(h) => h,
o @ _ => panic!("Bad result from parse_arguments: {:?}", o),
};
let hasher2 = hasher.clone();
let (cached, res) = hasher.get_cached_or_compile(creator.clone(),
storage.clone(),
arguments.clone(),
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool.clone(),
handle.clone()).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
match cached {
CompileResult::CacheMiss(MissType::Normal, _, f) => {
f.wait().unwrap();
}
_ => assert!(false, "Unexpected compile result: {:?}", cached),
}
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
fs::remove_file(&obj).unwrap();
next_command(&creator, Ok(MockChild::new(exit_status(0), "preprocessor output", "")));
let (cached, res) = hasher2.get_cached_or_compile(creator,
storage,
arguments,
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool,
handle).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
assert_eq!(CompileResult::CacheHit(Duration::new(0, 0)), cached);
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
}
#[test]
fn test_compiler_get_cached_or_compile_cache_error() {
use env_logger;
drop(env_logger::init());
let creator = new_creator();
let f = TestFixture::new();
let pool = CpuPool::new(1);
let core = Core::new().unwrap();
let handle = core.handle();
let storage = MockStorage::new();
let storage: Arc<MockStorage> = Arc::new(storage);
next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", "")));
let c = get_compiler_info(&creator,
&f.bins[0],
&pool).wait().unwrap();
next_command(&creator, Ok(MockChild::new(exit_status(0), "preprocessor output", "")));
const COMPILER_STDOUT : &'static [u8] = b"compiler stdout";
const COMPILER_STDERR : &'static [u8] = b"compiler stderr";
let obj = f.tempdir.path().join("foo.o");
let o = obj.clone();
next_command_calls(&creator, move |_| {
match File::create(&o)
.and_then(|mut f| f.write_all(b"file contents")) {
Ok(_) => Ok(MockChild::new(exit_status(0), COMPILER_STDOUT, COMPILER_STDERR)),
Err(e) => Err(e),
}
});
let cwd = f.tempdir.path();
let arguments = ovec!["-c", "foo.c", "-o", "foo.o"];
let hasher = match c.parse_arguments(&arguments, ".".as_ref()) {
CompilerArguments::Ok(h) => h,
o @ _ => panic!("Bad result from parse_arguments: {:?}", o),
};
storage.next_get(f_err("Some Error"));
let (cached, res) = hasher.get_cached_or_compile(creator.clone(),
storage.clone(),
arguments.clone(),
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool.clone(),
handle.clone()).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
match cached {
CompileResult::CacheMiss(MissType::CacheReadError, _, f) => {
f.wait().unwrap();
}
_ => assert!(false, "Unexpected compile result: {:?}", cached),
}
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
}
#[test]
fn test_compiler_get_cached_or_compile_force_recache() {
use env_logger;
drop(env_logger::init());
let creator = new_creator();
let f = TestFixture::new();
let pool = CpuPool::new(1);
let core = Core::new().unwrap();
let handle = core.handle();
let storage = DiskCache::new(&f.tempdir.path().join("cache"),
usize::MAX,
&pool);
let storage: Arc<Storage> = Arc::new(storage);
next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", "")));
let c = get_compiler_info(&creator,
&f.bins[0],
&pool).wait().unwrap();
const COMPILER_STDOUT: &'static [u8] = b"compiler stdout";
const COMPILER_STDERR: &'static [u8] = b"compiler stderr";
let obj = f.tempdir.path().join("foo.o");
for _ in 0..2 {
next_command(&creator, Ok(MockChild::new(exit_status(0), "preprocessor output", "")));
let o = obj.clone();
next_command_calls(&creator, move |_| {
match File::create(&o)
.and_then(|mut f| f.write_all(b"file contents")) {
Ok(_) => Ok(MockChild::new(exit_status(0), COMPILER_STDOUT, COMPILER_STDERR)),
Err(e) => Err(e),
}
});
}
let cwd = f.tempdir.path();
let arguments = ovec!["-c", "foo.c", "-o", "foo.o"];
let hasher = match c.parse_arguments(&arguments, ".".as_ref()) {
CompilerArguments::Ok(h) => h,
o @ _ => panic!("Bad result from parse_arguments: {:?}", o),
};
let hasher2 = hasher.clone();
let (cached, res) = hasher.get_cached_or_compile(creator.clone(),
storage.clone(),
arguments.clone(),
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool.clone(),
handle.clone()).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
match cached {
CompileResult::CacheMiss(MissType::Normal, _, f) => {
f.wait().unwrap();
}
_ => assert!(false, "Unexpected compile result: {:?}", cached),
}
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
fs::remove_file(&obj).unwrap();
let (cached, res) = hasher2.get_cached_or_compile(creator,
storage,
arguments,
cwd.to_path_buf(),
vec![],
CacheControl::ForceRecache,
pool,
handle).wait().unwrap();
assert_eq!(true, fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap());
match cached {
CompileResult::CacheMiss(MissType::ForcedRecache, _, f) => {
f.wait().unwrap();
}
_ => assert!(false, "Unexpected compile result: {:?}", cached),
}
assert_eq!(exit_status(0), res.status);
assert_eq!(COMPILER_STDOUT, res.stdout.as_slice());
assert_eq!(COMPILER_STDERR, res.stderr.as_slice());
}
#[test]
fn test_compiler_get_cached_or_compile_preprocessor_error() {
use env_logger;
drop(env_logger::init());
let creator = new_creator();
let f = TestFixture::new();
let pool = CpuPool::new(1);
let core = Core::new().unwrap();
let handle = core.handle();
let storage = DiskCache::new(&f.tempdir.path().join("cache"),
usize::MAX,
&pool);
let storage: Arc<Storage> = Arc::new(storage);
next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", "")));
let c = get_compiler_info(&creator,
&f.bins[0],
&pool).wait().unwrap();
const PREPROCESSOR_STDERR: &'static [u8] = b"something went wrong";
next_command(&creator, Ok(MockChild::new(exit_status(1), b"preprocessor output", PREPROCESSOR_STDERR)));
let cwd = f.tempdir.path();
let arguments = ovec!["-c", "foo.c", "-o", "foo.o"];
let hasher = match c.parse_arguments(&arguments, ".".as_ref()) {
CompilerArguments::Ok(h) => h,
o @ _ => panic!("Bad result from parse_arguments: {:?}", o),
};
let (cached, res) = hasher.get_cached_or_compile(creator,
storage,
arguments,
cwd.to_path_buf(),
vec![],
CacheControl::Default,
pool,
handle).wait().unwrap();
assert_eq!(cached, CompileResult::Error);
assert_eq!(exit_status(1), res.status);
assert_eq!(b"", res.stdout.as_slice());
assert_eq!(PREPROCESSOR_STDERR, res.stderr.as_slice());
}
}