Skip to content

Commit fa25a38

Browse files
Rust wrapper: update build.rs to support cross-compiling and bare-metal targets
1 parent 994a1fb commit fa25a38

2 files changed

Lines changed: 178 additions & 26 deletions

File tree

wrapper/rust/wolfssl-wolfcrypt/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,39 @@ functionality:
4848
* SRTP/SRTCP KDF
4949
* SSH KDF
5050
* TLSv1.3 HKDF
51+
52+
## Build Notes
53+
54+
### WOLFSSL_PREFIX
55+
56+
If the wolfSSL C library is not installed in a default location, you can
57+
specify the installation prefix with the `WOLFSSL_PREFIX` environment variable
58+
when building the `wolfssl-wolfcrypt` crate.
59+
60+
For example:
61+
62+
```
63+
WOLFSSL_PREFIX=/opt/my-wolfssl-build cargo build
64+
```
65+
66+
### Cross-Compiling
67+
68+
Ensure that the target you want to build for is installed for Rust.
69+
For example:
70+
71+
```
72+
rustup target add riscv64imac-unknown-none-elf
73+
```
74+
75+
Build with the `--target` option if building manually:
76+
77+
```
78+
export WOLFSSL_PREFIX=/opt/wolfssl-riscv64
79+
cargo build --target riscv64imac-unknown-none-elf
80+
```
81+
82+
To specify the linker for the target:
83+
84+
```
85+
export CARGO_TARGET_RISCV64IMAC_UNKNOWN_NONE_ELF_LINKER=riscv64-elf-gcc
86+
```

wrapper/rust/wolfssl-wolfcrypt/build.rs

Lines changed: 142 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,146 @@ fn run_build() -> Result<()> {
2525
Ok(())
2626
}
2727

28-
fn wrapper_dir() -> Result<String> {
28+
fn crate_dir() -> Result<String> {
2929
Ok(std::env::current_dir()?.display().to_string())
3030
}
3131

32-
fn wolfssl_base_dir() -> Result<String> {
33-
Ok(format!("{}/../../..", wrapper_dir()?))
32+
fn wolfssl_repo_base_dir() -> Result<String> {
33+
Ok(format!("{}/../../..", crate_dir()?))
3434
}
3535

36-
fn wolfssl_lib_dir() -> Result<String> {
37-
Ok(format!("{}/src/.libs", wolfssl_base_dir()?))
36+
fn wolfssl_repo_lib_dir() -> Result<String> {
37+
Ok(format!("{}/src/.libs", wolfssl_repo_base_dir()?))
38+
}
39+
40+
/// Returns the include directory for wolfssl headers.
41+
///
42+
/// If `WOLFSSL_PREFIX` is set, returns `{WOLFSSL_PREFIX}/include`.
43+
/// Otherwise falls back to the repo root if it exists (for in-tree host builds).
44+
fn wolfssl_include_dir() -> Result<Option<String>> {
45+
if let Ok(prefix) = env::var("WOLFSSL_PREFIX") {
46+
Ok(Some(format!("{}/include", prefix)))
47+
} else if Path::new(&wolfssl_repo_base_dir()?).exists() {
48+
Ok(Some(wolfssl_repo_base_dir()?))
49+
} else {
50+
Ok(None)
51+
}
52+
}
53+
54+
/// Returns the library directory for libwolfssl.
55+
///
56+
/// If `WOLFSSL_PREFIX` is set, returns `{WOLFSSL_PREFIX}/lib`.
57+
/// Otherwise falls back to the in-tree build output directory if it exists.
58+
fn wolfssl_lib_dir() -> Result<Option<String>> {
59+
if let Ok(prefix) = env::var("WOLFSSL_PREFIX") {
60+
Ok(Some(format!("{}/lib", prefix)))
61+
} else if Path::new(&wolfssl_repo_lib_dir()?).exists() {
62+
Ok(Some(wolfssl_repo_lib_dir()?))
63+
} else {
64+
Ok(None)
65+
}
3866
}
3967

4068
fn bindings_path() -> String {
4169
PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs").display().to_string()
4270
}
4371

44-
/// Generate Rust bindings for the wolfssl C library using bindgen.
72+
/// Map a Rust target triple to the equivalent clang target triple.
4573
///
46-
/// This function:
47-
/// 1. Sets up the library and include paths
48-
/// 2. Configures the build environment
49-
/// 3. Generates Rust bindings using bindgen
50-
/// 4. Writes the bindings to a file
74+
/// Rust triples embed ISA extensions in the arch component
75+
/// (e.g. `riscv64imac-unknown-none-elf`) while clang uses only the base arch
76+
/// (e.g. `riscv64-unknown-elf`). Bare-metal targets use `<arch>-<vendor>-elf`
77+
/// in clang convention.
78+
fn rust_target_to_clang_target(rust_target: &str) -> String {
79+
let parts: Vec<&str> = rust_target.splitn(4, '-').collect();
80+
if parts.len() < 3 {
81+
return rust_target.to_string();
82+
}
83+
84+
// Strip ISA extensions: riscv64imac → riscv64, riscv32imac → riscv32
85+
let arch = if parts[0].starts_with("riscv64") {
86+
"riscv64"
87+
} else if parts[0].starts_with("riscv32") {
88+
"riscv32"
89+
} else {
90+
parts[0]
91+
};
92+
93+
let vendor = parts[1];
94+
let os = parts[2];
95+
let abi = parts.get(3).copied().unwrap_or("");
96+
97+
// Bare-metal: (os=none, abi=elf) → <arch>-<vendor>-elf
98+
if os == "none" && abi == "elf" {
99+
format!("{}-{}-elf", arch, vendor)
100+
} else {
101+
format!("{}-{}-{}-{}", arch, vendor, os, abi)
102+
}
103+
}
104+
105+
/// Return the sysroot path for a bare-metal clang target triple, if it exists.
106+
///
107+
/// Queries the cross-compiler for its sysroot via `--print-sysroot` rather
108+
/// than assuming a fixed install prefix. Tries the candidate compiler names
109+
/// `<arch>-<vendor>-elf-gcc` and `<arch>-elf-gcc` (vendor omitted) in order.
110+
/// Returns `None` if no suitable compiler is found or its sysroot is invalid.
111+
fn bare_metal_sysroot(clang_target: &str) -> Option<String> {
112+
let parts: Vec<&str> = clang_target.splitn(3, '-').collect();
113+
if parts.len() < 3 || !clang_target.ends_with("-elf") {
114+
return None;
115+
}
116+
let (arch, vendor) = (parts[0], parts[1]);
117+
let candidates = [
118+
format!("{}-{}-elf-gcc", arch, vendor),
119+
format!("{}-elf-gcc", arch),
120+
];
121+
for compiler in &candidates {
122+
if let Ok(output) = std::process::Command::new(compiler)
123+
.arg("--print-sysroot")
124+
.output()
125+
&& output.status.success() {
126+
let sysroot = String::from_utf8_lossy(&output.stdout).trim().to_string();
127+
if !sysroot.is_empty() && sysroot != "/" && Path::new(&sysroot).exists() {
128+
return Some(sysroot);
129+
}
130+
}
131+
}
132+
None
133+
}
134+
135+
/// Generate Rust bindings for the wolfssl C library using bindgen.
51136
///
52137
/// Returns `Ok(())` if successful, or an error if binding generation fails.
53138
fn generate_bindings() -> Result<()> {
54-
let bindings = bindgen::Builder::default()
139+
let mut builder = bindgen::Builder::default()
55140
.header("headers.h")
56-
.clang_arg(format!("-I{}", wolfssl_base_dir()?))
57141
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
58-
.use_core()
142+
.use_core();
143+
144+
if let Some(include_dir) = wolfssl_include_dir()? {
145+
builder = builder.clang_arg(format!("-I{}", include_dir));
146+
}
147+
148+
// When cross-compiling, tell clang the target so it generates correct
149+
// type layouts and evaluates architecture-specific preprocessor guards.
150+
let target = env::var("TARGET").unwrap();
151+
let host = env::var("HOST").unwrap();
152+
if target != host && target.ends_with("-none-elf") {
153+
let clang_target = rust_target_to_clang_target(&target);
154+
builder = builder.clang_arg(format!("--target={}", clang_target));
155+
156+
// For bare-metal targets, add the toolchain C runtime headers
157+
// (newlib's time.h etc.) using -idirafter so they appear after
158+
// clang's own built-in includes. This lets clang's stdatomic.h
159+
// take priority over newlib's incompatible version.
160+
if let Some(sysroot) = bare_metal_sysroot(&clang_target) {
161+
builder = builder
162+
.clang_arg("-ffreestanding")
163+
.clang_arg(format!("-idirafter{}/include", sysroot));
164+
}
165+
}
166+
167+
let bindings = builder
59168
.generate()
60169
.map_err(|_| io::Error::other("Failed to generate bindings"))?;
61170

@@ -147,18 +256,25 @@ fn generate_fips_aliases() -> Result<()> {
147256
///
148257
/// Returns `Ok(())` if successful, or an error if any step fails.
149258
fn setup_wolfssl_link() -> Result<()> {
150-
println!("cargo:rustc-link-lib=wolfssl");
151-
152-
// TODO: do we need this if only a static library is built?
153-
// println!("cargo:rustc-link-lib=static=wolfssl");
154-
155-
let build_in_repo = Path::new(&wolfssl_lib_dir()?).exists();
156-
if build_in_repo {
157-
// When the crate is built in the wolfssl repository, link with the
158-
// locally build wolfssl library to allow testing any local changes
159-
// and running unit tests even if library is not installed.
160-
println!("cargo:rustc-link-search={}", wolfssl_lib_dir()?);
161-
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", wolfssl_lib_dir()?);
259+
if let Some(lib_dir) = wolfssl_lib_dir()? {
260+
println!("cargo:rustc-link-search={}", lib_dir);
261+
262+
// Prefer a shared library if present, otherwise fall back to static.
263+
let has_shared = Path::new(&lib_dir).join("libwolfssl.so").exists()
264+
|| Path::new(&lib_dir).join("libwolfssl.dylib").exists();
265+
if has_shared {
266+
println!("cargo:rustc-link-lib=wolfssl");
267+
// Only set rpath where a dynamic linker exists (not bare-metal).
268+
let target = env::var("TARGET").unwrap();
269+
if !target.ends_with("-none-elf") {
270+
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir);
271+
}
272+
} else {
273+
println!("cargo:rustc-link-lib=static=wolfssl");
274+
}
275+
} else {
276+
// No local lib dir found; rely on whatever is installed system-wide.
277+
println!("cargo:rustc-link-lib=wolfssl");
162278
}
163279

164280
Ok(())

0 commit comments

Comments
 (0)