use crate::core::NormalizedPath;
use std::sync::Arc;
use super::{CacheableCompilation, CompilerFamily, ParsedInvocation};
const RUSTC_CACHEABLE_CRATE_TYPES: &[&str] = &["lib", "rlib", "staticlib", "proc-macro", "bin"];
fn rustc_proc_macro_filename(crate_name: &str, extra: &str) -> String {
if cfg!(target_os = "windows") {
format!("{crate_name}{extra}.dll")
} else if cfg!(target_os = "macos") {
format!("lib{crate_name}{extra}.dylib")
} else {
format!("lib{crate_name}{extra}.so")
}
}
fn rustc_bin_filename(crate_name: &str, extra: &str) -> String {
if cfg!(target_os = "windows") {
format!("{crate_name}{extra}.exe")
} else {
format!("{crate_name}{extra}")
}
}
const RUSTC_FLAGS_WITH_VALUE: &[&str] = &[
"--edition",
"--crate-type",
"--crate-name",
"--emit",
"--out-dir",
"--target",
"--cap-lints",
"--extern",
"--error-format",
"--json",
"--color",
"--diagnostic-width",
"--sysroot",
"--cfg",
"--check-cfg",
"-o",
"-L",
"-C",
"-A",
"-W",
"-D",
"-F",
"--codegen",
"--remap-path-prefix",
"--env-set",
];
pub(crate) fn parse_rustc_invocation(compiler: &str, args: &[String]) -> ParsedInvocation {
let mut crate_types: Vec<String> = Vec::new();
let mut source_file: Option<String> = None;
let mut output_file: Option<String> = None;
let mut out_dir: Option<String> = None;
let mut crate_name: Option<String> = None;
let mut extra_filename: Option<String> = None;
let mut emit_types: Vec<String> = Vec::new();
let mut unknown_flags: Vec<String> = Vec::new();
let mut i = 0;
while i < args.len() {
let arg = &args[i];
if arg == "--crate-type" {
if let Some(next) = args.get(i + 1) {
crate_types.extend(next.split(',').map(|s| s.to_string()));
i += 2;
continue;
}
} else if let Some(val) = arg.strip_prefix("--crate-type=") {
crate_types.extend(val.split(',').map(|s| s.to_string()));
i += 1;
continue;
}
if arg == "--crate-name" {
if let Some(next) = args.get(i + 1) {
crate_name = Some(next.clone());
i += 2;
continue;
}
} else if let Some(val) = arg.strip_prefix("--crate-name=") {
crate_name = Some(val.to_string());
i += 1;
continue;
}
if arg == "--emit" {
if let Some(next) = args.get(i + 1) {
emit_types.extend(next.split(',').map(|s| {
s.split('=').next().unwrap_or(s).to_string()
}));
i += 2;
continue;
}
} else if let Some(val) = arg.strip_prefix("--emit=") {
emit_types.extend(
val.split(',')
.map(|s| s.split('=').next().unwrap_or(s).to_string()),
);
i += 1;
continue;
}
if arg == "--out-dir" {
if let Some(next) = args.get(i + 1) {
out_dir = Some(next.clone());
i += 2;
continue;
}
} else if let Some(val) = arg.strip_prefix("--out-dir=") {
out_dir = Some(val.to_string());
i += 1;
continue;
}
if arg == "-o" {
if let Some(next) = args.get(i + 1) {
output_file = Some(next.clone());
i += 2;
continue;
}
}
if arg == "-C" || arg == "--codegen" {
if let Some(next) = args.get(i + 1) {
if let Some(val) = next.strip_prefix("extra-filename=") {
extra_filename = Some(val.to_string());
}
i += 2;
continue;
}
} else if let Some(rest) = arg.strip_prefix("-C") {
if !rest.is_empty() {
if let Some(val) = rest.strip_prefix("extra-filename=") {
extra_filename = Some(val.to_string());
}
i += 1;
continue;
}
}
if let Some(&_flag) = RUSTC_FLAGS_WITH_VALUE.iter().find(|&&f| f == arg.as_str()) {
i += 2;
continue;
}
if arg.starts_with("--") && arg.contains('=') {
i += 1;
continue;
}
if arg.starts_with('-') {
unknown_flags.push(arg.clone());
i += 1;
continue;
}
if arg.ends_with(".rs") {
source_file = Some(arg.clone());
}
i += 1;
}
let source = match source_file {
Some(s) => s,
None => {
return ParsedInvocation::NonCacheable {
reason: "no .rs source file found".to_string(),
};
}
};
if crate_types.is_empty() {
crate_types.push("bin".to_string());
}
for ct in &crate_types {
if !RUSTC_CACHEABLE_CRATE_TYPES.contains(&ct.as_str()) {
return ParsedInvocation::NonCacheable {
reason: format!("non-cacheable crate type: {ct}"),
};
}
}
let has_link_emit = emit_types.iter().any(|t| t == "link");
let is_proc_macro = crate_types.iter().any(|t| t == "proc-macro");
let is_bin = crate_types.iter().any(|t| t == "bin");
let metadata_only = !has_link_emit && emit_types.iter().any(|t| t == "metadata");
let output = if let Some(o) = output_file {
o
} else if let Some(ref dir) = out_dir {
let name = crate_name.as_deref().unwrap_or("unknown");
let suffix = extra_filename.as_deref().unwrap_or("");
let filename = if metadata_only {
format!("lib{name}{suffix}.rmeta")
} else if is_proc_macro {
rustc_proc_macro_filename(name, suffix)
} else if is_bin {
rustc_bin_filename(name, suffix)
} else if crate_types.iter().any(|t| t == "staticlib") {
format!("lib{name}{suffix}.a")
} else {
format!("lib{name}{suffix}.rlib")
};
NormalizedPath::new(dir)
.join(filename)
.to_string_lossy()
.into_owned()
} else {
let name = crate_name.as_deref().unwrap_or_else(|| {
std::path::Path::new(&source)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
});
let filename = if metadata_only {
format!("lib{name}.rmeta")
} else if is_proc_macro {
rustc_proc_macro_filename(name, "")
} else if is_bin {
rustc_bin_filename(name, "")
} else if crate_types.iter().any(|t| t == "staticlib") {
format!("lib{name}.a")
} else {
format!("lib{name}.rlib")
};
filename
};
ParsedInvocation::Cacheable(CacheableCompilation {
compiler: NormalizedPath::new(compiler),
family: CompilerFamily::Rustc,
source_file: NormalizedPath::new(source),
output_file: NormalizedPath::new(output),
original_args: Arc::from(args.to_vec()),
unknown_flags,
})
}