use std::path::{Path, PathBuf};
use rucc_sysroot::argv::{Invocation, argv};
use rucc_sysroot::link::{glibc_loader, libc, loader, musl_loader};
use rucc_sysroot::{Libc, LinkLine, LinkMode, Sysroot};
use rucc_tuple::TargetTuple;
fn target(tuple: &str) -> TargetTuple {
tuple.parse().expect("a target this understands")
}
fn sysroot(tuple: &str) -> Sysroot {
Sysroot::in_cache(Path::new("/cache"), target(tuple))
}
fn names(paths: &[PathBuf]) -> Vec<String> {
paths
.iter()
.map(|path| path.file_name().expect("a file").to_string_lossy().into_owned())
.collect()
}
#[test]
fn crtn_goes_after_the_libraries_and_crti_goes_before_them() {
let line = LinkLine::musl(&sysroot("aarch64-linux-musl"), LinkMode::Static);
let objects = [PathBuf::from("main.o")];
assert_eq!(
names(&line.with_objects(&objects)),
["crt1.o", "crti.o", "main.o", "libc.a", "librucc_builtins.a", "crtn.o"]
);
}
#[test]
fn the_modes_differ_in_the_first_start_file() {
for (mode, first) in [
(LinkMode::Static, Some("crt1.o")),
(LinkMode::StaticPie, Some("rcrt1.o")),
(LinkMode::Dynamic, Some("Scrt1.o")),
(LinkMode::DynamicNoPie, Some("crt1.o")),
(LinkMode::Shared, None),
] {
let line = LinkLine::musl(&sysroot("x86_64-linux-musl"), mode);
let names = names(&line.start);
assert_eq!(names.first().map(String::as_str), first.or(Some("crti.o")), "{mode:?}");
assert_eq!(names.last().map(String::as_str), Some("crti.o"), "{mode:?}");
}
}
#[test]
fn a_dynamic_link_names_the_loader_and_a_static_one_does_not() {
let args = |mode| {
let sysroot = sysroot("aarch64-linux-musl");
let options = Invocation { mode, ..Invocation::default() };
argv(target("aarch64-linux-musl"), &sysroot, &options).expect("a line")
};
let dynamic = args(LinkMode::Dynamic);
let at = dynamic.iter().position(|arg| arg == "-dynamic-linker").expect("the flag");
assert_eq!(dynamic[at + 1], "/lib/ld-musl-aarch64.so.1");
assert_eq!(loader(target("aarch64-linux-musl")), Some("/lib/ld-musl-aarch64.so.1"));
let still = args(LinkMode::Static);
assert!(still.contains(&"-static".to_owned()));
assert!(!still.contains(&"-dynamic-linker".to_owned()));
}
#[test]
fn every_line_says_the_stack_is_not_executable() {
for mode in [LinkMode::Static, LinkMode::StaticPie, LinkMode::Dynamic, LinkMode::Shared] {
let sysroot = sysroot("riscv64-linux-musl");
let options = Invocation { mode, ..Invocation::default() };
let args = argv(target("riscv64-linux-musl"), &sysroot, &options).expect("a line");
assert!(args.join(" ").contains("-z noexecstack"), "{mode:?} did not say it");
}
}
#[test]
fn glibc_and_musl_name_different_loaders_for_the_same_machine() {
assert_eq!(glibc_loader(target("x86_64-linux-gnu")), "/lib64/ld-linux-x86-64.so.2");
assert_eq!(musl_loader(target("x86_64-linux-musl")), "/lib/ld-musl-x86_64.so.1");
assert_eq!(glibc_loader(target("s390x-linux-gnu")), "/lib/ld64.so.1");
assert_eq!(glibc_loader(target("powerpc64le-linux-gnu")), "/lib64/ld64.so.2");
assert_eq!(glibc_loader(target("i686-linux-gnu")), "/lib/ld-linux.so.2");
assert_eq!(glibc_loader(target("armv7a-linux-gnueabihf")), "/lib/ld-linux-armhf.so.3");
assert_eq!(glibc_loader(target("riscv64-linux-gnu")), "/lib/ld-linux-riscv64-lp64d.so.1");
}
#[test]
fn a_target_with_no_libc_of_ours_has_no_loader_to_name() {
for spelling in ["x86_64-none", "wasm32-wasi", "aarch64-macos", "x86_64-windows-gnu"] {
assert_eq!(loader(target(spelling)), None, "{spelling}");
}
}
#[test]
fn the_libc_the_target_names_picks_the_line() {
let gnu = LinkLine::for_target(&sysroot("x86_64-linux-gnu"), LinkMode::Dynamic);
assert_eq!(names(&gnu.libraries), ["libc.so", "librucc_builtins.a"]);
let musl = LinkLine::for_target(&sysroot("x86_64-linux-musl"), LinkMode::Static);
assert_eq!(names(&musl.libraries), ["libc.a", "librucc_builtins.a"]);
}
#[test]
fn the_cases_of_section_8_2_are_that_many_different_lines() {
assert_eq!(libc(target("x86_64-linux-musl")), Libc::Archive);
assert_eq!(libc(target("x86_64-linux-gnu")), Libc::Stub);
assert_eq!(libc(target("x86_64-freebsd")), Libc::Stub);
assert_eq!(libc(target("aarch64-linux-android")), Libc::Stub);
assert_eq!(libc(target("armv7m-none-eabi")), Libc::None);
assert_eq!(libc(target("x86_64-windows-gnu")), Libc::Import);
assert_eq!(libc(target("aarch64-windows-msvc")), Libc::Import);
let bare = LinkLine::for_target(&sysroot("armv7m-none-eabi"), LinkMode::Static);
assert!(bare.start.is_empty());
assert!(bare.end.is_empty());
assert_eq!(names(&bare.libraries), ["librucc_builtins.a"]);
}
#[test]
fn a_windows_line_is_one_start_file_and_a_set_of_libraries_rather_than_one() {
let line = LinkLine::for_target(&sysroot("x86_64-windows-gnu"), LinkMode::Dynamic);
assert_eq!(names(&line.start), ["crt2.o"]);
assert!(line.end.is_empty());
assert_eq!(
names(&line.libraries),
[
"libmingw32.a",
"libmoldname.a",
"libmingwex.a",
"libmsvcrt.a",
"libadvapi32.a",
"libshell32.a",
"libuser32.a",
"libkernel32.a",
"librucc_builtins.a",
]
);
}
#[test]
fn the_builtins_are_searched_after_the_libc_that_calls_them() {
let line = LinkLine::musl(&sysroot("armv7a-linux-musleabihf"), LinkMode::Static);
let libraries = names(&line.libraries);
let libc = libraries.iter().position(|name| name == "libc.a").expect("a libc");
let builtins =
libraries.iter().position(|name| name == "librucc_builtins.a").expect("the builtins");
assert!(libc < builtins);
}
#[test]
fn arm_has_two_loaders_and_the_float_abi_picks_between_them() {
assert_eq!(musl_loader(target("armv7a-linux-musleabihf")), "/lib/ld-musl-armhf.so.1");
assert_eq!(musl_loader(target("armv5te-linux-musleabi")), "/lib/ld-musl-arm.so.1");
}
#[test]
fn x32_has_its_own_loader_because_it_is_its_own_abi() {
assert_eq!(musl_loader(target("x86_64-linux-gnux32")), "/lib/ld-musl-x32.so.1");
assert_eq!(musl_loader(target("x86_64-linux-musl")), "/lib/ld-musl-x86_64.so.1");
}
#[test]
fn every_path_on_the_line_is_inside_the_sysroot() {
let sysroot = sysroot("s390x-linux-musl");
let line = LinkLine::musl(&sysroot, LinkMode::Static);
for path in line.with_objects(&[]) {
assert!(path.starts_with(sysroot.root()), "{} escaped the sysroot", path.display());
}
}