DEV Community

Hasan Yousef
Hasan Yousef

Posted on

Build Android app with rust

  1. Install rust
  2. Add $ANDROID_HOME, $NDK_HOME and $JAVA_HOME
Hasans-Air:~ h_ajsf$ sudo code $HOME/.bash_profile
// Write below 
PATH=$PATH:/usr/local/Cellar/flutter/bin
PATH=$PATH:/Users/$USER/.cargo/bin
PATH=$PATH:${ANDROID_HOME}/tools/

export JAVA_HOME=$(/usr/libexec/java_home)
export ANDROID_HOME=/usr/local/share/android-sdk
export NDK_HOME=$ANDROID_HOME/ndk-bundle
// save and Exit
// Confirm changes
Hasans-Air:~ h_ajsf$ source $HOME/.bash_profile
  1. Add Android targets:
Hasans-Air:ndk h_ajsf$ rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android arm-linux-androideabi

For iOS, below targets are require:

Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios
  1. Create rust lib
Hasans-Air:greetings h_ajsf$ cargo new rust_lib_for_android --lib
     Created binary (application) `cargo` project
Hasans-Air:greetings h_ajsf$ cd rust_lib_for_android
  1. Create chain tools Using rustup, like:
Hasans-MacBook-Air:~ h_ajsf$ rustup target add x86_64-linux-android
Hasans-MacBook-Air:~ h_ajsf$ rustup toolchain install stable-x86_64-linux-android
Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ rustup override set stable-x86_64-linux-android

see (here)[https://forge.rust-lang.org/platform-support.html] for supported platforms.
Or, using Android NDK, as not all Tier 2 platforms are working fine with rustup tolchain, NDK can be used as:

usage: make_standalone_toolchain.py [-h] --arch {arm,arm64,x86,x86_64}
                                    [--api API] [--stl STL] [--force] [-v]
                                    [--package-dir PACKAGE_DIR | --install-dir INSTALL_DIR]

Details commands are below:

Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ ${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch x86_64 --install-dir NDK/x86_64
Hasans-Air:rust_lib_for_android h_ajsf$ ${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch arm64 --install-dir NDK/arm64
Hasans-Air:rust_lib_for_android h_ajsf$ ${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch arm --install-dir NDK/arm
Hasans-Air:rust_lib_for_android h_ajsf$ ${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch x86 --install-dir NDK/x86

For iOS,cargo-lipo creates a universal library, and can be installed as below:

Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo install cargo-lipo
  1. Create /.cargo/config file
Hasans-Air:rust_lib_for_android h_ajsf$ mkdir .cargo
Hasans-Air:rust_lib_for_android h_ajsf$ cd .cargo
Hasans-Air:.cargo h_ajsf$ touch config
  1. Open the greetings folder in your favorite IFE, I'm using VS Code
Hasans-Air:.cargo h_ajsf$ cd ..
Hasans-Air:rust_lib_for_android h_ajsf$ cd ..
Hasans-Air:documents h_ajsf$ ls
rust_lib_for_android
Hasans-Air:documents h_ajsf$ code rust_app
  1. Add the below to the /.cargo/config file
[target.x86_64-linux-android]
ar = "NDK/x86/bin/x86_64-linux-android-ar"
linker = "NDK/x86_64/bin/x86_64-linux-android-clang"

[target.aarch64-linux-android]
ar = "NDK/arm64/bin/aarch64-linux-android-ar"
linker = "NDK/arm64/bin/aarch64-linux-android-clang"

[target.armv7-linux-androideabi]
ar = "NDK/arm/bin/arm-linux-androideabi-ar"
linker = "NDK/arm/bin/arm-linux-androideabi-clang"

[target.i686-linux-android]
ar = "NDK/x86/bin/i686-linux-android-ar"
linker = "NDK/x86/bin/i686-linux-android-clang"
  1. Replace the content of the src/lib.rs by the below:
use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

#[no_mangle]
pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {
    let c_str = unsafe { CStr::from_ptr(to) };
    let recipient = match c_str.to_str() {
        Err(_) => "there",
        Ok(string) => string,
    };

    CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()
}

/// Expose the JNI interface for android below
#[cfg(target_os="android")]
#[allow(non_snake_case)]
pub mod android {
    extern crate jni;

    use super::*;
    use self::jni::JNIEnv;
    use self::jni::objects::{JClass, JString};
    use self::jni::sys::{jstring};

    #[no_mangle]
    pub unsafe extern fn Java_com_mozilla_greetings_RustGreetings_greeting(env: JNIEnv, _: JClass, java_pattern: JString) -> jstring {
        // Our Java companion code might pass-in "world" as a string, hence the name.
        let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
        // Retake pointer so that we can use it below and allow memory to be freed when it goes out of scope.
        let world_ptr = CString::from_raw(world);
        let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");

        output.into_inner()
    }
}
  1. Add the below to the Cargo.toml
[target.'cfg(target_os="android")'.dependencies]
jni = { version = "0.5", default-features = false }

[lib]
name = "greetings"
crate-type = ["dylib"]
  1. Build the static library for each target
Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo build --target x86_64-linux-android --release
Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo build --target aarch64-linux-android --release
Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo build --target armv7-linux-androideabi --release
Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo build --target i686-linux-android --release

Note aarch64 can be used for both arm64 and arm64-v8a

For iOS, run cargo lipo --release

Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo lipo --release
Due to a known rustc issue, cargo-lipo can only be run on macOS. See https://github.com/rust-lang/rust/issues/36156#issuecomment-373201676 for more info.
   Compiling cargo v0.1.0 (/Users/h_ajsf/Documents/greetings/rust_lib_for_android)
warning: dropping unsupported crate type `dylib` for target `aarch64-apple-ios`                                                                                    

warning: dropping unsupported crate type `cdylib` for target `aarch64-apple-ios`                                                                                   

    Finished release [optimized] target(s) in 5.90s                                                                                                                
   Compiling cargo v0.1.0 (/Users/h_ajsf/Documents/greetings/rust_lib_for_android)
warning: dropping unsupported crate type `dylib` for target `armv7-apple-ios`                                                                                      

warning: dropping unsupported crate type `cdylib` for target `armv7-apple-ios`                                                                                     

    Finished release [optimized] target(s) in 0.98s                                                                                                                
   Compiling cargo v0.1.0 (/Users/h_ajsf/Documents/greetings/rust_lib_for_android)
warning: dropping unsupported crate type `dylib` for target `i386-apple-ios`                                                                                       

warning: dropping unsupported crate type `cdylib` for target `i386-apple-ios`                                                                                      

    Finished release [optimized] target(s) in 1.56s                                                                                                                
   Compiling cargo v0.1.0 (/Users/h_ajsf/Documents/greetings/rust_lib_for_android)
warning: dropping unsupported crate type `dylib` for target `x86_64-apple-ios`                                                                                     

warning: dropping unsupported crate type `cdylib` for target `x86_64-apple-ios`                                                                                    

    Finished release [optimized] target(s) in 0.88s 

And the universal iOS library can be found in cargo/target/universal/release/libgreetings.a

  1. For iOS:
  2. Add the greetings.h file, by: File\Add files to "Greetings"...
  3. Add native file libgreetings.a and the native interactive framework libresolv.tbd by General -> Linked Frameworks and Libraries
  4. Create bridging header Greetings-Bridging-Header.h by File\New\File.... Header File and import the greetings.h file into it so the file became as below:
#ifndef Greetings_Bridging_Header_h
#define Greetings_Bridging_Header_h

#import "greetings.h"

#endif
  • Update the Build Settings -> Objective-C Bridging Header by adding the path of Greetings-Bridging-Header.h file
  • Update the Build Settings -> Library Search Paths by adding the path of imported libgreetings.a file
  • Create RustGreetings swift file by File\New\File... and iOS\Source\Swift File, and add to it the:
class RustGreetings {
    func sayHello(to: String) -> String {
        let result = rust_greeting(to)
        let swift_result = String(cString: result!)
        rust_greeting_free(UnsafeMutablePointer(mutating: result))
        return swift_result
    }
}
  • Update the viewDidLoad in the ViewController.swift file, by adding:
let rustGreetings = RustGreetings()
print("\(rustGreetings.sayHello(to: "world"))")

Discussion (2)

Collapse
ondrejs profile image
Ondrej

Wow, I have never thought about building Android app in Rust, nice to see it's possible (despite the fact that I am not so good at coding in Rust as in other languages). Will probably experiment with it in the future. Thanks!

Collapse
jeikabu profile image
jeikabu

I saw that go(Lang) had the option to build for Android (and iOS) and with rust supporting wasm I meant to look into it. You verified it for me.
Was that "platform support" page the only resource you used?
Also, what's that lipo crate for? Is that the only way to make a universal binary because the tool chains can't do it themselves?