197 lines
5.4 KiB
Rust

use std::{
env,
fs::{self, DirEntry, File},
io::Write,
path::{Path, PathBuf},
process::Command,
};
const RENAMES: &[(&str, &str)] = &[
("CUsizeResult", "size_t"),
("CIsizeResult", "ssize_t"),
("CEofResult", "int"),
("CIntCountResult", "int"),
("CIntZeroResult", "int"),
("CFdResult", "int"),
("COffsetResult", "off_t"),
("CPidResult", "pid_t"),
("AtomicU32", "uint32_t"),
("AtomicBool", "bool"),
];
fn include_dir(d: &DirEntry) -> bool {
d.metadata().map(|m| m.is_dir()).unwrap_or(false)
&& d.path()
.iter()
.nth(2)
.is_some_and(|c| c.to_str().is_some_and(|x| !x.starts_with("_")))
}
fn generate_header(config_path: impl AsRef<Path>, header_output: impl AsRef<Path>) {
let config_path = config_path.as_ref();
let header_output = header_output.as_ref();
let relative_path = config_path
.strip_prefix("src/headers")
.ok()
.and_then(|p| p.parent())
.and_then(|p| p.to_str())
.unwrap();
// NOTE: a hack for nl_types.h
let relative_path = if relative_path.contains("nl_types") {
relative_path.to_owned()
} else {
relative_path.replace("_", "/")
};
// TODO use outer workspace's target directory?
let header_path = header_output.join(&relative_path).with_extension("h");
let mod_path = config_path.with_file_name("mod.rs");
let mut config = cbindgen::Config::from_file(config_path).unwrap();
config
.export
.rename
.extend(RENAMES.iter().map(|&(x, y)| (x.into(), y.into())));
config.cpp_compat = true;
config.function.no_return = Some("__attribute__((noreturn))".into());
cbindgen::Builder::new()
.with_config(config)
.with_src(mod_path)
.generate()
.unwrap()
.write_to_file(header_path);
}
fn compile_crt0(arch: &str, output_dir: impl AsRef<Path>) {
let output_dir = output_dir.as_ref();
let mut command = Command::new("clang");
let arch = if arch == "x86" { "i686" } else { arch };
let input_dir = PathBuf::from("crt").join(arch);
let crt0_c = input_dir.join("crt0.c");
let crt0_s = input_dir.join("crt0.S");
let input_file = if crt0_c.exists() {
crt0_c
} else if crt0_s.exists() {
crt0_s
} else {
panic!("No crt0.* file for {arch}");
};
command
.arg(format!("--target={}-unknown-none", arch))
.arg("-nostdlib")
.arg("-fPIC")
.arg("-c");
if arch == "i686" {
command.arg("-m32");
}
command
.arg("-o")
.arg(output_dir.join("crt0.o"))
.arg(input_file);
if !command.status().unwrap().success() {
panic!("Couldn't compile crt0.o");
}
}
fn ctype_item(ch: u8) -> String {
let mut traits = vec![];
// TODO include vertical tab here
if ch.is_ascii_whitespace() {
traits.push("_ISspace");
}
if ch.is_ascii_graphic() || ch == b' ' {
traits.push("_ISprint");
}
if ch.is_ascii_control() {
traits.push("_IScntrl");
}
if ch.is_ascii_uppercase() {
traits.push("_ISupper");
}
if ch.is_ascii_lowercase() {
traits.push("_ISlower");
}
if ch.is_ascii_alphabetic() {
traits.push("_ISalpha");
}
if ch.is_ascii_digit() {
traits.push("_ISdigit");
}
if ch.is_ascii_punctuation() {
traits.push("_ISpunct");
}
if ch.is_ascii_hexdigit() {
traits.push("_ISxdigit");
}
if ch == b' ' || ch == b'\t' {
traits.push("_ISblank");
}
if traits.is_empty() {
"0,".to_owned()
} else {
let mut str = String::new();
for (i, t) in traits.iter().enumerate() {
if i != 0 {
str.push('|');
}
str.push_str(t);
}
str.push(',');
str
}
}
fn generate_ctype_table(output_dir: impl AsRef<Path>) {
// Table size: 384, __ctype_b_loc points to 128th element to allow the table to be indexed
// by a signed char or EOF (-1) as well as any unsigned char.
let mut output = File::create(output_dir.as_ref().join("ctype_b.rs")).unwrap();
output
.write_all(b"pub(crate) static __ctype_b_table: [c_ushort; 384] = [")
.unwrap();
for ch in 128..=255u8 {
let traits_string = ctype_item(ch);
output.write_all(traits_string.as_bytes()).unwrap();
}
for ch in 0..=255u8 {
let traits_string = ctype_item(ch);
output.write_all(traits_string.as_bytes()).unwrap();
}
output.write_all(b"];").unwrap();
}
fn main() {
let target = env::var("TARGET").expect("$TARGET is not set");
let profile = env::var("PROFILE").expect("$PROFILE is not set");
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("$CARGO_CFG_TARGET_ARCH is not set");
let compile_output_dir = env::var("OUT_DIR").expect("$OUT_DIR is not set");
let output_dir = PathBuf::from(format!("target/{target}/{profile}"));
let header_output = output_dir.join("include");
generate_ctype_table(compile_output_dir);
compile_crt0(&arch, output_dir);
fs::read_dir("src/headers")
.unwrap()
.filter_map(Result::ok)
.filter(include_dir)
.map(|d| d.path().as_path().join("cbindgen.toml"))
.filter(|p| p.exists())
.for_each(|p| {
generate_header(&p, &header_output);
});
}