Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

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");
}