This is mainly a brain dump of my thoughts during my recent experiments. Time is still limited on my side due to job hunting, but I think it’s never too late to get the conversation going.
Motivation
Many of us probably would like to see Rust being used to develop native libraries for Android. There is already the jni-sys
crate and a wrapper around it, so I went ahead and recorded the roadblockers I’ve hit. These are:
- No way to compile for multiple NDK arches apart from multiple Cargo invocations
- NDK home is not automatically discovered (
ANDROID_NDK_HOME
is the canonical environment variable to look at AFAIK) - The toolchain sysroot can’t be automatically derived without using manually-exported standalone toolchains, in which case a
gcc
wrapper is needed -
RegisterNatives
is not wrapped in thejni
crate, so one must invoke the unsafe interface directly to register native methods if symbol names likeJava_Adder_add__II
are undesirable
Vision for first-class Android NDK integration
Overall from an Android developer’s perspective, one would expect the Rust build tools to integrate with the NDK, like when using CMake or ndk-build
. This integration could go in a Cargo subcommand and various support crates, instead of bloating rustc or cargo, but if things could be better streamlined it’s also OK.
There are several areas that need work for such integration:
- JNI wrapper crates:
- better ergonomics exporting functions
- native method announcement with
RegisterNatives
- Proposed Cargo subcommand (
cargo android
?) or Cargo itself:- Discovery of NDK home
- Configuration of NDK arches, toolchain types (gcc or clang) and sysroot API level
- Passing of said information to build scripts
-
gcc-rs
:- Deriving sysroot for linking with the information provided, in addition to the Android support present today
What does such an integrated workflow look like?
Ideally one just specifies the arches and API level they want, perhaps in a [target]
section in Cargo.toml
, or some other file like .cargo-android.toml
:
[ndk]
# host defaults to the native platform of the build tool, or you can override it if at all necessary:
# host = "linux-x86_64"
# platform API level
api-level = 16
# toolchain to use
toolchain = "gcc-4.9"
[target]
# NDK arches to compile for
# MIPS Android targets are TODO
arch = ["armeabi", "armeabi-v7a", "x86", "x86_64"]
Then write JNI wrappers and export them:
#![feature(proc_macro)]
extern crate jni;
extern crate jni_macros;
use jni::JNIEnv;
use jni::objects::JClass;
use jni::sys::jint;
use jni_macros::jni_export;
#[jni_export("com.example.Test.testMethod", "(II)I"]
fn test_method(_: JNIEnv, _: JClass, x: jint, y: jint) -> jint {
x + y
}
After that one simply cargo android ndk-build
to have all arches built, ready for use. Ideally the whole process could be driven from Gradle side, but let’s wait for Rust’s IDE support to mature before trying that.
I have implemented a simple jni_export
macro in my jnisupport crate (not published yet; a name change is of course needed ). But the rest is still very much TODO and have plenty of room for improvement. Hence the thread!