Skip to content

Commit 7efc962

Browse files
authored
Merge pull request #10031 from holtrop-wolfssl/rust-cross-compile-support
Rust wrapper: update build.rs to support cross-compiling and bare-metal targets
2 parents cf6c172 + 34afd28 commit 7efc962

2 files changed

Lines changed: 198 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: 162 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,166 @@ 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+
let include_dir = format!("{}/include", prefix);
47+
let wolfssl_dir = Path::new(&include_dir).join("wolfssl");
48+
if !wolfssl_dir.is_dir() {
49+
println!("cargo:warning=WOLFSSL_PREFIX is set but {} does not exist", wolfssl_dir.display());
50+
return Ok(None);
51+
}
52+
Ok(Some(include_dir))
53+
} else {
54+
let base = wolfssl_repo_base_dir()?;
55+
let base_path = Path::new(&base);
56+
// Treat this as an in-tree wolfSSL repo only if the expected layout exists.
57+
let wolfssl_dir = base_path.join("wolfssl");
58+
let wolfssl_options = wolfssl_dir.join("options.h");
59+
if wolfssl_options.is_file() {
60+
Ok(Some(base))
61+
} else {
62+
Ok(None)
63+
}
64+
}
65+
}
66+
67+
/// Returns the library directory for libwolfssl.
68+
///
69+
/// If `WOLFSSL_PREFIX` is set, returns `{WOLFSSL_PREFIX}/lib`.
70+
/// Otherwise falls back to the in-tree build output directory if it exists.
71+
fn wolfssl_lib_dir() -> Result<Option<String>> {
72+
if let Ok(prefix) = env::var("WOLFSSL_PREFIX") {
73+
Ok(Some(format!("{}/lib", prefix)))
74+
} else {
75+
let repo_lib_dir = wolfssl_repo_lib_dir()?;
76+
if Path::new(&repo_lib_dir).exists() {
77+
Ok(Some(repo_lib_dir))
78+
} else {
79+
Ok(None)
80+
}
81+
}
3882
}
3983

4084
fn bindings_path() -> String {
4185
PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs").display().to_string()
4286
}
4387

44-
/// Generate Rust bindings for the wolfssl C library using bindgen.
88+
/// Map a Rust target triple to the equivalent clang target triple.
4589
///
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
90+
/// Rust triples embed ISA extensions in the arch component
91+
/// (e.g. `riscv64imac-unknown-none-elf`) while clang uses only the base arch
92+
/// (e.g. `riscv64-unknown-elf`). Bare-metal targets use `<arch>-<vendor>-elf`
93+
/// in clang convention.
94+
fn rust_target_to_clang_target(rust_target: &str) -> String {
95+
let parts: Vec<&str> = rust_target.splitn(4, '-').collect();
96+
if parts.len() < 3 {
97+
return rust_target.to_string();
98+
}
99+
100+
// Strip ISA extensions: riscv64imac → riscv64, riscv32imac → riscv32
101+
let arch = if parts[0].starts_with("riscv64") {
102+
"riscv64"
103+
} else if parts[0].starts_with("riscv32") {
104+
"riscv32"
105+
} else {
106+
parts[0]
107+
};
108+
109+
let vendor = parts[1];
110+
let os = parts[2];
111+
let abi = parts.get(3).copied().unwrap_or("");
112+
113+
// Bare-metal: (os=none, abi=elf) → <arch>-<vendor>-elf
114+
if os == "none" && abi == "elf" {
115+
format!("{}-{}-elf", arch, vendor)
116+
} else if abi.is_empty() {
117+
format!("{}-{}-{}", arch, vendor, os)
118+
} else {
119+
format!("{}-{}-{}-{}", arch, vendor, os, abi)
120+
}
121+
}
122+
123+
/// Return the sysroot path for a bare-metal clang target triple, if it exists.
124+
///
125+
/// Queries the cross-compiler for its sysroot via `--print-sysroot` rather
126+
/// than assuming a fixed install prefix. Tries the candidate compiler names
127+
/// `<arch>-<vendor>-elf-gcc` and `<arch>-elf-gcc` (vendor omitted) in order.
128+
/// Returns `None` if no suitable compiler is found or its sysroot is invalid.
129+
fn bare_metal_sysroot(clang_target: &str) -> Option<String> {
130+
let parts: Vec<&str> = clang_target.splitn(3, '-').collect();
131+
if parts.len() < 3 || !clang_target.ends_with("-elf") {
132+
return None;
133+
}
134+
let (arch, vendor) = (parts[0], parts[1]);
135+
let candidates = [
136+
format!("{}-{}-elf-gcc", arch, vendor),
137+
format!("{}-elf-gcc", arch),
138+
];
139+
for compiler in &candidates {
140+
if let Ok(output) = std::process::Command::new(compiler)
141+
.arg("--print-sysroot")
142+
.output()
143+
&& output.status.success() {
144+
let sysroot = String::from_utf8_lossy(&output.stdout).trim().to_string();
145+
if !sysroot.is_empty() && sysroot != "/" && Path::new(&sysroot).exists() {
146+
return Some(sysroot);
147+
}
148+
}
149+
}
150+
None
151+
}
152+
153+
/// Generate Rust bindings for the wolfssl C library using bindgen.
51154
///
52155
/// Returns `Ok(())` if successful, or an error if binding generation fails.
53156
fn generate_bindings() -> Result<()> {
54-
let bindings = bindgen::Builder::default()
157+
let mut builder = bindgen::Builder::default()
55158
.header("headers.h")
56-
.clang_arg(format!("-I{}", wolfssl_base_dir()?))
57159
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
58-
.use_core()
160+
.use_core();
161+
162+
if let Some(include_dir) = wolfssl_include_dir()? {
163+
builder = builder.clang_arg(format!("-I{}", include_dir));
164+
}
165+
166+
// When cross-compiling, tell clang the target so it generates correct
167+
// type layouts and evaluates architecture-specific preprocessor guards.
168+
let target = env::var("TARGET").unwrap();
169+
let host = env::var("HOST").unwrap();
170+
if target != host {
171+
let clang_target = rust_target_to_clang_target(&target);
172+
builder = builder.clang_arg(format!("--target={}", clang_target));
173+
174+
if target.ends_with("-none-elf") {
175+
// For bare-metal targets, add the toolchain C runtime headers
176+
// (newlib's time.h etc.) using -idirafter so they appear after
177+
// clang's own built-in includes. This lets clang's stdatomic.h
178+
// take priority over newlib's incompatible version.
179+
if let Some(sysroot) = bare_metal_sysroot(&clang_target) {
180+
builder = builder
181+
.clang_arg("-ffreestanding")
182+
.clang_arg(format!("-idirafter{}/include", sysroot));
183+
}
184+
}
185+
}
186+
187+
let bindings = builder
59188
.generate()
60189
.map_err(|_| io::Error::other("Failed to generate bindings"))?;
61190

@@ -147,18 +276,25 @@ fn generate_fips_aliases() -> Result<()> {
147276
///
148277
/// Returns `Ok(())` if successful, or an error if any step fails.
149278
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()?);
279+
if let Some(lib_dir) = wolfssl_lib_dir()? {
280+
println!("cargo:rustc-link-search={}", lib_dir);
281+
282+
// Prefer a shared library if present, otherwise fall back to static.
283+
let has_shared = Path::new(&lib_dir).join("libwolfssl.so").exists()
284+
|| Path::new(&lib_dir).join("libwolfssl.dylib").exists();
285+
if has_shared {
286+
println!("cargo:rustc-link-lib=wolfssl");
287+
// Only set rpath where a dynamic linker exists (not bare-metal).
288+
let target = env::var("TARGET").unwrap();
289+
if !target.ends_with("-none-elf") {
290+
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir);
291+
}
292+
} else {
293+
println!("cargo:rustc-link-lib=static=wolfssl");
294+
}
295+
} else {
296+
// No local lib dir found; rely on whatever is installed system-wide.
297+
println!("cargo:rustc-link-lib=wolfssl");
162298
}
163299

164300
Ok(())

0 commit comments

Comments
 (0)