rust android 交叉编译 HumphreyDan 2022-03-03 2024-12-02 记录下使用 rust 绑定 libgit2 动态库进行 安卓交叉编译时的过程。
为 libgit2 生成 rust binding 如前所述,我们生成好了 libgit2 的安卓动态库,如果需要 rust 调用安卓动态库的话,不但手写很麻烦,还需要用到 unsafe, 可能会出现各种问题。 所以我们采用官方提供的 bindgen 来为我们自动生成 binding.
1、新建 cargo 项目,并将 libgit2 头文件复制进去
1 2 3 4 5 6 - deps - libgit2 - include - src build.rs Cargo.toml
2、添加 bindgen 依赖, 同时启用 build.rs
1 2 3 4 5 6 [package] ... build ="build.rs [build-dependencies] bindgen = " 0.59 .2
3、编辑 build.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 use bindgen;use std::env;use std::path::PathBuf;fn main () { let header_path = format! ("{}/deps/libgit2/include" , env::current_dir ().unwrap ()); let bindings = bindgen::Builder::default () .header ("deps/libgit2/include/git2.h" ) .clang_arg (format! ("-I{}" , git2_header_path)) .clang_arg (format! ("--sysroot={}/toolchains/llvm/prebuilt/darwin-x86_64/sysroot" , env::var ("ANDROID_NDK" ))) .clang_arg ("--target=armv7-linux-androideabi" ) .disable_header_comment () .generate_comments (false ) .parse_callbacks (Box ::new (bindgen::CargoCallbacks)) .generate () .expect ("Unable to generate bindings" ); let out_path = PathBuf::from (env::current_dir ().expect ("can't get current dir" ).join ("src" )); bindings .write_to_file (out_path.join ("lib.rs" )) .expect ("Couldn't write bindings!" ); println! ("cargo:warning=out:{:?}" , out_path.clone ().join ("bindings.rs" )); }
4、运行 cargo build 即可在 src 目录下生成 lib.rs
binding 文件了
用 rust 生成 jni 动态库 1、首先新建 rust 项目
cargo new --lib git2-sys
2、将编译好的 libgit2 安卓动态库复制到项目
1 2 3 4 5 6 -deps - libgit2 - include - lib - android-arm64 - android-x86_64
3、配置 rustup 安卓交叉编译工具链
可以使用 rustup target list 列出 rustup 当前支持的交叉编译工具链
rustup target add aarch64-linux-android x86_64-linux-android
4、 配置下 ar linker
在项目根目录新建 .cargo 目录, 配置文件为 config.toml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [target.aarch64-linux-android] ar = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android32-clang" linker = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android32-clang" [target.armv7-linux-androideabi] ar = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi32-clang" linker = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi32-clang" [target.i686-linux-android] ar = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android32-clang" linker = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android32-clang" [target.x86_64-linux-android] ar = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android32-clang" linker = "/Users/shuttle/Library/Android/sdk/ndk/24.0.7956693/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android32-clang"
5、配置项目生成动态库
1 2 3 4 [lib] name = "tano" crate-type = ["staticlib" , "cdylib" ]
6、开始编译
如果直接运行 cargo b --target x86_64-linux-android
会报错 unable to find library -lgcc
, 原因是 NDK r20+ 后移除了 gcc 库, 代替为 unwind 库。此时需要做特殊处理.
6.1 gcc 修复
1 2 3 4 - deps - gcc libgcc.a
新建 libgcc.a
文件,内容为 INPUT(-lunwind)
6.2 命令行编译
cargo rustc --target x86_64-linux-android -- -L /Users/shuttle/Project/Android/Ide/git2-sys/deps/gcc
此时可以通过 -L 来配置执行
6.3 build.rs 配置
如果不想每次都在命令行后指定 -L 参数,可以使用 build.rs 来编译
1 2 3 [package] ... build = "build.rs"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fn main () { let TARGET = env::var ("TARGET" ).unwrap (); let target = if TARGET.contains ("x86_64-linux-android" ) { "android_x86-64" } else if TARGET.contains ("aarch64-linux-android" ) { "android_arm64" } else { "" }; println! ("cargo:rustc-link-search=native=./deps/libgit2/lib/{}" , target); println! ("cargo:rustc-link-search=native=./deps/openssl/lib/{}" , target); println! ("cargo:rustc-link-lib=dylib=git2" ); println! ("cargo:rustc-link-lib=dylib=crypto" ); println! ("cargo:rustc-link-lib=dylib=ssl" ); println! ("cargo:rustc-flags=-L./deps/gcc" ); }
7.1、接下来执行 cargo b --target x86_64-linux-android
即可 7.2、也可以配置 Makefile.toml 使用 cargo-make 工具简化命令行运行输入
首先安装 cargo-make 工具, cargo install cargo-make
1 2 3 4 5 6 7 [tasks.a_arm64] command = "cargo" args = ["b" , "--target" , "aarch64-linux-android" ][tasks.a_x64] command = "cargo" args = ["b" , "--target" , "x86_64-linux-android" ]
然后执行 cargo make a_arm64
或者 makers a_arm64
即可
rust jni 代码(动态加载方式) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 use core::panic;use jni::{ descriptors::Desc, objects::{GlobalRef, JClass, JMethodID}, strings::JNIString, sys::{jclass, jint, JNI_VERSION_1_6}, JNIEnv, JavaVM, NativeMethod, }; use std::ffi::CString;use std::sync::Once;use std::{os::raw::c_void, panic::catch_unwind};const INVAL_JNI_VERSION: jint = 0 ;static INIT: Once = Once::new ();#[allow(non_snake_case)] #[no_mangle] pub extern "system" fn JNI_OnLoad (vm: JavaVM, _: *mut c_void) -> jint { let env = vm.get_env ().expect ("Cannot get reference to the JNIEnv" ); catch_unwind (|| { prepare (&env); JNI_VERSION_1_6 }) .unwrap_or (INVAL_JNI_VERSION) } pub (crate ) fn prepare (env: &JNIEnv) { INIT.call_once (|| unsafe { let host_jclass : GlobalRef = get_class (env, "io/shuttle/coder/MainActivity" ).unwrap (); let native_methods = [NativeMethod { name: "hello" .into (), sig: "()Ljava/lang/String;" .into (), fn_ptr: hello as *const c_void as *mut c_void, }]; env.register_native_methods (&host_jclass, &native_methods); }); } fn hello (env: JNIEnv, _: JClass) -> jclass { let output = env .new_string ("from rust string!!!" ) .expect ("Couldn't create java string!" ); output.into_inner () } fn get_class (env: &JNIEnv, class: &str ) -> Option <GlobalRef> { let class = env .find_class (class) .unwrap_or_else (|_| panic! ("Class not found: {}" , class)); Some (env.new_global_ref (class).unwrap ()) } fn get_method_id (env: &JNIEnv, class: &str , name: &str , sig: &str ) -> Option <JMethodID<'static >> { let method_id = env .get_method_id (class, name, sig) .map (|mid| mid.into_inner ().into ()) .unwrap_or_else (|_| { panic! ( "Method {} with signature {} of class {} not found" , name, sig, class ) }); Some (method_id) }
生成 kotlin 文件 jni 头文件的函数签名方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ➜ Coder ./gradlew -v ------------------------------------------------------------ Gradle 7.4 ------------------------------------------------------------ Build time: 2022-02-08 09:58:38 UTC Revision: f0d9291c04b90b59445041eaa75b2ee744162586 Kotlin: 1.5.31 Groovy: 3.0.9 Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021 JVM: 17.0.1 (Homebrew 17.0.1+0) OS: Mac OS X 12.2.1 x86_64
进入 build/tmp/kotlin-classes/debug/io/shuttle/coder
目录, 可以看到其下有两个 class 文件 MainActivity
和 MainActivityKt.class
执行 javap -s p MainActivity
就可以看到对应的 jni 函数签名了.