fn main() {
#[cfg(feature = "klu")]
klu::build();
#[cfg(feature = "pardiso")]
pardiso::build();
}
#[cfg(feature = "klu")]
mod klu {
use std::env;
use std::path::{Path, PathBuf};
pub fn build() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let vendor = manifest_dir.join("vendor/suitesparse");
if env::var_os("CARGO_FEATURE_KLU_DYNAMIC").is_some() {
println!("cargo:rustc-link-lib=dylib=klu");
} else {
build_static(&vendor);
}
generate_bindings(&vendor);
println!("cargo:rerun-if-changed=vendor/suitesparse");
}
fn build_static(vendor: &Path) {
let mut build = cc::Build::new();
build
.include(vendor.join("SuiteSparse_config"))
.include(vendor.join("AMD/Include"))
.include(vendor.join("COLAMD/Include"))
.include(vendor.join("BTF/Include"))
.include(vendor.join("KLU/Include"))
.warnings(false);
build.file(vendor.join("SuiteSparse_config/SuiteSparse_config.c"));
for f in [
"amd_1", "amd_2", "amd_aat", "amd_control", "amd_defaults", "amd_dump", "amd_info",
"amd_order", "amd_post_tree", "amd_postorder", "amd_preprocess", "amd_valid", "amd_version",
] {
build.file(vendor.join(format!("AMD/Source/{f}.c")));
}
for f in ["colamd", "colamd_version"] {
build.file(vendor.join(format!("COLAMD/Source/{f}.c")));
}
for f in ["btf_maxtrans", "btf_order", "btf_strongcomp", "btf_version"] {
build.file(vendor.join(format!("BTF/Source/{f}.c")));
}
for f in [
"klu", "klu_analyze", "klu_analyze_given", "klu_defaults", "klu_diagnostics", "klu_dump",
"klu_extract", "klu_factor", "klu_free_numeric", "klu_free_symbolic", "klu_kernel",
"klu_memory", "klu_refactor", "klu_scale", "klu_solve", "klu_sort", "klu_tsolve", "klu_version",
"klu_z", "klu_z_diagnostics", "klu_z_dump", "klu_z_extract", "klu_z_factor",
"klu_z_free_numeric", "klu_z_kernel", "klu_z_refactor", "klu_z_scale", "klu_z_solve",
"klu_z_sort", "klu_z_tsolve",
] {
build.file(vendor.join(format!("KLU/Source/{f}.c")));
}
build.compile("klu_vendored");
}
fn generate_bindings(vendor: &Path) {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let mut builder = bindgen::Builder::default()
.header(vendor.join("KLU/Include/klu.h").to_str().unwrap())
.clang_arg(format!("-I{}", vendor.join("SuiteSparse_config").display()))
.clang_arg(format!("-I{}", vendor.join("AMD/Include").display()))
.clang_arg(format!("-I{}", vendor.join("COLAMD/Include").display()))
.clang_arg(format!("-I{}", vendor.join("BTF/Include").display()))
.clang_arg(format!("-I{}", vendor.join("KLU/Include").display()))
.allowlist_function("klu_.*")
.allowlist_type("klu_.*")
.allowlist_var("KLU_.*");
if let Some(gcc_builtin_include) = find_gcc_builtin_include() {
builder = builder.clang_arg(format!("-I{}", gcc_builtin_include.display()));
}
let bindings = builder.generate().expect("failed to generate KLU FFI bindings");
bindings
.write_to_file(out_dir.join("klu_bindings.rs"))
.expect("failed to write KLU FFI bindings");
}
fn find_gcc_builtin_include() -> Option<PathBuf> {
let cc = env::var("CC").unwrap_or_else(|_| "cc".to_string());
let output = std::process::Command::new(cc).arg("-print-file-name=include").output().ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
path.is_dir().then_some(path)
}
}
#[cfg(feature = "pardiso")]
mod pardiso {
use std::env;
use std::path::PathBuf;
pub fn build() {
let mkl_root = env::var("MKLROOT").expect(
"the `pardiso` feature needs Intel oneMKL installed locally; set MKLROOT \
(e.g. `source /opt/intel/oneapi/setvars.sh`) before building",
);
let mkl_root = PathBuf::from(mkl_root);
let lib_dir = [mkl_root.join("lib"), mkl_root.join("lib/intel64")]
.into_iter()
.find(|p| p.join("libmkl_rt.so").is_file())
.unwrap_or_else(|| {
panic!(
"couldn't find libmkl_rt.so under {}/lib or {}/lib/intel64 — \
is MKLROOT ({}) a valid oneMKL install?",
mkl_root.display(),
mkl_root.display(),
mkl_root.display()
)
});
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=dylib=mkl_rt");
generate_bindings(&mkl_root);
println!("cargo:rerun-if-env-changed=MKLROOT");
}
fn generate_bindings(mkl_root: &std::path::Path) {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let include_dir = mkl_root.join("include");
let mut builder = bindgen::Builder::default()
.header(include_dir.join("mkl_pardiso.h").to_str().unwrap())
.clang_arg(format!("-I{}", include_dir.display()))
.allowlist_function("pardiso.*")
.allowlist_type("_MKL_DSS_HANDLE_t|MKL_INT");
if let Some(gcc_builtin_include) = find_gcc_builtin_include() {
builder = builder.clang_arg(format!("-I{}", gcc_builtin_include.display()));
}
let bindings = builder.generate().expect("failed to generate PARDISO FFI bindings");
bindings
.write_to_file(out_dir.join("pardiso_bindings.rs"))
.expect("failed to write PARDISO FFI bindings");
}
fn find_gcc_builtin_include() -> Option<PathBuf> {
let cc = env::var("CC").unwrap_or_else(|_| "cc".to_string());
let output = std::process::Command::new(cc).arg("-print-file-name=include").output().ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
path.is_dir().then_some(path)
}
}