crates/graphblas-sys/build.rs
//! Link the system SuiteSparse:GraphBLAS dynamically and check that the
//! header on this machine speaks the API the pregenerated bindings were made
//! from (7.x). With `--features regenerate`, run bindgen instead.
//!
//! Search order (header + library):
//! 1. `GRAPHBLAS_INCLUDE_DIR` / `GRAPHBLAS_LIB_DIR`
//! 2. `pkg-config GraphBLAS` (upstream ships a .pc since 8.x; Ubuntu's 7.4 does not)
//! 3. standard paths: /usr/include, /usr/local/include, /usr/include/suitesparse
use std::path::{Path, PathBuf};
use std::process::Command;
/// Major version the checked-in `src/bindings_7_4.rs` was generated from.
#[cfg(not(feature = "regenerate"))]
const PREGEN_MAJOR: u32 = 7;
fn main() {
println!("cargo:rerun-if-env-changed=GRAPHBLAS_INCLUDE_DIR");
println!("cargo:rerun-if-env-changed=GRAPHBLAS_LIB_DIR");
println!("cargo:rerun-if-changed=build.rs");
let (pc_inc, pc_lib) = pkg_config();
let inc = std::env::var_os("GRAPHBLAS_INCLUDE_DIR")
.map(PathBuf::from)
.or(pc_inc)
.or_else(|| {
[
"/usr/include",
"/usr/local/include",
"/usr/include/suitesparse",
"/usr/local/include/suitesparse",
]
.iter()
.map(PathBuf::from)
.find(|d| d.join("GraphBLAS.h").is_file())
});
let lib = std::env::var_os("GRAPHBLAS_LIB_DIR")
.map(PathBuf::from)
.or(pc_lib);
if let Some(lib) = &lib {
println!("cargo:rustc-link-search=native={}", lib.display());
}
println!("cargo:rustc-link-lib=dylib=graphblas");
let header = inc.as_ref().map(|d| d.join("GraphBLAS.h"));
if let Some(h) = &header {
println!("cargo:rerun-if-changed={}", h.display());
println!("cargo:include={}", h.parent().unwrap().display());
}
#[cfg(feature = "regenerate")]
regenerate(header.as_deref());
#[cfg(not(feature = "regenerate"))]
if let Some(h) = &header {
check_version(h);
} else {
// No header is fine for the pregenerated path (runtime-only boxes):
// the library's own GrB_getVersion is checked at init by the safe layer.
println!("cargo:warning=GraphBLAS.h not found; using pregenerated 7.4 bindings unchecked");
}
}
fn pkg_config() -> (Option<PathBuf>, Option<PathBuf>) {
let run = |arg: &str| -> Option<PathBuf> {
let out = Command::new("pkg-config")
.args([arg, "GraphBLAS"])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8(out.stdout).ok()?;
s.split_whitespace()
.find_map(|t| t.strip_prefix("-I").or_else(|| t.strip_prefix("-L")))
.map(PathBuf::from)
};
(run("--cflags-only-I"), run("--libs-only-L"))
}
fn header_version(h: &Path) -> Option<(u32, u32, u32)> {
let text = std::fs::read_to_string(h).ok()?;
let get = |name: &str| -> Option<u32> {
text.lines().find_map(|l| {
let l = l.trim();
let rest = l.strip_prefix("#define")?.trim_start();
let rest = rest.strip_prefix(name)?;
rest.split_whitespace().next()?.parse().ok()
})
};
Some((
get("GxB_IMPLEMENTATION_MAJOR")?,
get("GxB_IMPLEMENTATION_MINOR")?,
get("GxB_IMPLEMENTATION_SUB")?,
))
}
#[cfg(not(feature = "regenerate"))]
fn check_version(h: &Path) {
match header_version(h) {
Some((major, minor, sub)) => {
println!("cargo:version={major}.{minor}.{sub}");
if major != PREGEN_MAJOR {
panic!(
"GraphBLAS.h at {} is {major}.{minor}.{sub}; the checked-in bindings are for \
{PREGEN_MAJOR}.x. Build with `--features regenerate` (needs libclang) or point \
GRAPHBLAS_INCLUDE_DIR/GRAPHBLAS_LIB_DIR at a {PREGEN_MAJOR}.x install.",
h.display()
);
}
}
None => println!(
"cargo:warning=could not read GxB_IMPLEMENTATION_* from {}",
h.display()
),
}
}
#[cfg(feature = "regenerate")]
fn regenerate(header: Option<&Path>) {
let header = header.expect("regenerate: GraphBLAS.h not found (set GRAPHBLAS_INCLUDE_DIR)");
if let Some((major, minor, sub)) = header_version(header) {
println!("cargo:version={major}.{minor}.{sub}");
}
let out = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("bindings.rs");
bindgen::Builder::default()
.header(header.to_str().unwrap())
.allowlist_function("G[rx]B_.*")
.allowlist_var("G[rx]B_.*")
.allowlist_type("G[rx]B_.*")
// GB_* internals are only pulled in as dependencies of the above; the
// object structs are private to the library, so Rust sees them opaque
// (only pointers to them cross the ABI). Same for libc's FILE.
.blocklist_function("GB_.*")
.opaque_type("GB_.*_opaque")
.opaque_type("_IO_FILE")
.layout_tests(false)
.derive_debug(true)
.derive_default(false)
.prepend_enum_name(false)
.generate_comments(false)
.generate()
.expect("bindgen over GraphBLAS.h")
.write_to_file(&out)
.expect("write bindings.rs");
}