Skip to content

Commit

Permalink
Bundle MbedTLS for cross-platform building
Browse files Browse the repository at this point in the history
  • Loading branch information
sgoll committed Nov 15, 2024
1 parent b0223aa commit bd2e989
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "mbedtls"]
path = mbedtls
url = https://github.com/Mbed-TLS/mbedtls.git
[submodule "open62541"]
path = open62541
url = https://github.com/open62541/open62541.git
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ include = [
"src/",
"wrapper.c",
"wrapper.h",
"mbedtls/**/CMakeLists.txt",
"mbedtls/3rdparty/everest/",
"mbedtls/3rdparty/p256-m/",
"mbedtls/cmake/",
"mbedtls/include/",
"mbedtls/library/",
"mbedtls/pkgconfig/",
"open62541/**/CMakeLists.txt",
"open62541/arch/",
"open62541/deps/",
Expand Down Expand Up @@ -53,6 +60,10 @@ cc = "1.0.83"
cmake = "0.1.50"
version_check = "0.9.5"

[features]
default = []
mbedtls = []

[lints.rust]
future_incompatible = { level = "warn", priority = -1 }
keyword_idents = { level = "warn", priority = -1 }
Expand Down
181 changes: 156 additions & 25 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::panic)] // Panic only during build time.

use std::{
env,
fs::File,
Expand Down Expand Up @@ -29,9 +31,22 @@ 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());
// For now, we do not actually announce feature flag `openssl` in `Cargo.toml`.
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");

// Get derived paths relative to `src`.
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");
Expand All @@ -41,32 +56,14 @@ fn main() {
println!("cargo:rerun-if-changed={}", src_wrapper_c.display());
println!("cargo:rerun-if-changed={}", src_wrapper_h.display());

// Build bundled copy of `open62541` with CMake.
let mut cmake = cmake::Config::new(src_open62541);
cmake
// Use explicit paths here to avoid generating files where we do not expect them below.
.define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE)
// Some systems (Fedora) default to `lib64/` instead of `lib/` for 64-bit libraries.
.define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB)
// Explicitly set C99 standard to force Windows variants of `vsnprintf()` to conform to this
// standard. This also matches the expected (or supported) C standard of `open62541` itself.
.define("C_STANDARD", "99")
// Python defaults to creating bytecode in `__pycache__` directories. During build, this may
// happen when the tool `nodeset_compiler` is called. When we package a crate, builds should
// never modify files outside of `OUT_DIR`, so we disable the cache to prevent this.
.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");
// We require includes from the Linux headers which are not provided automatically when musl
// is targeted (see https://github.com/open62541/open62541/issues/6360).
// TODO: Remove this when `open62541` enables us to build without including Linux headers.
cmake
.cflag("-idirafter/usr/include")
.cflag(format!("-idirafter/usr/include/{arch}-linux-gnu"));
}
// Build related encryption libraries.
let encryption_dst = encryption.map(|encryption| match encryption {
Encryption::MbedTls => prepare_mbedtls(src_mbedtls),
Encryption::OpenSsl => prepare_openssl(),
});

let dst = cmake.build();
// Build `open62541` library.
let dst = build_open62541(src_open62541, encryption_dst.as_ref());

// Get derived paths relative to `dst`.
let dst_include = dst.join(CMAKE_INCLUDE);
Expand All @@ -81,6 +78,15 @@ fn main() {
println!("cargo:rustc-link-search={}", dst_lib.display());
println!("cargo:rustc-link-lib={LIB_BASE}");

// For encryption support enabled, we add the libraries that have to be used as dependencies for
// the final build artifact.
//
// Note: These must come _after_ adding `LIB_BASE` above for linker to resolve dependencies.
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"));

// Get derived paths relative to `out`.
Expand Down Expand Up @@ -165,6 +171,131 @@ fn main() {
.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 {
// Build bundled copy of `mbedtls` with CMake.
let mut cmake = cmake::Config::new(src);
cmake
// Use explicit paths here to avoid generating files where we do not expect them below.
.define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE)
// Some systems (Fedora) default to `lib64/` instead of `lib/` for 64-bit libraries.
.define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB)
// Use same C99 standard as is used for building `open62541`.
.define("C_STANDARD", "99")
// Skip building binary programs unnecessary for linking library.
.define("ENABLE_PROGRAMS", "OFF")
// Skip building test programs that we are not going to run anyway.
.define("ENABLE_TESTING", "OFF");

let dst = cmake.build();

// The set of MbedTLS libraries that must be linked to work with `open62541` has been taken from
// <https://github.com/open62541/open62541/blob/master/tools/cmake/FindMbedTLS.cmake>.
let libs = vec!["mbedtls", "mbedx509", "mbedcrypto"];

EncryptionDst::MbedTls { dst, libs }
}

fn prepare_openssl() -> EncryptionDst {
// For macOS, we require the precise link path because we expect OpenSSL to be provided by using
// Homebrew.
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 {
// Build bundled copy of `open62541` with CMake.
let mut cmake = cmake::Config::new(src);
cmake
// Use explicit paths here to avoid generating files where we do not expect them below.
.define("CMAKE_INSTALL_INCLUDEDIR", CMAKE_INCLUDE)
// Some systems (Fedora) default to `lib64/` instead of `lib/` for 64-bit libraries.
.define("CMAKE_INSTALL_LIBDIR", CMAKE_LIB)
// Explicitly set C99 standard to force Windows variants of `vsnprintf()` to conform to this
// standard. This also matches the expected (or supported) C standard of `open62541` itself.
.define("C_STANDARD", "99")
// Python defaults to creating bytecode in `__pycache__` directories. During build, this may
// happen when the tool `nodeset_compiler` is called. When we package a crate, builds should
// never modify files outside of `OUT_DIR`, so we disable the cache to prevent this.
.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");
// We require includes from the Linux headers which are not provided automatically when musl
// is targeted (see https://github.com/open62541/open62541/issues/6360).
// TODO: Remove this when `open62541` enables us to build without including Linux headers.
cmake
.cflag("-idirafter/usr/include")
.cflag(format!("-idirafter/usr/include/{arch}-linux-gnu"));
}

// When enabled, we build `open62541` with encryption support. This changes the library and also
// changes the resulting `bindings.rs`.
let encryption = match encryption {
None => "OFF",
Some(EncryptionDst::MbedTls { dst, .. }) => {
// Skip auto-detection and use explicit folders from `mbedtls` build.
cmake
.define("MBEDTLS_FOLDER_INCLUDE", dst.join(CMAKE_INCLUDE))
.define("MBEDTLS_FOLDER_LIBRARY", dst.join(CMAKE_LIB));
"MBEDTLS"
}
Some(EncryptionDst::OpenSsl { .. }) => "OPENSSL",
};

cmake.define("UA_ENABLE_ENCRYPTION", encryption);

cmake.build()
}

#[derive(Debug)]
struct CustomCallbacks {
/// Destination of CMake build of `open62541`.
Expand Down
1 change: 1 addition & 0 deletions mbedtls
Submodule mbedtls added at 107ea8

0 comments on commit bd2e989

Please sign in to comment.