Multirust - Use It

This post started life as an exploration in cross-compiling, but at this point it’s become more of an intro to using multirust-rs and cargo which simplify things considerably. So, we’ll get to the fun Android parts, but for now stick with me.

The first thing to do if you haven’t installed multirust is to go get that done (multirust-rs), see you back here in a minute.

.

.

.

Go ahead and start running multirust update. Depending on your connection speed this step may take a while. This will download the stable, beta, and nightly compilers from http://static.rust-lang.org/dist. We’ll need to keep this URL in mind for a bit as we’ll see it pop up again later.

The next thing we’ll want to do is setup our default toolchain multirust default stable. I choose to use stable by default as that seems like a decent default. Might as well stick with the blessed compiler version (We’ll be adding an override later because we will need nightly for cross-compiling android).

So, now that we have that out of the way, we can move on to running something.

New Crate

$ cargo new --bin android-test
$ cd android-test

Here we’ve set up our new executable crate that we’re targetting for android, but honestly, who wants to develop all their logic on an emulator or phone hardware fiddling with adb the whole time. I know I don’t. IDE integration would make this less of a pain, but things are still much slower to test and cycle times are directly linked to productivity, so let’s be efficient.

At this point you should be able to run cargo run and see a nice Hello world!. Beautiful!

Setting up the Cross-Compiler

I’m going to cheat a bit here, and only compile for ARM versions of android as opposed to atom x86, x64 versions. But hey, the majority of devices are ARM, Servo doesn’t even support this yet, and I don’t have an atom device to test on. Maybe I’ll update this later if I end up testing this further…

Onward!

It’s lucky for us that rust is build on top of LLVM, which is a cross-compiler by default. This will make things fairly smooth, but we haven’t needed to specify which architecture we’re compiling for. cargo build/run/test by default will pick your current architecture as the target.

cargo build is equivalent to cargo build --target=x86_64-unknown-linux-gnu on my machine. Your experience may vary.

INFO - arm-linux-androideabi is the target for android ARM devices.

You may be thinking that you can just cargo build --target=arm-linux-androideabi and away you go, but hold your horses. It would be awesome if that worked, it’s not quite that smooth.

$ cargo build --target=arm-linux-androideabi --verbose

Well, that gives us a nice error message.

Process didn't exit successfully: `rustc - --crate-name _ --crate-type dylib --crate-type bin --print=file-names --target arm-linux-androideabi` (exit code: 101)
--- stderr
error: Error loading target specification: Could not find specification for target "arm-linux-androideabi"

We need a “specification for target ‘arm-linux-androideabi’”. Where can we get one of those? After consulting the very friendly experts on the #rust IRC channel I found out that it meant we needed a stdlib compiled to target arm-linux-androideabi. Well, we could build the rust standard library for arm-linux-androideabi with

$ ./configure --target=arm-unknown-linux-gnueabihf,x86_64-unknown-linux-gnu
$ make -j$(nproc)
$ sudo make install

But that would take ~1 hr of compilation time… I lack that amount of patience… More details here if you’re trying to kill some time or heat your house or something.

Remember when I mentioned http://static.rust-lang.org/dist earlier? Well, a bit of sleuthing there and you’ll find that each dated folder not only contains rust tarballs for your desktop architecture, but the kindly build gnomes have produced android std libs as well! Conveniently, exactly the thing we need.

There is one caveat as of this writing that the android builds seem to be nightlies only. But that’s okay. As long as we match our compiler version with the android std version we’ll be fine.

I’ve picked the date 2015-12-14 to base my toolchain, but mostly likely you’ll use whatever the newest nightly build is at the time you do this.

NOTE: Not all nightlies are built equally… While updating this post, I tried 2016-01-05 which failed to compile rustc-serialize. Buyer beware!

UPDATE: rustc-serialize just needs to be updated to the latest version to work with the nightlies. Thanks go to @1011XX for the correction. Enjoy working with the latest and greatest!

Without further ado, multirust makes this a simple thing to set up.

$ multirust override nightly-2015-12-14 

Now download http://static.rust-lang.org/dist/2015-12-14/rust-std-nightly-arm-linux-androideabi.tar.gz and unzip it. You’ll find a folder rust-std-nightly-arm-linux-androideabi/rust-std-arm-linux-androideabi/lib/rustlib/arm-linux-androideabi which you can copy to ~/.multirust/toolchains/nightly-2015-12-14/lib/rustlib

Congrats! You now have a cross-compiler!

Linker Error!

Let’s try that compilation again for android.

$ cargo build --target=arm-linux-androideabi --verbose

Compiling android-test v0.1.0 (file:///mnt/storage/dev/android-test)
    Running `rustc src/main.rs --crate-name android_test --crate-type bin -g --out-dir /mnt/storage/dev/android-test/target/arm-linux-androideabi/debug --emit=dep-info,link --target arm-linux-androideabi -L dependency=/mnt/storage/dev/android-test/target/arm-linux-androideabi/debug -L dependency=/mnt/storage/dev/android-test/target/arm-linux-androideabi/debug/deps`
error: linking with `cc` failed: exit code: 1
...

So, our compiler worked, but now our linker is making things difficult… Luckily there’s android-glue which contains apk-builder which we will substitute for our linker.

# Create the .cargo/config file
$ mkdir -p .cargo
$ echo '[target.arm-linux-androideabi]
linker = "apk-builder/apk-builder/target/release/apk-builder"' > .cargo/config

# Add the apk-builder
$ git clone https://github.com/tomaka/android-rs-glue apk-builder
$ cd apk-builder/apk-builder
$ cargo update
$ cargo build --release
$ cd ../..

apk-builder depends upon the Android SDK and NDK, so we’ll need those as well.

Setting up your Android Toolchain (See Servo Wiki)

First, we need some android development tools like the NDK, SDK, and toolchain, for cross-compiling:

  • The Android NDK can be downloaded from http://developer.android.com/ndk/downloads/index.html
  • The Android SDK can be downloaded from http://developer.android.com/sdk/index.html

To install the NDK and SDK, refer to the guidelines on the website. When you install the SDK, run the UI tool at tools/android and update the SDK in order to install a full set of Android libraries. You may use the Android-18 platform. From the commandline, you may also do tools/android - update sdk --no-ui.

First, create an Android toolchain from the NDK. Example command to setup standalone Android tool chain: 'ndk root'/build/tools/make-standalone-toolchain.sh --platform="android-18" --toolchain=arm-linux-androideabi-4.8 --install-dir='dir to install' --ndk-dir='ndk dir' --arch=arm

Set the following environment variables while building. (You may want to export them from a configuration file like .bashrc.)

ANDROID_SDK="/path/to/sdk"
ANDROID_NDK="/path/to/ndk"
ANDROID_TOOLCHAIN="/path/to/toolchain"
PATH="$PATH:$ANDROID_TOOLCHAIN/bin" # add the toolchain to your $PATH

If you’re on Debian (not Ubuntu), you may need to install the packages below.

sudo apt-get install curl ia32-libs ant

If you’re on Ubuntu, you may need to do:

sudo apt-get install curl libc6:i386 ant lib32z1 openjdk-7-jdk

If you’re on OSX, install these packages:

brew install nspr ant

Cross-Compiling for the Android EABI

NOTE: I’ve added the ANDROID_* variables to my .bashrc, so the following commands assume you’ve done the same.

We are ready! Here we go!

$ ANDROID_HOME=$ANDROID_SDK NDK_HOME=$ANDROID_NDK NDK_STANDALONE=$ANDROID_TOOLCHAIN cargo build --target=arm-linux-androideabi
   Compiling android-test v0.1.0 (file:///mnt/storage/dev/android-test)

Kind of anti-climactic…

Testing If It Actually Worked

So, we have an apk, but it doesn’t actually do anything… So, in the tradition of typical programming examples we will be writing “Hello world!”.

First, add a dependency to android_glue in Cargo.toml:

[target.arm-linux-androideabi.dependencies.android_glue]
version = "^0.1"

Then, add extern crate android_glue and invoke android_start! in your main crate.

#[cfg(target_os = "android")]
#[macro_use] 
extern crate android_glue;

#[cfg(target_os = "android")]
android_start!(main);

fn main() {
    println!("Hello world from Rust!");
}

We’ll recompile, and deploy to an emulator:

$ ANDROID_HOME=$ANDROID_SDK NDK_HOME=$ANDROID_NDK NDK_STANDALONE=$ANDROID_TOOLCHAIN cargo build --target=arm-linux-androideabi --release

# Need the .apk extension or adb will complain
$ cd target/arm-linux-androideabi/release
$ mv android-test android-test.apk
$ adb install -r android-test.apk

# ~~~ Run the app by launching in the emulator here ~~~

$ adb logcat

You should see a few lines similar to:

01-06 11:14:56.321  2096  2111 D RustAndroidGlueStdouterr: Entering android_main
01-06 11:14:56.324  2096  2111 D RustAndroidGlueStdouterr: created application thread
01-06 11:14:56.329  2096  2112 D RustAndroidGlueStdouterr: Hello world from Rust!

It’s Working!!!!!

Thanks for sticking with me this far because this got much longer than intended. I may continue investigating further, but without any immediate use for this work it might hang out there a while. We shall see.

As a side note, I’m really enjoying exploring Rust. The community is great, and for a first foray into systems programming coming from a .Net/C# focussed day job it has been remarkably smooth. I recommend taking the leap for anyone who has the inclination. Farewell for now.