Rust 交叉编译 aarch64-linux-android

添加编译目标

首先明确一下,Rust 有个平台支持等级的概念,处于等级1的平台可以使用 rustup default [stable-x86_64-pc-windows-msvc] 设置为主机平台,其它等级的只能使用以下方式设置为编译目标:

rustup target add aarch64-linux-android

# rustup target add aarch64-linux-android
info: downloading component 'rust-std' for 'aarch64-linux-android'
info: installing component 'rust-std' for 'aarch64-linux-android'
 24.4 MiB /  24.4 MiB (100 %)  12.5 MiB/s in  1s ETA:  0s

查看工具链和编译目标:

rustup toolchain list
rustup target list

查询 Rust 支持的平台列表:
https://rust-lang.github.io/rustup-components-history/
https://doc.rust-lang.org/nightly/rustc/platform-support.html

指定 linker

交叉编译通常需要指定对应的编译器,否则 Rust 默认调用平台安装的 cc 编译器,这肯定不行。
在项目根路径下创建文件 .cargo/config,编写:

[build]
target = "aarch64-linux-android"
# 还可以指定链接器类型和静态链接
# 不过有点诡异的是,静态链接后使用file检测出来是动态链接的,ldd检测到的则是静态链接的
rustflags = ["-C", "linker-flavor=gcc", "-C", "target-feature=+crt-static"]
# rustflags = ["-C", "link-args=-fPIC"]

[target.aarch64-linux-android]
linker = "S:\\SDK\\ndk\\21.4.7075529\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\aarch64-linux-android21-clang.cmd"

然后就可以安心的执行编译啦:

cargo build

下载ndk

https://developer.android.com/ndk/downloads
历史版本:https://github.com/android/ndk/wiki/Unsupported-Downloads

https://dl.google.com/android/repository/android-ndk-r23c-windows.zip
https://dl.google.com/android/repository/android-ndk-r23c-linux.zip

https://dl.google.com/android/repository/android-ndk-r21e-windows-x86_64.zip

注意,如果你使用 r23c 及以上版本,那么 cargo build 将会出现以下错误,原因是 libgcc.a 已经被 libunwind.a 替代:

ld: error: unable to find library -lgcc

所以你最好回退到 r21e,或者直接把 libunwind.a 复制一份重命名为 libgcc.a,它的路径为:\android-ndk-r23c\toolchains\llvm\prebuilt\windows-x86_64\lib64\clang\12.0.9\lib\linux\aarch64

具体情况可以查阅:https://github.com/rust-lang/rust/pull/85806

链接静态库

(编译静态库似乎不需要指定 linker)
不考虑 ABI 兼容的情况下, 可以直接连接 .a 库:

target_link_libraries( # Specifies the target library.
        tcp2ws

        ${CMAKE_CURRENT_LIST_DIR}/rust/librust.a

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

通常需要指定构建 ABI :

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a'//, 'x86_64' // 我们开发测试时, 通常只指定一个 ABI, 否则构建会失败, 因为找不到对应平台下的符号
        }
    }
}

一般来说不应该这样的, 需要 add_library 或者 find_library 之类的.

# add_library( rust STATIC ${CMAKE_CURRENT_LIST_DIR}/rust/librust.a )
add_library( rust STATIC IMPORTED )
set_property( TARGET rust PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/rust/librust.a )
target_link_libraries( # Specifies the target library.
        tcp2ws
        rust
...

链接动态库

Rust 设置 crate-type 为 cdylib :

[lib]
crate-type = ["cdylib"]

执行构建:

RUSTC_BOOTSTRAP=1 cargo build --target "aarch64-linux-android" -Z unstable-options --out-dir .

在 Rust 项目根目录会产生一个 libxxx.so 文件.
现在如果我们直接 link 该文件, 是否可行呢?

target_link_libraries( # Specifies the target library.
        tcp2ws

        ${CMAKE_CURRENT_LIST_DIR}/rust/librust3.so # 编译可行, 但是没有将该文件打包, 运行时找不到库

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

运行报错示例:

    java.lang.UnsatisfiedLinkError: dlopen failed: library "librust.so" not found: needed by /data/app/~~grSmLKB5jcxADnomW4K_mA==/app.tcp2ws-0BqOKKAjf651SP5ZdDCZVg==/base.apk!/lib/arm64-v8a/libtcp2ws.so in namespace classloader-namespace

现在尝试使用 add_library( rust SHARED IMPORTED ) :

add_library( rust SHARED IMPORTED )
set_property( TARGET rust PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/rust/librust.so )

target_link_libraries( # Specifies the target library.
        tcp2ws

        rust # 打包成功, 但是 ld 路径错误

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

报错为:

    java.lang.UnsatisfiedLinkError: dlopen failed: library "Z:/AndroidProjs/Tcp2ws/app/src/main/cpp/rust/librust.so" not found: needed by /data/app/~~G_3hbDRuUS79sFqzgZrO8g==/app.tcp2ws-mFaM561z0q2YzhyFy2dCtw==/base.apk!/lib/arm64-v8a/libtcp2ws.so in namespace classloader-namespace

尝试使用 target_link_directories, 效果还是不太理想:

target_link_directories(tcp2ws PUBLIC ${CMAKE_CURRENT_LIST_DIR}/rust)
target_link_libraries( # Specifies the target library.
        tcp2ws

        rust
...

使用 IMPORTED_NO_SONAME 参数:

#link_directories(${CMAKE_CURRENT_LIST_DIR}/rust)
#add_library( rust SHARED IMPORTED )
add_library( rust SHARED IMPORTED GLOBAL)
set_property( TARGET rust PROPERTY IMPORTED_NO_SONAME 1 )
set_property( TARGET rust PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/rust/librust.so )
#set_target_properties( rust PROPERTIES IMPORTED_NO_SONAME 1 IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/rust/librust.so )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

#target_link_directories(tcp2ws PUBLIC ${CMAKE_CURRENT_LIST_DIR}/rust)

target_link_libraries( # Specifies the target library.
        tcp2ws

#        ${CMAKE_CURRENT_LIST_DIR}/rust/librust.so
        rust

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

注意:
cpp 构建目录为: \app\build\intermediates\cxx\Debug\{ID}\obj\arm64-v8a, 可能需要手动清理以重新构建 Apk, 但是清理之后又可能出现 so 未打包的情况, 似乎需要手动拷贝.

参考:
https://stackoverflow.com/questions/48611514/link-so-library-in-android-studio-project
https://stackoverflow.com/questions/8774593/cmake-link-to-external-library
IMPORTED_NO_SONAME: https://github.com/ARM-software/ComputeLibrary/issues/52

posted @ 2022-07-10 23:10  develon  阅读(3392)  评论(7编辑  收藏  举报