use crate::core::NormalizedPath;
use std::sync::Arc;
use super::detect::{detect_family, is_source_file, MODULE_EXTENSIONS};
use super::{parse_msvc, CacheableCompilation, CompilerFamily, ParsedInvocation, SourceMode};
pub(crate) fn source_mode_from_language(lang: &str) -> Option<SourceMode> {
match lang {
"c-header" | "c++-header" => Some(SourceMode::Header),
"c-header-unit" | "c++-header-unit" => Some(SourceMode::HeaderUnit),
"c++-module" => Some(SourceMode::Module),
_ => None,
}
}
pub(crate) fn source_mode_from_extension(path: &str) -> SourceMode {
if let Some(ext) = std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
{
if MODULE_EXTENSIONS.contains(&ext) {
return SourceMode::Module;
}
}
SourceMode::Normal
}
pub(crate) fn default_output(
source: &str,
family: CompilerFamily,
mode: SourceMode,
has_precompile: bool,
) -> String {
match mode {
SourceMode::Header => {
if let Some(ext) = family.pch_extension() {
let filename = std::path::Path::new(source)
.file_name()
.and_then(|f| f.to_str())
.unwrap_or(source);
return format!("{filename}.{ext}");
}
}
SourceMode::HeaderUnit => {
let filename = std::path::Path::new(source)
.file_name()
.and_then(|f| f.to_str())
.unwrap_or(source);
return format!("{filename}.pcm");
}
SourceMode::Module | SourceMode::Normal => {
if has_precompile {
let stem = std::path::Path::new(source)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("a");
return format!("{stem}.pcm");
}
}
}
let stem = std::path::Path::new(source)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("a");
format!("{stem}.o")
}
const FLAGS_WITH_VALUE: &[&str] = &[
"-o",
"-D",
"-U",
"-I",
"-isystem",
"-iquote",
"-idirafter",
"-include",
"-include-pch",
"-isysroot",
"-target",
"--target",
"-MF",
"-MQ",
"-MT",
"-std",
"-x",
"-arch",
"-Xclang",
"-mllvm",
"--serialize-diagnostics",
];
#[must_use]
pub fn parse_invocation(compiler: &str, args: &[String]) -> ParsedInvocation {
let family = detect_family(compiler);
if family == CompilerFamily::Rustfmt {
return ParsedInvocation::NonCacheable {
reason: "rustfmt is handled via the format cache path, not compile cache".to_string(),
};
}
if family == CompilerFamily::Rustc {
return super::parse_rustc::parse_rustc_invocation(compiler, args);
}
if family == CompilerFamily::Msvc || parse_msvc::looks_like_msvc_args(args) {
return parse_msvc::parse_msvc_invocation(compiler, args, family);
}
let mut has_c_flag = false;
let mut has_precompile_flag = false;
let mut source_files: Vec<(String, usize, SourceMode)> = Vec::new();
let mut output_file: Option<String> = None;
let mut current_mode = SourceMode::Normal;
let mut unknown_flags: Vec<String> = Vec::new();
let mut i = 0;
while i < args.len() {
let arg = &args[i];
if arg == "-E" || arg == "-M" || arg == "-MM" {
return ParsedInvocation::NonCacheable {
reason: format!("preprocessing-only flag: {arg}"),
};
}
if arg == "-" {
return ParsedInvocation::NonCacheable {
reason: "stdin source not cacheable".to_string(),
};
}
if arg == "-c" {
has_c_flag = true;
i += 1;
continue;
}
if arg == "--precompile" {
has_precompile_flag = true;
i += 1;
continue;
}
if arg == "-o" {
if let Some(next) = args.get(i + 1) {
output_file = Some(next.clone());
i += 2;
} else {
i += 1;
}
continue;
} else if let Some(path) = arg.strip_prefix("-o") {
output_file = Some(path.to_string());
i += 1;
continue;
}
if let Some(&flag) = FLAGS_WITH_VALUE.iter().find(|&&f| f == arg.as_str()) {
if flag == "-x" && i + 1 < args.len() {
current_mode =
source_mode_from_language(&args[i + 1]).unwrap_or(SourceMode::Normal);
}
i += 2;
continue;
}
if arg.starts_with('-') {
unknown_flags.push(arg.clone());
i += 1;
continue;
}
let effective_mode = if current_mode != SourceMode::Normal {
current_mode
} else {
source_mode_from_extension(arg)
};
if is_source_file(arg) || current_mode != SourceMode::Normal {
source_files.push((arg.clone(), i, effective_mode));
}
i += 1;
}
if !has_c_flag && !has_precompile_flag && !current_mode.implies_compilation() {
return ParsedInvocation::NonCacheable {
reason: "no -c flag (likely a link invocation)".to_string(),
};
}
if source_files.is_empty() {
return ParsedInvocation::NonCacheable {
reason: "no source file found".to_string(),
};
}
let family = detect_family(compiler);
if source_files.len() > 1 {
let source_indices: Vec<usize> = source_files.iter().map(|(_, idx, _)| *idx).collect();
let shared_args: Arc<[String]> = Arc::from(args.to_vec());
let compilations = source_files
.iter()
.map(|(src, _, mode)| CacheableCompilation {
compiler: NormalizedPath::new(compiler),
family,
source_file: NormalizedPath::new(src),
output_file: NormalizedPath::new(default_output(
src,
family,
*mode,
has_precompile_flag,
)),
original_args: Arc::clone(&shared_args),
unknown_flags: unknown_flags.clone(),
})
.collect();
return ParsedInvocation::MultiFile {
compilations,
original_args: shared_args,
source_indices,
};
}
let (source, _, mode) = source_files.into_iter().next().unwrap();
let output =
output_file.unwrap_or_else(|| default_output(&source, family, mode, has_precompile_flag));
ParsedInvocation::Cacheable(CacheableCompilation {
compiler: NormalizedPath::new(compiler),
family,
source_file: NormalizedPath::new(source),
output_file: NormalizedPath::new(output),
original_args: Arc::from(args.to_vec()),
unknown_flags,
})
}