Android

Disclaimer: Currently, the following steps are tested and confirmed to work on Linux only.

In order to export to Android, we need to compile our Rust source for the appropriate targets. Unlike compiling for our native targets, there are a few extra steps involved with cross-compiling for another target.

Installing prerequisites

First, we need to install the Android SDK with NDK enabled. This contains the necessary tools for each architecture. Once the Android SDK is installed, open Editor Settings in the Godot GUI (Editor > Editor Settings > Export > Android) and set the absolute paths to adb, jarsigner, and the debug keystore (debug.keystore), all of which can be found in the Android SDK installation.

About NDK versions

libgcc was removed in Android NDK version r23-beta3. Although Rust was updated accordingly (see this issue), it's not available in stable Rust yet (as of 2023-03-04). Therefore, you need to use the nightly toolchain if you have Android NDK version 23 or higher.

# Install nightly toolchain
rustup toolchain install nightly

After that, run all rustup or cargo commands in this tutorial using the nightly toolchain by adding +nightly argument. For example:

rustup +nightly target add aarch64-linux-android

Alternatively, you can change the toolchain for your project using rust-toolchain.toml file, or you can change the global default toolchain. See rustup book for more information.

Then, we'll install the Rust toolchains for the targets we want to support:

rustup target add aarch64-linux-android    # for arm64 (64-bit)
rustup target add x86_64-linux-android     # for x86_64 (64-bit)

32-bit targets

The aarch64-linux-android and x86_64-linux-android toolchains are our top priorities, because Google has been requiring 64-bit binaries for all new apps on Play Store since August 2019, and will stop serving 32-bit apps in 2021. If you, nevertheless, want to support 32-bit targets, there are a few more dependencies to install.

A bit of context

There are two major CPU providers in the Android ecosystem: ARM and Intel.

They were primarily supporting 32-bit OS, with notably ARMv7 and x86 architectures, until they started supporting 64-bit OS, by introducing ARMv8-A (often called ARM64) and x86-64 (often called Intel 64 or AMD64, in reference to a long-time conflict between Intel and AMD).

Aarch64 is the 64-bit execution state that is introduced in ARM64 chips. i686 (also called P6) is actually the sixth-generation Intel x86 microarchitecture.

Generally speaking, 32-bit programs can run on 64-bit systems, but 64-bit programs won't run on 32-bit systems.

Rust toolchains for 32-bit targets

rustup target add armv7-linux-androideabi  # for armv7 (32-bit)
rustup target add i686-linux-android       # for x86 (32-bit)

gcc libraries for cross-compilation

On Windows, we will need to setup a 32-bit/64-bit compatible MinGW instance.

On UNIX-like systems, the required packages are usually available under different names in the package managers for each distribution. On Debian-based Linuxes (including Ubuntu), for example, the required libraries can be installed using apt:

apt-get update
apt-get install g++-multilib gcc-multilib libc6-dev-i386 -y

Custom Godot build

Note that if you are using GDNative with custom-godot setting, you need to compile Godot for Android yourself. Follow the instructions in official Godot documentation and make sure that GDNative support is enabled (which will be enabled by default unless you add module_gdnative_enabled=no).

Setting up Cargo

To make Cargo aware of the proper platform-specific linkers that it needs to use for Android targets, we need to put the paths to the binaries in the Cargo configuration file, which can be found (or created) at $HOME/.cargo/config.toml on UNIX-like systems, or %USERPROFILE%\.cargo\config.toml on Windows), using [target] tables:

[target.armv7-linux-androideabi]
linker = "/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang"

... where the value of linker is an absolute path to the Android SDK linker for the target triple. Assuming $ANDROID_SDK_ROOT is the Android SDK path and $ANDROID_NDK_VERSION is the installed NDK instance version, these binaries can be found at:

  • Windows: $ANDROID_SDK_ROOT\ndk\$ANDROID_NDK_VERSION\toolchains\llvm\prebuilt\windows-x86_64\bin\
  • UNIX-like systems: $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION/toolchains/llvm/prebuilt/linux-x86_64/bin/

Alternatively, the NDK can be located under $ANDROID_SDK_ROOT/ndk-bundle instead of $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION, but this folder is deprecated because it doesn't allow for parallel versions installation.

Repeat for all targets installed in the previous step, until we get something that looks like:

# Example configuration on an UNIX-like system. `29` is the Android API version.

[target.armv7-linux-androideabi]
linker = "/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang"

[target.aarch64-linux-android]
linker = "/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang"

[target.i686-linux-android]
linker = "/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android29-clang"

[target.x86_64-linux-android]
linker = "/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android29-clang"

Alternatively, you can use cargo config environment variables:

export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang"
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang"
export CARGO_TARGET_I686_LINUX_ANDROID_LINKER="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android29-clang"
export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="/usr/local/lib/android/sdk/ndk/21.4.7075529/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android29-clang"

Setting up environment variables for gdnative-sys

The gdnative-sys crate can infer include paths for Android targets, but it requires the following environment variables:

  • $ANDROID_SDK_ROOT, which should point to the Android SDK root (which contains the ndk or ndk-bundle directory).
  • $ANDROID_NDK_VERSION, which should contain the selected ndk version (if omitted, the latest version available is used and a warning is issued).

Depending on your installation, these environment variables might have already been set. Otherwise, the variables may be set in bash:

export ANDROID_SDK_ROOT=/path/to/android/sdk
export ANDROID_NDK_VERSION=21.4.7075529

... or in PowerShell on Windows:

$env:ANDROID_SDK_ROOT = "C:\path\to\android\sdk"
$env:ANDROID_NDK_VERSION = "21.4.7075529"

Building the GDNative library

Finally, we can now build the GDNative library with Cargo for one or multiple targets:

cargo build --release --target x86_64-linux-android

Important note: ARM and x86 are, by design, different architectures. It is normal to get errors while running cargo test with a Rust library targeting ARMv7 on a x86-64 CPU, for example, since the CPU is unable to handle it.

Exporting in Godot

Linking to Android binaries in .gdns

After building the GDNative libraries, we need to link them to Godot, by adding new entries in the GDNative library declaration file (*.gdnlib) for Android.armeabi-v7a (ARMv7), arm64-v8a (ARM64), Android.x86 (x86) and/or Android.x86_64 (x86-64), depending of the toolchains we actually used in previous steps:

[entry]

Android.armeabi-v7a="res://target/armv7-linux-androideabi/release/lib.so"
Android.arm64-v8a="res://target/aarch64-linux-android/release/lib.so"
Android.x86="res://target/i686-linux-android/release/lib.so"
Android.x86_64="res://target/x86_64-linux-android/release/lib.so"

[dependencies]

Android.armeabi-v7a=[  ]
Android.arm64-v8a=[  ]
Android.x86=[  ]
Android.x86_64=[  ]

APK signing for publication

Usually, we can choose between releasing an app in Debug or Release mode. However, the Release mode is required when officially releasing to Play Store.

In order to configure Godot to sign Release APKs, we'll first need to generate a project-specific Release keystore using keytool, and set up an alias and a single password (as explained in the Godot docs, -storepass and -keypass option values must be the same):

keytool -genkeypair -v -keystore path/to/my.keystore -alias some-alias -keyalg RSA -keysize 2048 -validity 10000 -storepass my-password -keypass my-password

Then, we will register its path in Export Settings (Project > Export) or export_presets.cfg. Please note that passwords entered in the GUI will be stored in export_presets.cfg. Be sure to not commit it into any VCS!

# Remember to not commit the password as is in VCS!
keystore/release="path/to/my.keystore"
keystore/release_user="some-alias"
keystore/release_password="my-password"

Exporting

Finally, we can now export the project using the GUI (Project > Export... > Android (Runnable)) and uncheck "Export with Debug" in GUI when being asked to enter APK file name. We may also use one of the following commands from the CLI to do the same:

# Debug mode
godot --export-debug "Android" path/to/my.apk

# Release mode
godot --export "Android" path/to/my.apk

When trying to install the app directly from the APK on an Android device, Play Protect may display a warning explaining that the app developers are not recognized, so the app may be unsafe. This is the expected behavior for an APK in Release mode that isn't actually released on Play Store.

If not planning to release on Play Store, one may file an appeal from Play Protect using a form provided by Google.

Troubleshooting

Compile time:

  • unable to find library -lgcc: You need the nightly version of Rust toolchain. See "About NDK versions" section.

Runtime:

  • ERROR: No loader found for resource: res://*.gdns: Your Godot APK was compiled without GDNative support. Make sure that you compile without module_gdnative_enabled=no setting in your build command.