use std::path::{Path, PathBuf}; fn generate_bindings(includedir: &Path, headerfile: &str, allow_filter: &str, block_filter: &str) { let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); bindgen::Builder::default() .clang_arg(format!("-I{}", includedir.display())) .header( includedir .join("oqs") .join(format!("{headerfile}.h")) .to_str() .unwrap(), ) // Options .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: false, }) .size_t_is_usize(true) // Don't generate docs unless enabled // Otherwise it breaks tests .generate_comments(cfg!(feature = "docs")) // Allowlist/blocklist OQS stuff .allowlist_recursively(false) .allowlist_type(allow_filter) .allowlist_function(allow_filter) .allowlist_var(allow_filter) .blocklist_type(block_filter) .blocklist_function(block_filter) .allowlist_var(block_filter) // Use core and libc .use_core() .ctypes_prefix("::libc") // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. .expect("Unable to generate bindings") .write_to_file(out_path.join(format!("{headerfile}_bindings.rs"))) .expect("Couldn't write bindings!"); } fn build_from_source() -> PathBuf { let mut config = cmake::Config::new("liboqs"); config.profile("Release"); config.define("OQS_BUILD_ONLY_LIB", "Yes"); if cfg!(feature = "non_portable") { // Build with CPU feature detection or just enable whatever is available for this CPU config.define("OQS_DIST_BUILD", "No"); } else { config.define("OQS_DIST_BUILD", "Yes"); } macro_rules! algorithm_feature { ($typ:literal, $feat: literal) => { let configflag = format!("OQS_ENABLE_{}_{}", $typ, $feat.to_ascii_uppercase()); let value = if cfg!(feature = $feat) { "Yes" } else { "No" }; config.define(&configflag, value); }; } // KEMs // BIKE is not supported on Windows or Arm32, so if either is in the mix, // have it be opt-in explicitly except through the default kems feature. if cfg!(feature = "kems") && !(cfg!(windows) || cfg!(target_arch = "arm")) { println!("cargo:rustc-cfg=feature=\"bike\""); config.define("OQS_ENABLE_KEM_BIKE", "Yes"); } else { algorithm_feature!("KEM", "bike"); } algorithm_feature!("KEM", "classic_mceliece"); algorithm_feature!("KEM", "frodokem"); algorithm_feature!("KEM", "hqc"); algorithm_feature!("KEM", "kyber"); algorithm_feature!("KEM", "ml_kem"); algorithm_feature!("KEM", "ntruprime"); // signature schemes algorithm_feature!("SIG", "cross"); algorithm_feature!("SIG", "dilithium"); algorithm_feature!("SIG", "falcon"); algorithm_feature!("SIG", "mayo"); algorithm_feature!("SIG", "ml_dsa"); algorithm_feature!("SIG", "sphincs"); if cfg!(windows) { // Select the latest available Windows SDK // SDK version 10.0.17763.0 seems broken config.define("CMAKE_SYSTEM_VERSION", "10.0"); } if cfg!(feature = "openssl") { config.define("OQS_USE_OPENSSL", "Yes"); if cfg!(windows) { // Windows doesn't prefix with lib println!("cargo:rustc-link-lib=libcrypto"); } else { println!("cargo:rustc-link-lib=crypto"); } println!("cargo:rerun-if-env-changed=OPENSSL_ROOT_DIR"); if let Ok(dir) = std::env::var("OPENSSL_ROOT_DIR") { let dir = Path::new(&dir).join("lib"); println!("cargo:rustc-link-search={}", dir.display()); } else if cfg!(target_os = "windows") || cfg!(target_os = "macos") { println!("cargo:warning=You may need to specify OPENSSL_ROOT_DIR or disable the default `openssl` feature."); } } else { config.define("OQS_USE_OPENSSL", "No"); } let permit_unsupported = "OQS_PERMIT_UNSUPPORTED_ARCHITECTURE"; if let Ok(str) = std::env::var(permit_unsupported) { config.define(permit_unsupported, str); } // build the default (install) target. let outdir = config.build(); // remove the build folder let temp_build = outdir.join("build"); if let Err(e) = std::fs::remove_dir_all(temp_build) { println!( "cargo:warning=unexpected error while cleaning build files:{}", e ); } // lib is installed to $outdir/lib let libdir = outdir.join("lib"); if cfg!(windows) { // Static linking doesn't work on Windows println!("cargo:rustc-link-lib=oqs"); } else { // Statically linking makes it easier to use the sys crate println!("cargo:rustc-link-lib=static=oqs"); } println!("cargo:rustc-link-search=native={}", libdir.display()); outdir } fn includedir_from_source() -> PathBuf { let outdir = build_from_source(); outdir.join("include") } fn probe_includedir() -> PathBuf { if cfg!(feature = "vendored") { return includedir_from_source(); } println!("cargo:rerun-if-env-changed=LIBOQS_NO_VENDOR"); let force_no_vendor = std::env::var_os("LIBOQS_NO_VENDOR").map_or(false, |v| v != "0"); let version = env!("CARGO_PKG_VERSION"); let (_, liboqs_version) = version.split_once("+liboqs-").unwrap(); let &[major_version, minor_version, _] = liboqs_version.split('.').collect::>().as_slice() else { panic!("Failed to parse target liboqs version"); }; let minor_num: usize = minor_version.parse().unwrap(); let upper_bound = format!("{}.{}.0", major_version, minor_num + 1); let config = pkg_config::Config::new() .range_version(liboqs_version..upper_bound.as_str()) .probe("liboqs"); match config { Ok(lib) => lib.include_paths.first().cloned().unwrap(), _ => { if force_no_vendor { panic!("The env variable LIBOQS_NO_VENDOR has been set but a suitable system liboqs could not be found."); } includedir_from_source() } } } fn main() { // Check if clang is available before compiling anything. bindgen::clang_version(); let includedir = probe_includedir(); let gen_bindings = |file, allow_filter, block_filter| { generate_bindings(&includedir, file, allow_filter, block_filter) }; gen_bindings("common", "OQS_.*", ""); gen_bindings("rand", "OQS_(randombytes|RAND)_.*", ""); gen_bindings("kem", "OQS_KEM.*", ""); gen_bindings("sig", "OQS_SIG.*", "OQS_SIG_STFL.*"); // https://docs.rs/build-deps/0.1.4/build_deps/fn.rerun_if_changed_paths.html build_deps::rerun_if_changed_paths("liboqs/src/**/*").unwrap(); build_deps::rerun_if_changed_paths("liboqs/src").unwrap(); build_deps::rerun_if_changed_paths("liboqs/src/*").unwrap(); }