#![allow(clippy::panic)]
use std::{
env,
fs::File,
io::{self, Write as _},
path::{Path, PathBuf},
};
const CMAKE_INCLUDE: &str = "include";
const CMAKE_LIB: &str = "lib";
const LIB_BASE: &str = "open62541";
const LIB_EXT: &str = "open62541-ext";
const LEGACY_EXTERN_PATTERN: &str = r#"extern "C" {"#;
const LEGACY_EXTERN_REPLACEMENT: &str = r#"unsafe extern "C" {"#;
fn main() {
let with_mbedtls =
matches!(env::var("CARGO_FEATURE_MBEDTLS"), Ok(mbedtls) if !mbedtls.is_empty());
let with_openssl =
matches!(env::var("CARGO_FEATURE_OPENSSL"), Ok(openssl) if !openssl.is_empty());
let encryption = match (with_mbedtls, with_openssl) {
(false, false) => None,
(true, false) => Some(Encryption::MbedTls),
(false, true) => Some(Encryption::OpenSsl),
_ => panic!("conflicting encryption feature flags, only one must be enabled"),
};
let src = env::current_dir().expect("should get current directory");
let src_mbedtls = src.join("mbedtls");
let src_open62541 = src.join("open62541");
let src_wrapper_c = src.join("wrapper.c");
let src_wrapper_h = src.join("wrapper.h");
println!("cargo:rerun-if-changed={}", src_open62541.display());
println!("cargo:rerun-if-changed={}", src_wrapper_c.display());
println!("cargo:rerun-if-changed={}", src_wrapper_h.display());
let encryption_dst = encryption.map(|encryption| match encryption {
Encryption::MbedTls => prepare_mbedtls(src_mbedtls),
Encryption::OpenSsl => prepare_openssl(),
});
let dst = build_open62541(src_open62541, encryption_dst.as_ref());
let dst_include = dst.join(CMAKE_INCLUDE);
let dst_lib = dst.join(CMAKE_LIB);
if matches!(env::var("CARGO_CFG_TARGET_OS"), Ok(os) if os == "windows") {
println!("cargo:rustc-link-lib=iphlpapi");
}
println!("cargo:rustc-link-search={}", dst_lib.display());
println!("cargo:rustc-link-lib={LIB_BASE}");
if let Some(encryption_dst) = encryption_dst {
encryption_dst.rustc_link_search();
encryption_dst.rustc_link_lib();
}
let out = PathBuf::from(env::var("OUT_DIR").expect("should have OUT_DIR"));
let out_bindings_rs = out.join("bindings.rs");
let out_extern_c = out.join("extern.c");
let builder = bindgen::Builder::default()
.allowlist_function("(__)?RS_.*")
.allowlist_function("(__)?UA_.*")
.allowlist_type("(__)?RS_.*")
.allowlist_type("(__)?UA_.*")
.allowlist_var("(__)?RS_.*")
.allowlist_var("(__)?UA_.*")
.clang_arg("-std=c99")
.clang_arg(format!("-I{}", dst_include.display()))
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.rust_target(
bindgen::RustTarget::stable(72, 0)
.ok()
.expect("should be a valid Rust target"),
)
.derive_copy(false)
.derive_default(true)
.generate_comments(false)
.header(src_wrapper_h.to_str().expect("should be valid path"))
.parse_callbacks(Box::new(CustomCallbacks { dst }))
.use_core()
.wrap_static_fns(true)
.wrap_static_fns_path(out_extern_c.to_str().expect("should be valid path"));
let bindings = builder
.generate()
.expect("should generate `Bindings` instance");
bindings
.write_to_file(out_bindings_rs.clone())
.expect("should write `bindings.rs`");
if version_check::is_min_version("1.82.0") == Some(true) {
replace_in_file(
&out_bindings_rs,
LEGACY_EXTERN_PATTERN,
LEGACY_EXTERN_REPLACEMENT,
)
.expect("should add unsafe to extern statements");
}
cc::Build::new()
.file(out_extern_c)
.file(src_wrapper_c)
.include(dst_include)
.warnings(false)
.flag_if_supported("-Wno-deprecated-declarations")
.flag_if_supported("-Wno-deprecated")
.compile(LIB_EXT);
}
#[derive(Debug)]
enum Encryption {
MbedTls,
OpenSsl,
}
#[derive(Debug)]
enum EncryptionDst {
MbedTls {
dst: PathBuf,
libs: Vec<&'static str>,
},
OpenSsl {
search: Option<&'static str>,
libs: Vec<&'static str>,
},
}
impl EncryptionDst {
const fn search(&self) -> Option<&'static str> {
match self {
EncryptionDst::MbedTls { .. } => None,
EncryptionDst::OpenSsl { search, .. } => *search,
}
}
fn libs(&self) -> &[&'static str] {
match self {
EncryptionDst::MbedTls { libs, .. } | EncryptionDst::OpenSsl { libs, .. } => libs,
}
}
fn rustc_link_search(&self) {
if let Some(search) = self.search() {
println!("cargo:rustc-link-search={search}");
}
}
fn rustc_link_lib(&self) {
for lib in self.libs() {
println!("cargo:rustc-link-lib={lib}");
}
}
}
fn prepare_mbedtls(src: PathBuf) -> EncryptionDst {
let mut cmake = cmake::Config::new(src);
cmake
.define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE)
.define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB)
.define("C_STANDARD", "99")
.define("ENABLE_PROGRAMS", "OFF")
.define("ENABLE_TESTING", "OFF");
cmake.define("MBEDTLS_FATAL_WARNINGS", "OFF");
let dst = cmake.build();
let mut libs = vec!["mbedtls", "mbedx509", "mbedcrypto"];
if matches!(env::var("CARGO_CFG_TARGET_OS"), Ok(os) if os == "windows") {
libs.push("bcrypt");
}
EncryptionDst::MbedTls { dst, libs }
}
fn prepare_openssl() -> EncryptionDst {
let search = matches!(env::var("CARGO_CFG_TARGET_OS"), Ok(os) if os == "macos")
.then_some("/opt/homebrew/opt/openssl/lib");
let libs = vec!["ssl", "crypto"];
EncryptionDst::OpenSsl { search, libs }
}
fn build_open62541(src: PathBuf, encryption: Option<&EncryptionDst>) -> PathBuf {
let mut cmake = cmake::Config::new(src);
cmake
.define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE)
.define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB)
.define("C_STANDARD", "99")
.env("PYTHONDONTWRITEBYTECODE", "1");
if matches!(env::var("CARGO_CFG_TARGET_ENV"), Ok(env) if env == "musl") {
let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("should have CARGO_CFG_TARGET_ARCH");
cmake
.cflag("-idirafter/usr/include")
.cflag(format!("-idirafter/usr/include/{arch}-linux-gnu"));
}
let encryption = match encryption {
None => "OFF",
Some(EncryptionDst::MbedTls { dst, .. }) => {
cmake
.define("MBEDTLS_FOLDER_INCLUDE", dst.join(CMAKE_INCLUDE))
.define("MBEDTLS_FOLDER_LIBRARY", dst.join(CMAKE_LIB))
.define("MBEDCRYPTO_INCLUDE_DIRS", dst.join(CMAKE_INCLUDE))
.define("MBEDCRYPTO_LIBRARY", dst.join(CMAKE_LIB))
.define("MBEDTLS_INCLUDE_DIRS", dst.join(CMAKE_INCLUDE))
.define("MBEDTLS_LIBRARY", dst.join(CMAKE_LIB))
.define("MBEDX509_INCLUDE_DIRS", dst.join(CMAKE_INCLUDE))
.define("MBEDX509_LIBRARY", dst.join(CMAKE_LIB));
"MBEDTLS"
}
Some(EncryptionDst::OpenSsl { .. }) => "OPENSSL",
};
cmake.define("UA_ENABLE_ENCRYPTION", encryption);
cmake.build()
}
#[derive(Debug)]
struct CustomCallbacks {
dst: PathBuf,
}
impl CustomCallbacks {
fn inside_dst(&self, filename: &str) -> bool {
Path::new(filename).starts_with(&self.dst)
}
}
impl bindgen::callbacks::ParseCallbacks for CustomCallbacks {
fn header_file(&self, filename: &str) {
if !self.inside_dst(filename) {
println!("cargo:rerun-if-changed={filename}");
}
}
fn include_file(&self, filename: &str) {
if !self.inside_dst(filename) {
println!("cargo:rerun-if-changed={filename}");
}
}
fn read_env_var(&self, key: &str) {
println!("cargo:rerun-if-env-changed={key}");
}
fn item_name(&self, original_item_name: bindgen::callbacks::ItemInfo<'_>) -> Option<String> {
original_item_name
.name
.strip_prefix("RS_")
.map(str::to_owned)
}
}
fn replace_in_file(path: &Path, pattern: &str, replacement: &str) -> io::Result<()> {
let buf = io::read_to_string(File::open(path)?)?;
let buf = buf.replace(pattern, replacement);
File::create(path)?.write_all(buf.as_bytes())?;
Ok(())
}