将 Android 原生开发套件与 Bazel 搭配使用

报告问题 查看源代码

如果您刚开始接触 Bazel,请先学习使用 Bazel 构建 Android 教程。

概览

Bazel 可以在许多不同的 build 配置中运行,包括几种使用 Android 原生开发套件 (NDK) 工具链的 build 配置。这意味着,您可以直接在 Bazel 中针对 Android 编译普通的 cc_librarycc_binary 规则。Bazel 使用 android_ndk_repository 代码库规则完成此操作。

前提条件

请确保您已安装 Android SDK 和 NDK。

如需设置 SDK 和 NDK,请将以下代码段添加到 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.
)

如需详细了解 android_ndk_repository 规则,请参阅构建百科全书条目

快速入门

如需针对 Android 构建 C++,只需将 cc_library 依赖项添加到您的 android_binaryandroid_library 规则即可。

例如,假设存在以下某个 Android 应用的 BUILD 文件:

# 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",
)

BUILD 文件会生成以下目标图表:

示例结果

图 1. 具有 cc_library 依赖项的 Android 项目的构建图。

如需构建应用,只需运行以下命令:

bazel build //app/src/main:app

bazel build 命令会编译 Java 文件、Android 资源文件和 cc_library 规则,并将所有内容打包到 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 会将所有 cc_library 编译到一个默认情况下针对 armeabi-v7a ABI 的共享对象 (.so) 文件中。如需更改此设置或同时针对多个 ABI 进行构建,请参阅有关配置目标 ABI 的部分。

示例设置

Bazel 示例代码库中提供了此示例。

BUILD.bazel 文件中,使用 android_binaryandroid_librarycc_library 规则定义了三个目标。

android_binary 顶级目标会构建 APK。

cc_library 目标包含一个具有 JNI 函数实现的 C++ 源文件:

#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());
}

android_library 目标指定 Java 源文件、资源文件以及对 cc_library 目标的依赖项。在此示例中,MainActivity.java 会加载共享对象文件 libapp.so,并为 JNI 函数定义方法签名:

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromJNI();

}

配置 STL

如需配置 C++ STL,请使用 --android_crosstool_top 标志。

bazel build //:app --android_crosstool_top=target label

@androidndk 中可用的 STL 包括:

STL 目标标签
STLport @androidndk//:toolchain-stlport
libc++ @androidndk//:toolchain-libcpp
gnustl @androidndk//:toolchain-gnu-libstdcpp

对于 r16 及更低版本,默认的 STL 为 gnustl。对于 r17 及更高版本,为 libc++。为方便起见,目标 @androidndk//:default_crosstool 的别名为相应的默认 STL。

请注意,从 r18 开始,STLport 和 gnustl 将被移除,使 libc++ 成为 NDK 中的唯一 STL。

如需详细了解这些 STL,请参阅 NDK 文档

配置目标 ABI

如需配置目标 ABI,请使用 --fat_apk_cpu 标志,如下所示:

bazel build //:app --fat_apk_cpu=comma-separated list of ABIs

默认情况下,Bazel 会为 armeabi-v7a 构建原生 Android 代码。如需针对 x86(例如模拟器)进行构建,请传递 --fat_apk_cpu=x86。如需为多个架构创建胖 APK,您可以指定多个 CPU:--fat_apk_cpu=armeabi-v7a,x86

如果指定了多个 ABI,Bazel 会为每个 ABI 构建一个包含共享对象的 APK。

根据 NDK 修订版本和 Android API 级别,可使用以下 ABI:

NDK 修订版本 ABI
16 及更低版本 armeabi、armeabi-v7a、arm64-v8a、mips、mips64、x86、x86_64
17 岁及以上 armeabi-v7a、arm64-v8a、x86、x86_64

如需详细了解这些 ABI,请参阅 NDK 文档

不建议将多 ABI Fat APK 用于发布 build,因为它们会增加 APK 的大小,但对开发和 QA 构建很有用。

选择 C++ 标准

使用以下标志根据 C++ 标准进行构建:

C++ 标准 标志
C++98 默认,无需任何标志
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14

例如:

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

如需详细了解如何使用 --cxxopt--copt--linkopt 传递编译器标记和链接器标记,请参阅用户手册

您也可以使用 coptslinkopts 将编译器标记和链接器标记指定为 cc_library 中的属性。例如:

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

与平台和工具链集成

Bazel 的配置模型正在转向平台工具链。如果您的 build 使用 --platforms 标志来选择要针对哪种架构或操作系统进行构建,您需要将 --extra_toolchains 标志传递给 Bazel 才能使用 NDK。

例如,如需与 Go 规则提供的 android_arm64_cgo 工具链集成,除了传递 --platforms 标志之外,还应传递 --extra_toolchains=@androidndk//:all

bazel build //my/cc:lib \
  --platforms=@io_bazel_rules_go//go/toolchain:android_arm64_cgo \
  --extra_toolchains=@androidndk//:all

您也可以直接在 WORKSPACE 文件中注册它们:

android_ndk_repository(name = "androidndk")
register_toolchains("@androidndk//:all")

注册这些工具链会告知 Bazel,在解析架构和操作系统限制条件时,在 NDK BUILD 文件(适用于 NDK 20)中查找它们:

toolchain(
  name = "x86-clang8.0.7-libcpp_toolchain",
  toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
  target_compatible_with = [
      "@platforms//os:android",
      "@platforms//cpu:x86_32",
  ],
  toolchain = "@androidndk//:x86-clang8.0.7-libcpp",
)

toolchain(
  name = "x86_64-clang8.0.7-libcpp_toolchain",
  toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
  target_compatible_with = [
      "@platforms//os:android",
      "@platforms//cpu:x86_64",
  ],
  toolchain = "@androidndk//:x86_64-clang8.0.7-libcpp",
)

toolchain(
  name = "arm-linux-androideabi-clang8.0.7-v7a-libcpp_toolchain",
  toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
  target_compatible_with = [
      "@platforms//os:android",
      "@platforms//cpu:arm",
  ],
  toolchain = "@androidndk//:arm-linux-androideabi-clang8.0.7-v7a-libcpp",
)

toolchain(
  name = "aarch64-linux-android-clang8.0.7-libcpp_toolchain",
  toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
  target_compatible_with = [
      "@platforms//os:android",
      "@platforms//cpu:aarch64",
  ],
  toolchain = "@androidndk//:aarch64-linux-android-clang8.0.7-libcpp",
)

工作原理:引入 Android 配置转换

android_binary 规则可以明确要求 Bazel 在 Android 兼容配置中构建其依赖项,以便 Bazel 构建在没有任何特殊标志的情况下正常运行,但用于 ABI 和 STL 配置的 --fat_apk_cpu--android_crosstool_top 除外。

在后台,这种自动配置使用 Android 配置转换

兼容的规则(如 android_binary)会自动将其依赖项配置更改为 Android 配置,因此只有 build 的 Android 专用子树会受到影响。构建图的其他部分使用顶级目标配置进行处理。它甚至可以在两种配置中处理单个目标(如果 build 图中存在支持此操作的路径)。

一旦 Bazel 进入与 Android 兼容的配置(无论是在顶级指定还是由更高级别的过渡点指定),则遇到的其他过渡点不会进一步修改配置。

触发向 Android 配置转换的唯一内置位置是 android_binarydeps 属性。

例如,如果您尝试构建具有 cc_library 依赖项且没有任何标志的 android_library 目标,则可能会遇到有关缺少 JNI 标头的错误:

ERROR: project/app/src/main/BUILD.bazel:16:1: C++ compilation of rule '//app/src/main:jni_lib' failed (Exit 1)
app/src/main/cpp/native-lib.cpp:1:10: fatal error: 'jni.h' file not found
#include <jni.h>
         ^~~~~~~
1 error generated.
Target //app/src/main:lib failed to build
Use --verbose_failures to see the command lines of failed build steps.

理想情况下,这些自动转换应该能够让 Bazel 在大多数情况下执行正确的操作。但是,如果 Bazel 命令行上的目标已经低于其中任何转换规则(例如,C++ 开发者测试特定 cc_library),则必须使用自定义 --crosstool_top

在不使用 android_binary 的情况下构建适用于 Android 的 cc_library

如需在不使用 android_binary 的情况下针对 Android 构建独立的 cc_binarycc_library,请使用 --crosstool_top--cpu--host_crosstool_top 标志。

例如:

bazel build //my/cc/jni:target \
      --crosstool_top=@androidndk//:default_crosstool \
      --cpu=<abi> \
      --host_crosstool_top=@bazel_tools//tools/cpp:toolchain

在此示例中,顶级 cc_librarycc_binary 目标是使用 NDK 工具链构建的。但是,这会导致系统使用 NDK 工具链(以及 Android 也因此能够构建 Bazel 自己的主机工具)来构建 Bazel 自己的主机工具,因为主机工具链是从目标工具链复制的。如需解决此问题,请将 --host_crosstool_top 的值设置为 @bazel_tools//tools/cpp:toolchain,以明确设置主机的 C++ 工具链。

使用此方法,整个 build 树都会受到影响。

这些标志可以放入 project/.bazelrc 中的 bazelrc 配置(每个 ABI 一个配置):

common:android_x86 --crosstool_top=@androidndk//:default_crosstool
common:android_x86 --cpu=x86
common:android_x86 --host_crosstool_top=@bazel_tools//tools/cpp:toolchain

common:android_armeabi-v7a --crosstool_top=@androidndk//:default_crosstool
common:android_armeabi-v7a --cpu=armeabi-v7a
common:android_armeabi-v7a --host_crosstool_top=@bazel_tools//tools/cpp:toolchain

# In general
common:android_<abi> --crosstool_top=@androidndk//:default_crosstool
common:android_<abi> --cpu=<abi>
common:android_<abi> --host_crosstool_top=@bazel_tools//tools/cpp:toolchain

然后,如需为 x86 构建 cc_library,请运行以下命令:

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

一般来说,此方法适用于低级别目标(如 cc_library),或者您确切知道自己在构建什么;针对高级别目标,您可以依赖 android_binary 的自动配置转换,在这些高级别目标上构建大量您无法控制的目标。