如果您是第一次使用 Bazel,請先瀏覽使用 Bazel 建構 Android 教學課程。
總覽
Bazel 可以在許多不同的建構設定中執行,包括部分使用 Android Native Development Kit (NDK) 工具鍊的建構設定。這表示可以直接在 Bazel 中針對 Android 編譯一般的 cc_library
和 cc_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 NDK (r22 以上版本),請使用 android_ndk_repository
的 Starlark 實作。按照其 README 中的指示操作。
快速入門
如要建構 Android 適用的 C++,只要將 cc_library
依附元件新增至 android_binary
或 android_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_libraries 編譯成單一共用物件 (.so
) 檔案,且預設針對 armeabi-v7a
ABI。如要同時為多個 ABI 變更此屬性或建構,請參閱設定目標 ABI 一節。
設定範例
您可以在 Bazel 範例存放區中找到這個範例。
在 BUILD.bazel
檔案中,android_binary
、android_library
和 cc_library
規則定義了三個目標。
android_binary
頂層目標會建構 APK。
cc_library
目標包含單一 C++ 來源檔案,並實作 JNI 函式:
#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,因為這類 APK 會增加 APK 的大小,但對於開發和品質確保版本非常實用。
選取 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
傳遞編譯器和連結器標記,請參閱「使用者手冊」。
您也可以使用 copts
和 linkopts
,在 cc_library
中指定編譯器和連結器標記做為屬性。例如:
cc_library(
name = "jni_lib",
srcs = ["cpp/native-lib.cpp"],
copts = ["-std=c++11"],
linkopts = ["-ldl"], # link against libdl
)
與平台和工具鍊整合
Bazel 的設定模型即將移至平台和工具鍊。如果您的建構作業使用 --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 設定,因此只有該版本的 Android 專屬子樹狀結構受到影響。建構圖表的其他部分會使用頂層目標設定進行處理。如果建構圖中有支援該路徑的路徑,也可能在這兩種設定中處理單一目標。
只要 Bazel 在與 Android 相容的設定中 (無論是在頂層指定),還是因為有較高層級的轉換點,都不會進一步修改設定。
唯一會觸發 Android 設定轉換作業的內建位置是 android_binary
的 deps
屬性。
例如,如果您嘗試使用 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_binary
或 cc_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
在此範例中,系統會使用 NDK 工具鍊建構頂層 cc_library
和 cc_binary
目標。不過,這會導致使用 NDK 工具鍊 (以及 Android 適用) 建構 Bazel 自己的主機工具,因為主機工具鍊是從目標工具鍊中複製。如要解決這個問題,請將 --host_crosstool_top
的值指定為 @bazel_tools//tools/cpp:toolchain
,明確設定主機的 C++ 工具鍊。
使用這個方法時,整個建構樹狀結構都會受到影響。
這些旗標可放入 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
自動轉換設定。