use std::path::{Path, PathBuf};
use rucc_sysroot::{Kernel, Options, Origin, Sysroot, include_paths};
use rucc_tuple::{TARGETS, TargetEntry, TargetTuple};
fn target(tuple: &str) -> TargetTuple {
tuple.parse().expect("a target this understands")
}
fn path(s: &str) -> PathBuf {
PathBuf::from(s)
}
#[test]
fn the_four_steps_come_out_in_the_order_the_rule_states_them() {
let host = target("x86_64-linux-gnu");
let user = [path("/project/include"), path("/project/vendor")];
let bundled = Sysroot::in_cache(Path::new("/cache"), host);
let kernel = Kernel::for_target(Path::new("/cache"), host).expect("a Linux target has one");
let found = include_paths(
host,
Some(host),
&Options {
user: &user,
resources: Some(Path::new("/opt/rucc")),
bundled: Some(&bundled),
kernel: Some(&kernel),
host_include: &[path("/usr/include")],
..Options::default()
},
);
let origins: Vec<Origin> = found.iter().map(|entry| entry.origin).collect();
assert_eq!(
origins,
[
Origin::User,
Origin::User,
Origin::Compiler,
Origin::Bundled,
Origin::Bundled,
Origin::Kernel,
Origin::Kernel,
]
);
assert_eq!(found[0].path, path("/project/include"));
assert_eq!(found[1].path, path("/project/vendor"));
assert_eq!(found[2].path, path("/opt/rucc/include"));
assert_eq!(found[3].path, path("/cache/sysroots/x86_64-linux-gnu/include/x86"));
assert_eq!(found[4].path, path("/cache/sysroots/x86_64-linux-gnu/include/generic"));
assert_eq!(found[5].path, path("/cache/kernel-headers/x86"));
assert_eq!(found[6].path, path("/cache/kernel-headers/generic"));
assert!(!found.iter().any(|entry| entry.origin.is_host()));
}
#[test]
fn no_host_directory_reaches_a_cross_build() {
let host = target("x86_64-linux-gnu");
let host_dirs = [path("/usr/include"), path("/usr/local/include")];
for entry in TARGETS {
let row = TargetEntry::parse(entry).expect("the table parses");
if row == host {
continue;
}
let found = include_paths(
row,
Some(host),
&Options { host_include: &host_dirs, ..Options::default() },
);
assert!(
!found.iter().any(|found| found.origin.is_host()),
"{} picked up a host directory",
entry.tuple
);
}
}
#[test]
fn an_unknown_host_is_treated_as_not_being_the_target() {
let found = include_paths(
target("x86_64-linux-gnu"),
None,
&Options { host_include: &[path("/usr/include")], ..Options::default() },
);
assert!(found.is_empty());
}
#[test]
fn the_host_directories_are_used_when_the_target_is_the_host_and_there_is_nothing_else() {
let host = target("aarch64-macos");
let found = include_paths(
host,
Some(host),
&Options { host_include: &[path("/usr/include")], ..Options::default() },
);
assert_eq!(found.len(), 1);
assert_eq!(found[0].origin, Origin::Host);
}
#[test]
fn an_installed_sdk_serves_every_apple_target_and_not_only_the_one_this_machine_is() {
let host = target("aarch64-macos");
let sdk = [path("/SDKs/MacOSX.sdk/usr/include")];
for tuple in ["aarch64-macos", "x86_64-macos", "aarch64-ios"] {
let found =
include_paths(target(tuple), Some(host), &Options { sdk: &sdk, ..Options::default() });
assert_eq!(found.len(), 1, "{tuple}");
assert_eq!(found[0].origin, Origin::Sdk, "{tuple}");
assert_eq!(found[0].path, sdk[0], "{tuple}");
assert!(!found[0].origin.is_host(), "{tuple}");
}
}
#[test]
fn an_installed_windows_kit_serves_every_msvc_target_and_not_only_the_one_this_machine_is() {
let host = target("x86_64-windows-msvc");
let sdk =
[path("C:/Kits/10/Include/10.0.26100.0/ucrt"), path("C:/Kits/10/Include/10.0.26100.0/um")];
for tuple in ["x86_64-windows-msvc", "aarch64-windows-msvc", "arm64ec-windows-msvc"] {
let found =
include_paths(target(tuple), Some(host), &Options { sdk: &sdk, ..Options::default() });
assert_eq!(found.len(), 2, "{tuple}");
assert!(found.iter().all(|entry| entry.origin == Origin::Sdk), "{tuple}");
assert!(found.iter().all(|entry| !entry.origin.is_host()), "{tuple}");
}
}
#[test]
fn a_target_behind_a_licence_wall_is_never_given_a_tree_of_ours() {
for tuple in ["aarch64-macos", "aarch64-ios", "x86_64-pc-windows-msvc"] {
let walled = target(tuple);
let bundled = Sysroot::in_cache(Path::new("/cache"), walled);
let found = include_paths(
walled,
None,
&Options {
resources: Some(Path::new("/opt/rucc")),
bundled: Some(&bundled),
..Options::default()
},
);
assert_eq!(found.len(), 1, "{tuple} was offered a tree of ours");
assert_eq!(found[0].origin, Origin::Compiler, "{tuple}");
}
let gnu = target("x86_64-pc-windows-gnu");
let bundled = Sysroot::in_cache(Path::new("/cache"), gnu);
let found =
include_paths(gnu, None, &Options { bundled: Some(&bundled), ..Options::default() });
assert!(found.iter().all(|entry| entry.origin == Origin::Bundled));
assert_eq!(found.len(), 2);
}
#[test]
fn a_sysroot_the_user_named_beats_an_sdk_on_the_machine() {
let host = target("aarch64-macos");
let named = [path("/opt/sdks/MacOSX14.sdk/usr/include")];
let sdk = [path("/SDKs/MacOSX.sdk/usr/include")];
let found = include_paths(
host,
Some(host),
&Options { sysroot: &named, sdk: &sdk, ..Options::default() },
);
assert_eq!(found.len(), 1);
assert_eq!(found[0].origin, Origin::Sysroot);
assert_eq!(found[0].path, named[0]);
}
#[test]
fn a_sysroot_the_user_named_beats_the_one_we_bundle() {
let host = target("aarch64-linux-musl");
let bundled = Sysroot::in_cache(Path::new("/cache"), host);
let named = [path("/opt/buildroot/usr/include")];
let found = include_paths(
host,
Some(host),
&Options {
sysroot: &named,
bundled: Some(&bundled),
host_include: &[path("/usr/include")],
..Options::default()
},
);
assert!(found.iter().all(|entry| entry.origin == Origin::Sysroot));
assert_eq!(found.len(), 1);
assert_eq!(found[0].path, path("/opt/buildroot/usr/include"));
}
#[test]
fn a_tree_the_user_laid_out_our_way_is_named_by_the_layout_rather_than_by_hand() {
let host = target("aarch64-linux-musl");
let named = Sysroot::at(path("/opt/ours"), host);
let includes = named.includes();
let found =
include_paths(host, Some(host), &Options { sysroot: &includes, ..Options::default() });
assert_eq!(found[0].path, path("/opt/ours/include/aarch64"));
assert_eq!(found[1].path, path("/opt/ours/include/generic"));
}
#[test]
fn nostdinc_removes_step_three_and_nobuiltininc_removes_step_two() {
let host = target("x86_64-linux-musl");
let bundled = Sysroot::in_cache(Path::new("/cache"), host);
let base = Options {
resources: Some(Path::new("/opt/rucc")),
bundled: Some(&bundled),
..Options::default()
};
let without_libc =
include_paths(host, Some(host), &Options { no_std_inc: true, ..base.clone() });
assert_eq!(without_libc.len(), 1);
assert_eq!(without_libc[0].origin, Origin::Compiler);
let without_ours =
include_paths(host, Some(host), &Options { no_builtin_inc: true, ..base.clone() });
assert!(without_ours.iter().all(|entry| entry.origin == Origin::Bundled));
let neither = include_paths(
host,
Some(host),
&Options { no_std_inc: true, no_builtin_inc: true, ..base },
);
assert!(neither.is_empty());
}
#[test]
fn the_compilers_own_headers_are_offered_on_every_target_including_freestanding() {
for entry in TARGETS {
let row = TargetEntry::parse(entry).expect("the table parses");
let found = include_paths(
row,
None,
&Options { resources: Some(Path::new("/opt/rucc")), ..Options::default() },
);
assert_eq!(found.len(), 1, "{} did not get the compiler's own headers", entry.tuple);
assert_eq!(found[0].origin, Origin::Compiler);
assert_eq!(found[0].path, path("/opt/rucc/include"));
}
}
#[test]
fn the_answer_does_not_depend_on_which_host_is_asking() {
let cross = target("riscv64-linux-musl");
let bundled = Sysroot::in_cache(Path::new("/cache"), cross);
let options = Options {
resources: Some(Path::new("/opt/rucc")),
bundled: Some(&bundled),
host_include: &[path("/usr/include")],
..Options::default()
};
let from_linux = include_paths(cross, Some(target("x86_64-linux-gnu")), &options);
let from_mac = include_paths(cross, Some(target("aarch64-macos")), &options);
assert_eq!(from_linux, from_mac);
}
#[test]
fn the_kernel_headers_come_after_the_libcs_and_not_before() {
let target = target("aarch64-linux-musl");
let bundled = Sysroot::in_cache(Path::new("/cache"), target);
let kernel = Kernel::for_target(Path::new("/cache"), target).expect("a Linux target has one");
let found = include_paths(
target,
None,
&Options { bundled: Some(&bundled), kernel: Some(&kernel), ..Options::default() },
);
let libc_last =
found.iter().rposition(|entry| entry.origin == Origin::Bundled).expect("a libc");
let kernel_first =
found.iter().position(|entry| entry.origin == Origin::Kernel).expect("a kernel");
assert!(libc_last < kernel_first);
}
#[test]
fn a_sysroot_the_user_named_does_not_get_our_kernel_headers_underneath_it() {
let target = target("x86_64-linux-gnu");
let bundled = Sysroot::in_cache(Path::new("/cache"), target);
let kernel = Kernel::for_target(Path::new("/cache"), target).expect("a Linux target has one");
let named = [path("/opt/buildroot/usr/include")];
let found = include_paths(
target,
None,
&Options {
sysroot: &named,
bundled: Some(&bundled),
kernel: Some(&kernel),
..Options::default()
},
);
assert_eq!(found.len(), 1);
assert!(!found.iter().any(|entry| entry.origin == Origin::Kernel));
}
#[test]
fn nostdinc_removes_the_kernel_headers_with_the_rest_of_step_three() {
let target = target("x86_64-linux-gnu");
let bundled = Sysroot::in_cache(Path::new("/cache"), target);
let kernel = Kernel::for_target(Path::new("/cache"), target).expect("a Linux target has one");
let found = include_paths(
target,
None,
&Options {
resources: Some(Path::new("/opt/rucc")),
bundled: Some(&bundled),
kernel: Some(&kernel),
no_std_inc: true,
..Options::default()
},
);
assert_eq!(found.len(), 1, "only the compiler's own headers survive");
assert_eq!(found[0].origin, Origin::Compiler);
}
#[test]
fn a_target_without_a_kernel_tree_searches_the_two_directories_it_always_did() {
for tuple in ["x86_64-pc-windows-gnu", "armv7m-none-eabi"] {
let target = target(tuple);
let bundled = Sysroot::in_cache(Path::new("/cache"), target);
assert!(Kernel::for_target(Path::new("/cache"), target).is_none());
let found = include_paths(
target,
None,
&Options { bundled: Some(&bundled), kernel: None, ..Options::default() },
);
assert_eq!(found.len(), 2, "{tuple} searches more than the libc's two");
assert!(found.iter().all(|entry| entry.origin == Origin::Bundled));
}
}