本文主要介绍在Mac平台环境下进行的交叉编译技术,覆盖了对PC端Windows、Linux系统的编译支持,以及针对移动端Android、iOS、鸿蒙系统的支持。此外,还列出在交叉编译过程中可能遇到的一些常见问题及其解决方案。
1. PC端
PC端包括x86_64的Window、Linux平台
1.1 交叉编译工具链安装
| 目标平台 | 安装target | 安装工具链 |
|---|---|---|
| window | rustup target add x86_64-pc-windows-gnu | brew install mingw-w64 |
| linux | rustup target add x86_64-unknown-linux-gnu | brew install SergioBenitez/osxct/x86_64-unknown-linux-gnu |
1.2 配置下交叉编译工具
在文件 ~/.cargo/config中配置交叉编译工具链
[target.x86_64-unknown-linux-gnu]
ar = "x86_64-unknown-linux-gnu-gcc-ar"
linker = "x86_64-unknown-linux-gnu-gcc"
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-gcc-ar"
linker 选项告诉 Rust 编译器使用哪一个链接器来生成最终的可执行文件
ar 选项指定用于创建静态库的工具
1.3 编译不同平台产物
执行命令:cargo build --relese --target xxx
比如编译 x86_64 Linux平台,cargo build --relese --target x86_64-unknown-linux-gnu
2 移动端
移动端都有配套开发工具和工具链,也有一些开源的构建工具,详细参考另一篇文章:Rust移动端跨平台开发实践
3. 交叉编译错误
3.1 Target 没有安装
此类报错分两种情况,官方提供了标准库的crate,本地没有安装,只需按照报错提示安装即可。
error[E0463]: can't find crate for `core`
|
= note: the `x86_64-pc-windows-gnu` target may not be installed
= help: consider downloading the target with `rustup target add x86_64-pc-windows-gnu`
另外一种情况是官方没有提供标准库的crate,但是rustc支持的target(可通过查看rustup target list),例如armv7s-apple-ios, 此时可以通过如下命令本地编译标准库crate
cargo +nightly build -Z build-std=std --release --target=armv7s-apple-ios
3.2 依赖C、汇编源码
一般纯Rust的工程或者库比较方便交叉编译。但是不少三方库依赖了C源码,此时三方库通常会依赖 cc crate对C、汇编进行编译,如果没有没有合理设置编译器、创建静态库的工具会导致变异错误,比如下面的代码
// 被rust使用的C源码
#include <stdio.h>
int square(int x) {
return x * x;
}
// build.rs
fn main() {
println!("cargo:rerun-if-changed=square.c"); // 如果 C 代码改变,重新编译
println!("cargo:lib=square"); //
cc::Build::new()
.file("src/square.c") // 编译 C 文件
.compile("libsquare.a"); // 输出为静态库
}
// main.rs
extern "C" {
fn square(x: std::ffi::c_int) ->
std::ffi::c_int;
}
fn main() {
let num = 5;
let result = unsafe {
square(num)
};
println!("The square of {} is {}", num, 10);
}
此时如果直接执行
cargo build --release --target=aarch64-linux-android,会有如下报错,原因就是没有设置CC、AR
OPT_LEVEL = Some(3)
TARGET = Some(aarch64-linux-android)
HOST = Some(aarch64-apple-darwin)
cargo:rerun-if-env-changed=CC_aarch64-linux-android
CC_aarch64-linux-android = None
cargo:rerun-if-env-changed=CC_aarch64_linux_android
CC_aarch64_linux_android = None
cargo:rerun-if-env-changed=TARGET_CC
TARGET_CC = None
cargo:rerun-if-env-changed=CC
CC = None
cargo:rerun-if-env-changed=CC_ENABLE_DEBUG_OUTPUT
cargo:warning=Compiler family detection failed due to error: ToolNotFound: Failed to find tool. Is `aarch64-linux-android-clang` installed?
--- stderr
error occurred: Failed to find tool. Is `aarch64-linux-android-clang` installed?
改用如下命令可编译成功
CC=xxx/bin/aarch64-linux-android23-clang AR=xxx/bin/llvm-ar cargo build --target aarch64-linux-android --release
一般使用cargo ndk会避免这种情况,cargo ndk中已经完整设置了编译需要的环境和工具。
另外一种情况是设置了CC、AR也不能编译成功,比如ring已经不支持armv7s,此时可以考虑换库。
3.3 依赖动态库
一般一些binding库,例如rusqlite、rust-openssl,默认会动态链接对应的C库,此时直接编译一般会报错。
[dependencies]
rusqlite = "0.31.0"
比如上面这种依赖rusqlite的方式,直接使用cargo ndk编译会报错
= note: ld.lld: error: unable to find library -lsqlite3
clang: error: linker command failed with exit code 1 (use -v to see invocation)
此时有两种解决方法
- 编译或者下载对应的C库,在build.rs中指定动态链接库和路径
fn main() {
// 链接 libsqlite3.so
println!("cargo:rustc-link-lib=dylib=sqlite3");
// 指定库文件位置
println!("cargo:rustc-link-search=native=/xxx/jni/arm64-v8a");
}
- 一般此类binding库都会有feature支持源码编译为静态库使用。例如openssl的vendored,rusqlite的bundled,此时涉及静态库,动态库的优缺点,具体可自行评估使用。
openssl = { version = "0.10.64", features = ["vendored"] }
rusqlite = { version = "0.31.0", features = ["bundled"] }
4. 参考文档
Rustup: rust-lang.github.io/rustup/inde…
Rustc: doc.rust-lang.org/rustc/what-…
Rust-openssl: docs.rs/openssl/0.1…
rusqlite: docs.rs/rusqlite/la…