Platform APIs

The latest version of this document is available at https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md.

Note: This document covers the new method of getting platform APIs into the NDK. For the time being, the old method is still in use as well, and is covered by Generating Sysroots.

Implications of Adding a New Platform API

Before adding a platform API to the NDK, there are some guarantees and restrictions that need to be considered.

ABI Compatibility

The NDK ABI must be forward compatible. Apps that are in the Play Store must continue to function on new devices. This means that once something becomes NDK ABI, it must continue to be exposed and behavior must be preserved by the platform for the lifetime of the ABI (for all intents and purposes, forever). The NDK ABI includes but is not limited to:

  • Exposed functions and global data.
  • Layout and size of publicly visible data structures.
  • Values of constants (such as enums).

Source Compatibility

Source compatibility should be maintained, though exceptions have been made. When appropriate, source compatibility breaking features can be limited to only breaking when the user is targeting at least the API level that broke the change (e.g. #if __ANDROID_API__ >= 24), which ensures that any currently building app will continue building until the developer chooses to upgrade to a new platform level.

Note that the definition of target API level in the NDK differs from the SDK. For the NDK, the target API level is the minimum supported API level for the app. If any non-ABI features are guarded by the target API level, they will only be available to apps with a minimum target that includes that API level.

As a practical example of this, the NDK historically did not expose the ALOG* log macros, only the __android_log_* functions. If the macros were to be added but only exposed for API levels android-24 and newer, these helper macros that could otherwise be available to Gingerbread would only be usable by developers that chose to forfeit all Android users on devices older than android-24.

API Restrictions

NDK APIs are C APIs only. This restriction may be lifted in the future, but at the moment Android's C++ ABI is not guaranteed to be stable. Note that this does not restrict the implementation of the API to C, only the interface that is exposed in the headers.

NDK API headers can only depend on other NDK API headers. Platform headers from android-base, libcutils, libnativehelper, etc are not available to the NDK.

For Platform Developers

To get your API into the NDK you‘ll generally need to define the two pieces that implement it: the headers and the libraries. Often the library is libandroid.so and you won’t need to add a new library, but you'll need to modify an existing one. For this reason, libraries and the headers for those libraries are defined separately.

Headers

To add headers to the NDK, create an ndk_headers module in your Android.bp. Examples of this module type can be found in bionic/libc/Android.bp.

These module definitions are as follows:

// Will install $MODULE_PATH/include/foo/bar/baz.h to qux/bar/baz.h (relative to
// $NDK_OUT/sysroot/usr/include).
ndk_headers {
    name: "foo",

    // Base directory of the headers being installed. This path will be stripped
    // when installed.
    from: "include/foo",

    // Install path within the sysroot.
    to: "qux",

    // List of headers to install. Relative to the Android.bp. Glob compatible.
    // The common case is "include/**/*.h".
    srcs: ["include/foo/bar/baz.h"],
}

Libraries

To add a library to the NDK, create an ndk_library module in your Android.bp. An example of this module type can be found in bionic/libc/Android.bp.

These module defintions are as follows:

// The name of the generated file will be based on the module name by stripping
// the ".ndk" suffix from the module name. Module names must end with ".ndk"
// (as a convention to allow soong to guess the NDK name of a dependency when
// needed). "libfoo.ndk" will generate "libfoo.so.
ndk_library {
    name: "libfoo.ndk",

    // A version script with some metadata that encodes version and arch
    // mappings so that only one script is needed instead of one per API/arch.
    // An example of this file can be found at [bionic/libc/libc.map.txt].
    //
    // For a new library, this is something you'll need to create yourself.
    // These should *not* be generated by `readelf -sW`ing your library. The
    // purpose of these files is to explicitly list your APIs so you never
    // accidentally expose private API that you'll need to support forever.
    symbol_file: "libfoo.map.txt",

    // The first API level a library was available. A library will be generated
    // for every API level beginning with this one.
    first_version: "9",
}

Linker namespaces' public libraries list

You wouldn't be adding a library to the NDK unless you actually wanted apps to be able to use your library, but in Android N or later, apps are only allowed to access libraries on a specific whitelist of NDK libraries. This list is stored in system/core/rootdir/etc/public.libraries.android.txt with another subset in system/core/rootdir/etc/public.libraries.wear.txt for Android Wear devices.

CTS

There‘s also a CTS test to ensure that non-NDK libraries don’t get added to the public libraries list. This test can be found at cts/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java. Simply add your library to the PUBLIC_SYSTEM_LIBRARIES list in that file.

Making sure it works

The best way to make sure you‘ve set everything up properly is to add a test to CTS. Make sure this test is built using the NDK (make sure LOCAL_SDK_VERSION is set, sdk_version if you’re using Android.bp). If your test passes in CTS when it is built with the NDK, then everything has been set up properly.

Note that without this step, it is possible that one of the above steps will have been done incorrectly and you wouldn't know without inspecting everything yourself. If the ndk_library rule ends up in an Android.bp that never gets parsed and there are no tests built with the NDK that use that library, your build will still pass.

For NDK Developers

The platform APIs reach the NDK via the “sysroot” and “platforms” modules in checkbuild.py. The sysroot currently is just the headers, whereas the libraries and CRT objects are in platforms (as are the deprecated headers).

The sysroot module is copied from prebuilts/ndk/platform/sysroot. These prebuilts are updated with prebuilts/ndk/update_platform.py, which pulls NDK artifacts from the build servers.

The platforms module is built partially from prebuilts/ndk/platform/sysroot and partially from development/ndk/platforms. The former contains the library stubs from the platform, and the latter contains the deprecated headers, the CRT objects, and static libraries. For more information on this process, see Generating Sysroots.