use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let go_dir = manifest_dir.join("go");
let header_path = manifest_dir.join("headers").join("coraza.h");
let _lib_path = build_go_library(&go_dir, &out_dir);
generate_bindings(&header_path, &out_dir);
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=coraza");
let target = env::var("TARGET").unwrap();
if target.contains("linux") {
println!("cargo:rustc-link-lib=dylib=pthread");
println!("cargo:rustc-link-lib=dylib=dl");
println!("cargo:rustc-link-lib=dylib=m");
println!("cargo:rustc-link-lib=dylib=c");
} else if target.contains("apple") {
println!("cargo:rustc-link-lib=framework=CoreFoundation");
println!("cargo:rustc-link-lib=framework=Security");
println!("cargo:rustc-link-lib=dylib=resolv");
println!("cargo:rustc-link-lib=dylib=m");
println!("cargo:rustc-link-lib=dylib=c");
} else if target.contains("windows") {
println!("cargo:rustc-link-lib=dylib=ws2_32");
println!("cargo:rustc-link-lib=dylib=winmm");
println!("cargo:rustc-link-lib=dylib=advapi32");
println!("cargo:rustc-link-lib=dylib=ntdll");
println!("cargo:rustc-link-lib=dylib=bcrypt");
}
println!(
"cargo:rerun-if-changed={}",
go_dir.join("coraza.go").display()
);
println!("cargo:rerun-if-changed={}", go_dir.join("log.go").display());
println!("cargo:rerun-if-changed={}", go_dir.join("go.mod").display());
println!("cargo:rerun-if-changed={}", header_path.display());
}
fn build_go_library(go_dir: &Path, out_dir: &Path) -> PathBuf {
let lib_path = out_dir.join("libcoraza.a");
let go_binary = find_go_binary();
let mut cmd = Command::new(&go_binary);
cmd.arg("build")
.arg("-buildmode=c-archive")
.arg("-o")
.arg(&lib_path)
.arg("./...");
cmd.current_dir(go_dir);
cmd.env("CGO_ENABLED", "1");
if let Ok(target) = env::var("TARGET") {
let (goarch, goos) = parse_target_triple(&target);
cmd.env("GOARCH", goarch);
cmd.env("GOOS", goos);
}
let output = cmd
.output()
.expect("Failed to execute `go build`. Is Go installed and in PATH?");
if !output.status.success() {
panic!(
"Go build failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
lib_path
}
fn parse_target_triple(target: &str) -> (&str, &str) {
let parts: Vec<&str> = target.split('-').collect();
let goarch = match parts[0] {
"x86_64" => "amd64",
"aarch64" => "arm64",
"i686" | "i386" => "386",
"armv7" | "arm" => "arm",
other => other,
};
let goos = if parts.len() >= 3 {
match parts[2] {
"linux" => "linux",
"darwin" => "darwin",
"windows" => "windows",
"musl" => "linux",
other => other,
}
} else if parts.len() >= 2 {
match parts[1] {
"linux" => "linux",
"darwin" => "darwin",
"windows" => "windows",
"musl" => "linux",
other => other,
}
} else {
"linux" };
(goarch, goos)
}
fn find_go_binary() -> String {
if let Ok(go) = env::var("GO") {
return go;
}
which("go").expect(
"Go toolchain not found. Please install Go (https://go.dev/dl/) and ensure it is in PATH.",
)
}
fn which(command: &str) -> Option<String> {
let mut which_cmd = if cfg!(target_os = "windows") {
Command::new("where")
} else {
Command::new("which")
};
let output = which_cmd.arg(command).output().ok()?;
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !path.is_empty() {
return Some(path);
}
}
None
}
fn generate_bindings(header_path: &Path, out_dir: &Path) {
let bindings_path = out_dir.join("bindings.rs");
let bindings = bindgen::Builder::default()
.header(header_path.to_str().unwrap())
.allowlist_function("coraza_.*")
.allowlist_type("coraza_.*")
.allowlist_var("CORAZA_.*")
.use_core()
.derive_debug(true)
.derive_default(true)
.derive_eq(true)
.derive_hash(true)
.generate_comments(true)
.layout_tests(true)
.generate()
.expect("Failed to generate bindings. Check that libclang is installed.");
bindings
.write_to_file(&bindings_path)
.expect("Failed to write bindings");
println!("cargo:rerun-if-changed={}", header_path.display());
}