use crate::compiler::args::*;
use crate::compiler::c::{
ArtifactDescriptor, CCompilerImpl, CCompilerKind, Language, ParsedArguments,
};
use crate::compiler::{clang, Cacheable, ColorMode, CompileCommand, CompilerArguments};
use crate::mock_command::{CommandCreatorSync, RunCommand};
use crate::util::{run_input_output, OsStrExt};
use crate::{counted_array, dist};
use log::Level::Trace;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process;
use crate::errors::*;
#[derive(Clone, Debug)]
pub struct Gcc {
pub gplusplus: bool,
pub version: Option<String>,
}
#[async_trait]
impl CCompilerImpl for Gcc {
fn kind(&self) -> CCompilerKind {
CCompilerKind::Gcc
}
fn plusplus(&self) -> bool {
self.gplusplus
}
fn version(&self) -> Option<String> {
self.version.clone()
}
fn parse_arguments(
&self,
arguments: &[OsString],
cwd: &Path,
) -> CompilerArguments<ParsedArguments> {
parse_arguments(arguments, cwd, &ARGS[..], self.gplusplus, self.kind())
}
#[allow(clippy::too_many_arguments)]
async fn preprocess<T>(
&self,
creator: &T,
executable: &Path,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
may_dist: bool,
rewrite_includes_only: bool,
) -> Result<process::Output>
where
T: CommandCreatorSync,
{
preprocess(
creator,
executable,
parsed_args,
cwd,
env_vars,
may_dist,
self.kind(),
rewrite_includes_only,
vec!["-P".to_string()],
)
.await
}
fn generate_compile_commands(
&self,
path_transformer: &mut dist::PathTransformer,
executable: &Path,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
rewrite_includes_only: bool,
) -> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)> {
generate_compile_commands(
path_transformer,
executable,
parsed_args,
cwd,
env_vars,
self.kind(),
rewrite_includes_only,
)
}
}
ArgData! { pub
TooHardFlag,
TooHard(OsString),
DiagnosticsColor(OsString),
DiagnosticsColorFlag,
NoDiagnosticsColorFlag,
PassThrough(OsString),
PassThroughPath(PathBuf),
PreprocessorArgumentFlag,
PreprocessorArgument(OsString),
PreprocessorArgumentPath(PathBuf),
DoCompilation,
Output(PathBuf),
NeedDepTarget,
DepTarget(OsString),
DepArgumentPath(PathBuf),
Language(OsString),
SplitDwarf,
ProfileGenerate,
ClangProfileUse(PathBuf),
TestCoverage,
Coverage,
ExtraHashFile(PathBuf),
XClang(OsString),
Arch(OsString),
PedanticFlag,
Standard(OsString),
}
use self::ArgData::*;
counted_array!(pub static ARGS: [ArgInfo<ArgData>; _] = [
flag!("-", TooHardFlag),
flag!("--coverage", Coverage),
take_arg!("--param", OsString, Separated, PassThrough),
flag!("--save-temps", TooHardFlag),
take_arg!("--serialize-diagnostics", PathBuf, Separated, PassThroughPath),
take_arg!("--sysroot", PathBuf, Separated, PassThroughPath),
take_arg!("-A", OsString, Separated, PassThrough),
take_arg!("-B", PathBuf, CanBeSeparated, PassThroughPath),
take_arg!("-D", OsString, CanBeSeparated, PassThrough),
flag!("-E", TooHardFlag),
take_arg!("-F", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-G", OsString, Separated, PassThrough),
take_arg!("-I", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-L", OsString, Separated, PassThrough),
flag!("-M", TooHardFlag),
flag!("-MD", NeedDepTarget),
take_arg!("-MF", PathBuf, Separated, DepArgumentPath),
flag!("-MM", TooHardFlag),
flag!("-MMD", NeedDepTarget),
flag!("-MP", NeedDepTarget),
take_arg!("-MQ", OsString, Separated, DepTarget),
take_arg!("-MT", OsString, Separated, DepTarget),
flag!("-P", TooHardFlag),
take_arg!("-U", OsString, CanBeSeparated, PassThrough),
take_arg!("-V", OsString, Separated, PassThrough),
flag!("-Werror=pedantic", PedanticFlag),
flag!("-Wpedantic", PedanticFlag),
take_arg!("-Xassembler", OsString, Separated, PassThrough),
take_arg!("-Xlinker", OsString, Separated, PassThrough),
take_arg!("-Xpreprocessor", OsString, Separated, PreprocessorArgument),
take_arg!("-arch", OsString, Separated, Arch),
take_arg!("-aux-info", OsString, Separated, PassThrough),
take_arg!("-b", OsString, Separated, PassThrough),
flag!("-c", DoCompilation),
take_arg!("-fdiagnostics-color", OsString, Concatenated('='), DiagnosticsColor),
flag!("-fno-diagnostics-color", NoDiagnosticsColorFlag),
flag!("-fno-profile-generate", TooHardFlag),
flag!("-fno-profile-use", TooHardFlag),
flag!("-fno-working-directory", PreprocessorArgumentFlag),
flag!("-fplugin=libcc1plugin", TooHardFlag),
flag!("-fprofile-arcs", ProfileGenerate),
flag!("-fprofile-generate", ProfileGenerate),
take_arg!("-fprofile-use", OsString, Concatenated, TooHard),
flag!("-frepo", TooHardFlag),
flag!("-fsyntax-only", TooHardFlag),
flag!("-ftest-coverage", TestCoverage),
flag!("-fworking-directory", PreprocessorArgumentFlag),
flag!("-gsplit-dwarf", SplitDwarf),
take_arg!("-idirafter", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-iframework", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-imacros", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-imultilib", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-include", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-install_name", OsString, Separated, PassThrough),
take_arg!("-iprefix", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-iquote", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-isysroot", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-isystem", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-iwithprefix", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
take_arg!("-iwithprefixbefore", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
flag!("-nostdinc", PreprocessorArgumentFlag),
flag!("-nostdinc++", PreprocessorArgumentFlag),
take_arg!("-o", PathBuf, CanBeSeparated, Output),
flag!("-pedantic", PedanticFlag),
flag!("-pedantic-errors", PedanticFlag),
flag!("-remap", PreprocessorArgumentFlag),
flag!("-save-temps", TooHardFlag),
take_arg!("-std", OsString, Concatenated('='), Standard),
take_arg!("-stdlib", OsString, Concatenated('='), PreprocessorArgument),
flag!("-trigraphs", PreprocessorArgumentFlag),
take_arg!("-u", OsString, CanBeSeparated, PassThrough),
take_arg!("-x", OsString, CanBeSeparated, Language),
take_arg!("-z", OsString, CanBeSeparated, PassThrough),
take_arg!("@", OsString, Concatenated, TooHard),
]);
pub fn parse_arguments<S>(
arguments: &[OsString],
cwd: &Path,
arg_info: S,
plusplus: bool,
kind: CCompilerKind,
) -> CompilerArguments<ParsedArguments>
where
S: SearchableArgInfo<ArgData>,
{
let mut output_arg = None;
let mut input_arg = None;
let mut dep_target = None;
let mut dep_flag = OsString::from("-MT");
let mut common_args = vec![];
let mut preprocessor_args = vec![];
let mut dependency_args = vec![];
let mut extra_hash_files = vec![];
let mut compilation = false;
let mut multiple_input = false;
let mut multiple_input_files = Vec::new();
let mut pedantic_flag = false;
let mut language_extensions = true; let mut split_dwarf = false;
let mut need_explicit_dep_target = false;
enum DepArgumentRequirePath {
NotNeeded,
Missing,
Provided,
}
let mut need_explicit_dep_argument_path = DepArgumentRequirePath::NotNeeded;
let mut language = None;
let mut compilation_flag = OsString::new();
let mut profile_generate = false;
let mut outputs_gcno = false;
let mut xclangs: Vec<OsString> = vec![];
let mut color_mode = ColorMode::Auto;
let mut seen_arch = None;
let it = ExpandIncludeFile::new(cwd, arguments);
for arg in ArgsIter::new(it, arg_info) {
let arg = try_or_cannot_cache!(arg, "argument parse");
match arg {
Argument::WithValue(_, ref v, ArgDisposition::Separated)
| Argument::WithValue(_, ref v, ArgDisposition::CanBeConcatenated(_))
| Argument::WithValue(_, ref v, ArgDisposition::CanBeSeparated(_)) => {
if v.clone().into_arg_os_string().starts_with("@") {
cannot_cache!("@");
}
}
Argument::WithValue(_, _, ArgDisposition::Concatenated(_))
| Argument::Raw(_)
| Argument::UnknownFlag(_)
| Argument::Flag(_, _) => {}
}
match arg.get_data() {
Some(TooHardFlag) | Some(TooHard(_)) => {
cannot_cache!(arg.flag_str().expect("Can't be Argument::Raw/UnknownFlag",))
}
Some(PedanticFlag) => pedantic_flag = true,
Some(Standard(version)) => language_extensions = version.starts_with("gnu"),
Some(SplitDwarf) => split_dwarf = true,
Some(DoCompilation) => {
compilation = true;
compilation_flag =
OsString::from(arg.flag_str().expect("Compilation flag expected"));
}
Some(ProfileGenerate) => profile_generate = true,
Some(ClangProfileUse(path)) => {
extra_hash_files.push(clang::resolve_profile_use_path(path, cwd));
}
Some(TestCoverage) => outputs_gcno = true,
Some(Coverage) => {
outputs_gcno = true;
profile_generate = true;
}
Some(DiagnosticsColorFlag) => color_mode = ColorMode::On,
Some(NoDiagnosticsColorFlag) => color_mode = ColorMode::Off,
Some(DiagnosticsColor(value)) => {
color_mode = match value.to_str().unwrap_or("auto") {
"" | "always" => ColorMode::On,
"never" => ColorMode::Off,
_ => ColorMode::Auto,
};
}
Some(Output(p)) => output_arg = Some(p.clone()),
Some(NeedDepTarget) => {
need_explicit_dep_target = true;
if let DepArgumentRequirePath::NotNeeded = need_explicit_dep_argument_path {
need_explicit_dep_argument_path = DepArgumentRequirePath::Missing;
}
}
Some(DepTarget(s)) => {
dep_flag = OsString::from(arg.flag_str().expect("Dep target flag expected"));
dep_target = Some(s.clone());
}
Some(DepArgumentPath(_)) => {
need_explicit_dep_argument_path = DepArgumentRequirePath::Provided
}
Some(ExtraHashFile(_))
| Some(PreprocessorArgumentFlag)
| Some(PreprocessorArgument(_))
| Some(PreprocessorArgumentPath(_))
| Some(PassThrough(_))
| Some(PassThroughPath(_)) => {}
Some(Language(lang)) => {
language = match lang.to_string_lossy().as_ref() {
"c" => Some(Language::C),
"c++" => Some(Language::Cxx),
"objective-c" => Some(Language::ObjectiveC),
"objective-c++" => Some(Language::ObjectiveCxx),
"cu" => Some(Language::Cuda),
_ => cannot_cache!("-x"),
};
}
Some(Arch(arch)) => {
match seen_arch {
Some(s) if &s != arch => cannot_cache!("multiple different -arch"),
_ => {}
};
seen_arch = Some(arch.clone());
}
Some(XClang(s)) => xclangs.push(s.clone()),
None => match arg {
Argument::Raw(ref val) => {
if input_arg.is_some() {
multiple_input = true;
multiple_input_files.push(val.clone());
}
input_arg = Some(val.clone());
}
Argument::UnknownFlag(_) => {}
_ => unreachable!(),
},
}
let args = match arg.get_data() {
Some(SplitDwarf)
| Some(PedanticFlag)
| Some(Standard(_))
| Some(ProfileGenerate)
| Some(ClangProfileUse(_))
| Some(TestCoverage)
| Some(Coverage)
| Some(DiagnosticsColor(_))
| Some(DiagnosticsColorFlag)
| Some(NoDiagnosticsColorFlag)
| Some(Arch(_))
| Some(PassThrough(_))
| Some(PassThroughPath(_)) => &mut common_args,
Some(ExtraHashFile(path)) => {
extra_hash_files.push(cwd.join(path));
&mut common_args
}
Some(PreprocessorArgumentFlag)
| Some(PreprocessorArgument(_))
| Some(PreprocessorArgumentPath(_)) => &mut preprocessor_args,
Some(DepArgumentPath(_)) | Some(NeedDepTarget) => &mut dependency_args,
Some(DoCompilation) | Some(Language(_)) | Some(Output(_)) | Some(XClang(_))
| Some(DepTarget(_)) => continue,
Some(TooHardFlag) | Some(TooHard(_)) => unreachable!(),
None => match arg {
Argument::Raw(_) => continue,
Argument::UnknownFlag(_) => &mut common_args,
_ => unreachable!(),
},
};
let norm = match arg.flag_str() {
Some(s) if s.len() == 2 => NormalizedDisposition::Concatenated,
_ => NormalizedDisposition::Separated,
};
args.extend(arg.normalize(norm).iter_os_strings());
}
let xclang_it = ExpandIncludeFile::new(cwd, &xclangs);
let mut follows_plugin_arg = false;
for arg in ArgsIter::new(xclang_it, (&ARGS[..], &clang::ARGS[..])) {
let arg = try_or_cannot_cache!(arg, "argument parse");
let args = match arg.get_data() {
Some(SplitDwarf)
| Some(PedanticFlag)
| Some(Standard(_))
| Some(ProfileGenerate)
| Some(ClangProfileUse(_))
| Some(TestCoverage)
| Some(Coverage)
| Some(DoCompilation)
| Some(Language(_))
| Some(Output(_))
| Some(TooHardFlag)
| Some(XClang(_))
| Some(TooHard(_)) => cannot_cache!(arg
.flag_str()
.unwrap_or("Can't handle complex arguments through clang",)),
None => match arg {
Argument::Raw(_) if follows_plugin_arg => &mut common_args,
Argument::Raw(_) => cannot_cache!("Can't handle Raw arguments with -Xclang"),
Argument::UnknownFlag(_) => {
cannot_cache!("Can't handle UnknownFlag arguments with -Xclang")
}
_ => unreachable!(),
},
Some(DiagnosticsColor(_))
| Some(DiagnosticsColorFlag)
| Some(NoDiagnosticsColorFlag)
| Some(Arch(_))
| Some(PassThrough(_))
| Some(PassThroughPath(_)) => &mut common_args,
Some(ExtraHashFile(path)) => {
extra_hash_files.push(cwd.join(path));
&mut common_args
}
Some(PreprocessorArgumentFlag)
| Some(PreprocessorArgument(_))
| Some(PreprocessorArgumentPath(_)) => &mut preprocessor_args,
Some(DepTarget(_)) | Some(DepArgumentPath(_)) | Some(NeedDepTarget) => {
&mut dependency_args
}
};
follows_plugin_arg = match arg.flag_str() {
Some(s) => s == "-plugin-arg",
_ => false,
};
let norm = match arg.flag_str() {
Some(s) if s.len() == 2 => NormalizedDisposition::Concatenated,
_ => NormalizedDisposition::Separated,
};
for arg in arg.normalize(norm).iter_os_strings() {
args.push("-Xclang".into());
args.push(arg)
}
}
if !compilation {
return CompilerArguments::NotCompilation;
}
if multiple_input {
cannot_cache!(
"multiple input files",
format!("{:?}", multiple_input_files)
);
}
let input = match input_arg {
Some(i) => i,
None => cannot_cache!("no input file"),
};
let language = match language {
None => {
let mut lang = Language::from_file_name(Path::new(&input));
if let (Some(Language::C), true) = (lang, plusplus) {
lang = Some(Language::Cxx);
}
lang
}
l => l,
};
let language = match language {
Some(l) => l,
None => cannot_cache!("unknown source language"),
};
let mut outputs = HashMap::new();
let output = match output_arg {
None => PathBuf::from(Path::new(&input).with_extension("o").file_name().unwrap()),
Some(o) => o,
};
if split_dwarf {
let dwo = output.with_extension("dwo");
outputs.insert(
"dwo",
ArtifactDescriptor {
path: dwo,
optional: true,
},
);
}
let suppress_rewrite_includes_only = match kind {
CCompilerKind::Gcc => language_extensions && pedantic_flag,
_ => false,
};
if outputs_gcno {
let gcno = output.with_extension("gcno");
outputs.insert(
"gcno",
ArtifactDescriptor {
path: gcno,
optional: false,
},
);
profile_generate = true;
}
if need_explicit_dep_target {
dependency_args.push(dep_flag);
dependency_args.push(dep_target.unwrap_or_else(|| output.clone().into_os_string()));
}
if let DepArgumentRequirePath::Missing = need_explicit_dep_argument_path {
dependency_args.push(OsString::from("-MF"));
dependency_args.push(Path::new(&output).with_extension("d").into_os_string());
}
outputs.insert(
"obj",
ArtifactDescriptor {
path: output,
optional: false,
},
);
CompilerArguments::Ok(ParsedArguments {
input: input.into(),
language,
compilation_flag,
depfile: None,
outputs,
dependency_args,
preprocessor_args,
common_args,
extra_hash_files,
msvc_show_includes: false,
profile_generate,
color_mode,
suppress_rewrite_includes_only,
})
}
#[allow(clippy::too_many_arguments)]
fn preprocess_cmd<T>(
cmd: &mut T,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
may_dist: bool,
kind: CCompilerKind,
rewrite_includes_only: bool,
ignorable_whitespace_flags: Vec<String>,
) where
T: RunCommand,
{
let language = match parsed_args.language {
Language::C => "c",
Language::Cxx => "c++",
Language::ObjectiveC => "objective-c",
Language::ObjectiveCxx => "objective-c++",
Language::Cuda => "cu",
};
cmd.arg("-x").arg(language).arg("-E");
if !may_dist && !parsed_args.profile_generate {
cmd.args(&ignorable_whitespace_flags);
}
if rewrite_includes_only {
if parsed_args.suppress_rewrite_includes_only {
if log_enabled!(Trace) {
trace!("preprocess: pedantic arguments disable rewrite_includes_only");
}
} else {
match kind {
CCompilerKind::Clang => {
cmd.arg("-frewrite-includes");
}
CCompilerKind::Gcc => {
cmd.arg("-fdirectives-only");
}
_ => {}
}
}
}
cmd.arg(&parsed_args.input)
.args(&parsed_args.preprocessor_args)
.args(&parsed_args.dependency_args)
.args(&parsed_args.common_args)
.env_clear()
.envs(env_vars.iter().map(|&(ref k, ref v)| (k, v)))
.current_dir(cwd);
}
#[allow(clippy::too_many_arguments)]
pub async fn preprocess<T>(
creator: &T,
executable: &Path,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
may_dist: bool,
kind: CCompilerKind,
rewrite_includes_only: bool,
ignorable_whitespace_flags: Vec<String>,
) -> Result<process::Output>
where
T: CommandCreatorSync,
{
trace!("preprocess");
let mut cmd = creator.clone().new_command_sync(executable);
preprocess_cmd(
&mut cmd,
parsed_args,
cwd,
env_vars,
may_dist,
kind,
rewrite_includes_only,
ignorable_whitespace_flags,
);
if log_enabled!(Trace) {
trace!("preprocess: {:?}", cmd);
}
run_input_output(cmd, None).await
}
pub fn generate_compile_commands(
path_transformer: &mut dist::PathTransformer,
executable: &Path,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
kind: CCompilerKind,
rewrite_includes_only: bool,
) -> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)> {
#[cfg(not(feature = "dist-client"))]
{
let _ = path_transformer;
let _ = kind;
let _ = rewrite_includes_only;
}
trace!("compile");
let out_file = match parsed_args.outputs.get("obj") {
Some(obj) => &obj.path,
None => return Err(anyhow!("Missing object file output")),
};
let language = match parsed_args.language {
Language::C => "c",
Language::Cxx => "c++",
Language::ObjectiveC => "objective-c",
Language::ObjectiveCxx => "objective-c++",
Language::Cuda => "cu",
};
let mut arguments: Vec<OsString> = vec![
"-x".into(),
language.into(),
parsed_args.compilation_flag.clone(),
parsed_args.input.clone().into(),
"-o".into(),
out_file.into(),
];
arguments.extend(parsed_args.preprocessor_args.clone());
arguments.extend(parsed_args.common_args.clone());
let command = CompileCommand {
executable: executable.to_owned(),
arguments,
env_vars: env_vars.to_owned(),
cwd: cwd.to_owned(),
};
#[cfg(not(feature = "dist-client"))]
let dist_command = None;
#[cfg(feature = "dist-client")]
let dist_command = (|| {
let mut language: String = match parsed_args.language {
Language::C => "c",
Language::Cxx => "c++",
Language::ObjectiveC => "objective-c",
Language::ObjectiveCxx => "objective-c++",
Language::Cuda => "cu",
}
.into();
if !rewrite_includes_only {
match parsed_args.language {
Language::C => language = "cpp-output".into(),
_ => language.push_str("-cpp-output"),
}
}
let mut arguments: Vec<String> = vec![
"-x".into(),
language,
parsed_args.compilation_flag.clone().into_string().ok()?,
path_transformer.as_dist(&parsed_args.input)?,
"-o".into(),
path_transformer.as_dist(out_file)?,
];
if let CCompilerKind::Gcc = kind {
if rewrite_includes_only && !parsed_args.suppress_rewrite_includes_only {
arguments.push("-fdirectives-only".into());
}
arguments.push("-fpreprocessed".into());
}
arguments.extend(dist::osstrings_to_strings(&parsed_args.common_args)?);
Some(dist::CompileCommand {
executable: path_transformer.as_dist(executable)?,
arguments,
env_vars: dist::osstring_tuples_to_strings(env_vars)?,
cwd: path_transformer.as_dist_abs(cwd)?,
})
})();
Ok((command, dist_command, Cacheable::Yes))
}
pub struct ExpandIncludeFile<'a> {
cwd: &'a Path,
stack: Vec<OsString>,
}
impl<'a> ExpandIncludeFile<'a> {
pub fn new(cwd: &'a Path, args: &[OsString]) -> Self {
ExpandIncludeFile {
stack: args.iter().rev().map(|a| a.to_owned()).collect(),
cwd,
}
}
}
impl<'a> Iterator for ExpandIncludeFile<'a> {
type Item = OsString;
fn next(&mut self) -> Option<OsString> {
loop {
let arg = match self.stack.pop() {
Some(arg) => arg,
None => return None,
};
let file = match arg.split_prefix("@") {
Some(arg) => self.cwd.join(&arg),
None => return Some(arg),
};
let mut contents = String::new();
let res = File::open(&file).and_then(|mut f| f.read_to_string(&mut contents));
if let Err(e) = res {
debug!("failed to read @-file `{}`: {}", file.display(), e);
return Some(arg);
}
if contents.contains('"') || contents.contains('\'') {
return Some(arg);
}
let new_args = contents.split_whitespace().collect::<Vec<_>>();
self.stack.extend(new_args.iter().rev().map(|s| s.into()));
}
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use std::io::Write;
use super::*;
use crate::compiler::*;
use crate::mock_command::*;
use crate::test::utils::*;
fn parse_arguments_(
arguments: Vec<String>,
plusplus: bool,
) -> CompilerArguments<ParsedArguments> {
let args = arguments.iter().map(OsString::from).collect::<Vec<_>>();
parse_arguments(&args, ".".as_ref(), &ARGS[..], plusplus, CCompilerKind::Gcc)
}
#[test]
fn test_parse_arguments_simple() {
let args = stringvec!["-c", "foo.c", "-o", "foo.o"];
let ParsedArguments {
input,
language,
compilation_flag,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_eq!(Some("-c"), compilation_flag.to_str());
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_default_name() {
let args = stringvec!["-c", "foo.c"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_default_outputdir() {
let args = stringvec!["-c", "/tmp/foo.c"];
let ParsedArguments { outputs, .. } = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
}
#[test]
fn test_parse_arguments_split_dwarf() {
let args = stringvec!["-gsplit-dwarf", "-c", "foo.cpp", "-o", "foo.o"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.cpp"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
),
(
"dwo",
ArtifactDescriptor {
path: "foo.dwo".into(),
optional: true
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["-gsplit-dwarf"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_linker_options() {
let args = stringvec![
"-Wl,--unresolved-symbols=report-all",
"-z",
"call-nop=suffix-nop",
"-z",
"deps",
"-c",
"foo.c",
"-o",
"foo.o"
];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(3, common_args.len());
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_coverage_outputs_gcno() {
let args = stringvec!["--coverage", "-c", "foo.cpp", "-o", "foo.o"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
profile_generate,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.cpp"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
),
(
"gcno",
ArtifactDescriptor {
path: PathBuf::from("foo.gcno"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["--coverage"], common_args);
assert!(!msvc_show_includes);
assert!(profile_generate);
}
#[test]
fn test_parse_arguments_test_coverage_outputs_gcno() {
let args = stringvec!["-ftest-coverage", "-c", "foo.cpp", "-o", "foo.o"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
profile_generate,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.cpp"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
),
(
"gcno",
ArtifactDescriptor {
path: PathBuf::from("foo.gcno"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["-ftest-coverage"], common_args);
assert!(!msvc_show_includes);
assert!(profile_generate);
}
#[test]
fn test_parse_arguments_profile_generate() {
let args = stringvec!["-fprofile-generate", "-c", "foo.cpp", "-o", "foo.o"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
profile_generate,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.cpp"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["-fprofile-generate"], common_args);
assert!(!msvc_show_includes);
assert!(profile_generate);
}
#[test]
fn test_parse_arguments_extra() {
let args = stringvec!["-c", "foo.cc", "-fabc", "-o", "foo.o", "-mxyz"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.cc"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["-fabc", "-mxyz"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_values() {
let args = stringvec![
"-c", "foo.cxx", "-fabc", "-I", "include", "-o", "foo.o", "-include", "file"
];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.cxx"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert_eq!(ovec!["-Iinclude", "-include", "file"], preprocessor_args);
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_preprocessor_args() {
let args = stringvec![
"-c",
"foo.c",
"-fabc",
"-MF",
"file",
"-o",
"foo.o",
"-MQ",
"abc",
"-nostdinc"
];
let ParsedArguments {
input,
language,
outputs,
dependency_args,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert_eq!(ovec!["-MF", "file"], dependency_args);
assert_eq!(ovec!["-nostdinc"], preprocessor_args);
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_explicit_dep_target() {
let args =
stringvec!["-c", "foo.c", "-MT", "depfile", "-fabc", "-MF", "file", "-o", "foo.o"];
let ParsedArguments {
input,
language,
outputs,
dependency_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert_eq!(ovec!["-MF", "file"], dependency_args);
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_explicit_dep_target_needed() {
let args = stringvec![
"-c", "foo.c", "-MT", "depfile", "-fabc", "-MF", "file", "-o", "foo.o", "-MD"
];
let ParsedArguments {
input,
language,
outputs,
dependency_args,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert_eq!(
ovec!["-MF", "file", "-MD", "-MT", "depfile"],
dependency_args
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_explicit_mq_dep_target_needed() {
let args = stringvec![
"-c", "foo.c", "-MQ", "depfile", "-fabc", "-MF", "file", "-o", "foo.o", "-MD"
];
let ParsedArguments {
input,
language,
outputs,
dependency_args,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert_eq!(
ovec!["-MF", "file", "-MD", "-MQ", "depfile"],
dependency_args
);
assert!(preprocessor_args.is_empty());
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_diagnostics_color() {
fn get_color_mode(color_flag: &str) -> ColorMode {
let args = stringvec!["-c", "foo.c", color_flag];
match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args.color_mode,
o => panic!("Got unexpected parse result: {:?}", o),
}
}
assert_eq!(get_color_mode("-fdiagnostics-color=always"), ColorMode::On);
assert_eq!(get_color_mode("-fdiagnostics-color=never"), ColorMode::Off);
assert_eq!(get_color_mode("-fdiagnostics-color=auto"), ColorMode::Auto);
assert_eq!(get_color_mode("-fno-diagnostics-color"), ColorMode::Off);
assert_eq!(get_color_mode("-fdiagnostics-color"), ColorMode::On);
}
#[test]
fn color_mode_preprocess() {
let args = stringvec!["-c", "foo.c", "-fdiagnostics-color"];
let args = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert!(args.common_args.contains(&"-fdiagnostics-color".into()));
}
#[test]
fn pedantic_default() {
let args = stringvec!["-pedantic", "-c", "foo.cc"];
let parsed_args = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
let mut cmd = MockCommand {
child: None,
args: vec![],
};
preprocess_cmd(
&mut cmd,
&parsed_args,
Path::new(""),
&[],
true,
CCompilerKind::Gcc,
true,
vec![],
);
assert!(!cmd.args.contains(&"-fdirectives-only".into()));
}
#[test]
fn pedantic_std() {
let args = stringvec!["-pedantic-errors", "-c", "-std=c++14", "foo.cc"];
let parsed_args = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
let mut cmd = MockCommand {
child: None,
args: vec![],
};
preprocess_cmd(
&mut cmd,
&parsed_args,
Path::new(""),
&[],
true,
CCompilerKind::Gcc,
true,
vec![],
);
assert!(cmd.args.contains(&"-fdirectives-only".into()));
}
#[test]
fn pedantic_gnu() {
let args = stringvec!["-pedantic-errors", "-c", "-std=gnu++14", "foo.cc"];
let parsed_args = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
let mut cmd = MockCommand {
child: None,
args: vec![],
};
preprocess_cmd(
&mut cmd,
&parsed_args,
Path::new(""),
&[],
true,
CCompilerKind::Gcc,
true,
vec![],
);
assert!(!cmd.args.contains(&"-fdirectives-only".into()));
}
#[test]
fn test_parse_arguments_dep_target_needed() {
let args = stringvec!["-c", "foo.c", "-fabc", "-MF", "file", "-o", "foo.o", "-MD"];
let ParsedArguments {
input,
language,
outputs,
dependency_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert_eq!(ovec!["-MF", "file", "-MD", "-MT", "foo.o"], dependency_args);
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_dep_target_and_file_needed() {
let args = stringvec!["-c", "foo/bar.c", "-fabc", "-o", "foo/bar.o", "-MMD"];
let ParsedArguments {
input,
language,
outputs,
dependency_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo/bar.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: PathBuf::from("foo/bar.o"),
optional: false
}
)
);
assert_eq!(
ovec!["-MMD", "-MT", "foo/bar.o", "-MF", "foo/bar.d"],
dependency_args
);
assert_eq!(ovec!["-fabc"], common_args);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_empty_args() {
assert_eq!(
CompilerArguments::NotCompilation,
parse_arguments_(vec!(), false)
);
}
#[test]
fn test_parse_arguments_not_compile() {
assert_eq!(
CompilerArguments::NotCompilation,
parse_arguments_(stringvec!["-o", "foo"], false)
);
}
#[test]
fn test_parse_arguments_too_many_inputs_single() {
assert_eq!(
CompilerArguments::CannotCache("multiple input files", Some("[\"bar.c\"]".to_string())),
parse_arguments_(stringvec!["-c", "foo.c", "-o", "foo.o", "bar.c"], false)
);
}
#[test]
fn test_parse_arguments_too_many_inputs_multiple() {
assert_eq!(
CompilerArguments::CannotCache(
"multiple input files",
Some("[\"bar.c\", \"baz.c\"]".to_string())
),
parse_arguments_(
stringvec!["-c", "foo.c", "-o", "foo.o", "bar.c", "baz.c"],
false
)
);
}
#[test]
fn test_parse_arguments_link() {
assert_eq!(
CompilerArguments::NotCompilation,
parse_arguments_(
stringvec!["-shared", "foo.o", "-o", "foo.so", "bar.o"],
false
)
);
}
#[test]
fn test_parse_arguments_pgo() {
assert_eq!(
CompilerArguments::CannotCache("-fprofile-use", None),
parse_arguments_(
stringvec!["-c", "foo.c", "-fprofile-use", "-o", "foo.o"],
false
)
);
assert_eq!(
CompilerArguments::CannotCache("-fprofile-use", None),
parse_arguments_(
stringvec!["-c", "foo.c", "-fprofile-use=file", "-o", "foo.o"],
false
)
);
}
#[test]
fn test_parse_arguments_response_file() {
assert_eq!(
CompilerArguments::CannotCache("@", None),
parse_arguments_(stringvec!["-c", "foo.c", "@foo", "-o", "foo.o"], false)
);
assert_eq!(
CompilerArguments::CannotCache("@", None),
parse_arguments_(stringvec!["-c", "foo.c", "-o", "@foo"], false)
);
}
#[test]
fn test_parse_arguments_multiple_arch() {
match parse_arguments_(
stringvec!["-arch", "arm64", "-o", "foo.o", "-c", "foo.cpp"],
false,
) {
CompilerArguments::Ok(_) => {}
o => panic!("Got unexpected parse result: {:?}", o),
}
match parse_arguments_(
stringvec!["-arch", "arm64", "-arch", "arm64", "-o", "foo.o", "-c", "foo.cpp"],
false,
) {
CompilerArguments::Ok(_) => {}
o => panic!("Got unexpected parse result: {:?}", o),
}
assert_eq!(
CompilerArguments::CannotCache("multiple different -arch", None),
parse_arguments_(
stringvec![
"-fPIC", "-arch", "arm64", "-arch", "i386", "-o", "foo.o", "-c", "foo.cpp"
],
false
)
);
}
#[test]
fn at_signs() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
File::create(td.path().join("foo"))
.unwrap()
.write_all(
b"\
-c foo.c -o foo.o\
",
)
.unwrap();
let arg = format!("@{}", td.path().join("foo").display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(vec![arg], false) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn test_compile_simple() {
let creator = new_creator();
let f = TestFixture::new();
let parsed_args = ParsedArguments {
input: "foo.c".into(),
language: Language::C,
compilation_flag: "-c".into(),
depfile: None,
outputs: vec![(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false,
},
)]
.into_iter()
.collect(),
dependency_args: vec![],
preprocessor_args: vec![],
common_args: vec![],
extra_hash_files: vec![],
msvc_show_includes: false,
profile_generate: false,
color_mode: ColorMode::Auto,
suppress_rewrite_includes_only: false,
};
let compiler = &f.bins[0];
next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
let mut path_transformer = dist::PathTransformer::default();
let (command, dist_command, cacheable) = generate_compile_commands(
&mut path_transformer,
compiler,
&parsed_args,
f.tempdir.path(),
&[],
CCompilerKind::Gcc,
false,
)
.unwrap();
#[cfg(feature = "dist-client")]
assert!(dist_command.is_some());
#[cfg(not(feature = "dist-client"))]
assert!(dist_command.is_none());
let _ = command.execute(&creator).wait();
assert_eq!(Cacheable::Yes, cacheable);
assert_eq!(0, creator.lock().unwrap().children.len());
}
#[test]
fn test_parse_arguments_plusplus() {
let args = stringvec!["-c", "foo.c", "-o", "foo.o"];
let ParsedArguments {
input,
language,
compilation_flag,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments_(args, true) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert_eq!(Language::Cxx, language);
assert_eq!(Some("-c"), compilation_flag.to_str());
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: "foo.o".into(),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
}