use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use rucc_sysroot::argv::{Invocation, Item, argv, emulation, pe_machine};
use rucc_sysroot::link::loader;
use rucc_sysroot::{LinkMode, Sysroot};
use rucc_tuple::{TARGETS, TargetTuple};
use crate::CACHE;
const DIR: &str = "tests/link-lines";
const BUILTINS_DIR: &str = "<builtins>";
const MODES: &[(LinkMode, &str)] = &[
(LinkMode::Static, "static"),
(LinkMode::StaticPie, "static-pie"),
(LinkMode::Dynamic, "dynamic"),
(LinkMode::DynamicNoPie, "dynamic-no-pie"),
(LinkMode::Shared, "shared"),
];
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Mode {
Write,
Check,
}
pub(crate) fn one(target: TargetTuple) -> ExitCode {
print!("{}", render(target));
ExitCode::SUCCESS
}
pub(crate) fn run(root: &Path, mode: Mode) -> ExitCode {
let dir = root.join(DIR);
let mut written = 0;
let mut stale = Vec::new();
for entry in TARGETS {
let Ok(target) = entry.tuple.parse::<TargetTuple>() else {
eprintln!("error: the target table holds `{}`, which does not parse", entry.tuple);
return ExitCode::FAILURE;
};
let path = dir.join(format!("{}.txt", target.to_canonical_string()));
let wanted = render(target);
if mode == Mode::Check {
if std::fs::read_to_string(&path).unwrap_or_default() != wanted {
stale.push(entry.tuple);
}
written += 1;
continue;
}
if let Err(error) = std::fs::create_dir_all(&dir) {
eprintln!("error: {error}");
return ExitCode::FAILURE;
}
if let Err(error) = std::fs::write(&path, wanted) {
eprintln!("error: {}: {error}", path.display());
return ExitCode::FAILURE;
}
written += 1;
}
if mode == Mode::Check {
if stale.is_empty() {
println!("link-lines: {written} files are up to date");
return ExitCode::SUCCESS;
}
println!("link-lines: {} files are out of date, run `cargo xtask link-lines`", stale.len());
for tuple in stale {
println!(" {tuple}");
}
return ExitCode::FAILURE;
}
println!("link-lines: wrote {written} files to {DIR}");
ExitCode::SUCCESS
}
fn render(target: TargetTuple) -> String {
let sysroot = Sysroot::in_cache(Path::new(CACHE), target);
let mut out = String::new();
let _ = writeln!(out, "target {}", target.to_canonical_string());
let _ = writeln!(out, "format {}", target.object_format().as_str());
let machine = emulation(target).or_else(|| pe_machine(target)).unwrap_or("none");
let _ = writeln!(out, "emulation {machine}");
let _ = writeln!(out, "loader {}", loader(target).unwrap_or("none"));
let _ = writeln!(out, "sysroot {}", sysroot.root().display());
let inputs = [Item::File(PathBuf::from("main.o"))];
let builtins = PathBuf::from(BUILTINS_DIR).join(rucc_sysroot::link::BUILTINS);
for (mode, name) in MODES {
let options = Invocation {
inputs: &inputs,
output: Some(Path::new("main")),
mode: *mode,
builtins: Some(&builtins),
..Invocation::default()
};
let _ = writeln!(out);
let _ = writeln!(out, "[{name}]");
match argv(target, &sysroot, &options) {
Ok(args) => {
for arg in args {
let _ = writeln!(out, " {arg}");
}
}
Err(why) => {
let _ = writeln!(out, " refused: {why}");
}
}
}
out
}