Project: /_project.yaml Book: /_book.yaml

Using the Android Native Development Kit with Bazel

{% include “_buttons.html” %}

If you're new to Bazel, please start with the Building Android with Bazel tutorial.

Overview {:#overview}

Bazel can run in many different build configurations, including several that use the Android Native Development Kit (NDK) toolchain. This means that normal cc_library and cc_binary rules can be compiled for Android directly within Bazel. Bazel accomplishes this by using the android_ndk_repository repository rule.

Prerequisites {:#prerequisites}

Please ensure that you have installed the Android SDK and NDK.

To set up the SDK and NDK, add the following snippet to your WORKSPACE:

android_sdk_repository(
    name = "androidsdk", # Required. Name *must* be "androidsdk".
    path = "/path/to/sdk", # Optional. Can be omitted if `ANDROID_HOME` environment variable is set.
)

android_ndk_repository(
    name = "androidndk", # Required. Name *must* be "androidndk".
    path = "/path/to/ndk", # Optional. Can be omitted if `ANDROID_NDK_HOME` environment variable is set.
)

For more information about the android_ndk_repository rule, see the Build Encyclopedia entry.

If you're using a recent version of the Android NDK (r22 and beyond), use the Starlark implementation of android_ndk_repository. Follow the instructions in its README.

Quick start {:#quick-start}

To build C++ for Android, simply add cc_library dependencies to your android_binary or android_library rules.

For example, given the following BUILD file for an Android app:

# In <project>/app/src/main/BUILD.bazel

cc_library(
    name = "jni_lib",
    srcs = ["cpp/native-lib.cpp"],
)

android_library(
    name = "lib",
    srcs = ["java/com/example/android/bazel/MainActivity.java"],
    resource_files = glob(["res/**/*"]),
    custom_package = "com.example.android.bazel",
    manifest = "LibraryManifest.xml",
    deps = [":jni_lib"],
)

android_binary(
    name = "app",
    deps = [":lib"],
    manifest = "AndroidManifest.xml",
)

This BUILD file results in the following target graph:

Example results

Figure 1. Build graph of Android project with cc_library dependencies.

To build the app, simply run:

bazel build //app/src/main:app

The bazel build command compiles the Java files, Android resource files, and cc_library rules, and packages everything into an APK:

$ zipinfo -1 bazel-bin/app/src/main/app.apk
nativedeps
lib/armeabi-v7a/libapp.so
classes.dex
AndroidManifest.xml
...
res/...
...
META-INF/CERT.SF
META-INF/CERT.RSA
META-INF/MANIFEST.MF

Bazel compiles all of the cc_libraries into a single shared object (.so) file, targeted for the armeabi-v7a ABI by default. To change this or build for multiple ABIs at the same time, see the section on configuring the target ABI.

Example setup {:#example-setup}

This example is available in the Bazel examples repository{: .external}.

In the BUILD.bazel file, three targets are defined with the android_binary, android_library, and cc_library rules.

The android_binary top-level target builds the APK.

The cc_library target contains a single C++ source file with a JNI function implementation:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_example_android_bazel_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

The android_library target specifies the Java sources, resource files, and the dependency on a cc_library target. For this example, MainActivity.java loads the shared object file libapp.so, and defines the method signature for the JNI function:

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("app");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // ...
    }

    public native String stringFromJNI();

}

Note: The name of the native library is derived from the name of the top level android_binary target. In this example, it is app.

Configuring the target ABI {:#configuring-target-abi}

To configure the target ABI, use the --android_platforms flag as follows:

bazel build //:app --android_platforms={{ "<var>" }}comma-separated list of platforms{{ "</var>" }}

Just like the --platforms flag, the values passed to --android_platforms are the labels of platform targets, using standard constraint values to describe your device.

For example, for an Android device with a 64-bit ARM processor, you'd define your platform like this:

platform(
    name = "android_arm64",
    constraint_values = [
        "@platforms//os:android",
        "@platforms//cpu:arm64",
    ],
)

Every Android platform should use the @platforms//os:android OS constraint. To migrate the CPU constraint, check this chart:

CPU ValuePlatform
armeabi-v7a@platforms//cpu:arm
arm64-v8a@platforms//cpu:arm64
x86@platforms//cpu:x86_32
x86_64@platforms//cpu:x86_64

And, of course, for a multi-architecture APK, you pass multiple labels, for example: --android_platforms=//:arm64,//:x86_64 (assuming you defined those in your top-level BUILD.bazel file).

Bazel is unable to select a default Android platform, so one must be defined and specified with --android_platforms.

Depending on the NDK revision and Android API level, the following ABIs are available:

NDK revisionABIs
16 and lowerarmeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 and abovearmeabi-v7a, arm64-v8a, x86, x86_64

See the NDK docs{: .external} for more information on these ABIs.

Multi-ABI Fat APKs are not recommended for release builds since they increase the size of the APK, but can be useful for development and QA builds.

Selecting a C++ standard {:#selecting-c-standard}

Use the following flags to build according to a C++ standard:

C++ StandardFlag
C++98Default, no flag needed
C++11--cxxopt=-std=c++11
C++14--cxxopt=-std=c++14
C++17--cxxopt=-std=c++17

For example:

bazel build //:app --cxxopt=-std=c++11

Read more about passing compiler and linker flags with --cxxopt, --copt, and --linkopt in the User Manual.

Compiler and linker flags can also be specified as attributes in cc_library using copts and linkopts. For example:

cc_library(
    name = "jni_lib",
    srcs = ["cpp/native-lib.cpp"],
    copts = ["-std=c++11"],
    linkopts = ["-ldl"], # link against libdl
)

Building a cc_library for Android without using android_binary {:#cclibrary-android}

To build a standalone cc_binary or cc_library for Android without using an android_binary, use the --platforms flag.

For example, assuming you have defined Android platforms in my/platforms/BUILD:

bazel build //my/cc/jni:target \
      --platforms=//my/platforms:x86_64

With this approach, the entire build tree is affected.

Note: All of the targets on the command line must be compatible with building for Android when specifying these flags, which may make it difficult to use Bazel wild-cards like /... and :all.

These flags can be put into a bazelrc config (one for each ABI), in {{ "<var>" }}project{{ "</var>" }}/.bazelrc:

common:android_x86 --platforms=//my/platforms:x86

common:android_armeabi-v7a --platforms=//my/platforms:armeabi-v7a

# In general
common:android_<abi> --platforms=//my/platforms:<abi>

Then, to build a cc_library for x86 for example, run:

bazel build //my/cc/jni:target --config=android_x86

In general, use this method for low-level targets (like cc_library) or when you know exactly what you‘re building; rely on the automatic configuration transitions from android_binary for high-level targets where you’re expecting to build a lot of targets you don't control.