Build Android app with rust

h_ajsf

Hasan Yousef

Posted on December 15, 2018

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"))")
💖 💪 🙅 🚩
h_ajsf
Hasan Yousef

Posted on December 15, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related