use std::{env, path::PathBuf};
fn main() {
let (include_dir, lib_dir, link_mode) = locate_librnp();
let rnp_header = include_dir.join("rnp").join("rnp.h");
if !rnp_header.exists() {
panic!(
"Could not find <rnp/rnp.h> under {}. Expected the header at {}.",
include_dir.display(),
rnp_header.display()
);
}
if let Some(dir) = &lib_dir {
println!("cargo:rustc-link-search=native={}", dir.display());
}
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-changed={}", rnp_header.display());
println!("cargo:rerun-if-env-changed=RNP_INCLUDE_DIR");
println!("cargo:rerun-if-env-changed=RNP_LIB_DIR");
let mut builder = bindgen::Builder::default()
.header("wrapper.h")
.clang_arg(format!("-I{}", include_dir.display()))
.clang_arg("-DRNP_USE_64BIT_STRICT")
.clang_arg("-Wno-deprecated-declarations");
let pqc_on = cfg!(feature = "pqc");
let crypto_refresh_on = cfg!(feature = "crypto-refresh");
if pqc_on {
println!(
"cargo:warning=rnp-rs: building with RNP_EXPERIMENTAL_PQC — requires \
librnp built with ENABLE_PQC=ON"
);
builder = builder.clang_arg("-DRNP_EXPERIMENTAL_PQC");
}
if crypto_refresh_on {
println!(
"cargo:warning=rnp-rs: building with RNP_EXPERIMENTAL_CRYPTO_REFRESH — \
requires librnp built with ENABLE_CRYPTO_REFRESH=ON"
);
builder = builder.clang_arg("-DRNP_EXPERIMENTAL_CRYPTO_REFRESH");
}
let bindings = builder
.allowlist_function("rnp_.*")
.allowlist_type("rnp_.*")
.allowlist_var("RNP_.*")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.layout_tests(false)
.default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
.generate()
.expect("Unable to generate rnp bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings");
match link_mode {
LinkMode::System => {
println!("cargo:rustc-link-lib=dylib=rnp");
}
LinkMode::Explicit => {
println!("cargo:rustc-link-lib=dylib=rnp");
}
LinkMode::Vendored { static_lib_path } => {
println!("cargo:rustc-link-lib=static=rnp");
println!("cargo:rustc-link-lib=static=sexpp");
println!("cargo:rustc-link-lib=static=json-c");
println!("cargo:rerun-if-changed={}", static_lib_path.display());
add_system_link_search();
println!("cargo:rustc-link-lib=dylib=botan-3");
println!("cargo:rustc-link-lib=dylib=z");
println!("cargo:rustc-link-lib=dylib=bz2");
if cfg!(target_os = "macos") {
println!("cargo:rustc-link-lib=dylib=c++");
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
}
}
if pqc_on {
println!("cargo:rustc-cfg=feature_pqc");
}
if crypto_refresh_on {
println!("cargo:rustc-cfg=feature_crypto_refresh");
}
}
enum LinkMode {
System,
Explicit,
#[allow(dead_code)]
Vendored { static_lib_path: PathBuf },
}
fn locate_librnp() -> (PathBuf, Option<PathBuf>, LinkMode) {
#[cfg(feature = "vendored")]
{
let (include_dir, lib_dir, static_lib) = build_vendored();
return (include_dir, Some(lib_dir), LinkMode::Vendored { static_lib_path: static_lib });
}
#[allow(unreachable_code)]
{
if let Ok(dir) = env::var("RNP_INCLUDE_DIR") {
let include_dir = PathBuf::from(dir);
let lib_dir = env::var("RNP_LIB_DIR").ok().map(PathBuf::from).or_else(|| {
let mut candidate = include_dir.clone();
candidate.pop(); candidate.push("lib");
if candidate.exists() {
Some(candidate)
} else {
None
}
});
return (include_dir, lib_dir, LinkMode::Explicit);
}
let candidate_dirs: Vec<PathBuf> = if cfg!(target_os = "macos") {
vec![
PathBuf::from("/opt/homebrew/include"),
PathBuf::from("/usr/local/include"),
PathBuf::from("/usr/include"),
]
} else {
vec![PathBuf::from("/usr/include")]
};
let include_dir = candidate_dirs
.iter()
.find(|d| d.join("rnp").join("rnp.h").exists())
.map(PathBuf::from)
.unwrap_or_else(|| {
PathBuf::from("/usr/include")
});
let lib_dir = if cfg!(target_os = "macos") {
let mut candidate = include_dir.clone();
candidate.pop();
candidate.push("lib");
if candidate.exists() {
Some(candidate)
} else {
None
}
} else {
None
};
(include_dir, lib_dir, LinkMode::System)
}
}
#[cfg(feature = "vendored")]
fn build_vendored() -> (PathBuf, PathBuf, PathBuf) {
let target = std::env::var("TARGET").unwrap_or_default();
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
let prebuilt_dir = manifest_dir.join("prebuilt").join(&target);
let include_dir = prebuilt_dir.join("include");
let lib_dir = prebuilt_dir.join("lib");
let static_lib = lib_dir.join("librnp.a");
if !static_lib.exists() {
return build_vendored_from_source();
}
(include_dir, lib_dir, static_lib)
}
#[cfg(feature = "vendored")]
fn build_vendored_from_source() -> (PathBuf, PathBuf, PathBuf) {
let vendor_dir = PathBuf::from("vendor/rnp");
if !vendor_dir.join("CMakeLists.txt").exists() {
panic!(
"vendored feature is enabled but no prebuilt libs found for target {} and \
`vendor/rnp/CMakeLists.txt` is missing. Pre-built static libraries are \
available for: aarch64-apple-darwin. For other targets, initialize the \
submodule:\n \
git submodule update --init --recursive\n\
Or drop the `--features vendored` flag to use the system librnp.",
std::env::var("TARGET").unwrap_or_default()
);
}
let backend = env::var("RNP_VENDOR_BACKEND").unwrap_or_else(|_| "botan".to_string());
let pqc = if cfg!(feature = "pqc") { "ON" } else { "OFF" };
let crypto_refresh = if cfg!(feature = "crypto-refresh") {
"ON"
} else {
"OFF"
};
let mut config = cmake::Config::new(&vendor_dir);
config
.define("CRYPTO_BACKEND", &backend)
.define("BUILD_SHARED_LIBS", "OFF")
.define("BUILD_TESTING", "OFF")
.define("ENABLE_PQC", pqc)
.define("ENABLE_CRYPTO_REFRESH", crypto_refresh)
.define("ENABLE_DOC", "OFF")
.define("CMAKE_BUILD_TYPE", "Release");
if let Ok(extra) = env::var("RNP_VENDOR_CMAKE_ARGS") {
for arg in extra.split_whitespace() {
if let Some((k, v)) = arg.split_once('=') {
config.define(k, v);
}
}
}
let install_dir = config.build();
let include_dir = install_dir.join("include");
let lib_dir = install_dir.join("lib");
let static_lib = lib_dir.join("librnp.a");
if !static_lib.exists() {
panic!(
"vendored build completed but `librnp.a` not found at {}. \
Inspect the CMake output above.",
static_lib.display()
);
}
(include_dir, lib_dir, static_lib)
}
fn add_system_link_search() {
if !cfg!(target_os = "macos") {
return;
}
for lib in &["botan", "json-c", "zlib"] {
let output = std::process::Command::new("brew")
.args(["--prefix", lib])
.output();
if let Ok(out) = output
&& out.status.success()
{
let prefix = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !prefix.is_empty() {
let lib_dir = PathBuf::from(prefix).join("lib");
if lib_dir.exists() {
println!("cargo:rustc-link-search=native={}", lib_dir.display());
}
}
}
}
}