VNDK Build System Support

In Android 8.1 and higher, the build system has built-in VNDK support. When VNDK support is enabled, the build system checks the dependencies between modules, builds a vendor-specific variant for vendor modules, and automatically installs those modules into designated directories.

VNDK build support example

In this example, the Android.bp module definition defines a library named libexample. The vendor_available property indicates framework modules and vendor modules may depend on libexample:

libexample vendor_available:true and vndk.enabled:true

Figure 1. VNDK support enabled

Both the framework executable /system/bin/foo and the vendor executable /vendor/bin/bar depend on libexample and have libexample in their shared_libs properties.

If libexample is used by both framework modules and vendor modules, two variants of libexample are built. The core variant (named after libexample) is used by framework modules and the vendor variant (named after libexample.vendor) is used by vendor modules. The two variants are installed into different directories:

  • The core variant is installed into /system/lib[64]/libexample.so.
  • The vendor variant is installed into VNDK APEX because vndk.enabled is true.

For more details, see Module definition.

Configuring build support

To enable full build system support for a product device, add BOARD_VNDK_VERSION to BoardConfig.mk:

BOARD_VNDK_VERSION := current

This setting has a global effect: When defined in BoardConfig.mk, all modules are checked. As there is no mechanism to blacklist or whitelist an offending module, you should clean all unnecessary dependencies before adding BOARD_VNDK_VERSION. You can test and compile a module by setting BOARD_VNDK_VERSION in your environment variables:

$ BOARD_VNDK_VERSION=current m module_name.vendor

When BOARD_VNDK_VERSION is enabled, several default global header search paths are removed. These include:

  • frameworks/av/include
  • frameworks/native/include
  • frameworks/native/opengl/include
  • hardware/libhardware/include
  • hardware/libhardware_legacy/include
  • hardware/ril/include
  • libnativehelper/include
  • libnativehelper/include_deprecated
  • system/core/include
  • system/media/audio/include

If a module depends on the headers from these directories, you must specify (explicitly) the dependencies with header_libs, static_libs, and/or shared_libs.

VNDK APEX

In Android 10 and lower, modules with vndk.enabled were installed in /system/lib[64]/vndk[-sp]-${VER}. In Android 11 and higher, VNDK libraries are packaged in an APEX format and the name of VNDK APEX is com.android.vndk.v${VER}. Depending on the device configuration, VNDK APEX is flattened or unflattened and is available from the canonical path /apex/com.android.vndk.v${VER}.

VNDK APEX

Figure 2. VNDK APEX

Module definition

To build Android with BOARD_VNDK_VERSION, you must revise the module definition in either Android.mk or Android.bp. This section describes different kinds of module definitions, several VNDK-related module properties, and dependency checks implemented in the build system.

Vendor modules

Vendor modules are vendor-specific executables or shared libraries that must be installed into a vendor partition. In Android.bp files, vendor modules must set vendor or proprietary property to true. In Android.mk files, vendor modules must set LOCAL_VENDOR_MODULE or LOCAL_PROPRIETARY_MODULE to true.

If BOARD_VNDK_VERSION is defined, the build system disallows dependencies between vendor modules and framework modules and emits errors if:

  • a module without vendor:true depends on a module with vendor:true, or
  • a module with vendor:true depends on a non-llndk_library module that has neither vendor:true nor vendor_available:true.

The dependency check applies to header_libs, static_libs, and shared_libs in Android.bp, and to LOCAL_HEADER_LIBRARIES, LOCAL_STATIC_LIBRARIES and LOCAL_SHARED_LIBRARIES in Android.mk.

LL-NDK

LL-NDK shared libraries are shared libraries with stable ABIs. Both framework and vendor modules share the same and the latest implementation. For each LL-NDK shared library, the cc_library contains a llndk property with a symbol file:

cc_library {
    name: "libvndksupport",
    llndk: {
        symbol_file: "libvndksupport.map.txt",
    },
}

The symbol file describes the symbols visible to vendor modules. For example:

LIBVNDKSUPPORT {
  global:
    android_load_sphal_library; # llndk
    android_unload_sphal_library; # llndk
  local:
    *;
};

Based on the symbol file, the build system generates a stub shared library for vendor modules, which link with these libraries when BOARD_VNDK_VERSION is enabled. A symbol is included in the stub shared library only if it:

  • Is not defined in the section end with _PRIVATE or _PLATFORM,
  • Does not have #platform-only tag, and
  • Does not have #introduce* tags or the tag matches with the target.

VNDK

In Android.bp files, cc_library, cc_library_static, cc_library_shared, and cc_library_headers module definitions support three VNDK-related properties: vendor_available, vndk.enabled, and vndk.support_system_process.

If vendor_available or vndk.enabled is true, two variants (core and vendor) may be built. The core variant should be treated as a framework module and the vendor variant should be treated as a vendor module. If some framework modules depend on this module, the core variant is built. If some vendor modules depend on this module, the vendor variant is built. The build system enforces the following dependency checks:

  • The core variant is always framework-only and inaccessible to vendor modules.
  • The vendor variant is always inaccessible to framework modules.
  • All dependencies of the vendor variant, which are specified in header_libs, static_libs, and/or shared_libs, must be either an llndk_library or a module with vendor_available or vndk.enabled.
  • If vendor_available is true, the vendor variant is accessible to all vendor modules.
  • If vendor_available is false, the vendor variant is accessible only to other VNDK or VNDK-SP modules (i.e., modules with vendor:true cannot link vendor_available:false modules).

The default installation path for cc_library or cc_library_shared is determined by the following rules:

  • The core variant is installed to /system/lib[64].
  • The vendor variant installation path may vary:
    • If vndk.enabled is false, the vendor variant is installed into /vendor/lib[64].
    • If vndk.enabled is true, the vendor variant is installed into VNDK APEX(com.android.vndk.v${VER}).

The table below summarizes how the build system handles the vendor variants:

vendor_available vndk
enabled
vndk
support_same_process
Vendor variant descriptions
true false false The vendor variants are VND-ONLY. Shared libraries are installed into /vendor/lib[64].
true Invalid (Build error)
true false The vendor variants are VNDK. Shared libraries are installed to VNDK APEX.
true The vendor variants are VNDK-SP. Shared libraries are installed to VNDK APEX.

false

false

false

No vendor variants. This module is FWK-ONLY.

true Invalid (Build error)
true false The vendor variants are VNDK-Private. Shared libraries are installed to VNDK APEX. These must not be directly used by vendor modules.
true The vendor variants are VNDK-SP-Private. Shared libraries are installed to VNDK APEX. These must not be directly used by vendor modules.

VNDK extensions

VNDK extensions are VNDK shared libraries with additional APIs. Extensions are installed to /vendor/lib[64]/vndk[-sp] (without version suffix) and override the original VNDK shared libraries at runtime.

Defining VNDK extensions

In Android 9 and higher, Android.bp natively supports VNDK extensions. To build a VNDK extension, define another module with a vendor:true and an extends property:

cc_library {
    name: "libvndk",
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libvndk_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk",
    },
}

A module with vendor:true, vndk.enabled:true, and extends properties defines the VNDK extension:

  • The extends property must specify a base VNDK shared library name (or VNDK-SP shared library name).
  • VNDK extensions (or VNDK-SP extensions) are named after the base module names from which they extend. For example, the output binary of libvndk_ext is libvndk.so instead of libvndk_ext.so.
  • VNDK extensions are installed into /vendor/lib[64]/vndk.
  • VNDK-SP extensions are installed into /vendor/lib[64]/vndk-sp.
  • The base shared libraries must have both vndk.enabled:true and vendor_available:true.

A VNDK-SP extension must extend from a VNDK-SP shared library (vndk.support_system_process must be equal):

cc_library {
    name: "libvndk_sp",
    vendor_available: true,
    vndk: {
        enabled: true,
        support_system_process: true,
    },
}

cc_library {
    name: "libvndk_sp_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk_sp",
        support_system_process: true,
    },
}

VNDK extensions (or VNDK-SP extensions) may depend on other vendor shared libraries:

cc_library {
    name: "libvndk",
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libvndk_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk",
    },
    shared_libs: [
        "libvendor",
    ],
}

cc_library {
    name: "libvendor",
    vendor: true,
}

Using VNDK extensions

If a vendor module depends on additional APIs defined by VNDK extensions, the module must specify the name of the VNDK extension in its shared_libs property:

// A vendor shared library example
cc_library {
    name: "libvendor",
    vendor: true,
    shared_libs: [
        "libvndk_ext",
    ],
}

// A vendor executable example
cc_binary {
    name: "vendor-example",
    vendor: true,
    shared_libs: [
        "libvndk_ext",
    ],
}

If a vendor module depends on VNDK extensions, those VNDK extensions are installed to /vendor/lib[64]/vndk[-sp] automatically. If a module no longer depends on a VNDK extension, add a clean step to CleanSpec.mk to remove the shared library. For example:

$(call add-clean-step, rm -rf $(TARGET_OUT_VENDOR)/lib/libvndk.so)

Conditional compilation

This section describes how to deal with the subtle differences (e.g. adding or removing a feature from one of the variants) between the following three VNDK shared libraries:

  • Core variant (e.g. /system/lib[64]/libexample.so)
  • Vendor variant (e.g. /apex/com.android.vndk.v${VER}/lib[64]/libexample.so)
  • VNDK extension (e.g. /vendor/lib[64]/vndk[-sp]/libexample.so)

Conditional compiler flags

The Android build system defines __ANDROID_VNDK__ for vendor variants and VNDK extensions by default. You may guard the code with the C preprocessor guards:

void all() { }

#if !defined(__ANDROID_VNDK__)
void framework_only() { }
#endif

#if defined(__ANDROID_VNDK__)
void vndk_only() { }
#endif

In addition to __ANDROID_VNDK__, different cflags or cppflags may be specified in Android.bp. The cflags or cppflags specified in target.vendor is specific to the vendor variant.

For example, the following Android.bp defines libexample and libexample_ext:

cc_library {
    name: "libexample",
    srcs: ["src/example.c"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
    target: {
        vendor: {
            cflags: ["-DLIBEXAMPLE_ENABLE_VNDK=1"],
        },
    },
}

cc_library {
    name: "libexample_ext",
    srcs: ["src/example.c"],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample",
    },
    cflags: [
        "-DLIBEXAMPLE_ENABLE_VNDK=1",
        "-DLIBEXAMPLE_ENABLE_VNDK_EXT=1",
    ],
}

And this is the code listing of src/example.c:

void all() { }

#if !defined(LIBEXAMPLE_ENABLE_VNDK)
void framework_only() { }
#endif

#if defined(LIBEXAMPLE_ENABLE_VNDK)
void vndk() { }
#endif

#if defined(LIBEXAMPLE_ENABLE_VNDK_EXT)
void vndk_ext() { }
#endif

According to these two files, the build system generates shared libraries with following exported symbols:

Installation path Exported symbols
/system/lib[64]/libexample.so all, framework_only
/apex/com.android.vndk.v${VER}/lib[64]/libexample.so all, vndk
/vendor/lib[64]/vndk/libexample.so all, vndk, vndk_ext

Requirements on the exported symbols

The VNDK ABI checker compares the ABI of VNDK vendor variants and VNDK extensions to the reference ABI dumps under prebuilts/abi-dumps/vndk.

  • Symbols exported by VNDK vendor variants (e.g. /apex/com.android.vndk.v${VER}/lib[64]/libexample.so) must be identical to (not the supersets of) the symbols defined in ABI dumps.
  • Symbols exported by VNDK extensions (e.g. /vendor/lib[64]/vndk/libexample.so) must be supersets of the symbols defined in ABI dumps.

If VNDK vendor variants or VNDK extensions fail to follow the requirements above, VNDK ABI checker emits build errors and stops the build.

Excluding source files or shared libraries from vendor variants

To exclude source files from the vendor variant, add them to the exclude_srcs property. Similarly, to ensure shared libraries are not linked with the vendor variant, add those libraries to the exclude_shared_libs property. For example:

cc_library {
    name: "libexample_cond_exclude",
    srcs: ["fwk.c", "both.c"],
    shared_libs: ["libfwk_only", "libboth"],
    vendor_available: true,
    target: {
        vendor: {
            exclude_srcs: ["fwk.c"],
            exclude_shared_libs: ["libfwk_only"],
        },
    },
}

In this example, the core variant of libexample_cond_exclude includes the code from fwk.c and both.c and depends on the shared libraries libfwk_only and libboth. The vendor variant of libexample_cond_exclude includes only the code from both.c because fwk.c is excluded by the exclude_srcs property. Similarly, it depends on only the shared library libboth because libfwk_only is excluded by the exclude_shared_libs property.

Export headers from VNDK extensions

A VNDK extension may add new classes or new functions to a VNDK shared library. It is suggested to keep those declarations in independent headers and avoid changing the existing headers.

For example, a new header file include-ext/example/ext/feature_name.h is created for the VNDK extension libexample_ext:

  • Android.bp
  • include-ext/example/ext/feature_name.h
  • include/example/example.h
  • src/example.c
  • src/ext/feature_name.c

In the following Android.bp, libexample exports only include, whereas libexample_ext exports both include and include-ext. This ensures feature_name.h won't be incorrectly included by the users of libexample:

cc_library {
    name: "libexample",
    srcs: ["src/example.c"],
    export_include_dirs: ["include"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libexample_ext",
    srcs: [
        "src/example.c",
        "src/ext/feature_name.c",
    ],
    export_include_dirs: [
        "include",
        "include-ext",
    ],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample",
    },
}

If separating extensions to independent header files is not feasible, an alternative is to add #ifdef guards. However, make sure that all VNDK extension users add the define flags. You may define cc_defaults to add define flags to cflags and link shared libraries with shared_libs.

For example, to add a new member function Example2::get_b() to the VNDK extension libexample2_ext, you must modify the existing header file and add a #ifdef guard:

#ifndef LIBEXAMPLE2_EXAMPLE_H_
#define LIBEXAMPLE2_EXAMPLE_H_

class Example2 {
 public:
  Example2();

  void get_a();

#ifdef LIBEXAMPLE2_ENABLE_VNDK_EXT
  void get_b();
#endif

 private:
  void *impl_;
};

#endif  // LIBEXAMPLE2_EXAMPLE_H_

A cc_defaults named libexample2_ext_defaults is defined for the users of libexample2_ext:

cc_library {
    name: "libexample2",
    srcs: ["src/example2.cpp"],
    export_include_dirs: ["include"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libexample2_ext",
    srcs: ["src/example2.cpp"],
    export_include_dirs: ["include"],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample2",
    },
    cflags: [
        "-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
    ],
}

cc_defaults {
    name: "libexample2_ext_defaults",
    shared_libs: [
        "libexample2_ext",
    ],
    cflags: [
        "-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
    ],
}

The users of libexample2_ext may simply include libexample2_ext_defaults in their defaults property:

cc_binary {
    name: "example2_user_executable",
    defaults: ["libexample2_ext_defaults"],
    vendor: true,
}

Product packages

In the Android build system, the variable PRODUCT_PACKAGES specifies the executables, shared libraries, or packages that should be installed into the device. The transitive dependencies of the specified modules are implicitly installed into the device as well.

If BOARD_VNDK_VERSION is enabled, modules with vendor_available or vndk.enabled get special treatment. If a framework module depends on a module with vendor_available or vndk.enabled, the core variant is included in the transitive installation set. If a vendor module depends on a module with vendor_available, the vendor variant is included in the transitive installation set. However, vendor variants of modules with vndk.enabled are installed whether or not they are used by vendor modules.

When the dependencies are invisible to the build system (e.g. shared libraries that may be opened with dlopen() in runtime), you should specify the module names in PRODUCT_PACKAGES to install those modules explicitly.

If a module has vendor_available or vndk.enabled, the module name stands for its core variant. To explicitly specify the vendor variant in PRODUCT_PACKAGES, append a .vendor suffix to the module name. For example:

cc_library {
    name: "libexample",
    srcs: ["example.c"],
    vendor_available: true,
}

In this example, libexample stands for /system/lib[64]/libexample.so and libexample.vendor stands for /vendor/lib[64]/libexample.so. To install /vendor/lib[64]/libexample.so, add libexample.vendor to PRODUCT_PACKAGES:

PRODUCT_PACKAGES += libexample.vendor