Rust交叉编译实践及常见问题整理

1,549 阅读4分钟

本文主要介绍在Mac平台环境下进行的交叉编译技术,覆盖了对PC端Windows、Linux系统的编译支持,以及针对移动端Android、iOS、鸿蒙系统的支持。此外,还列出在交叉编译过程中可能遇到的一些常见问题及其解决方案。

1. PC端

PC端包括x86_64的Window、Linux平台

1.1 交叉编译工具链安装

目标平台安装target安装工具链
windowrustup target add x86_64-pc-windows-gnubrew install mingw-w64
linuxrustup target add x86_64-unknown-linux-gnubrew 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)

此时有两种解决方法

  1. 编译或者下载对应的C库,在build.rs中指定动态链接库和路径
fn main() {
   // 链接 libsqlite3.so
   println!("cargo:rustc-link-lib=dylib=sqlite3"); 
   // 指定库文件位置
   println!("cargo:rustc-link-search=native=/xxx/jni/arm64-v8a"); 
}
  1. 一般此类binding库都会有feature支持源码编译为静态库使用。例如openssl的vendored,rusqlite的bundled,此时涉及静态库,动态库的优缺点,具体可自行评估使用。
openssl = { version = "0.10.64", features = ["vendored"] }
rusqlite = { version = "0.31.0", features = ["bundled"] }

4. 参考文档

CC: docs.rs/cc/latest/c…

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…