Build Android app with rust
Published: 2018-12-15 18:25:50


1. Install rust
2. Add $ANDROID_HOME, $NDK_HOME and $JAVA_HOME
```bash
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
```
2. Add Android targets:
```bash
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:
```bash
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
```
3. Create rust lib
```bash
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
```
4. Create chain tools
Using rustup, like:
```bash
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:
```bash
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:
```bash
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:
```bash
Hasans-MacBook-Air:rust_lib_for_android h_ajsf$ cargo install cargo-lipo
```
5. Create /.cargo/config file
```bash
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
```
6. Open the greetings folder in your favorite IFE, I'm using VS Code 
```bash
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
```
7. Add the below to the /.cargo/config file
```config
[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"
```

8. Replace the content of the src/lib.rs by the below:
```rust
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()
    }
}
``` 
9. Add the below to the Cargo.toml
```toml
[target.'cfg(target_os="android")'.dependencies]
jni = { version = "0.5", default-features = false }

[lib]
name = "greetings"
crate-type = ["dylib"]
```
10. Build the static library for each target
```bash
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`
```bash
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`
11. For iOS:
- Add the `greetings.h` file, by: `File\Add files to "Greetings"...`
- Add native file `libgreetings.a` and the native interactive framework `libresolv.tbd` by `General -> Linked Frameworks and Libraries`
- 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:
```header
#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:
```swift
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:
```swift
let rustGreetings = RustGreetings()
print("\(rustGreetings.sayHello(to: "world"))")
```