use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
const GRAPHBLAS_REPO: &str = "https://github.com/DrTimothyAldenDavis/GraphBLAS.git";
const GRAPHBLAS_TAG: &str = "v10.3.1";
const LAGRAPH_REL_PATH: &str = "deps/LAGraph";
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=DOCS_RS");
if env::var_os("DOCS_RS").is_some() {
eprintln!("pathrex-sys: detected DOCS_RS, skipping native build");
return;
}
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR set by Cargo"));
let lagraph_src = manifest_dir.join(LAGRAPH_REL_PATH);
assert_lagraph_submodule_present(&lagraph_src);
let graphblas_src = fetch_graphblas(&out_dir);
let graphblas_install = build_graphblas_static(&graphblas_src);
let lagraph_install = build_lagraph_static(&lagraph_src, &graphblas_install);
emit_link_directives(&lagraph_install, &graphblas_install);
#[cfg(feature = "regenerate-bindings")]
regenerate_bindings(&graphblas_install);
}
fn assert_lagraph_submodule_present(lagraph_src: &Path) {
let marker = lagraph_src.join("CMakeLists.txt");
if !marker.exists() {
panic!(
"LAGraph submodule not initialized: {} does not exist.\n\
Run: git submodule update --init --recursive",
marker.display()
);
}
}
fn fetch_graphblas(out_dir: &Path) -> PathBuf {
let src_dir = out_dir.join("graphblas-src");
let sentinel = src_dir.join(".pathrex-fetched");
if let Ok(contents) = std::fs::read_to_string(&sentinel) {
if contents.trim() == GRAPHBLAS_TAG {
return src_dir;
}
eprintln!(
"pathrex-sys: GraphBLAS pin changed (was '{}', want '{}'), re-fetching",
contents.trim(),
GRAPHBLAS_TAG
);
std::fs::remove_dir_all(&src_dir).unwrap_or_else(|e| {
panic!(
"failed to remove stale GraphBLAS clone at {}: {e}",
src_dir.display()
)
});
} else if src_dir.exists() {
eprintln!(
"pathrex-sys: incomplete GraphBLAS clone at {}, removing",
src_dir.display()
);
std::fs::remove_dir_all(&src_dir).unwrap_or_else(|e| {
panic!(
"failed to remove incomplete GraphBLAS clone at {}: {e}",
src_dir.display()
)
});
}
eprintln!("pathrex-sys: cloning GraphBLAS {GRAPHBLAS_TAG} into {src_dir:#?}");
let status = Command::new("git")
.args([
"clone",
"--depth=1",
"--branch",
GRAPHBLAS_TAG,
GRAPHBLAS_REPO,
])
.arg(&src_dir)
.status()
.unwrap_or_else(|e| {
panic!(
"failed to invoke `git clone` for GraphBLAS: {e}\n\
Is git installed and on PATH?"
)
});
if !status.success() {
panic!(
"`git clone` of {GRAPHBLAS_REPO} (tag {GRAPHBLAS_TAG}) failed with status {status}.\n\
If you are offline, pre-populate {} with a checked-out tree.",
src_dir.display()
);
}
std::fs::write(&sentinel, GRAPHBLAS_TAG).unwrap_or_else(|e| {
panic!(
"failed to write fetch sentinel at {}: {e}",
sentinel.display()
)
});
src_dir
}
fn build_graphblas_static(graphblas_src: &Path) -> PathBuf {
cmake::Config::new(graphblas_src)
.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_STATIC_LIBS", "ON")
.define("GRAPHBLAS_BUILD_STATIC_LIBS", "ON")
.define("GRAPHBLAS_USE_JIT", "OFF")
.define("GRAPHBLAS_COMPACT", "OFF")
.define("GRAPHBLAS_USE_OPENMP", "ON")
.define("GRAPHBLAS_USE_CUDA", "OFF")
.define("SUITESPARSE_DEMOS", "OFF")
.define("BUILD_TESTING", "OFF")
.profile("Release")
.build()
}
fn build_lagraph_static(lagraph_src: &Path, graphblas_install: &Path) -> PathBuf {
cmake::Config::new(lagraph_src)
.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_STATIC_LIBS", "ON")
.define("BUILD_TESTING", "OFF")
.define("CMAKE_PREFIX_PATH", graphblas_install)
.define("GRAPHBLAS_ROOT", graphblas_install)
.define("LAGRAPH_USE_OPENMP", "ON")
.profile("Release")
.build()
}
fn emit_link_directives(lagraph_install: &Path, graphblas_install: &Path) {
let lagraph_libdir = pick_libdir(lagraph_install, "liblagraph.a");
let graphblas_libdir = pick_libdir(graphblas_install, "libgraphblas.a");
println!(
"cargo:rustc-link-search=native={}",
lagraph_libdir.display()
);
println!(
"cargo:rustc-link-search=native={}",
graphblas_libdir.display()
);
println!("cargo:rustc-link-lib=static=lagraphx");
println!("cargo:rustc-link-lib=static=lagraph");
println!("cargo:rustc-link-lib=static=graphblas");
link_runtime_libs();
}
fn pick_libdir(install_prefix: &Path, archive_name: &str) -> PathBuf {
let candidates = [install_prefix.join("lib"), install_prefix.join("lib64")];
candidates
.iter()
.find(|p| p.join(archive_name).exists())
.cloned()
.unwrap_or_else(|| {
panic!("cmake build succeeded but {archive_name} not found in any of: {candidates:?}")
})
}
fn link_runtime_libs() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
match target_os.as_str() {
"linux" => {
println!("cargo:rustc-link-lib=dylib=gomp");
println!("cargo:rustc-link-lib=dylib=pthread");
println!("cargo:rustc-link-lib=dylib=dl");
println!("cargo:rustc-link-lib=dylib=m");
}
"macos" => {
println!("cargo:rustc-link-lib=dylib=omp");
println!("cargo:rustc-link-lib=dylib=pthread");
}
"windows" => {
}
other => {
eprintln!(
"warning: pathrex-sys: unknown target OS '{other}', \
defaulting runtime libs to Linux conventions"
);
println!("cargo:rustc-link-lib=dylib=gomp");
println!("cargo:rustc-link-lib=dylib=pthread");
println!("cargo:rustc-link-lib=dylib=dl");
println!("cargo:rustc-link-lib=dylib=m");
}
}
}
#[cfg(feature = "regenerate-bindings")]
fn regenerate_bindings(graphblas_install: &Path) {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let lagraph_include = manifest_dir.join(LAGRAPH_REL_PATH).join("include");
assert!(
lagraph_include.join("LAGraph.h").exists(),
"LAGraph.h not found at {}.\n\
Fetch the submodule:\n git submodule update --init --recursive",
lagraph_include.display()
);
let graphblas_include = locate_graphblas_header(graphblas_install);
let bindings = bindgen::Builder::default()
.header(
lagraph_include
.join("LAGraph.h")
.to_str()
.expect("non-utf8 header path"),
)
.header(
lagraph_include
.join("LAGraphX.h")
.to_str()
.expect("non-utf8 header path"),
)
.clang_arg(format!("-I{}", graphblas_include.display()))
.clang_arg(format!("-I{}", lagraph_include.display()))
.allowlist_type("GrB_Index")
.allowlist_type("GrB_Matrix")
.allowlist_type("GrB_Vector")
.allowlist_item("GrB_BOOL")
.allowlist_item("GrB_LOR")
.allowlist_item("GrB_LOR_LAND_SEMIRING_BOOL")
.allowlist_item("GrB_Info")
.allowlist_function("GrB_Matrix_new")
.allowlist_function("GrB_Matrix_nvals")
.allowlist_function("GrB_Matrix_dup")
.allowlist_function("GrB_Matrix_free")
.allowlist_function("GrB_Matrix_extractElement_BOOL")
.allowlist_function("GrB_Matrix_build_BOOL")
.allowlist_function("GrB_Vector_new")
.allowlist_function("GrB_Vector_free")
.allowlist_function("GrB_Vector_setElement_BOOL")
.allowlist_function("GrB_Vector_nvals")
.allowlist_function("GrB_Vector_extractTuples_BOOL")
.allowlist_function("GrB_vxm")
.allowlist_item("LAGRAPH_MSG_LEN")
.allowlist_item("RPQMatrixOp")
.allowlist_type("RPQMatrixPlan")
.allowlist_type("LAGraph_Graph")
.allowlist_type("LAGraph_Kind")
.allowlist_function("LAGraph_CheckGraph")
.allowlist_function("LAGraph_Init")
.allowlist_function("LAGraph_Finalize")
.allowlist_function("LAGraph_SetNumThreads")
.allowlist_function("LAGraph_GetNumThreads")
.allowlist_function("LAGraph_New")
.allowlist_function("LAGraph_Delete")
.allowlist_function("LAGraph_Cached_AT")
.allowlist_function("LAGraph_MMRead")
.allowlist_function("LAGraph_RPQMatrix")
.allowlist_function("LAGraph_RPQMatrix_reduce")
.allowlist_function("LAGraph_DestroyRpqMatrixPlan")
.allowlist_function("LAGraph_RPQMatrix_label")
.allowlist_function("LAGraph_RPQMatrix_Free")
.allowlist_function("LAGraph_RegularPathQuery")
.default_enum_style(bindgen::EnumVariation::Rust {
non_exhaustive: false,
})
.derive_debug(true)
.derive_copy(true)
.layout_tests(false)
.generate_comments(false)
.generate()
.expect("bindgen failed to generate bindings");
bindings
.write_to_file(manifest_dir.join("src/lagraph_sys_generated.rs"))
.expect("failed to write bindgen output to src/lagraph_sys_generated.rs");
}
#[cfg(feature = "regenerate-bindings")]
fn locate_graphblas_header(install_prefix: &Path) -> PathBuf {
let candidates = [
install_prefix.join("include").join("suitesparse"),
install_prefix.join("include"),
];
candidates
.iter()
.find(|p| p.join("GraphBLAS.h").exists())
.cloned()
.unwrap_or_else(|| {
panic!(
"GraphBLAS.h not found in any of {candidates:?}.\n\
Did the GraphBLAS cmake install step run?"
)
})
}