Merge pull request #10031 from holtrop-wolfssl/rust-cross-compile-support

Rust wrapper: update build.rs to support cross-compiling and bare-metal targets
This commit is contained in:
Daniel Pouzzner
2026-03-25 09:46:40 -05:00
committed by GitHub
2 changed files with 197 additions and 25 deletions
+36
View File
@@ -48,3 +48,39 @@ functionality:
* SRTP/SRTCP KDF
* SSH KDF
* TLSv1.3 HKDF
## Build Notes
### WOLFSSL_PREFIX
If the wolfSSL C library is not installed in a default location, you can
specify the installation prefix with the `WOLFSSL_PREFIX` environment variable
when building the `wolfssl-wolfcrypt` crate.
For example:
```
WOLFSSL_PREFIX=/opt/my-wolfssl-build cargo build
```
### Cross-Compiling
Ensure that the target you want to build for is installed for Rust.
For example:
```
rustup target add riscv64imac-unknown-none-elf
```
Build with the `--target` option if building manually:
```
export WOLFSSL_PREFIX=/opt/wolfssl-riscv64
cargo build --target riscv64imac-unknown-none-elf
```
To specify the linker for the target:
```
export CARGO_TARGET_RISCV64IMAC_UNKNOWN_NONE_ELF_LINKER=riscv64-elf-gcc
```
+161 -25
View File
@@ -25,37 +25,166 @@ fn run_build() -> Result<()> {
Ok(())
}
fn wrapper_dir() -> Result<String> {
fn crate_dir() -> Result<String> {
Ok(std::env::current_dir()?.display().to_string())
}
fn wolfssl_base_dir() -> Result<String> {
Ok(format!("{}/../../..", wrapper_dir()?))
fn wolfssl_repo_base_dir() -> Result<String> {
Ok(format!("{}/../../..", crate_dir()?))
}
fn wolfssl_lib_dir() -> Result<String> {
Ok(format!("{}/src/.libs", wolfssl_base_dir()?))
fn wolfssl_repo_lib_dir() -> Result<String> {
Ok(format!("{}/src/.libs", wolfssl_repo_base_dir()?))
}
/// Returns the include directory for wolfssl headers.
///
/// If `WOLFSSL_PREFIX` is set, returns `{WOLFSSL_PREFIX}/include`.
/// Otherwise falls back to the repo root if it exists (for in-tree host builds).
fn wolfssl_include_dir() -> Result<Option<String>> {
if let Ok(prefix) = env::var("WOLFSSL_PREFIX") {
let include_dir = format!("{}/include", prefix);
let wolfssl_dir = Path::new(&include_dir).join("wolfssl");
if !wolfssl_dir.is_dir() {
println!("cargo:warning=WOLFSSL_PREFIX is set but {} does not exist", wolfssl_dir.display());
return Ok(None);
}
Ok(Some(include_dir))
} else {
let base = wolfssl_repo_base_dir()?;
let base_path = Path::new(&base);
// Treat this as an in-tree wolfSSL repo only if the expected layout exists.
let wolfssl_dir = base_path.join("wolfssl");
let wolfssl_options = wolfssl_dir.join("options.h");
if wolfssl_options.is_file() {
Ok(Some(base))
} else {
Ok(None)
}
}
}
/// Returns the library directory for libwolfssl.
///
/// If `WOLFSSL_PREFIX` is set, returns `{WOLFSSL_PREFIX}/lib`.
/// Otherwise falls back to the in-tree build output directory if it exists.
fn wolfssl_lib_dir() -> Result<Option<String>> {
if let Ok(prefix) = env::var("WOLFSSL_PREFIX") {
Ok(Some(format!("{}/lib", prefix)))
} else {
let repo_lib_dir = wolfssl_repo_lib_dir()?;
if Path::new(&repo_lib_dir).exists() {
Ok(Some(repo_lib_dir))
} else {
Ok(None)
}
}
}
fn bindings_path() -> String {
PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs").display().to_string()
}
/// Generate Rust bindings for the wolfssl C library using bindgen.
/// Map a Rust target triple to the equivalent clang target triple.
///
/// This function:
/// 1. Sets up the library and include paths
/// 2. Configures the build environment
/// 3. Generates Rust bindings using bindgen
/// 4. Writes the bindings to a file
/// Rust triples embed ISA extensions in the arch component
/// (e.g. `riscv64imac-unknown-none-elf`) while clang uses only the base arch
/// (e.g. `riscv64-unknown-elf`). Bare-metal targets use `<arch>-<vendor>-elf`
/// in clang convention.
fn rust_target_to_clang_target(rust_target: &str) -> String {
let parts: Vec<&str> = rust_target.splitn(4, '-').collect();
if parts.len() < 3 {
return rust_target.to_string();
}
// Strip ISA extensions: riscv64imac → riscv64, riscv32imac → riscv32
let arch = if parts[0].starts_with("riscv64") {
"riscv64"
} else if parts[0].starts_with("riscv32") {
"riscv32"
} else {
parts[0]
};
let vendor = parts[1];
let os = parts[2];
let abi = parts.get(3).copied().unwrap_or("");
// Bare-metal: (os=none, abi=elf) → <arch>-<vendor>-elf
if os == "none" && abi == "elf" {
format!("{}-{}-elf", arch, vendor)
} else if abi.is_empty() {
format!("{}-{}-{}", arch, vendor, os)
} else {
format!("{}-{}-{}-{}", arch, vendor, os, abi)
}
}
/// Return the sysroot path for a bare-metal clang target triple, if it exists.
///
/// Queries the cross-compiler for its sysroot via `--print-sysroot` rather
/// than assuming a fixed install prefix. Tries the candidate compiler names
/// `<arch>-<vendor>-elf-gcc` and `<arch>-elf-gcc` (vendor omitted) in order.
/// Returns `None` if no suitable compiler is found or its sysroot is invalid.
fn bare_metal_sysroot(clang_target: &str) -> Option<String> {
let parts: Vec<&str> = clang_target.splitn(3, '-').collect();
if parts.len() < 3 || !clang_target.ends_with("-elf") {
return None;
}
let (arch, vendor) = (parts[0], parts[1]);
let candidates = [
format!("{}-{}-elf-gcc", arch, vendor),
format!("{}-elf-gcc", arch),
];
for compiler in &candidates {
if let Ok(output) = std::process::Command::new(compiler)
.arg("--print-sysroot")
.output()
&& output.status.success() {
let sysroot = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !sysroot.is_empty() && sysroot != "/" && Path::new(&sysroot).exists() {
return Some(sysroot);
}
}
}
None
}
/// Generate Rust bindings for the wolfssl C library using bindgen.
///
/// Returns `Ok(())` if successful, or an error if binding generation fails.
fn generate_bindings() -> Result<()> {
let bindings = bindgen::Builder::default()
let mut builder = bindgen::Builder::default()
.header("headers.h")
.clang_arg(format!("-I{}", wolfssl_base_dir()?))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.use_core()
.use_core();
if let Some(include_dir) = wolfssl_include_dir()? {
builder = builder.clang_arg(format!("-I{}", include_dir));
}
// When cross-compiling, tell clang the target so it generates correct
// type layouts and evaluates architecture-specific preprocessor guards.
let target = env::var("TARGET").unwrap();
let host = env::var("HOST").unwrap();
if target != host {
let clang_target = rust_target_to_clang_target(&target);
builder = builder.clang_arg(format!("--target={}", clang_target));
if target.ends_with("-none-elf") {
// For bare-metal targets, add the toolchain C runtime headers
// (newlib's time.h etc.) using -idirafter so they appear after
// clang's own built-in includes. This lets clang's stdatomic.h
// take priority over newlib's incompatible version.
if let Some(sysroot) = bare_metal_sysroot(&clang_target) {
builder = builder
.clang_arg("-ffreestanding")
.clang_arg(format!("-idirafter{}/include", sysroot));
}
}
}
let bindings = builder
.generate()
.map_err(|_| io::Error::other("Failed to generate bindings"))?;
@@ -147,18 +276,25 @@ fn generate_fips_aliases() -> Result<()> {
///
/// Returns `Ok(())` if successful, or an error if any step fails.
fn setup_wolfssl_link() -> Result<()> {
println!("cargo:rustc-link-lib=wolfssl");
if let Some(lib_dir) = wolfssl_lib_dir()? {
println!("cargo:rustc-link-search={}", lib_dir);
// TODO: do we need this if only a static library is built?
// println!("cargo:rustc-link-lib=static=wolfssl");
let build_in_repo = Path::new(&wolfssl_lib_dir()?).exists();
if build_in_repo {
// When the crate is built in the wolfssl repository, link with the
// locally build wolfssl library to allow testing any local changes
// and running unit tests even if library is not installed.
println!("cargo:rustc-link-search={}", wolfssl_lib_dir()?);
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", wolfssl_lib_dir()?);
// Prefer a shared library if present, otherwise fall back to static.
let has_shared = Path::new(&lib_dir).join("libwolfssl.so").exists()
|| Path::new(&lib_dir).join("libwolfssl.dylib").exists();
if has_shared {
println!("cargo:rustc-link-lib=wolfssl");
// Only set rpath where a dynamic linker exists (not bare-metal).
let target = env::var("TARGET").unwrap();
if !target.ends_with("-none-elf") {
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir);
}
} else {
println!("cargo:rustc-link-lib=static=wolfssl");
}
} else {
// No local lib dir found; rely on whatever is installed system-wide.
println!("cargo:rustc-link-lib=wolfssl");
}
Ok(())