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, header_output: impl AsRef) { 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) { 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"); match arch { "i686" => { command.arg("-m32"); } "riscv64" => { command.arg("-march=rv64gc"); } _ => (), } 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) { // 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); }); }