use std::collections::HashMap;
use std::path::PathBuf;
const KNOWN_FFI_VERSIONS: [(u32, u32); 7] = [
(3, 20250506), (3, 20240408), (3, 20231009), (3, 20230711), (3, 20230403), (2, 20210220), (2, 20191214), ];
const LATEST_KNOWN_FFI: u32 = 20250506;
#[cfg(feature = "vendored")]
fn emit_dylibs() -> Vec<&'static str> {
if cfg!(target_os = "windows") {
return vec!["user32", "crypt32"];
}
if cfg!(any(target_os = "linux")) {
return vec!["stdc++"];
}
vec!["c++"]
}
fn sanity_check_ffi(major_version: u32, minor_version: u32, ffi_version: u32) -> u32 {
if ffi_version == 0 {
panic!("The libbotan found does not support the FFI feature");
}
if ffi_version < 20191214 {
panic!("This version of Botan is too old; at least 2.13.0 is required");
}
if major_version < 2 {
panic!("Major version 1 or lower not supported");
}
if major_version > 4 {
panic!("Major version unexpectedly high");
}
if major_version >= 3 && ffi_version > LATEST_KNOWN_FFI {
return LATEST_KNOWN_FFI;
}
for (mv, fv) in &KNOWN_FFI_VERSIONS {
if ffi_version == *fv && major_version >= *mv {
return *fv;
}
}
panic!(
"Unexpected version settings major={major_version} minor={minor_version} ffi={ffi_version}"
);
}
#[allow(dead_code)]
fn env_var(key: &str) -> Option<String> {
println!("cargo:rerun-if-env-changed={key}");
std::env::var(key).ok()
}
#[derive(Debug, Copy, Clone)]
struct DetectedVersionInfo {
major_version: u32,
#[allow(dead_code)]
minor_version: u32,
ffi_version: u32,
}
impl DetectedVersionInfo {
fn library_link_name(&self) -> String {
if cfg!(target_os = "windows") && self.major_version == 2 {
"botan".to_string()
} else {
format!("botan-{}", self.major_version)
}
}
#[allow(dead_code)]
fn latest_for_docs_rs() -> Self {
Self {
major_version: 3,
minor_version: 8,
ffi_version: LATEST_KNOWN_FFI,
}
}
fn from_map(map: HashMap<String, u32>) -> Self {
let major_version = *map.get("MAJOR_VERSION").expect("Missing MAJOR_VERSION");
let minor_version = *map.get("MINOR_VERSION").expect("Missing MINOR_VERSION");
let ffi_version = *map.get("FFI_VERSION").expect("Missing FFI_VERSION");
let ffi_version = sanity_check_ffi(major_version, minor_version, ffi_version);
Self {
major_version,
minor_version,
ffi_version,
}
}
fn from_header(include_dir: &PathBuf) -> Self {
println!("cargo:rerun-if-changed=build/version.c");
let mut cc = cc::Build::new();
cc.include(include_dir);
match cc.file("build/version.c").try_expand() {
Ok(o) => {
let expanded = String::from_utf8(o).expect("Output is not valid UTF8");
let mut map = HashMap::new();
for line in expanded.split('\n') {
if line.is_empty() || line.starts_with('#') {
continue;
}
let line = line.replace('\r', "");
let parts = line.split(' ').collect::<Vec<_>>();
if parts.len() != 2 {
continue;
}
if parts[0] == "MAJOR_VERSION"
|| parts[0] == "MINOR_VERSION"
|| parts[0] == "FFI_VERSION"
{
if let Ok(code) = parts[1].parse::<u32>() {
map.insert(parts[0].to_owned(), code);
} else {
panic!("Unexpected line '{line}'");
}
}
}
DetectedVersionInfo::from_map(map)
}
Err(e) => {
panic!("Failed to expand header {e:?}");
}
}
}
}
#[cfg(not(feature = "vendored"))]
fn find_botan_include_dir() -> std::path::PathBuf {
#[cfg(feature = "pkg-config")]
{
for major in [3, 2] {
let lib_name = format!("botan-{}", major);
let statik = cfg!(feature = "static");
if let Ok(config) = pkg_config::Config::new().statik(statik).probe(&lib_name) {
return config.include_paths[0].clone();
}
}
panic!("Unable to find the headers corresponding with any supported version of Botan");
}
#[cfg(not(feature = "pkg-config"))]
{
if let Some(dir) = env_var("BOTAN_INCLUDE_DIR") {
return dir.into();
}
fn possible_header_locations() -> Vec<std::path::PathBuf> {
let dirs = [
"/opt/homebrew/include",
"/usr/local/include",
"/usr/include",
"/opt/include",
];
let mut paths = vec![];
for dirname in dirs {
let path = PathBuf::from(dirname);
if path.exists() {
paths.push(path);
}
}
paths
}
for major_version in [3, 2] {
let dir = PathBuf::from(format!("botan-{major_version}"));
for basedir in possible_header_locations() {
let inc_dir = basedir.join(dir.clone());
if inc_dir.exists() {
return inc_dir;
}
}
}
panic!("Unable to find the headers corresponding with any supported version of Botan");
}
}
fn main() {
for (_, v) in &KNOWN_FFI_VERSIONS {
println!("cargo:rustc-check-cfg=cfg(botan_ffi_{v})");
}
#[cfg(feature = "vendored")]
{
let (lib_dir, inc_dir) = botan_src::build();
let version = DetectedVersionInfo::from_header(&inc_dir);
println!("cargo:vendored=1");
println!("cargo:rustc-link-search=native={}", &lib_dir);
println!(
"cargo:rustc-link-lib=static={}",
version.library_link_name()
);
for dylib in emit_dylibs() {
println!("cargo:rustc-flags=-l dylib={}", dylib);
}
println!("cargo:ffi_version={}", version.ffi_version);
for (_, ffi) in &KNOWN_FFI_VERSIONS {
if *ffi <= version.ffi_version {
println!("cargo:rustc-cfg=botan_ffi_{}", ffi);
}
}
}
#[cfg(not(feature = "vendored"))]
{
let version = if std::env::var("DOCS_RS").is_ok() {
DetectedVersionInfo::latest_for_docs_rs()
} else {
DetectedVersionInfo::from_header(&find_botan_include_dir())
};
if let Some(dir) = env_var("BOTAN_LIB_DIR") {
println!("cargo:rustc-link-search=native={dir}");
}
if cfg!(feature = "static") {
println!(
"cargo:rustc-link-lib=static={}",
version.library_link_name()
);
} else {
println!("cargo:rustc-link-lib={}", version.library_link_name());
}
println!("cargo:ffi_version={}", version.ffi_version);
for (_, ffi) in &KNOWN_FFI_VERSIONS {
if *ffi <= version.ffi_version {
println!("cargo:rustc-cfg=botan_ffi_{ffi}");
}
}
}
}