Building a shared library for android

I'm attempting to build a shared library for android, however I haven't found a single definitive source saying exactly what I need to do. I've been piecing together a build script but... it's not going well. What I'm trying to do is ultimately rather simple - I have 2 static libraries (opus and speexdsp). I import both of these into rust and then build a shared library with rust (which exports exactly the symbols I need from opus and speexdsp, slightly modified to suit my needs).

I have this working for windows and linux, but unfortunately I know next to nothing about building native code for Android! So far I have:

# Build opus
cd $OPUS
echo "APP_ABI := armeabi-v7a, x86" > Application.mk
cd $NDK
./ndk-build -C $OPUS NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk APP_BUILD_SCRIPT=Android.mk

I do this for both opus and speexdsp, this produces me the static inputs to rust. My rust file (which works on windows and linux, so I'm fairly confident this isn't wrong) looks a bit like this:

extern "C" {
    pub fn opus_encoder_create(Fs: i32, channels: i32, application: i32, error: *mut i32) -> *mut OpusEncoder;
}

#[no_mangle]
pub extern "C" fn do_stuff(samplingRate: i32, channels: i32, application: i32, error: mut i32) -> *mut ::OpusEncoder {
    unsafe {
        return ::opus_encoder_create(samplingRate, channels, application, error);
    }
}

Finally I invoke cargo on this rust project. This is the bit that's going wrong. My script for this bit:

python $NDK/build/tools/make_standalone_toolchain.py --arch x86 --api 18 --install-dir x86_TOOLCHAIN
export ANDROID_NDK=$NDK
export ANDROID_SDK=$SDK
export ANDROID_TOOLCHAIN=x86_TOOLCHAIN
export PATH=$PATH:$ANDROID_TOOLCHAIN/bin
cargo clean
ANDROID_HOME=$ANDROID_SDK NDK_HOME=$ANDROID_NDK NDK_STANDALONE=$ANDROID_TOOLCHAIN cargo build --target=i686-linux-android

This is largely based on this guide from the servo wiki. This dies completely with a massive linker error:

error: linking with `cc` failed: exit code: 1
note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-Wl,--allow-multiple-definition" "-L" "/home/martin/.multirust/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-linux-android/lib" "/home/martin/Desktop/DissonanceNative/target/i686-linux-android/debug/DissonanceNative.0.o" "-o" "/home/martin/Desktop/DissonanceNative/target/i686-linux-android/debug/libDissonanceNative.so" "/home/martin/Desktop/DissonanceNative/target/i686-linux-android/debug/DissonanceNative.metadata.o" "-nodefaultlibs" "-L" "/home/martin/Desktop/DissonanceNative/target/i686-linux-android/debug" "-L" "/home/martin/Desktop/DissonanceNative/target/i686-linux-android/debug/deps" "-L" "/home/martin/Desktop/DissonanceNative/links/android/x86" "-L" "/home/martin/.multirust/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-linux-android/lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "-l" "opus" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "-l" "speexdsp" "-Wl,--no-whole-archive" "-Wl,-Bdynamic" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/libstd-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/libpanic_unwind-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/libunwind-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/librand-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/libcollections-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/librustc_unicode-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/liballoc-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/liballoc_system-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/liblibc-39b92f95.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.fV3f6epx1dY2/libcore-39b92f95.rlib" "-Wl,--no-whole-archive" "-l" "dl" "-l" "log" "-l" "gcc" "-l" "gcc" "-l" "c" "-l" "m" "-shared" "-l" "compiler-rt"
note: /usr/bin/ld: skipping incompatible /home/martin/Desktop/DissonanceNative/links/android/x86/libopus.a when searching for -lopus
/usr/bin/ld: cannot find -lopus
/usr/bin/ld: skipping incompatible /home/martin/Desktop/DissonanceNative/links/android/x86/libspeexdsp.a when searching for -lspeexdsp
/usr/bin/ld: cannot find -lspeexdsp
/usr/bin/ld: cannot find -llog
collect2: error: ld returned 1 exit status

Notably, this completely skipped over the opus and speexdsp inputs because they're incompatible.

Does anyone have any advice or suggestions? Even just a single guide which walks through all the steps would be extremely helpful!

1 Like

A bit of an update. I figured out that the servo wiki was probably leading me down a bit of a dead end. Instead I explicitly set the linker in the .cargo/config file. This means my build script now looks like this:

cd $DISSONANCE_NATIVE
python $NDK/build/tools/make_standalone_toolchain.py --arch x86 --api 18 --install-dir x86_TOOLCHAIN
cargo build --release --target=i686-linux-android

And the same again, but for arm:

cd $DISSONANCE_NATIVE
python $NDK/build/tools/make_standalone_toolchain.py --arch arm --api 18 --install-dir ARM_TOOLCHAIN
cargo build --release --target=armv7-linux-androideabi

Finally, my config file for cargo:

[target.armv7-linux-androideabi]
linker = "ARM_TOOLCHAIN/bin/arm-linux-androideabi-gcc"

[target.i686-linux-android]
linker = "x86_TOOLCHAIN/bin/i686-linux-android-gcc"

Now this does actually build, yay! However I am not confident that this is entirely correct. for example I set ---arch arm for the NDK, but then use armv7-linux-androideabi (i.e. v7) for rust, does this mismatch matter?

2 Likes

The NDK's "arm" toolchain supports two different ABIs, named armeabi and armeabi-v7a. The Rust target triple armv7-linux-androideabi corresponds to the armeabi-v7a API. (There is not yet a built-in Rust target triple that corresponds to the armeabi API, but the long-term plan is to change the arm-linux-androideabi target to match this ABI.)

This means that you can build your Rust libraries with --target armv7-linux-androideabi and package them in the /lib/armeabi-v7a directory of your APK. This will support the majority of current Android devices. If you want to support additional architectures, you can include libraries built for other ABIs too.

1 Like

I don't actually build an APK myself, the output of all this is a .so file which I then give to Unity (the game engine) and Unity builds the final APK. I assume Unity does what you describe when it builds the final game for the Android platform.

Other than that, does all of the build process look sane?

Yes, I think so!

1 Like