use std::{env, fs::read_dir, path::PathBuf};
const QHULL_SRC_DIR: &str = "qhull/src/libqhull_r";
fn main() {
println!("cargo:rerun-if-changed=src/error_handling.h");
println!("cargo:rerun-if-changed=src/error_handling.c");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let target_triple = env::var("TARGET").unwrap();
let all_headers = std::env::var("CARGO_FEATURE_ALL_HEADERS").is_ok();
let include_programs = std::env::var("CARGO_FEATURE_INCLUDE_PROGRAMS").is_ok();
let mut sources = vec![];
let mut headers = vec![];
for entry in read_dir(QHULL_SRC_DIR).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() {
let file = path.file_name().unwrap().to_str().unwrap().to_string();
if file.ends_with(".c") {
sources.push(file);
} else if file.ends_with(".h") {
headers.push(file);
}
}
}
let mut builder = cc::Build::new();
builder.files(sources.iter().map(|s| format!("{}/{}", QHULL_SRC_DIR, s)));
builder.file("src/error_handling.c");
builder.include(QHULL_SRC_DIR);
builder.include("qhull/src");
let wrapper = if all_headers {
let mut wrapper = String::new();
for include in headers {
wrapper.push_str(&format!("#include <{}>\n", include));
}
let wrapper_path = out_path.join("qhull_wrapper.h");
std::fs::write(&wrapper_path, wrapper).unwrap();
wrapper_path
} else {
println!("cargo:rerun-if-changed=wrapper.h");
PathBuf::from("wrapper.h")
};
let mut bindings_builder = bindgen::Builder::default()
.header(wrapper.to_str().unwrap())
.header("src/error_handling.h")
.use_core() .blocklist_item("FP_(NAN|INFINITE|ZERO|SUBNORMAL|NORMAL)")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.clang_args([
"-Iqhull/src/libqhull_r".to_string(),
"-Iqhull/src".to_string(),
"-target".to_string(),
target_triple,
]);
if include_programs {
let programs = [
("qconvex", "qconvex_r"),
("qdelaunay", "qdelaun_r"),
("qhalf", "qhalf_r"),
("qhull", "unix_r"),
("qvoronoi", "qvoronoi_r"),
("rbox", "rbox_r"), ];
let mut header = String::new();
let mut programs_changed = false;
for (program, main_file) in &programs {
let program_path = format!("qhull/src/{program}/{main_file}.c");
let main_function_name = format!("qhull_sys__{}_main", program);
let program_source = patch_program_source(&main_function_name, &program_path);
let program_source_path = out_path.join(format!("{}.c", program));
let current_content = std::fs::read_to_string(&program_source_path).unwrap_or_default();
if current_content != program_source {
std::fs::write(&program_source_path, program_source).unwrap();
programs_changed = true;
}
builder.file(program_source_path.to_str().unwrap());
header.push_str(&format!(
"int {}(int argc, char* argv[]);\n",
main_function_name
));
}
let header_path = out_path.join("qhull_programs.h");
if programs_changed {
std::fs::write(&header_path, header).unwrap();
}
bindings_builder = bindings_builder.header(header_path.to_str().unwrap());
}
builder.compile("qhull_r");
let bindings = bindings_builder
.generate()
.expect("Unable to generate bindings");
let out_path = out_path.join("bindings.rs");
bindings
.write_to_file(out_path)
.expect("Couldn't write bindings!");
}
fn patch_program_source(main_function_name: &str, program_path: &str) -> String {
let program_source = std::fs::read_to_string(program_path).unwrap();
let mut program_source = program_source
.replace("int main(", &format!("int {}(", main_function_name))
.replace("char hidden_options", "static char hidden_options")
.replace("char qh_prompt", "static char qh_prompt")
.replace("char prompt", "static char prompt");
if program_path.contains("rbox_r") {
program_source = program_source.replace(" char *command;\n int command_size;\n", "");
}
program_source
}