use std::env;
use std::path::{Path, PathBuf};
#[cfg(feature = "vendored")]
const SUNDIALS_VERSION: &str = "7.4.0";
#[cfg(feature = "vendored")]
const SUNDIALS_URL: &str = concat!(
"https://github.com/LLNL/sundials/releases/download/",
"v7.4.0/sundials-7.4.0.tar.gz"
);
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-env-changed=SUNDIALS_DIR");
println!("cargo:rerun-if-env-changed=DOCS_RS");
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
if env::var("DOCS_RS").is_ok() {
std::fs::copy("pregenerated/bindings.rs", out_dir.join("bindings.rs"))
.expect("failed to copy pregenerated bindings for docs.rs");
return;
}
let (lib_dir, include_dir) = if let Ok(dir) = env::var("SUNDIALS_DIR") {
let root = PathBuf::from(&dir);
(root.join("lib"), root.join("include"))
} else if cfg!(feature = "vendored") {
build_vendored(&out_dir)
} else {
locate_system()
};
println!("cargo:rustc-link-search=native={}", lib_dir.display());
if cfg!(feature = "vendored") {
for lib in &[
"sundials_cvodes",
"sundials_idas",
"sundials_nvecserial",
"sundials_sunmatrixdense",
"sundials_sunlinsoldense",
"sundials_core",
] {
println!("cargo:rustc-link-lib=static={lib}");
}
#[cfg(unix)]
println!("cargo:rustc-link-lib=m");
} else {
let has = |name: &str| -> bool {
lib_dir.join(format!("lib{name}.so")).exists()
|| lib_dir.join(format!("lib{name}.a")).exists()
};
if has("sundials_core") {
println!("cargo:rustc-link-lib=sundials_core");
}
if has("sundials_cvodes") {
println!("cargo:rustc-link-lib=sundials_cvodes");
} else if has("sundials_cvode") {
println!("cargo:rustc-link-lib=sundials_cvode");
}
if has("sundials_idas") {
println!("cargo:rustc-link-lib=sundials_idas");
} else if has("sundials_ida") {
println!("cargo:rustc-link-lib=sundials_ida");
}
for lib in &[
"sundials_nvecserial",
"sundials_sunmatrixdense",
"sundials_sunlinsoldense",
] {
if has(lib) {
println!("cargo:rustc-link-lib={lib}");
}
}
}
let bindings = bindgen::Builder::default()
.header("wrapper.h")
.clang_arg(format!("-I{}", include_dir.display()))
.allowlist_function("CV.*|IDA.*|N_V.*|SUN.*|Sundials.*")
.allowlist_type(
"CV.*|IDA.*|N_V.*|SUN.*|_generic.*|_sundials.*\
|realtype|sunindextype|booleantype|SUNComm|SUNErrCode",
)
.allowlist_var("CV_.*|IDA_.*|SUN_.*|SUNTRUE|SUNFALSE")
.derive_debug(true)
.derive_default(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("bindgen failed to generate bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("failed to write bindings.rs");
}
#[cfg(feature = "vendored")]
fn build_vendored(out_dir: &Path) -> (PathBuf, PathBuf) {
use flate2::read::GzDecoder;
use std::fs;
use tar::Archive;
let tarball = out_dir.join(format!("sundials-{SUNDIALS_VERSION}.tar.gz"));
let src_dir = out_dir.join(format!("sundials-{SUNDIALS_VERSION}"));
let lib_dir = out_dir.join("lib");
let include_dir = out_dir.join("include");
if !tarball.exists() {
eprintln!("[sundials-rs-sys] Downloading SUNDIALS {SUNDIALS_VERSION}...");
let resp = ureq::get(SUNDIALS_URL)
.call()
.expect("failed to download SUNDIALS; check network / proxy settings");
let mut buf = Vec::new();
use std::io::Read;
resp.into_reader().read_to_end(&mut buf)
.expect("failed to read SUNDIALS tarball");
fs::write(&tarball, buf).expect("failed to write SUNDIALS tarball");
eprintln!("[sundials-rs-sys] Download complete ({} bytes)", tarball.metadata().unwrap().len());
}
if !src_dir.exists() {
eprintln!("[sundials-rs-sys] Extracting...");
let f = fs::File::open(&tarball).expect("failed to open tarball");
let gz = GzDecoder::new(f);
let mut archive = Archive::new(gz);
archive.unpack(out_dir).expect("failed to extract tarball");
}
if !lib_dir.join("libsundials_cvodes.a").exists() {
eprintln!("[sundials-rs-sys] Building SUNDIALS with CMake (this takes ~1 minute the first time)...");
cmake::Config::new(&src_dir)
.define("CMAKE_BUILD_TYPE", "Release")
.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_STATIC_LIBS", "ON")
.define("ENABLE_MPI", "OFF")
.define("BUILD_TESTING", "OFF")
.define("EXAMPLES_ENABLE_C", "OFF")
.define("EXAMPLES_ENABLE_CXX", "OFF")
.define("BUILD_CVODE", "OFF")
.define("BUILD_CVODES", "ON")
.define("BUILD_IDA", "OFF")
.define("BUILD_IDAS", "ON")
.define("BUILD_ARKODE", "OFF")
.define("BUILD_KINSOL", "OFF")
.build();
eprintln!("[sundials-rs-sys] SUNDIALS build complete.");
}
(lib_dir, include_dir)
}
#[cfg(not(feature = "vendored"))]
fn build_vendored(_out_dir: &Path) -> (PathBuf, PathBuf) {
unreachable!("vendored feature is not enabled")
}
fn locate_system() -> (PathBuf, PathBuf) {
if let Ok(lib_info) = pkg_config::Config::new()
.atleast_version("6.0")
.cargo_metadata(false)
.probe("sundials_cvode")
{
if let (Some(lib_dir), Some(inc_dir)) = (
lib_info.link_paths.into_iter().next(),
lib_info.include_paths.into_iter().next(),
) {
return (lib_dir, inc_dir);
}
}
let candidates: &[(&str, &str)] = &[
("/usr/local/lib", "/usr/local/include"),
("/usr/lib/x86_64-linux-gnu", "/usr/include"),
("/usr/lib", "/usr/include"),
("/opt/sundials/lib", "/opt/sundials/include"),
("/usr/local/lib64", "/usr/local/include"),
];
for (lib_str, inc_str) in candidates {
let lib = PathBuf::from(lib_str);
let inc = PathBuf::from(inc_str);
let header = inc.join("cvode/cvode.h");
if header.exists()
&& (lib.join("libsundials_cvode.so").exists()
|| lib.join("libsundials_cvode.a").exists())
{
return (lib, inc);
}
}
panic!(
"Could not locate SUNDIALS.\n\
Options:\n\
1. Install it: sudo apt install libsundials-dev\n\
2. Set env var: SUNDIALS_DIR=/path/to/install\n\
3. Build from source: add features = [\"vendored\"] to sundials-rs-sys\n"
);
}