use crate::compiler::args::*;
use crate::compiler::c::{ArtifactDescriptor, CCompilerImpl, CCompilerKind, ParsedArguments};
use crate::compiler::{
CCompileCommand, Cacheable, ColorMode, CompileCommand, CompilerArguments, Language,
SingleCompileCommand, clang, gcc, write_temp_file,
};
use crate::mock_command::{CommandCreatorSync, RunCommand};
use crate::util::{OsStrExt, encode_path, run_input_output};
use crate::{counted_array, dist};
use async_trait::async_trait;
use fs::File;
use fs_err as fs;
use log::Level::Debug;
use std::collections::{HashMap, HashSet};
use std::ffi::{OsStr, OsString};
use std::io::{self, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::process;
use crate::errors::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Msvc {
pub includes_prefix: String,
pub is_clang: bool,
pub version: Option<String>,
}
#[async_trait]
impl CCompilerImpl for Msvc {
fn kind(&self) -> CCompilerKind {
CCompilerKind::Msvc
}
fn plusplus(&self) -> bool {
false
}
fn version(&self) -> Option<String> {
self.version.clone()
}
fn parse_arguments(
&self,
arguments: &[OsString],
cwd: &Path,
_env_vars: &[(OsString, OsString)],
) -> CompilerArguments<ParsedArguments> {
parse_arguments(arguments, cwd, self.is_clang)
}
#[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,
_preprocessor_cache_mode: bool,
) -> Result<process::Output>
where
T: CommandCreatorSync,
{
preprocess(
creator,
executable,
parsed_args,
cwd,
env_vars,
may_dist,
&self.includes_prefix,
rewrite_includes_only,
self.is_clang,
)
.await
}
fn generate_compile_commands<T>(
&self,
path_transformer: &mut dist::PathTransformer,
executable: &Path,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
_rewrite_includes_only: bool,
) -> Result<(
Box<dyn CompileCommand<T>>,
Option<dist::CompileCommand>,
Cacheable,
)>
where
T: CommandCreatorSync,
{
generate_compile_commands(path_transformer, executable, parsed_args, cwd, env_vars).map(
|(command, dist_command, cacheable)| {
(CCompileCommand::new(command), dist_command, cacheable)
},
)
}
}
#[cfg(not(windows))]
fn from_local_codepage(multi_byte_str: &[u8]) -> io::Result<String> {
String::from_utf8(multi_byte_str.to_vec())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
}
#[cfg(windows)]
pub fn from_local_codepage(multi_byte_str: &[u8]) -> io::Result<String> {
use windows_sys::Win32::Globalization::{CP_OEMCP, MB_ERR_INVALID_CHARS, MultiByteToWideChar};
let codepage = CP_OEMCP;
let flags = MB_ERR_INVALID_CHARS;
if multi_byte_str.is_empty() {
return Ok(String::new());
}
unsafe {
let len = MultiByteToWideChar(
codepage,
flags,
multi_byte_str.as_ptr().cast(),
multi_byte_str.len() as i32,
std::ptr::null_mut(),
0,
);
if len > 0 {
let mut wstr: Vec<u16> = Vec::with_capacity(len as usize);
let len = MultiByteToWideChar(
codepage,
flags,
multi_byte_str.as_ptr().cast(),
multi_byte_str.len() as i32,
wstr.as_mut_ptr().cast(),
len,
);
if len > 0 {
wstr.set_len(len as usize);
return String::from_utf16(&wstr[0..(len as usize)])
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e));
}
}
Err(io::Error::last_os_error())
}
}
pub async fn detect_showincludes_prefix<T>(
creator: &T,
exe: &OsStr,
is_clang: bool,
env: Vec<(OsString, OsString)>,
pool: &tokio::runtime::Handle,
) -> Result<String>
where
T: CommandCreatorSync,
{
let (tempdir, input) =
write_temp_file(pool, "test.c".as_ref(), b"#include \"test.h\"\n".to_vec()).await?;
let exe = exe.to_os_string();
let mut creator = creator.clone();
let pool = pool.clone();
let header = tempdir.path().join("test.h");
let tempdir = pool
.spawn_blocking(move || {
let mut file = File::create(&header)?;
file.write_all(b"/* empty */\n")?;
Ok::<_, std::io::Error>(tempdir)
})
.await?
.context("Failed to write temporary file")?;
let mut cmd = creator.new_command_sync(&exe);
if is_clang {
cmd.arg("--driver-mode=cl");
}
cmd.args(&["-nologo", "-showIncludes", "-c", "-Fonul", "-I.", "-E"])
.arg(&input)
.current_dir(tempdir.path());
for (k, v) in env {
cmd.env(k, v);
}
trace!("detect_showincludes_prefix: {:?}", cmd);
let output = run_input_output(cmd, None).await?;
if !output.status.success() {
warn!(
"Failed to detect showIncludes prefix (status: {:?})",
output.status.code().unwrap_or(-1)
);
}
let process::Output {
stderr: stderr_bytes,
..
} = output;
let preprocessor_output = from_local_codepage(&stderr_bytes)
.context("Failed to convert compiler stderr while detecting showIncludes prefix")?;
for line in preprocessor_output.lines() {
if !line.ends_with("test.h") {
continue;
}
for (i, c) in line.char_indices().rev() {
if c != ' ' {
continue;
}
let path = tempdir.path().join(&line[i + 1..]);
if path.exists() {
return Ok(line[..=i].to_owned());
}
}
}
drop(tempdir);
debug!(
"failed to detect showIncludes prefix with output: {}",
preprocessor_output
);
bail!("Failed to detect showIncludes prefix")
}
ArgData! {
TooHardFlag,
TooHard(OsString),
TooHardPath(PathBuf),
PreprocessorArgument(OsString),
PreprocessorArgumentPath(PathBuf),
SuppressCompilation,
DoCompilation,
ShowIncludes,
Output(PathBuf),
DepFile(PathBuf),
ProgramDatabase(PathBuf),
DebugInfo,
PassThrough, PassThroughWithPath(PathBuf), PassThroughWithSuffix(OsString), Ignore, IgnoreWithSuffix(OsString), ExtraHashFile(PathBuf),
XClang(OsString), Clang(OsString), ExternalIncludePath(PathBuf),
}
use self::ArgData::*;
macro_rules! msvc_args {
(static ARGS: [$t:ty; _] = [$($macro:ident ! ($($v:tt)*),)*]) => {
counted_array!(static ARGS: [$t; _] = [$(msvc_args!(@one "-", $macro!($($v)*)),)*]);
counted_array!(static SLASH_ARGS: [$t; _] = [$(msvc_args!(@one "/", $macro!($($v)*)),)*]);
};
(@one $prefix:expr, msvc_take_arg!($s:expr, $($t:tt)*)) => {
take_arg!(concat!($prefix, $s), $($t)+)
};
(@one $prefix:expr, msvc_flag!($s:expr, $($t:tt)+)) => {
flag!(concat!($prefix, $s), $($t)+)
};
(@one $prefix:expr, $other:expr) => { $other };
}
msvc_args!(static ARGS: [ArgInfo<ArgData>; _] = [
msvc_flag!("?", SuppressCompilation),
msvc_flag!("Brepro", PassThrough),
msvc_flag!("C", PassThrough), msvc_take_arg!("D", OsString, CanBeSeparated, PreprocessorArgument),
msvc_flag!("E", SuppressCompilation),
msvc_take_arg!("EH", OsString, Concatenated, PassThroughWithSuffix), msvc_flag!("EP", SuppressCompilation),
msvc_take_arg!("F", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("FA", OsString, Concatenated, TooHard),
msvc_flag!("FC", PassThrough), msvc_take_arg!("FI", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
msvc_take_arg!("FR", PathBuf, Concatenated, TooHardPath),
msvc_flag!("FS", Ignore),
msvc_take_arg!("FU", PathBuf, CanBeSeparated, TooHardPath),
msvc_take_arg!("Fa", PathBuf, Concatenated, TooHardPath),
msvc_take_arg!("Fd", PathBuf, Concatenated, ProgramDatabase),
msvc_take_arg!("Fe", PathBuf, Concatenated, TooHardPath),
msvc_take_arg!("Fi", PathBuf, Concatenated, TooHardPath),
msvc_take_arg!("Fm", PathBuf, Concatenated, PassThroughWithPath), msvc_take_arg!("Fo", PathBuf, Concatenated, Output),
msvc_take_arg!("Fp", PathBuf, Concatenated, TooHardPath), msvc_take_arg!("Fr", PathBuf, Concatenated, TooHardPath),
msvc_flag!("Fx", TooHardFlag),
msvc_flag!("GA", PassThrough),
msvc_flag!("GF", PassThrough),
msvc_flag!("GH", PassThrough),
msvc_flag!("GL", PassThrough),
msvc_flag!("GL-", PassThrough),
msvc_flag!("GR", PassThrough),
msvc_flag!("GR-", PassThrough),
msvc_flag!("GS", PassThrough),
msvc_flag!("GS-", PassThrough),
msvc_flag!("GT", PassThrough),
msvc_flag!("GX", PassThrough),
msvc_flag!("GZ", PassThrough),
msvc_flag!("Gd", PassThrough),
msvc_flag!("Ge", PassThrough),
msvc_flag!("Gh", PassThrough),
msvc_flag!("Gm", TooHardFlag), msvc_flag!("Gm-", PassThrough), msvc_flag!("Gr", PassThrough),
msvc_take_arg!("Gs", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("Gv", PassThrough),
msvc_flag!("Gw", PassThrough),
msvc_flag!("Gw-", PassThrough),
msvc_flag!("Gy", PassThrough),
msvc_flag!("Gy-", PassThrough),
msvc_flag!("Gz", PassThrough),
msvc_take_arg!("H", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("HELP", SuppressCompilation),
msvc_take_arg!("I", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
msvc_flag!("J", PassThrough),
msvc_flag!("JMC", PassThrough),
msvc_flag!("JMC-", PassThrough),
msvc_flag!("LD", PassThrough),
msvc_flag!("LDd", PassThrough),
msvc_flag!("MD", PassThrough),
msvc_flag!("MDd", PassThrough),
msvc_take_arg!("MP", OsString, Concatenated, IgnoreWithSuffix),
msvc_flag!("MT", PassThrough),
msvc_flag!("MTd", PassThrough),
msvc_flag!("O1", PassThrough),
msvc_flag!("O2", PassThrough),
msvc_flag!("Ob0", PassThrough),
msvc_flag!("Ob1", PassThrough),
msvc_flag!("Ob2", PassThrough),
msvc_flag!("Ob3", PassThrough),
msvc_flag!("Od", PassThrough),
msvc_flag!("Og", PassThrough),
msvc_flag!("Oi", PassThrough),
msvc_flag!("Oi-", PassThrough),
msvc_flag!("Os", PassThrough),
msvc_flag!("Ot", PassThrough),
msvc_flag!("Ox", PassThrough),
msvc_flag!("Oy", PassThrough),
msvc_flag!("Oy-", PassThrough),
msvc_flag!("P", SuppressCompilation),
msvc_flag!("QIfist", PassThrough),
msvc_flag!("QIntel-jcc-erratum", PassThrough),
msvc_flag!("Qfast_transcendentals", PassThrough),
msvc_flag!("Qimprecise_fwaits", PassThrough),
msvc_flag!("Qpar", PassThrough),
msvc_flag!("Qpar-", PassThrough),
msvc_flag!("Qsafe_fp_loads", PassThrough),
msvc_flag!("Qspectre", PassThrough),
msvc_flag!("Qspectre-load", PassThrough),
msvc_flag!("Qspectre-load-cf", PassThrough),
msvc_flag!("Qvec-report:1", PassThrough),
msvc_flag!("Qvec-report:2", PassThrough),
msvc_take_arg!("RTC", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("TC", PassThrough), msvc_flag!("TP", PassThrough), msvc_take_arg!("U", OsString, Concatenated, PreprocessorArgument),
msvc_take_arg!("V", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("W0", PassThrough),
msvc_flag!("W1", PassThrough),
msvc_flag!("W2", PassThrough),
msvc_flag!("W3", PassThrough),
msvc_flag!("W4", PassThrough),
msvc_flag!("WL", PassThrough),
msvc_flag!("WX", PassThrough),
msvc_flag!("WX-", PassThrough),
msvc_flag!("Wall", PassThrough),
msvc_take_arg!("Wv:", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("X", PassThrough),
msvc_take_arg!("Xclang", OsString, Separated, XClang),
msvc_flag!("Y-", PassThrough), msvc_take_arg!("YI", OsString, Concatenated, PassThroughWithSuffix), msvc_flag!("YI-", PassThrough), msvc_take_arg!("Yc", PathBuf, Concatenated, TooHardPath), msvc_flag!("Yd", PassThrough),
msvc_flag!("Z7", PassThrough), msvc_take_arg!("ZH:", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("ZI", DebugInfo), msvc_flag!("ZW", PassThrough),
msvc_flag!("Za", PassThrough),
msvc_take_arg!("Zc:", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("Ze", PassThrough),
msvc_flag!("Zf", PassThrough),
msvc_flag!("Zi", DebugInfo),
msvc_take_arg!("Zm", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("Zo", PassThrough),
msvc_flag!("Zo-", PassThrough),
msvc_flag!("Zp1", PassThrough),
msvc_flag!("Zp16", PassThrough),
msvc_flag!("Zp2", PassThrough),
msvc_flag!("Zp4", PassThrough),
msvc_flag!("Zp8", PassThrough),
msvc_flag!("Zs", SuppressCompilation),
msvc_flag!("analyze", PassThrough),
msvc_flag!("analyze-", PassThrough),
msvc_take_arg!("analyze:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("arch:", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("await", PassThrough),
msvc_flag!("await:strict", PassThrough),
msvc_flag!("bigobj", PassThrough),
msvc_flag!("c", DoCompilation),
msvc_take_arg!("cgthreads", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("clang:", OsString, Concatenated, Clang),
msvc_flag!("clr", PassThrough),
msvc_take_arg!("clr:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("constexpr:", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("d1nodatetime", PassThrough),
msvc_take_arg!("deps", PathBuf, Concatenated, DepFile),
msvc_take_arg!("diagnostics:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("doc", PathBuf, Concatenated, TooHardPath), msvc_take_arg!("errorReport:", OsString, Concatenated, PassThroughWithSuffix), msvc_take_arg!("execution-charset:", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("experimental:deterministic", PassThrough),
msvc_flag!("experimental:external", PassThrough),
msvc_flag!("experimental:module", TooHardFlag),
msvc_flag!("experimental:module-", PassThrough), msvc_take_arg!("experimental:preprocessor", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("external:I", PathBuf, CanBeSeparated, ExternalIncludePath),
msvc_flag!("external:W0", PassThrough),
msvc_flag!("external:W1", PassThrough),
msvc_flag!("external:W2", PassThrough),
msvc_flag!("external:W3", PassThrough),
msvc_flag!("external:W4", PassThrough),
msvc_flag!("external:anglebrackets", PassThrough),
msvc_take_arg!("favor:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("fp:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("fsanitize-blacklist", PathBuf, Concatenated(b'='), ExtraHashFile),
msvc_flag!("fsanitize=address", PassThrough),
msvc_flag!("fsyntax-only", SuppressCompilation),
msvc_take_arg!("guard:cf", OsString, Concatenated, PassThroughWithSuffix),
msvc_flag!("homeparams", PassThrough),
msvc_flag!("hotpatch", PassThrough),
msvc_take_arg!("ifcMap", PathBuf, Separated, TooHardPath),
msvc_flag!("ifcOnly", TooHardFlag),
msvc_take_arg!("ifcOutput", PathBuf, Separated, TooHardPath),
msvc_take_arg!("ifcSearchDir", PathBuf, Separated, TooHardPath),
msvc_take_arg!("imsvc", PathBuf, CanBeSeparated, PreprocessorArgumentPath),
msvc_flag!("interface", TooHardFlag),
msvc_flag!("internalPartition", TooHardFlag),
msvc_flag!("kernel", PassThrough),
msvc_flag!("kernel-", PassThrough),
msvc_flag!("nologo", PassThrough),
msvc_take_arg!("o", PathBuf, Separated, Output), msvc_flag!("openmp", PassThrough),
msvc_flag!("openmp-", PassThrough),
msvc_flag!("openmp:experimental", PassThrough),
msvc_flag!("permissive", PassThrough),
msvc_flag!("permissive-", PassThrough),
msvc_take_arg!("reference", OsString, Separated, TooHard),
msvc_flag!("sdl", PassThrough),
msvc_flag!("sdl-", PassThrough),
msvc_flag!("showIncludes", ShowIncludes),
msvc_take_arg!("source-charset:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("sourceDependencies", PathBuf, CanBeSeparated, DepFile),
msvc_take_arg!("std:", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("stdIfcDir", PathBuf, Separated, TooHardPath),
msvc_flag!("u", PassThrough),
msvc_flag!("utf-8", PassThrough),
msvc_flag!("validate-charset", PassThrough),
msvc_flag!("validate-charset-", PassThrough),
msvc_flag!("vd0", PassThrough),
msvc_flag!("vd1", PassThrough),
msvc_flag!("vd2", PassThrough),
msvc_flag!("vmb", PassThrough),
msvc_flag!("vmg", PassThrough),
msvc_flag!("vmm", PassThrough),
msvc_flag!("vms", PassThrough),
msvc_flag!("vmv", PassThrough),
msvc_flag!("volatile:iso", PassThrough),
msvc_flag!("volatile:ms", PassThrough),
msvc_flag!("w", PassThrough),
msvc_take_arg!("w1", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("w2", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("w3", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("w4", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("wd", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("we", OsString, Concatenated, PassThroughWithSuffix),
msvc_take_arg!("winsysroot", PathBuf, CanBeSeparated, PassThroughWithPath),
msvc_take_arg!("wo", OsString, Concatenated, PassThroughWithSuffix),
take_arg!("@", PathBuf, Concatenated, TooHardPath),
]);
pub fn parse_arguments(
arguments: &[OsString],
cwd: &Path,
is_clang: bool,
) -> CompilerArguments<ParsedArguments> {
let mut output_arg = None;
let mut input_arg = None;
let mut double_dash_input = false;
let mut common_args = vec![];
let mut unhashed_args = vec![];
let mut preprocessor_args = vec![];
let mut dependency_args = vec![];
let mut extra_hash_files = vec![];
let mut compilation = false;
let mut compilation_flag = OsString::new();
let mut debug_info = false;
let mut pdb = None;
let mut depfile = None;
let mut show_includes = false;
let mut xclangs: Vec<OsString> = vec![];
let mut clangs: Vec<OsString> = vec![];
let mut profile_generate = false;
let mut multiple_input = false;
let mut multiple_input_files = Vec::new();
let it = ExpandIncludeFile::new(cwd, arguments);
let mut it = ArgsIter::new(it, (&ARGS[..], &SLASH_ARGS[..]));
if is_clang {
it = it.with_double_dashes();
}
for arg in it {
let arg = try_or_cannot_cache!(arg, "argument parse");
match arg.get_data() {
Some(PassThrough) | Some(PassThroughWithPath(_)) | Some(PassThroughWithSuffix(_)) => {}
Some(TooHardFlag) | Some(TooHard(_)) | Some(TooHardPath(_)) => {
cannot_cache!(arg.flag_str().expect("Can't be Argument::Raw/UnknownFlag",))
}
Some(DoCompilation) => {
compilation = true;
compilation_flag =
OsString::from(arg.flag_str().expect("Compilation flag expected"));
}
Some(ShowIncludes) => {
show_includes = true;
dependency_args.push(arg.to_os_string());
}
Some(Output(out)) => {
output_arg = Some(out.clone());
if out.as_os_str() == "nul" {
cannot_cache!("output to nul")
}
}
Some(DepFile(p)) => depfile = Some(p.clone()),
Some(ProgramDatabase(p)) => pdb = Some(p.clone()),
Some(DebugInfo) => debug_info = true,
Some(PreprocessorArgument(_))
| Some(PreprocessorArgumentPath(_))
| Some(ExtraHashFile(_))
| Some(Ignore)
| Some(IgnoreWithSuffix(_))
| Some(ExternalIncludePath(_)) => {}
Some(SuppressCompilation) => {
return CompilerArguments::NotCompilation;
}
Some(XClang(s)) => xclangs.push(s.clone()),
Some(Clang(s)) => clangs.push(s.clone()),
None => {
match arg {
Argument::Raw(ref val) if val == "--" => {
if input_arg.is_none() {
double_dash_input = true;
}
}
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(ref flag) => common_args.push(flag.clone()),
_ => unreachable!(),
}
}
}
match arg.get_data() {
Some(PreprocessorArgument(_)) | Some(PreprocessorArgumentPath(_)) => preprocessor_args
.extend(
arg.normalize(NormalizedDisposition::Concatenated)
.iter_os_strings(),
),
Some(ProgramDatabase(_))
| Some(DebugInfo)
| Some(PassThrough)
| Some(PassThroughWithPath(_))
| Some(PassThroughWithSuffix(_)) => common_args.extend(
arg.normalize(NormalizedDisposition::Concatenated)
.iter_os_strings(),
),
Some(ExtraHashFile(path)) => {
extra_hash_files.push(cwd.join(path));
common_args.extend(
arg.normalize(NormalizedDisposition::Concatenated)
.iter_os_strings(),
);
}
Some(ExternalIncludePath(_)) => common_args.extend(
arg.normalize(NormalizedDisposition::Separated)
.iter_os_strings(),
),
Some(Ignore) | Some(IgnoreWithSuffix(_)) => {}
_ => {}
}
}
fn xclang_append(arg: OsString, args: &mut Vec<OsString>) {
args.push("-Xclang".into());
args.push(arg);
}
fn dash_clang_append(arg: OsString, args: &mut Vec<OsString>) {
let mut a = OsString::from("-clang:");
a.push(arg);
args.push(a);
}
for (args, append_fn) in Iterator::zip(
[xclangs, clangs].iter(),
&[xclang_append, dash_clang_append],
) {
let it = gcc::ExpandIncludeFile::new(cwd, args);
for arg in ArgsIter::new(it, (&gcc::ARGS[..], &clang::ARGS[..])) {
let arg = try_or_cannot_cache!(arg, "argument parse");
use crate::compiler::gcc::ArgData::*;
let args = match arg.get_data() {
Some(SplitDwarf)
| Some(TestCoverage)
| Some(Coverage)
| Some(DoCompilation)
| Some(Language(_))
| Some(Output(_))
| Some(TooHardFlag)
| Some(XClang(_))
| Some(ClangModuleOutput(_))
| Some(ExtraHashFileClangModuleFile(_))
| Some(ModuleOnlyFlag)
| Some(TooHard(_)) => cannot_cache!(
arg.flag_str()
.unwrap_or("Can't handle complex arguments through clang",)
),
None => match arg {
Argument::Raw(_) | Argument::UnknownFlag(_) => &mut common_args,
_ => unreachable!(),
},
Some(DiagnosticsColor(_))
| Some(DiagnosticsColorFlag)
| Some(NoDiagnosticsColorFlag)
| Some(Arch(_))
| Some(PassThroughFlag)
| Some(PassThrough(_))
| Some(PassThroughPath(_))
| Some(PedanticFlag)
| Some(Standard(_))
| Some(SerializeDiagnostics(_)) => &mut common_args,
Some(UnhashedFlag) | Some(Unhashed(_)) => &mut unhashed_args,
Some(ProfileGenerate) => {
profile_generate = true;
&mut common_args
}
Some(ClangProfileUse(path)) => {
extra_hash_files.push(clang::resolve_profile_use_path(path, cwd));
&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(DepTarget(_)) | Some(NeedDepTarget) => {
&mut dependency_args
}
};
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() {
append_fn(arg, args);
}
}
}
if !compilation {
return CompilerArguments::NotCompilation;
}
if multiple_input {
cannot_cache!(
"multiple input files",
format!("{:?}", multiple_input_files)
);
}
let (input, language) = match input_arg {
Some(i) => match Language::from_file_name(Path::new(&i)) {
Some(l) => (i.clone(), l),
None => cannot_cache!("unknown source language"),
},
None => cannot_cache!("no input file"),
};
let mut outputs = HashMap::new();
match output_arg {
None => {
outputs.insert(
"obj",
ArtifactDescriptor {
path: Path::new(&input).with_extension("obj"),
optional: false,
},
);
}
Some(o) => {
if o.as_os_str()
.to_string_lossy()
.ends_with(['\\', '/'])
{
match Path::new(&input).file_name() {
Some(i) => outputs.insert(
"obj",
ArtifactDescriptor {
path: o.join(Path::new(i)).with_extension("obj"),
optional: false,
},
),
None => cannot_cache!("invalid input file"),
};
} else if o.extension().is_none() {
outputs.insert(
"obj",
ArtifactDescriptor {
path: o.with_extension("obj"),
optional: false,
},
);
} else {
outputs.insert(
"obj",
ArtifactDescriptor {
path: o,
optional: false,
},
);
}
}
}
if language == Language::Cxx {
if let Some(obj) = outputs.get("obj") {
let tlh = obj.path.with_extension("tlh");
let tli = obj.path.with_extension("tli");
outputs.insert(
"tlh",
ArtifactDescriptor {
path: tlh,
optional: true,
},
);
outputs.insert(
"tli",
ArtifactDescriptor {
path: tli,
optional: true,
},
);
}
}
if debug_info && !is_clang {
match pdb {
Some(p) => {
let path = if p.extension().is_none() {
let mut path = p;
path.set_extension("pdb");
path
} else {
p
};
outputs.insert(
"pdb",
ArtifactDescriptor {
path,
optional: false,
},
)
}
None => {
cannot_cache!("shared pdb");
}
};
}
CompilerArguments::Ok(ParsedArguments {
input: input.into(),
double_dash_input,
language,
compilation_flag,
depfile,
outputs,
dependency_args,
preprocessor_args,
common_args,
arch_args: vec![],
unhashed_args,
extra_dist_files: vec![],
extra_hash_files,
msvc_show_includes: show_includes,
profile_generate,
color_mode: ColorMode::Auto,
suppress_rewrite_includes_only: false,
too_hard_for_preprocessor_cache_mode: None,
})
}
#[cfg(windows)]
fn normpath(path: &str) -> String {
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
use std::ptr;
use windows_sys::Win32::Storage::FileSystem::GetFinalPathNameByHandleW;
File::open(path)
.and_then(|f| {
let handle = f.as_raw_handle() as _;
let size = unsafe { GetFinalPathNameByHandleW(handle, ptr::null_mut(), 0, 0) };
if size == 0 {
return Err(io::Error::last_os_error());
}
let mut wchars = vec![0; size as usize];
if unsafe {
GetFinalPathNameByHandleW(handle, wchars.as_mut_ptr(), wchars.len() as u32, 0)
} == 0
{
return Err(io::Error::last_os_error());
}
let o = OsString::from_wide(&wchars[4..wchars.len() - 1]);
o.into_string()
.map(|s| s.replace('\\', "/"))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Error converting string"))
})
.unwrap_or_else(|_| path.replace('\\', "/"))
}
#[cfg(not(windows))]
fn normpath(path: &str) -> String {
path.to_owned()
}
#[allow(clippy::too_many_arguments)]
pub fn preprocess_cmd<T>(
cmd: &mut T,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
may_dist: bool,
rewrite_includes_only: bool,
is_clang: bool,
) where
T: RunCommand,
{
if may_dist || parsed_args.profile_generate {
cmd.arg("-E");
} else {
cmd.arg("-EP");
}
cmd.arg("-nologo")
.args(&parsed_args.preprocessor_args)
.args(&parsed_args.dependency_args)
.args(&parsed_args.common_args)
.env_clear()
.envs(env_vars.to_vec())
.current_dir(cwd);
if is_clang {
if parsed_args.depfile.is_some() && !parsed_args.msvc_show_includes {
cmd.arg("-showIncludes");
}
} else {
if let Some(ref depfile) = parsed_args.depfile {
cmd.arg("/sourceDependencies");
cmd.arg(depfile);
}
cmd.arg("/WX-");
}
if rewrite_includes_only && is_clang {
cmd.arg("-clang:-frewrite-includes");
}
if parsed_args.double_dash_input {
cmd.arg("--");
}
cmd.arg(&parsed_args.input);
}
#[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,
includes_prefix: &str,
rewrite_includes_only: bool,
is_clang: bool,
) -> Result<process::Output>
where
T: CommandCreatorSync,
{
let mut cmd = creator.clone().new_command_sync(executable);
preprocess_cmd(
&mut cmd,
parsed_args,
cwd,
env_vars,
may_dist,
rewrite_includes_only,
is_clang,
);
if log_enabled!(Debug) {
debug!("preprocess: {:?}", cmd);
}
let parsed_args = parsed_args.clone();
let includes_prefix = includes_prefix.to_string();
let cwd = cwd.to_owned();
let output = run_input_output(cmd, None).await?;
if !is_clang {
return Ok(output);
}
let parsed_args = &parsed_args;
if let (Some(obj), Some(depfile)) = (parsed_args.outputs.get("obj"), &parsed_args.depfile) {
let objfile = &obj.path;
let f = File::create(cwd.join(depfile))?;
let mut f = BufWriter::new(f);
encode_path(&mut f, objfile)
.with_context(|| format!("Couldn't encode objfile filename: '{:?}'", objfile))?;
write!(f, ": ")?;
encode_path(&mut f, &parsed_args.input)
.with_context(|| format!("Couldn't encode input filename: '{:?}'", objfile))?;
write!(f, " ")?;
let process::Output {
status,
stdout,
stderr: stderr_bytes,
} = output;
let stderr =
from_local_codepage(&stderr_bytes).context("Failed to convert preprocessor stderr")?;
let mut deps = HashSet::new();
let mut stderr_bytes = vec![];
for line in stderr.lines() {
if line.starts_with(&includes_prefix) {
let dep = normpath(line[includes_prefix.len()..].trim());
trace!("included: {}", dep);
if deps.insert(dep.clone()) && !dep.contains(' ') {
write!(f, "{} ", dep)?;
}
if !parsed_args.msvc_show_includes {
continue;
}
}
stderr_bytes.extend_from_slice(line.as_bytes());
stderr_bytes.push(b'\n');
}
writeln!(f)?;
encode_path(&mut f, &parsed_args.input)
.with_context(|| format!("Couldn't encode filename: '{:?}'", parsed_args.input))?;
writeln!(f, ":")?;
let mut sorted = deps.into_iter().collect::<Vec<_>>();
sorted.sort();
for dep in sorted {
if !dep.contains(' ') {
writeln!(f, "{}:", dep)?;
}
}
Ok(process::Output {
status,
stdout,
stderr: stderr_bytes,
})
} else {
Ok(output)
}
}
fn generate_compile_commands(
path_transformer: &mut dist::PathTransformer,
executable: &Path,
parsed_args: &ParsedArguments,
cwd: &Path,
env_vars: &[(OsString, OsString)],
) -> Result<(
SingleCompileCommand,
Option<dist::CompileCommand>,
Cacheable,
)> {
#[cfg(not(feature = "dist-client"))]
let _ = path_transformer;
trace!("compile");
let out_file = match parsed_args.outputs.get("obj") {
Some(obj) => &obj.path,
None => bail!("Missing object file output"),
};
let cacheable = parsed_args
.outputs
.get("pdb")
.map_or(Cacheable::Yes, |pdb| {
if Path::new(&cwd).join(pdb.path.clone()).exists() {
Cacheable::No
} else {
Cacheable::Yes
}
});
let mut fo = OsString::from("-Fo");
fo.push(out_file);
let mut arguments: Vec<OsString> = vec![parsed_args.compilation_flag.clone(), fo];
arguments.extend_from_slice(&parsed_args.preprocessor_args);
arguments.extend_from_slice(&parsed_args.dependency_args);
arguments.extend_from_slice(&parsed_args.unhashed_args);
arguments.extend_from_slice(&parsed_args.common_args);
if parsed_args.double_dash_input {
arguments.push("--".into());
}
arguments.push(parsed_args.input.clone().into());
let command = SingleCompileCommand {
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 fo = String::from("-Fo");
fo.push_str(&path_transformer.as_dist(out_file)?);
let mut arguments: Vec<String> =
vec![parsed_args.compilation_flag.clone().into_string().ok()?, fo];
arguments.extend(dist::osstrings_to_strings(&parsed_args.common_args)?);
if parsed_args.double_dash_input {
arguments.push("--".into());
}
arguments.push(path_transformer.as_dist(&parsed_args.input)?);
Some(dist::CompileCommand {
executable: path_transformer.as_dist(executable)?,
arguments,
env_vars: dist::osstring_tuples_to_strings(env_vars)?,
cwd: path_transformer.as_dist(cwd)?,
})
})();
Ok((command, dist_command, cacheable))
}
struct ExpandIncludeFile<'a> {
cwd: &'a Path,
args: Vec<OsString>,
stack: Vec<OsString>,
}
impl<'a> ExpandIncludeFile<'a> {
pub fn new(cwd: &'a Path, args: &[OsString]) -> Self {
ExpandIncludeFile {
args: args.iter().rev().map(|a| a.to_owned()).collect(),
stack: Vec::new(),
cwd,
}
}
}
impl Iterator for ExpandIncludeFile<'_> {
type Item = OsString;
fn next(&mut self) -> Option<OsString> {
loop {
if let Some(response_file_arg) = self.stack.pop() {
return Some(response_file_arg);
}
let arg = self.args.pop()?;
let file_arg = match arg.split_prefix("@") {
Some(file_arg) => file_arg,
None => return Some(arg),
};
let file_path = self.cwd.join(file_arg);
let content = match File::open(&file_path).and_then(|mut file| read_text(&mut file)) {
Ok(content) => content,
Err(err) => {
debug!("failed to read @-file `{}`: {}", file_path.display(), err);
return Some(arg);
}
};
trace!("Expanded response file {:?} to {:?}", file_path, content);
let resp_file_args = SplitMsvcResponseFileArgs::from(&content).collect::<Vec<_>>();
let rev_args = resp_file_args.iter().rev().map(|s| s.into());
self.stack.extend(rev_args);
}
}
}
fn read_text<R>(reader: &mut R) -> io::Result<String>
where
R: Read,
{
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
let (result, _, has_error) = encoding_rs::WINDOWS_1252.decode(&buf);
if has_error {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"failed to decode text",
))
} else {
Ok(result.to_string())
}
}
#[derive(Clone, Debug)]
struct SplitMsvcResponseFileArgs<'a> {
file_content: &'a str,
}
impl<'a, T> From<&'a T> for SplitMsvcResponseFileArgs<'a>
where
T: AsRef<str> + 'static,
{
fn from(file_content: &'a T) -> Self {
Self {
file_content: file_content.as_ref(),
}
}
}
impl SplitMsvcResponseFileArgs<'_> {
fn append_backslashes_to(target: &mut String, count: &mut usize, step: usize) {
while *count >= step {
target.push('\\');
*count -= step;
}
}
}
impl Iterator for SplitMsvcResponseFileArgs<'_> {
type Item = String;
fn next(&mut self) -> Option<String> {
let mut in_quotes = false;
let mut backslash_count: usize = 0;
let is_whitespace = |c| matches!(c, ' ' | '\t' | '\n' | '\r');
self.file_content = self.file_content.trim_start_matches(is_whitespace);
if self.file_content.is_empty() {
return None;
}
let mut arg = String::new();
let mut chars = self.file_content.chars();
for c in &mut chars {
match c {
'\\' => backslash_count += 1,
'"' => {
Self::append_backslashes_to(&mut arg, &mut backslash_count, 2);
match backslash_count == 0 {
true => in_quotes = !in_quotes,
false => {
backslash_count = 0;
arg.push('"');
}
}
}
' ' | '\t' | '\n' | '\r' => {
Self::append_backslashes_to(&mut arg, &mut backslash_count, 1);
if !in_quotes {
break;
}
arg.push(c);
}
_ => {
Self::append_backslashes_to(&mut arg, &mut backslash_count, 1);
arg.push(c);
}
}
}
Self::append_backslashes_to(&mut arg, &mut backslash_count, 1);
self.file_content = chars.as_str();
Some(arg)
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
use crate::compiler::*;
use crate::mock_command::*;
use crate::server;
use crate::test::mock_storage::MockStorage;
use crate::test::utils::*;
fn parse_arguments(arguments: Vec<OsString>) -> CompilerArguments<ParsedArguments> {
super::parse_arguments(&arguments, &std::env::current_dir().unwrap(), false)
}
fn parse_arguments_clang(arguments: Vec<OsString>) -> CompilerArguments<ParsedArguments> {
super::parse_arguments(&arguments, &std::env::current_dir().unwrap(), true)
}
#[test]
fn test_detect_showincludes_prefix() {
drop(env_logger::try_init());
let creator = new_creator();
let runtime = single_threaded_runtime();
let pool = runtime.handle().clone();
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 stderr = format!("blah: {}\r\n", s);
let stdout = String::from("some\r\nstdout\r\n");
next_command(&creator, Ok(MockChild::new(exit_status(0), stdout, stderr)));
assert_eq!(
"blah: ",
detect_showincludes_prefix(&creator, "cl.exe".as_ref(), false, Vec::new(), &pool)
.wait()
.unwrap()
);
}
#[test]
fn test_parse_arguments_simple() {
let args = ovec!["-c", "foo.c", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
compilation_flag,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn test_cpp_parse_arguments_collects_type_library_headers() {
let args = ovec!["-c", "foo.cpp", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
),
(
"tlh",
ArtifactDescriptor {
path: PathBuf::from("foo.tlh"),
optional: true
}
),
(
"tli",
ArtifactDescriptor {
path: PathBuf::from("foo.tli"),
optional: true
}
)
);
}
#[test]
fn test_c_parse_arguments_does_not_collect_type_library_headers() {
let args = ovec!["-c", "foo.c", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
}
#[test]
fn test_parse_compile_flag() {
let args = ovec!["/c", "foo.c", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
compilation_flag,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
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 = ovec!["-c", "foo.c"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_double_dash() {
let args = ovec!["-c", "-Fofoo.obj", "--", "foo.c"];
let ParsedArguments {
input,
double_dash_input,
common_args,
..
} = match parse_arguments(args.clone()) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert!(!double_dash_input);
assert_eq!(ovec!["--"], common_args);
let ParsedArguments {
input,
double_dash_input,
common_args,
..
} = match parse_arguments_clang(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert!(double_dash_input);
assert!(common_args.is_empty());
let args = ovec!["-c", "-Fofoo.obj", "foo.c", "--"];
let ParsedArguments {
input,
double_dash_input,
common_args,
..
} = match parse_arguments_clang(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.c"), input.to_str());
assert!(!double_dash_input);
assert!(common_args.is_empty());
let args = ovec!["-c", "-Fofoo.obj", "foo.c", "--", "bar.c"];
assert_eq!(
CompilerArguments::CannotCache("multiple input files", Some("[\"bar.c\"]".to_string())),
parse_arguments_clang(args)
);
let args = ovec!["-c", "-Fofoo.obj", "foo.c", "--", "-fPIC"];
assert_eq!(
CompilerArguments::CannotCache("multiple input files", Some("[\"-fPIC\"]".to_string())),
parse_arguments_clang(args)
);
}
#[test]
fn parse_argument_slashes() {
let args = ovec!["-c", "foo.c", "/Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn parse_deps_arguments() {
let arg_sets = vec![
ovec!["-c", "foo.c", "/Fofoo.obj", "/depsfoo.obj.json"],
ovec![
"-c",
"foo.c",
"/Fofoo.obj",
"/sourceDependenciesfoo.obj.json"
],
ovec![
"-c",
"foo.c",
"/Fofoo.obj",
"/sourceDependencies",
"foo.obj.json"
],
];
for args in arg_sets {
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
depfile,
..
} = match parse_arguments(args) {
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(PathBuf::from_str("foo.obj.json").unwrap()), depfile);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
}
#[test]
fn test_parse_arguments_clang_passthrough() {
let args = ovec![
"-Fohost_dictionary.obj",
"-c",
"-Xclang",
"-MP",
"-Xclang",
"-dependency-file",
"-Xclang",
".deps/host_dictionary.obj.pp",
"-Xclang",
"-MT",
"-Xclang",
"host_dictionary.obj",
"-clang:-fprofile-generate",
"-clang:-fprofile-use=xyz.profdata",
"dictionary.c"
];
let ParsedArguments {
dependency_args,
preprocessor_args,
common_args,
profile_generate,
extra_hash_files,
..
} = match parse_arguments(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert!(profile_generate);
assert!(preprocessor_args.is_empty());
assert_eq!(
dependency_args,
ovec!(
"-Xclang",
"-MP",
"-Xclang",
"-dependency-file",
"-Xclang",
".deps/host_dictionary.obj.pp",
"-Xclang",
"-MT",
"-Xclang",
"host_dictionary.obj"
)
);
assert_eq!(
common_args,
ovec!(
"-clang:-fprofile-generate",
"-clang:-fprofile-use=xyz.profdata"
)
);
assert_eq!(
extra_hash_files,
ovec!(std::env::current_dir().unwrap().join("xyz.profdata"))
);
}
#[test]
fn test_parse_arguments_extra() {
let args = ovec!["-c", "foo.c", "-foo", "-Fofoo.obj", "-bar"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(common_args, ovec!["-foo", "-bar"]);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_values() {
let args = ovec![
"-c",
"foo.c",
"-FI",
"file",
"-imsvc",
"/a/b/c",
"-Fofoo.obj",
"/showIncludes",
"/winsysroot../../some/dir"
];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
dependency_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert_eq!(preprocessor_args, ovec!["-FIfile", "-imsvc/a/b/c"]);
assert_eq!(dependency_args, ovec!["/showIncludes"]);
assert_eq!(common_args, ovec!["/winsysroot../../some/dir"]);
assert!(msvc_show_includes);
}
#[test]
#[cfg(windows)]
fn parse_argument_output_file_trailing_backslash() {
let args = ovec!["-c", "foo.c", "/Fomyrelease\\folder\\"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("myrelease/folder/foo.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
#[cfg(windows)]
fn parse_argument_output_file_trailing_slash_multi_extension() {
let args = ovec!["/c", "foo.pb.c", "-Fomyrelease\\folder/"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("foo.pb.c"), input.to_str());
assert_eq!(Language::C, language);
assert_map_contains!(
outputs,
(
"obj",
ArtifactDescriptor {
path: PathBuf::from("myrelease/folder/foo.pb.obj"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert!(common_args.is_empty());
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_pdb() {
let args = ovec!["-c", "foo.c", "-Zi", "-Fdfoo.pdb", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
),
(
"pdb",
ArtifactDescriptor {
path: PathBuf::from("foo.pdb"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(common_args, ovec!["-Zi", "-Fdfoo.pdb"]);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_pdb_no_extension() {
let args = ovec!["-c", "foo.c", "-Zi", "-Fdfoo", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
),
(
"pdb",
ArtifactDescriptor {
path: PathBuf::from("foo.pdb"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(common_args, ovec!["-Zi", "-Fdfoo"]);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_pdb_with_extension() {
let args = ovec!["-c", "foo.c", "-Zi", "-Fdfoo.pdb", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
),
(
"pdb",
ArtifactDescriptor {
path: PathBuf::from("foo.pdb"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(common_args, ovec!["-Zi", "-Fdfoo.pdb"]);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_pdb_custom_extension() {
let args = ovec!["-c", "foo.c", "-Zi", "-Fdfoo.db", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
),
(
"pdb",
ArtifactDescriptor {
path: PathBuf::from("foo.db"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(common_args, ovec!["-Zi", "-Fdfoo.db"]);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_pdb_path_with_extension() {
let args = ovec!["-c", "foo.c", "-Zi", "-Fdoutput/foo", "-Fofoo.obj"];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
),
(
"pdb",
ArtifactDescriptor {
path: PathBuf::from("output/foo.pdb"),
optional: false
}
)
);
assert!(preprocessor_args.is_empty());
assert_eq!(common_args, ovec!["-Zi", "-Fdoutput/foo"]);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_external_include() {
let args = ovec![
"-c",
"foo.c",
"-Fofoo.obj",
"-experimental:external",
"-external:templates-",
"-external:I",
"path/to/system/includes"
];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert_eq!(1, outputs.len());
assert!(preprocessor_args.is_empty());
assert_eq!(
common_args,
ovec![
"-experimental:external",
"-external:templates-",
"-external:I",
"path/to/system/includes"
]
);
assert!(!msvc_show_includes);
}
#[test]
fn test_parse_arguments_external_warning_suppression_forward_slashes() {
for n in 0..5 {
let args = ovec![
"-c",
"foo.c",
"/Fofoo.obj",
"/experimental:external",
format!("/external:W{}", n)
];
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(args) {
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: PathBuf::from("foo.obj"),
optional: false
}
)
);
assert_eq!(1, outputs.len());
assert!(preprocessor_args.is_empty());
assert_eq!(
common_args,
ovec!["/experimental:external", format!("/external:W{}", n)]
);
assert!(!msvc_show_includes);
}
}
#[test]
fn test_parse_arguments_empty_args() {
assert_eq!(CompilerArguments::NotCompilation, parse_arguments(vec!()));
}
#[test]
fn test_parse_arguments_not_compile() {
assert_eq!(
CompilerArguments::NotCompilation,
parse_arguments(ovec!["-Fofoo", "foo.c"])
);
}
#[test]
fn test_parse_arguments_passthrough() {
let args = ovec![
"-Oy",
"-Qpar",
"-Qpar-",
"-Gw",
"/d1nodatetime",
"-EHa",
"-await:strict",
"/YI",
"-Y-",
"/YI-",
"-Zf",
"-Fmdictionary-map",
"-c",
"-Fohost_dictionary.obj",
"dictionary.c"
];
let ParsedArguments {
input,
common_args,
dependency_args,
preprocessor_args,
..
} = match parse_arguments(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(Some("dictionary.c"), input.to_str());
assert!(preprocessor_args.is_empty());
assert!(dependency_args.is_empty());
assert!(!common_args.is_empty());
assert_eq!(
common_args,
ovec!(
"-Oy",
"-Qpar",
"-Qpar-",
"-Gw",
"/d1nodatetime",
"-EHa",
"-await:strict",
"/YI",
"-Y-",
"/YI-",
"-Zf",
"-Fmdictionary-map"
)
);
}
#[test]
fn test_parse_arguments_too_many_inputs_single() {
assert_eq!(
CompilerArguments::CannotCache("multiple input files", Some("[\"bar.c\"]".to_string())),
parse_arguments(ovec!["-c", "foo.c", "-Fofoo.obj", "bar.c"])
);
}
#[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(ovec!["-c", "foo.c", "-Fofoo.obj", "bar.c", "baz.c"])
);
}
#[test]
fn test_parse_arguments_unsupported() {
assert_eq!(
CompilerArguments::CannotCache("-FA", None),
parse_arguments(ovec!["-c", "foo.c", "-Fofoo.obj", "-FA"])
);
assert_eq!(
CompilerArguments::CannotCache("-Fa", None),
parse_arguments(ovec!["-Fa", "-c", "foo.c", "-Fofoo.obj"])
);
assert_eq!(
CompilerArguments::CannotCache("-FR", None),
parse_arguments(ovec!["-c", "foo.c", "-FR", "-Fofoo.obj"])
);
assert_eq!(
CompilerArguments::CannotCache("-Fp", None),
parse_arguments(ovec!["-c", "-Fpfoo.h", "foo.c"])
);
assert_eq!(
CompilerArguments::CannotCache("-Yc", None),
parse_arguments(ovec!["-c", "-Ycfoo.h", "foo.c"])
);
}
#[test]
fn test_parse_arguments_cxx20_modules_unsupported() {
assert_eq!(
CompilerArguments::CannotCache("-interface", None),
parse_arguments(ovec!["-c", "foo.ixx", "-Fofoo.obj", "-interface"])
);
assert_eq!(
CompilerArguments::CannotCache("-internalPartition", None),
parse_arguments(ovec!["-c", "foo.ixx", "-Fofoo.obj", "-internalPartition"])
);
assert_eq!(
CompilerArguments::CannotCache("-ifcOutput", None),
parse_arguments(ovec![
"-c",
"foo.ixx",
"-Fofoo.obj",
"-ifcOutput",
"foo.ifc"
])
);
assert_eq!(
CompilerArguments::CannotCache("-ifcOnly", None),
parse_arguments(ovec!["-c", "foo.ixx", "-ifcOnly"])
);
assert_eq!(
CompilerArguments::CannotCache("-ifcSearchDir", None),
parse_arguments(ovec![
"-c",
"foo.cpp",
"-Fofoo.obj",
"-ifcSearchDir",
"/path/to/ifcs"
])
);
assert_eq!(
CompilerArguments::CannotCache("-reference", None),
parse_arguments(ovec![
"-c",
"foo.cpp",
"-Fofoo.obj",
"-reference",
"mymodule=mymodule.ifc"
])
);
assert_eq!(
CompilerArguments::CannotCache("-stdIfcDir", None),
parse_arguments(ovec![
"-c",
"foo.cpp",
"-Fofoo.obj",
"-stdIfcDir",
"/path/to/std/ifcs"
])
);
assert_eq!(
CompilerArguments::CannotCache("-ifcMap", None),
parse_arguments(ovec![
"-c",
"foo.cpp",
"-Fofoo.obj",
"-ifcMap",
"module.map"
])
);
}
#[test]
fn test_responsefile_missing() {
assert_eq!(
CompilerArguments::CannotCache("@", None),
parse_arguments(ovec!["-c", "foo.c", "@foo", "-Fofoo.obj"])
);
}
#[test]
fn test_responsefile_absolute_path() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
let cmd_file_path = td.path().join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content = b"-c foo.c -o foo.o";
file.write_all(content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", 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_responsefile_relative_path() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir_in("./")
.unwrap();
let relative_to_tmp = td
.path()
.strip_prefix(std::env::current_dir().unwrap())
.unwrap();
let cmd_file_path = relative_to_tmp.join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content = b"-c foo.c -o foo.o";
file.write_all(content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", 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_responsefile_with_quotes() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
let cmd_file_path = td.path().join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content = b"-c \"Foo Bar.c\" -o foo.o";
file.write_all(content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", o),
};
assert_eq!(Some("Foo Bar.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_responsefile_multiline() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
let cmd_file_path = td.path().join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content = b"\n-c foo.c\n-o foo.o";
file.write_all(content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", 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_responsefile_multiline_cr() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
let cmd_file_path = td.path().join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content = b"\r-c foo.c\r-o foo.o";
file.write_all(content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", 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_responsefile_encoding_utf16le() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
let cmd_file_path = td.path().join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content: [u8; 0x26] = [
0xFF, 0xFE, 0x2D, 0x00, 0x63, 0x00, 0x20, 0x00, 0x66, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0xAC, 0x20,
0x2E, 0x00, 0x63, 0x00, 0x20, 0x00, 0x2D, 0x00, 0x6F, 0x00, 0x20, 0x00, 0x66, 0x00,
0x6F, 0x00, 0x6F, 0x00, 0x2E, 0x00, 0x6F, 0x00,
];
file.write_all(&content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", 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_responsefile_encoding_win1252() {
let td = tempfile::Builder::new()
.prefix("sccache")
.tempdir()
.unwrap();
let cmd_file_path = td.path().join("foo");
{
let mut file = File::create(&cmd_file_path).unwrap();
let content: [u8; 0x12] = [
0x2D, 0x63, 0x20, 0x66, 0x6F, 0x6F, 0x80, 0x2E, 0x63, 0x20, 0x2D, 0x6F, 0x20, 0x66,
0x6F, 0x6F, 0x2E, 0x6F,
];
file.write_all(&content).unwrap();
}
let arg = format!("@{}", cmd_file_path.display());
let ParsedArguments {
input,
language,
outputs,
preprocessor_args,
msvc_show_includes,
common_args,
..
} = match parse_arguments(ovec![arg]) {
CompilerArguments::Ok(args) => args,
o => panic!("Failed to parse @-file, err: {:?}", 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_missing_pdb() {
assert_eq!(
CompilerArguments::CannotCache("shared pdb", None),
parse_arguments(ovec!["-c", "foo.c", "-Zi", "-Fofoo.obj"])
);
}
#[test]
fn test_parse_arguments_missing_edit_and_continue_pdb() {
assert_eq!(
CompilerArguments::CannotCache("shared pdb", None),
parse_arguments(ovec!["-c", "foo.c", "-ZI", "-Fofoo.obj"])
);
}
#[test]
fn test_preprocess_double_dash_input() {
let args = ovec!["-c", "-Fofoo.o.bj", "--", "foo.c"];
let parsed_args = match parse_arguments_clang(args) {
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, true, true);
let expected_args = ovec!["-E", "-nologo", "-clang:-frewrite-includes", "--", "foo.c"];
assert_eq!(cmd.args, expected_args);
}
#[test]
fn test_compile_simple() {
let creator = new_creator();
let f = TestFixture::new();
let parsed_args = ParsedArguments {
input: "foo.c".into(),
double_dash_input: false,
language: Language::C,
compilation_flag: "-c".into(),
depfile: None,
outputs: vec![(
"obj",
ArtifactDescriptor {
path: "foo.obj".into(),
optional: false,
},
)]
.into_iter()
.collect(),
dependency_args: vec![],
preprocessor_args: vec![],
common_args: vec![],
arch_args: vec![],
unhashed_args: vec![],
extra_dist_files: vec![],
extra_hash_files: vec![],
msvc_show_includes: false,
profile_generate: false,
color_mode: ColorMode::Auto,
suppress_rewrite_includes_only: false,
too_hard_for_preprocessor_cache_mode: None,
};
let runtime = single_threaded_runtime();
let storage = MockStorage::new(None, false);
let storage: std::sync::Arc<MockStorage> = std::sync::Arc::new(storage);
let service = server::SccacheService::mock_with_storage(storage, runtime.handle().clone());
let compiler = &f.bins[0];
next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
let mut path_transformer = dist::PathTransformer::new();
let (command, dist_command, cacheable) = generate_compile_commands(
&mut path_transformer,
compiler,
&parsed_args,
f.tempdir.path(),
&[],
)
.unwrap();
#[cfg(feature = "dist-client")]
assert!(dist_command.is_some());
#[cfg(not(feature = "dist-client"))]
assert!(dist_command.is_none());
let _ = command.execute(&service, &creator).wait();
assert_eq!(Cacheable::Yes, cacheable);
assert_eq!(0, creator.lock().unwrap().children.len());
}
#[test]
fn test_compile_double_dash_input() {
let args = ovec!["-c", "-Fofoo.obj", "--", "foo.c"];
let parsed_args = match parse_arguments_clang(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
let f = TestFixture::new();
let compiler = &f.bins[0];
let mut path_transformer = dist::PathTransformer::new();
let (command, _, _) = generate_compile_commands(
&mut path_transformer,
compiler,
&parsed_args,
f.tempdir.path(),
&[],
)
.unwrap();
let expected_args = ovec!["-c", "-Fofoo.obj", "--", "foo.c"];
assert_eq!(command.arguments, expected_args);
}
#[test]
fn test_compile_not_cacheable_pdb() {
let creator = new_creator();
let f = TestFixture::new();
let pdb = f.touch("foo.pdb").unwrap();
let parsed_args = ParsedArguments {
input: "foo.c".into(),
double_dash_input: false,
language: Language::C,
compilation_flag: "/c".into(),
depfile: None,
outputs: vec![
(
"obj",
ArtifactDescriptor {
path: "foo.obj".into(),
optional: false,
},
),
(
"pdb",
ArtifactDescriptor {
path: pdb,
optional: false,
},
),
]
.into_iter()
.collect(),
dependency_args: vec![],
preprocessor_args: vec![],
common_args: vec![],
arch_args: vec![],
unhashed_args: vec![],
extra_dist_files: vec![],
extra_hash_files: vec![],
msvc_show_includes: false,
profile_generate: false,
color_mode: ColorMode::Auto,
suppress_rewrite_includes_only: false,
too_hard_for_preprocessor_cache_mode: None,
};
let runtime = single_threaded_runtime();
let storage = MockStorage::new(None, false);
let storage: std::sync::Arc<MockStorage> = std::sync::Arc::new(storage);
let service = server::SccacheService::mock_with_storage(storage, runtime.handle().clone());
let compiler = &f.bins[0];
next_command(&creator, Ok(MockChild::new(exit_status(0), "", "")));
let mut path_transformer = dist::PathTransformer::new();
let (command, dist_command, cacheable) = generate_compile_commands(
&mut path_transformer,
compiler,
&parsed_args,
f.tempdir.path(),
&[],
)
.unwrap();
#[cfg(feature = "dist-client")]
assert!(dist_command.is_some());
#[cfg(not(feature = "dist-client"))]
assert!(dist_command.is_none());
let _ = command.execute(&service, &creator).wait();
assert_eq!(Cacheable::No, cacheable);
assert_eq!(0, creator.lock().unwrap().children.len());
}
#[test]
fn test_parse_fsanitize_blacklist() {
let args = ovec![
"-c",
"foo.c",
"-o",
"foo.o",
"-fsanitize-blacklist=list.txt"
];
let ParsedArguments {
common_args,
extra_hash_files,
..
} = match parse_arguments(args) {
CompilerArguments::Ok(args) => args,
o => panic!("Got unexpected parse result: {:?}", o),
};
assert_eq!(ovec!["-fsanitize-blacklist=list.txt"], common_args);
assert_eq!(
ovec![std::env::current_dir().unwrap().join("list.txt")],
extra_hash_files
);
}
#[test]
#[cfg(windows)]
fn local_oem_codepage_conversions() {
use crate::util::wide_char_to_multi_byte;
use windows_sys::Win32::Globalization::GetOEMCP;
let current_oemcp = unsafe { GetOEMCP() };
if current_oemcp == 437 || current_oemcp == 850 || current_oemcp == 858 {
const INPUT_STRING: &str = "ÇüéâäàåçêëèïîìÄÅ";
const INPUT_BYTES: [u8; 16] = [
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
];
assert_eq!(from_local_codepage(&INPUT_BYTES).unwrap(), INPUT_STRING);
const INPUT_WORDS: [u16; 16] = [
199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, 239, 238, 236, 196, 197,
];
assert_eq!(wide_char_to_multi_byte(&INPUT_WORDS).unwrap(), INPUT_BYTES);
}
}
}