use std::path::Path;
use crate::core::NormalizedPath;
use super::args::{ParsedArgs, UserDepFlags};
use super::search_paths::IncludeSearchPaths;
pub fn parse_msvc_args(args: &[String], cwd: &Path) -> ParsedArgs {
let mut result = ParsedArgs {
source_file: NormalizedPath::new(""),
output_file: None,
include_search: IncludeSearchPaths::default(),
defines: Vec::new(),
undefines: Vec::new(),
flags: Vec::new(),
force_includes: Vec::new(),
compiler: None,
dep_flags: UserDepFlags::default(),
unknown_flags: Vec::new(),
};
let mut i = 0;
let mut source_candidates: Vec<NormalizedPath> = Vec::new();
while i < args.len() {
let arg = &args[i];
if arg == "/I" {
if let Some(next) = args.get(i + 1) {
result.include_search.user.push(resolve_path(next, cwd));
i += 2;
continue;
}
} else if let Some(dir) = arg.strip_prefix("/I") {
result.include_search.user.push(resolve_path(dir, cwd));
i += 1;
continue;
}
if arg == "-I" {
if let Some(next) = args.get(i + 1) {
result.include_search.user.push(resolve_path(next, cwd));
i += 2;
continue;
}
} else if let Some(dir) = arg.strip_prefix("-I") {
result.include_search.user.push(resolve_path(dir, cwd));
i += 1;
continue;
}
if arg == "/D" {
if let Some(next) = args.get(i + 1) {
result.defines.push(next.clone());
i += 2;
continue;
}
} else if let Some(def) = arg.strip_prefix("/D") {
result.defines.push(def.to_string());
i += 1;
continue;
}
if arg == "/U" {
if let Some(next) = args.get(i + 1) {
result.undefines.push(next.clone());
i += 2;
continue;
}
} else if let Some(undef) = arg.strip_prefix("/U") {
result.undefines.push(undef.to_string());
i += 1;
continue;
}
if let Some(out) = arg.strip_prefix("/Fo") {
if out.is_empty() {
if let Some(next) = args.get(i + 1) {
result.output_file = Some(resolve_path(next, cwd));
i += 2;
continue;
}
} else {
result.output_file = Some(resolve_path(out, cwd));
i += 1;
continue;
}
}
if let Some(fi) = arg.strip_prefix("/FI") {
if fi.is_empty() {
if let Some(next) = args.get(i + 1) {
result.force_includes.push(resolve_path(next, cwd));
i += 2;
continue;
}
} else {
result.force_includes.push(resolve_path(fi, cwd));
i += 1;
continue;
}
}
if arg.starts_with("/std:") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg.starts_with("/O") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg.starts_with("/W") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg.starts_with("/Yu") || arg.starts_with("/Yc") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg == "/MD" || arg == "/MDd" || arg == "/MT" || arg == "/MTd" {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg.starts_with("/EH") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg.starts_with("/Z") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg == "/c" {
i += 1;
continue;
}
if arg == "/nologo" {
i += 1;
continue;
}
if arg == "/showIncludes" {
result.dep_flags.has_md = true;
i += 1;
continue;
}
if arg.starts_with("/Fp") {
result.flags.push(arg.clone());
i += 1;
continue;
}
if arg.starts_with("/Fe") {
i += 1;
continue;
}
if arg.starts_with("/Fd") {
i += 1;
continue;
}
if arg.starts_with('/') || arg.starts_with('-') {
result.unknown_flags.push(arg.clone());
i += 1;
continue;
}
source_candidates.push(resolve_path(arg, cwd));
i += 1;
}
if let Some(src) = source_candidates
.iter()
.find(|p| is_source_file(p))
.cloned()
{
result.source_file = src;
} else if let Some(first) = source_candidates.into_iter().next() {
result.source_file = first;
}
result.defines.sort();
result.undefines.sort();
result.flags.sort();
result.unknown_flags.sort();
result
}
fn resolve_path(path: &str, cwd: &Path) -> NormalizedPath {
let p = Path::new(path);
if p.is_absolute() {
NormalizedPath::new(p)
} else {
NormalizedPath::new(cwd.join(p))
}
}
fn is_source_file(path: &Path) -> bool {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
matches!(
ext.as_str(),
"c" | "cc" | "cpp" | "cxx" | "c++" | "m" | "mm" | "s" | "sx" | "i" | "ii"
)
}
#[cfg(test)]
mod tests {
use super::*;
fn args(s: &[&str]) -> Vec<String> {
s.iter().map(|x| x.to_string()).collect()
}
#[test]
#[cfg(windows)]
fn basic_msvc_compile() {
let parsed = parse_msvc_args(
&args(&["/c", "foo.cpp", "/Fofoo.obj"]),
Path::new("C:\\project"),
);
assert_eq!(parsed.source_file, Path::new("C:\\project\\foo.cpp"));
assert_eq!(
parsed.output_file.as_deref(),
Some(Path::new("C:\\project\\foo.obj"))
);
}
#[test]
#[cfg(windows)]
fn include_dirs() {
let parsed = parse_msvc_args(
&args(&["/I", "inc", "/Ilib\\include", "/c", "x.cpp"]),
Path::new("C:\\p"),
);
assert_eq!(parsed.include_search.user.len(), 2);
assert_eq!(parsed.include_search.user[0], Path::new("C:\\p\\inc"));
assert_eq!(
parsed.include_search.user[1],
Path::new("C:\\p\\lib\\include")
);
}
#[test]
fn defines_extracted_and_sorted() {
let parsed = parse_msvc_args(
&args(&["/DBAR=1", "/DFOO", "/D", "AAA=2", "/c", "x.cpp"]),
Path::new("C:\\p"),
);
assert_eq!(parsed.defines, vec!["AAA=2", "BAR=1", "FOO"]);
}
#[test]
fn undefines_extracted() {
let parsed = parse_msvc_args(
&args(&["/UFOO", "/U", "BAR", "/c", "x.cpp"]),
Path::new("C:\\p"),
);
assert_eq!(parsed.undefines, vec!["BAR", "FOO"]);
}
#[test]
fn flags_extracted() {
let parsed = parse_msvc_args(
&args(&["/std:c++17", "/O2", "/W4", "/EHsc", "/MD", "/c", "x.cpp"]),
Path::new("C:\\p"),
);
assert!(parsed.flags.contains(&"/std:c++17".to_string()));
assert!(parsed.flags.contains(&"/O2".to_string()));
assert!(parsed.flags.contains(&"/W4".to_string()));
assert!(parsed.flags.contains(&"/EHsc".to_string()));
assert!(parsed.flags.contains(&"/MD".to_string()));
}
#[test]
#[cfg(windows)]
fn force_include() {
let parsed = parse_msvc_args(&args(&["/FIpch.h", "/c", "x.cpp"]), Path::new("C:\\p"));
assert_eq!(parsed.force_includes, vec![Path::new("C:\\p\\pch.h")]);
}
#[test]
fn pch_flags() {
let parsed = parse_msvc_args(
&args(&["/Yupch.h", "/Fppch.pch", "/c", "x.cpp"]),
Path::new("C:\\p"),
);
assert!(parsed.flags.contains(&"/Yupch.h".to_string()));
assert!(parsed.flags.contains(&"/Fppch.pch".to_string()));
}
#[test]
fn unknown_flags_collected() {
let parsed = parse_msvc_args(
&args(&["/c", "foo.cpp", "/experimental:module", "/await"]),
Path::new("C:\\p"),
);
assert!(parsed
.unknown_flags
.contains(&"/experimental:module".to_string()));
assert!(parsed.unknown_flags.contains(&"/await".to_string()));
}
#[test]
fn show_includes_detected() {
let parsed = parse_msvc_args(
&args(&["/showIncludes", "/c", "foo.cpp"]),
Path::new("C:\\p"),
);
assert!(parsed.dep_flags.has_md);
}
#[test]
#[cfg(windows)]
fn dash_prefix_include() {
let parsed = parse_msvc_args(&args(&["-I", "inc", "/c", "x.cpp"]), Path::new("C:\\p"));
assert_eq!(parsed.include_search.user, vec![Path::new("C:\\p\\inc")]);
}
}