Nếu bạn mới sử dụng Bazel, vui lòng bắt đầu với hướng dẫn Xây dựng Android bằng Bazel.
Tổng quan
Bazel có thể chạy trong nhiều cấu hình xây dựng, trong đó có một số cấu hình sử dụng chuỗi công cụ Phát triển mã gốc của Android (NDK). Điều này có nghĩa là bạn có thể biên dịch các quy tắc thông thường
cc_library
và cc_binary
cho Android ngay trong
Bazel. Bazel thực hiện việc này bằng cách sử dụng quy tắc kho lưu trữ android_ndk_repository
.
Điều kiện tiên quyết
Vui lòng đảm bảo rằng bạn đã cài đặt SDK Android và NDK.
Để thiết lập SDK và NDK, hãy thêm đoạn mã sau vào WORKSPACE
của bạn:
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.
)
Để biết thêm thông tin về quy tắc android_ndk_repository
, vui lòng xem nội dung Tạo bài viết về Bách khoa toàn thư.
Bắt đầu nhanh
Để tạo C++ cho Android, bạn chỉ cần thêm các phần phụ thuộc cc_library
vào các quy tắc android_binary
hoặc android_library
.
Ví dụ như đối với tệp BUILD
sau đây cho một ứng dụng Android:
# 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",
)
Tệp BUILD
này dẫn đến biểu đồ mục tiêu sau:
Hình 1. Tạo biểu đồ cho dự án Android có các phần phụ thuộc cc_library.
Để tạo ứng dụng, bạn chỉ cần chạy:
bazel build //app/src/main:app
Lệnh bazel build
biên dịch các tệp Java, tệp tài nguyên Android và các quy tắc
cc_library
, đồng thời đóng gói mọi thứ vào một tệp 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 biên dịch tất cả cc_libraries thành một tệp đối tượng dùng chung (.so
), được nhắm mục tiêu cho ABI armeabi-v7a
theo mặc định. Để thay đổi điều này hoặc xây dựng cho nhiều ABI cùng lúc, hãy xem phần định cấu hình ABI mục tiêu.
Thiết lập mẫu
Ví dụ này có trong Kho lưu trữ mẫu ví dụ.
Trong tệp BUILD.bazel
, có ba mục tiêu được xác định bằng quy tắc android_binary
,
android_library
và cc_library
.
Mục tiêu cấp cao nhất của android_binary
là tạo tệp APK.
Mục tiêu cc_library
chứa một tệp nguồn C++ duy nhất có phương thức triển khai hàm 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());
}
Mục tiêu android_library
chỉ định các nguồn Java, tệp tài nguyên và phần phụ thuộc vào mục tiêu cc_library
. Trong ví dụ này, MainActivity.java
sẽ tải
tệp đối tượng dùng chung libapp.so
và xác định chữ ký phương thức cho hàm JNI:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("app");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
}
public native String stringFromJNI();
}
Định cấu hình STL
Để định cấu hình STL C++, hãy sử dụng cờ --android_crosstool_top
.
bazel build //:app --android_crosstool_top=target label
Các STL hiện có trong @androidndk
là:
STL (STL) | Nhãn mục tiêu |
---|---|
STLport (STLport) | @androidndk//:toolchain-stlport |
libc++ | @androidndk//:toolchain-libcpp |
gnustl | @androidndk//:toolchain-gnu-libstdcpp |
Đối với phiên bản r16 trở xuống, STL mặc định là gnustl
. Đối với phiên bản r17 trở lên, đó là
libc++
. Để thuận tiện, mục tiêu @androidndk//:default_crosstool
được đặt bí danh là các STL mặc định tương ứng.
Xin lưu ý rằng từ phiên bản 18 trở đi, STLport và gnustl sẽ bị xoá, khiến libc++
trở thành STL duy nhất trong NDK.
Xem tài liệu về NDK để biết thêm thông tin về các STL này.
Định cấu hình ABI mục tiêu
Để định cấu hình ABI mục tiêu, hãy sử dụng cờ --fat_apk_cpu
như sau:
bazel build //:app --fat_apk_cpu=comma-separated list of ABIs
Theo mặc định, Bazel tạo mã Android gốc cho armeabi-v7a
. Để tạo dành cho x86 (chẳng hạn như các trình mô phỏng), hãy truyền --fat_apk_cpu=x86
. Để tạo một APK lớn cho nhiều cấu trúc, bạn có thể chỉ định nhiều CPU: --fat_apk_cpu=armeabi-v7a,x86
.
Nếu có nhiều ABI được chỉ định, Bazel sẽ tạo một APK chứa một đối tượng dùng chung cho mỗi ABI.
Tuỳ thuộc vào bản sửa đổi NDK và cấp độ API của Android, bạn có thể sử dụng các ABI sau:
Sửa đổi NDK | ABI |
---|---|
16 trở xuống | armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64 |
17 trở lên | armeabi-v7a, arm64-v8a, x86, x86_64 |
Xem tài liệu về NDK để biết thêm thông tin về các ABI này.
Bạn không nên sử dụng tệp APK ABI Fat cho nhiều bản dựng phát hành vì chúng sẽ làm tăng kích thước của tệp APK, nhưng có thể hữu ích cho việc phát triển và tạo bản dựng đảm bảo chất lượng.
Chọn một tiêu chuẩn C++
Sử dụng các cờ sau để tạo theo tiêu chuẩn C++:
Tiêu chuẩn C++ | Cờ |
---|---|
C+99 | Mặc định, không cần gắn cờ |
C++11 | --cxxopt=-std=c++11 |
C++14 | --cxxopt=-std=c++14 |
Ví dụ:
bazel build //:app --cxxopt=-std=c++11
Đọc thêm về cách truyền cờ trình biên dịch và trình liên kết bằng --cxxopt
, --copt
và
--linkopt
trong Hướng dẫn sử dụng.
Bạn cũng có thể chỉ định cờ trình biên dịch và trình liên kết làm thuộc tính trong cc_library
bằng cách sử dụng copts
và linkopts
. Ví dụ:
cc_library(
name = "jni_lib",
srcs = ["cpp/native-lib.cpp"],
copts = ["-std=c++11"],
linkopts = ["-ldl"], # link against libdl
)
Tích hợp với các nền tảng và chuỗi công cụ
Mô hình cấu hình của Bazel đang chuyển sang
các nền tảng và
chuỗi công cụ. Nếu bản dựng của bạn sử dụng cờ --platforms
để chọn cấu trúc hoặc hệ điều hành để tạo bản dựng, bạn sẽ cần gắn cờ --extra_toolchains
cho Bazel để sử dụng NDK.
Ví dụ: để tích hợp với chuỗi công cụ android_arm64_cgo
do quy tắc Go cung cấp, hãy truyền --extra_toolchains=@androidndk//:all
ngoài cờ --platforms
.
bazel build //my/cc:lib \
--platforms=@io_bazel_rules_go//go/toolchain:android_arm64_cgo \
--extra_toolchains=@androidndk//:all
Bạn cũng có thể đăng ký trực tiếp các tệp đó trong tệp WORKSPACE
:
android_ndk_repository(name = "androidndk")
register_toolchains("@androidndk//:all")
Việc đăng ký các chuỗi công cụ này yêu cầu Bazel tìm các chuỗi đó trong tệp BUILD
NDK (dành cho NDK 20) khi giải quyết các ràng buộc về cấu trúc và hệ điều hành:
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",
)
Cách hoạt động: Giới thiệu chuyển đổi cấu hình Android
Quy tắc android_binary
có thể yêu cầu rõ ràng Bazel tạo các phần phụ thuộc trong cấu hình tương thích với Android để bản dựng Bazel chỉ hoạt động mà không cần bất kỳ cờ đặc biệt nào, ngoại trừ --fat_apk_cpu
và --android_crosstool_top
cho cấu hình ABI và STL.
Ở hậu trường, cấu hình tự động này sử dụng các lượt chuyển đổi cấu hình của Android.
Quy tắc tương thích, chẳng hạn như android_binary
, tự động thay đổi cấu hình của các phần phụ thuộc thành cấu hình Android, vì vậy, chỉ các cây con của bản dựng bị ảnh hưởng. Các phần khác của biểu đồ bản dựng được xử lý bằng cách sử dụng cấu hình mục tiêu cấp cao nhất. Nó thậm chí có thể xử lý một mục tiêu duy nhất trong cả hai cấu hình, nếu có các đường dẫn thông qua biểu đồ bản dựng để hỗ trợ việc này.
Khi Bazel đang ở cấu hình tương thích với Android, được chỉ định ở cấp cao nhất hoặc do một điểm chuyển đổi cấp cao hơn, các điểm chuyển đổi bổ sung gặp phải sẽ không sửa đổi cấu hình đó nữa.
Vị trí tích hợp duy nhất kích hoạt quá trình chuyển đổi sang cấu hình Android là thuộc tính deps
của android_binary
.
Ví dụ: nếu bạn cố gắng tạo một mục tiêu android_library
có phần phụ thuộc cc_library
mà không có bất kỳ cờ nào, bạn có thể gặp lỗi về tiêu đề JNI
bị thiếu:
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.
Lý tưởng nhất là những chuyển đổi tự động này sẽ giúp Bazel thực hiện đúng việc trong phần lớn trường hợp. Tuy nhiên, nếu mục tiêu trên dòng lệnh Bazel đã nằm dưới bất kỳ quy tắc chuyển đổi nào trong số này, chẳng hạn như nhà phát triển C++ đang kiểm thử một cc_library
cụ thể, thì bạn phải sử dụng --crosstool_top
tuỳ chỉnh.
Xây dựng cc_library
cho Android mà không cần sử dụng android_binary
Để tạo cc_binary
hoặc cc_library
độc lập cho Android mà không cần sử dụng android_binary
, hãy sử dụng cờ --crosstool_top
, --cpu
và --host_crosstool_top
.
Ví dụ:
bazel build //my/cc/jni:target \
--crosstool_top=@androidndk//:default_crosstool \
--cpu=<abi> \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain
Trong ví dụ này, mục tiêu cc_library
và cc_binary
cấp cao nhất được tạo bằng cách sử dụng chuỗi công cụ NDK. Tuy nhiên, điều này khiến các công cụ lưu trữ của Bazel được xây dựng bằng chuỗi công cụ NDK (và do đó cũng dành cho Android), vì chuỗi công cụ lưu trữ được sao chép từ chuỗi công cụ mục tiêu. Để giải quyết vấn đề này, hãy chỉ định giá trị của --host_crosstool_top
là @bazel_tools//tools/cpp:toolchain
để đặt chuỗi công cụ C++ của máy chủ một cách rõ ràng.
Với phương pháp này, toàn bộ cây bản dựng sẽ bị ảnh hưởng.
Bạn có thể đặt các cờ này vào cấu hình bazelrc
(một cờ cho mỗi ABI) trong
project/.bazelrc
:
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
Sau đó, để tạo cc_library
cho x86
chẳng hạn, hãy chạy:
bazel build //my/cc/jni:target --config=android_x86
Nhìn chung, hãy sử dụng phương thức này cho các mục tiêu cấp thấp (như cc_library
) hoặc khi bạn biết chính xác những gì mình đang tạo; dựa vào các lượt chuyển đổi cấu hình tự động từ android_binary
cho các mục tiêu cấp cao mà bạn muốn xây dựng rất nhiều mục tiêu mà bạn không kiểm soát.