use regex::Regex; use std::{ env, fs::File, io::Write, path::Path, process::{Command, Stdio}, }; fn compile_irrt(irrt_dir: &Path, out_dir: &Path) { let irrt_cpp_path = irrt_dir.join("irrt.cpp"); /* * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * Compiling for WASM32 and filtering the output with regex is the closest we can get. */ let flags: &[&str] = &[ "--target=wasm32", irrt_cpp_path.to_str().unwrap(), "-x", "c++", "-fno-discard-value-names", "-fno-exceptions", "-fno-rtti", match env::var("PROFILE").as_deref() { Ok("debug") => "-O0", Ok("release") => "-O3", flavor => panic!("Unknown or missing build flavor {flavor:?}"), }, "-emit-llvm", "-S", "-Wall", "-Wextra", "-Werror=return-type", "-I", irrt_dir.to_str().unwrap(), "-o", "-", ]; println!("cargo:rerun-if-changed={}", out_dir.to_str().unwrap()); let output = Command::new("clang-irrt") .args(flags) .output() .map(|o| { assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap()); o }) .unwrap(); // https://github.com/rust-lang/regex/issues/244 let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n"); let mut filtered_output = String::with_capacity(output.len()); // (?ms:^define.*?\}$) to capture `define` blocks // (?m:^declare.*?$) to capture `declare` blocks // (?m:^%.+?=\s*type\s*\{.+?\}$) to capture `type` declarations let regex_filter = Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)|(?m:^%.+?=\s*type\s*\{.+?\}$)").unwrap(); for f in regex_filter.captures_iter(&output) { assert_eq!(f.len(), 1); filtered_output.push_str(&f[0]); filtered_output.push('\n'); } let filtered_output = Regex::new("(#\\d+)|(, *![0-9A-Za-z.]+)|(![0-9A-Za-z.]+)|(!\".*?\")") .unwrap() .replace_all(&filtered_output, ""); println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT"); if env::var("DEBUG_DUMP_IRRT").is_ok() { let mut file = File::create(out_dir.join("irrt.ll")).unwrap(); file.write_all(output.as_bytes()).unwrap(); let mut file = File::create(out_dir.join("irrt-filtered.ll")).unwrap(); file.write_all(filtered_output.as_bytes()).unwrap(); } let mut llvm_as = Command::new("llvm-as-irrt") .stdin(Stdio::piped()) .arg("-o") .arg(out_dir.join("irrt.bc")) .spawn() .unwrap(); llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap(); assert!(llvm_as.wait().unwrap().success()); } fn compile_irrt_test(irrt_dir: &Path, out_dir: &Path) { let irrt_test_cpp_path = irrt_dir.join("irrt_test.cpp"); let exe_path = out_dir.join("irrt_test.out"); let flags: &[&str] = &[ irrt_test_cpp_path.to_str().unwrap(), "-x", "c++", "-I", irrt_dir.to_str().unwrap(), "-g", "-fno-discard-value-names", "-O0", "-Wall", "-Wextra", "-Werror=return-type", "-lm", // for `tgamma()`, `lgamma()` "-o", exe_path.to_str().unwrap(), ]; Command::new("clang-irrt-test") .args(flags) .output() .map(|o| { assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap()); o }) .unwrap(); println!("cargo:rerun-if-changed={}", out_dir.to_str().unwrap()); } fn main() { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); let irrt_dir = Path::new("./irrt"); compile_irrt(irrt_dir, out_dir); // https://github.com/rust-lang/cargo/issues/2549 // `cargo test -F test` to also build `irrt_test.cpp if cfg!(feature = "test") { compile_irrt_test(irrt_dir, out_dir); } }