Skip to content

Commit

Permalink
Always pass a -x argument to gcc/clang
Browse files Browse the repository at this point in the history
First and foremost, to generically convey the type of compilation, we
normalize the file extension, and will use that information to feed
the right -x argument to the preprocessor and compiler.

When the compiler command sccache is invoked with already contains a -x
argument, we set the normalized file extension accordingly, such that
it matches what the caller wants, rather than what the source file
extension says.

On the preprocessor end, we then always pass one of `-x c`, `-x c++`,
`-x objective-c` or `-x objective-c++` according to the normalized file
extension.

On the compiler end, we always pass one of `-x cpp-output`, `-x
c++-cpp-output`, `-x objective-c-cpp-output` or `-x
objective-c++-cpp-output` accordingly.

Note that we used to pass `-x objc-cpp-output` to gcc, but that's a
deprecated form that it now warns about. The new form has been available
since at least gcc 4.3, so is fine to use.

And because a same source compiled as C or C++ will yield different
object code, include the normalized extension when computing the hashed
value.

Fixes #163.
  • Loading branch information
glandium authored and luser committed Aug 16, 2017
1 parent af8d6b0 commit ee10eae
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 38 deletions.
40 changes: 26 additions & 14 deletions src/compiler/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ impl Language {
}
}
}

pub fn as_str(&self) -> &'static str {
match *self {
Language::C => "c",
Language::Cxx => "c++",
Language::ObjectiveC => "objc",
Language::ObjectiveCxx => "objc++",
}
}
}

/// A generic implementation of the `Compilation` trait for C/C++ compilers.
Expand Down Expand Up @@ -233,6 +242,7 @@ impl<T, I> CompilerHasher<T> for CCompilerHasher<I>

let key = {
hash_key(&executable_digest,
parsed_args.language,
&parsed_args.common_args,
&env_vars,
&preprocessor_result.stdout)
Expand Down Expand Up @@ -281,7 +291,7 @@ impl<T: CommandCreatorSync, I: CCompilerImpl> Compilation<T> for CCompilation<I>
}

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

/// Environment variables that are factored into the cache key.
pub const CACHED_ENV_VARS : &'static [&'static str] = &[
Expand All @@ -291,6 +301,7 @@ pub const CACHED_ENV_VARS : &'static [&'static str] = &[

/// Compute the hash key of `compiler` compiling `preprocessor_output` with `args`.
pub fn hash_key(compiler_digest: &str,
language: Language,
arguments: &[OsString],
env_vars: &[(OsString, OsString)],
preprocessor_output: &[u8]) -> String
Expand All @@ -299,6 +310,7 @@ pub fn hash_key(compiler_digest: &str,
let mut m = Digest::new();
m.update(compiler_digest.as_bytes());
m.update(CACHE_VERSION);
m.update(language.as_str().as_bytes());
for arg in arguments {
arg.hash(&mut HashToDigest { digest: &mut m });
}
Expand All @@ -323,8 +335,8 @@ mod test {
fn test_hash_key_executable_contents_differs() {
let args = ovec!["a", "b", "c"];
const PREPROCESSED : &'static [u8] = b"hello world";
assert_neq!(hash_key("abcd",&args, &[], &PREPROCESSED),
hash_key("wxyz",&args, &[], &PREPROCESSED));
assert_neq!(hash_key("abcd", Language::C, &args, &[], &PREPROCESSED),
hash_key("wxyz", Language::C, &args, &[], &PREPROCESSED));
}

#[test]
Expand All @@ -335,21 +347,21 @@ mod test {
let ab = ovec!["a", "b"];
let a = ovec!["a"];
const PREPROCESSED: &'static [u8] = b"hello world";
assert_neq!(hash_key(digest, &abc, &[], &PREPROCESSED),
hash_key(digest, &xyz, &[], &PREPROCESSED));
assert_neq!(hash_key(digest, Language::C, &abc, &[], &PREPROCESSED),
hash_key(digest, Language::C, &xyz, &[], &PREPROCESSED));

assert_neq!(hash_key(digest, &abc, &[], &PREPROCESSED),
hash_key(digest, &ab, &[], &PREPROCESSED));
assert_neq!(hash_key(digest, Language::C, &abc, &[], &PREPROCESSED),
hash_key(digest, Language::C, &ab, &[], &PREPROCESSED));

assert_neq!(hash_key(digest, &abc, &[], &PREPROCESSED),
hash_key(digest, &a, &[], &PREPROCESSED));
assert_neq!(hash_key(digest, Language::C, &abc, &[], &PREPROCESSED),
hash_key(digest, Language::C, &a, &[], &PREPROCESSED));
}

#[test]
fn test_hash_key_preprocessed_content_differs() {
let args = ovec!["a", "b", "c"];
assert_neq!(hash_key("abcd", &args, &[], &b"hello world"[..]),
hash_key("abcd", &args, &[], &b"goodbye"[..]));
assert_neq!(hash_key("abcd", Language::C, &args, &[], &b"hello world"[..]),
hash_key("abcd", Language::C, &args, &[], &b"goodbye"[..]));
}

#[test]
Expand All @@ -358,11 +370,11 @@ mod test {
let digest = "abcd";
const PREPROCESSED: &'static [u8] = b"hello world";
for var in CACHED_ENV_VARS.iter() {
let h1 = hash_key(digest, &args, &[], &PREPROCESSED);
let h1 = hash_key(digest, Language::C, &args, &[], &PREPROCESSED);
let vars = vec![(OsString::from(var), OsString::from("something"))];
let h2 = hash_key(digest, &args, &vars, &PREPROCESSED);
let h2 = hash_key(digest, Language::C, &args, &vars, &PREPROCESSED);
let vars = vec![(OsString::from(var), OsString::from("something else"))];
let h3 = hash_key(digest, &args, &vars, &PREPROCESSED);
let h3 = hash_key(digest, Language::C, &args, &vars, &PREPROCESSED);
assert_neq!(h1, h2);
assert_neq!(h2, h3);
}
Expand Down
67 changes: 43 additions & 24 deletions src/compiler/gcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ where
let mut multiple_input = false;
let mut split_dwarf = false;
let mut need_explicit_dep_target = false;
let mut language = None;

// Custom iterator to expand `@` arguments which stand for reading a file
// and interpreting it as a list of more arguments.
Expand Down Expand Up @@ -209,8 +210,18 @@ where
Some(NeedDepTarget) => need_explicit_dep_target = true,
Some(DepTarget) => dep_target = item.arg.get_value().map(OsString::from),
Some(PreprocessorArgument) |
Some(PassThrough) |
Some(Language) => {}
Some(PassThrough) => {}
Some(Language) => {
let lang = item.arg.get_value().map(OsString::from);
let lang = lang.as_ref().map(|a| a.to_string_lossy());
language = match lang.as_ref().map(|a| a.as_ref()) {
Some("c") => Some(Language::C),
Some("c++") => Some(Language::Cxx),
Some("objective-c") => Some(Language::ObjectiveC),
Some("objective-c++") => Some(Language::ObjectiveCxx),
_ => return CompilerArguments::CannotCache("-x"),
};
}
None => {
match item.arg {
Argument::Raw(ref val) => {
Expand All @@ -226,11 +237,11 @@ where
}
let args = match item.data {
Some(SplitDwarf) |
Some(PassThrough) |
Some(Language) => Some(&mut common_args),
Some(PassThrough) => Some(&mut common_args),
Some(PreprocessorArgument) |
Some(NeedDepTarget) => Some(&mut preprocessor_args),
Some(DoCompilation) |
Some(Language) |
Some(Output) |
Some(DepTarget) => None,
Some(TooHard) => unreachable!(),
Expand Down Expand Up @@ -262,18 +273,18 @@ where
if multiple_input {
return CompilerArguments::CannotCache("multiple input files");
}
let (input, language) = match input_arg {
Some(i) => {
// When compiling from the preprocessed output given as stdin, we need
// to explicitly pass its file type.
match Language::from_file_name(Path::new(&i)) {
Some(l) => (i.to_owned(), l),
None => return CompilerArguments::CannotCache("unknown source language"),
}
}
let input = match input_arg {
Some(i) => i.to_owned(),
// We can't cache compilation without an input.
None => return CompilerArguments::CannotCache("no input file"),
};
if language == None {
language = Language::from_file_name(Path::new(&input));
}
let language = match language {
Some(l) => l,
None => return CompilerArguments::CannotCache("unknown source language"),
};
let mut outputs = HashMap::new();
let output = match output_arg {
// We can't cache compilation that doesn't go to a file
Expand Down Expand Up @@ -311,14 +322,22 @@ pub fn preprocess<T>(creator: &T,
where T: CommandCreatorSync
{
trace!("preprocess");
let language = match parsed_args.language {
Language::C => "c",
Language::Cxx => "c++",
Language::ObjectiveC => "objective-c",
Language::ObjectiveCxx => "objective-c++",
};
let mut cmd = creator.clone().new_command_sync(executable);
cmd.arg("-E")
cmd.arg("-x").arg(language)
.arg("-E")
.arg(&parsed_args.input)
.args(&parsed_args.preprocessor_args)
.args(&parsed_args.common_args)
.env_clear()
.envs(env_vars.iter().map(|&(ref k, ref v)| (k, v)))
.current_dir(cwd);

if log_enabled!(Trace) {
trace!("preprocess: {:?}", cmd);
}
Expand All @@ -345,24 +364,24 @@ pub fn compile<T>(creator: &T,
}
};

// When reading from stdin the language argument is needed
let language = match parsed_args.language {
Language::C => "cpp-output",
Language::Cxx => "c++-cpp-output",
Language::ObjectiveC => "objective-c-cpp-output",
Language::ObjectiveCxx => "objective-c++-cpp-output",
};
let mut attempt = creator.clone().new_command_sync(executable);
attempt.arg("-c")
attempt.arg("-x").arg(language)
.arg("-c")
.arg("-o").arg(&out_file)
.args(&parsed_args.common_args)
.env_clear()
.envs(env_vars.iter().map(|&(ref k, ref v)| (k, v)))
.current_dir(&cwd);

// When reading from stdin the language argument is needed
let language = parsed_args.language;
let pre = pre.unwrap_or(Box::new(pool.spawn_fn(move || {
let language = match language {
Language::C => "cpp-output",
Language::Cxx => "c++-cpp-output",
Language::ObjectiveC => "objective-c-cpp-output",
Language::ObjectiveCxx => "objective-c++-cpp-output",
};
let args = vec!("-x".to_owned(), language.to_owned(), "-".to_owned());
let args = vec!("-".to_owned());
Ok((Some(preprocessor_result.stdout), args, None))
})));

Expand Down

0 comments on commit ee10eae

Please sign in to comment.