use rucc_sysroot::msvc::Chip;
const PARTS: [&str; 3] = ["ucrt", "um", "shared"];
#[must_use]
pub const fn in_tree(chip: Chip) -> &'static str {
match chip {
Chip::X86 => "x86",
Chip::X64 => "x86_64",
Chip::Arm => "aarch",
Chip::Arm64 => "aarch64",
}
}
#[must_use]
pub fn crt(member: &str, chip: Chip) -> Option<String> {
let rest = under_tools(member)?;
if let Some(under) = rest.strip_prefix("include/") {
return (!under.is_empty()).then(|| format!("crt/include/{under}"));
}
let under = rest.strip_prefix("lib/")?;
let mut parts = under.split('/');
let dir = parts.next()?;
let file = parts.next()?;
if parts.next().is_some() || !dir.eq_ignore_ascii_case(chip.in_installer()) {
return None;
}
linkable(file).then(|| format!("crt/lib/{}/{file}", in_tree(chip)))
}
#[must_use]
pub fn sdk(directory: &str, name: &str, chip: Chip) -> Option<String> {
let parts: Vec<&str> = directory.split(['/', '\\']).filter(|part| !part.is_empty()).collect();
if parts.len() < 5 || !parts[0].eq_ignore_ascii_case("Windows Kits") || parts[1] != "10" {
return None;
}
let part = parts[4].to_ascii_lowercase();
if parts[2].eq_ignore_ascii_case("Include") {
if !PARTS.contains(&part.as_str()) {
return None;
}
let deeper = parts[5..].iter().map(|part| format!("{part}/")).collect::<String>();
return Some(format!("sdk/include/{part}/{deeper}{name}"));
}
if parts[2].eq_ignore_ascii_case("Lib") {
if parts.len() != 6 || !matches!(part.as_str(), "ucrt" | "um") {
return None;
}
if !parts[5].eq_ignore_ascii_case(chip.in_installer()) {
return None;
}
return Some(format!("sdk/lib/{part}/{}/{name}", in_tree(chip)));
}
None
}
#[must_use]
pub fn lowercase(path: &str) -> Option<String> {
let (directory, name) = match path.rsplit_once('/') {
Some((directory, name)) => (directory, name),
None => ("", path),
};
let lower = name.to_ascii_lowercase();
if lower == name {
return None;
}
Some(if directory.is_empty() { lower } else { format!("{directory}/{lower}") })
}
fn under_tools(member: &str) -> Option<&str> {
let rest = member.strip_prefix("Contents/VC/Tools/MSVC/")?;
let (_version, rest) = rest.split_once('/')?;
(!rest.is_empty()).then_some(rest)
}
fn linkable(name: &str) -> bool {
let Some((_, ext)) = name.rsplit_once('.') else {
return false;
};
ext.eq_ignore_ascii_case("lib") || ext.eq_ignore_ascii_case("obj")
}
#[cfg(test)]
mod tests {
use super::{crt, in_tree, lowercase, sdk};
use rucc_sysroot::msvc::Chip;
fn at(path: &str) -> Option<String> {
Some(path.to_owned())
}
#[test]
fn the_crt_headers_keep_their_shape_under_the_tree() {
let member = "Contents/VC/Tools/MSVC/14.44.35207/include/stdio.h";
assert_eq!(crt(member, Chip::X64), at("crt/include/stdio.h"));
let member = "Contents/VC/Tools/MSVC/14.44.35207/include/CodeAnalysis/sourceannotations.h";
assert_eq!(crt(member, Chip::X64), at("crt/include/CodeAnalysis/sourceannotations.h"));
let member = "Contents/VC/Tools/MSVC/14.44.35207/include/vcruntime.h";
assert_eq!(crt(member, Chip::Arm64), crt(member, Chip::X86));
}
#[test]
fn a_crt_library_goes_under_the_architecture_the_tree_names() {
let member = "Contents/VC/Tools/MSVC/14.44.35207/lib/x64/libcmt.lib";
assert_eq!(crt(member, Chip::X64), at("crt/lib/x86_64/libcmt.lib"));
let member = "Contents/VC/Tools/MSVC/14.44.35207/lib/x64/chkstk.obj";
assert_eq!(crt(member, Chip::X64), at("crt/lib/x86_64/chkstk.obj"));
let member = "Contents/VC/Tools/MSVC/14.44.35207/lib/arm64/libvcruntime.lib";
assert_eq!(crt(member, Chip::Arm64), at("crt/lib/aarch64/libvcruntime.lib"));
assert_eq!(crt(member, Chip::X64), None);
}
#[test]
fn what_a_link_cannot_read_is_left_in_the_package() {
for member in [
"Contents/VC/Tools/MSVC/14.44.35207/lib/x64/libcmt.amd64.pdb",
"Contents/VC/Tools/MSVC/14.44.35207/lib/x64/Microsoft.VisualC.STLCLR.dll",
"Contents/VC/Tools/MSVC/14.44.35207/lib/x64/enclave/libvcruntime.lib",
"Contents/VC/Tools/MSVC/14.44.35207/lib/x64/store/msvcrt.lib",
"Contents/VC/Tools/MSVC/14.44.35207/lib/x64/uwp/vccorlib.lib",
"Contents/VC/Tools/MSVC/14.44.35207/crt/src/x64/memmove.asm",
"Contents/VC/Tools/MSVC/14.44.35207/Auxiliary/Microsoft.VC.Paths.x64.Store.props",
"manifest.json",
"[Content_Types].xml",
"_rels/.rels",
] {
assert_eq!(crt(member, Chip::X64), None, "{member}");
}
}
#[test]
fn the_three_parts_of_the_kit_a_c_compiler_reads_are_the_ones_that_land() {
let kit = "Windows Kits/10/Include/10.0.26100.0";
let um = format!("{kit}/um");
assert_eq!(sdk(&um, "windows.h", Chip::X64), at("sdk/include/um/windows.h"));
assert_eq!(
sdk(&format!("{kit}/ucrt"), "stdio.h", Chip::X64),
at("sdk/include/ucrt/stdio.h")
);
assert_eq!(
sdk(&format!("{kit}/ucrt/sys"), "stat.h", Chip::X64),
at("sdk/include/ucrt/sys/stat.h")
);
assert_eq!(
sdk(&format!("{kit}/shared/netcx/shared/1.0/net"), "ring.h", Chip::X64),
at("sdk/include/shared/netcx/shared/1.0/net/ring.h")
);
assert_eq!(sdk(&um, "windows.h", Chip::Arm), sdk(&um, "windows.h", Chip::X86));
}
#[test]
fn the_cpp_only_headers_and_the_installer_s_own_files_are_left_in_the_kit() {
for directory in [
"Windows Kits/10/Include/10.0.26100.0/winrt",
"Windows Kits/10/Include/10.0.26100.0/cppwinrt/winrt/impl",
"Windows Kits/10/Catalogs",
"Windows Kits/10/Source/10.0.26100.0/ucrt/string",
"Windows Kits/10/DesignTime/CommonConfiguration/Neutral",
"Windows Kits/10/bin/10.0.26100.0/x64/ucrt",
"Windows Kits/10/Lib/10.0.26100.0/ucrt_enclave/x64",
] {
assert_eq!(sdk(directory, "thing.h", Chip::X64), None, "{directory}");
}
}
#[test]
fn a_kit_library_goes_under_its_part_and_its_architecture() {
let kit = "Windows Kits/10/Lib/10.0.26100.0";
assert_eq!(
sdk(&format!("{kit}/um/x64"), "kernel32.Lib", Chip::X64),
at("sdk/lib/um/x86_64/kernel32.Lib")
);
assert_eq!(
sdk(&format!("{kit}/ucrt/arm64"), "libucrt.lib", Chip::Arm64),
at("sdk/lib/ucrt/aarch64/libucrt.lib")
);
assert_eq!(sdk(&format!("{kit}/um/x86"), "kernel32.Lib", Chip::X64), None);
assert_eq!(sdk(&format!("{kit}/um/arm64"), "kernel32.Lib", Chip::X64), None);
assert_eq!(sdk(&format!("{kit}/shared/x64"), "thing.lib", Chip::X64), None);
}
#[test]
fn a_name_with_a_capital_in_it_gets_a_lowercase_one_beside_it() {
assert_eq!(lowercase("sdk/lib/um/x86_64/AclUI.Lib"), at("sdk/lib/um/x86_64/aclui.lib"));
assert_eq!(lowercase("sdk/include/um/Windows.h"), at("sdk/include/um/windows.h"));
assert_eq!(lowercase("crt/include/CodeAnalysis"), at("crt/include/codeanalysis"));
assert_eq!(lowercase("sdk/include/um/winbase.h"), None);
}
#[test]
fn the_tree_spells_architectures_the_way_the_tool_that_produced_this_shape_does() {
assert_eq!(in_tree(Chip::X86), "x86");
assert_eq!(in_tree(Chip::X64), "x86_64");
assert_eq!(in_tree(Chip::Arm), "aarch");
assert_eq!(in_tree(Chip::Arm64), "aarch64");
}
}