Nếu bạn mới sử dụng Bazel, vui lòng bắt đầu bằng 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 bản dựng, bao gồm cả một số cấu hình sử dụng chuỗi công cụ Bộ công cụ phát triển gốc của Android (NDK). Điều này có nghĩa là các quy tắc cc_library
và cc_binary
thông thường có thể được biên dịch 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
và tiện ích bzlmod có liên quan.
Để biên dịch Android nói chung, hãy dùng rules_android
.
Hướng dẫn này minh hoạ cách tích hợp các phần phụ thuộc thư viện C++ vào ứng dụng Android và sử dụng rules_android_ndk
để khám phá và đăng ký chuỗi công cụ NDK.
Điều kiện tiên quyết
Vui lòng đảm bảo rằng bạn đã cài đặt SDK và NDK Android.
Thiết lập NDK và SDK
Chế độ thiết lập kho lưu trữ bên ngoài sẽ khác nhau tuỳ thuộc vào việc bạn đang sử dụng WORKSPACE hay bzlmod (MODULE.bazel). Bzlmod là giải pháp ưu tiên cho Bazel 7 trở lên. Xin lưu ý rằng các đoạn thiết lập MODULE.bazel và WORKSPACE độc lập với nhau. Nếu đang sử dụng một giải pháp quản lý phần phụ thuộc, bạn không cần thêm mã khởi tạo cho giải pháp còn lại.
Thiết lập MODULE.bazel của Bzlmod
Thêm đoạn mã sau vào MODULE.bazel:
# NDK
bazel_dep(name = "rules_android_ndk", version = "0.1.3")
android_ndk_repository_extension = use_extension("@rules_android_ndk//:extension.bzl", "android_ndk_repository_extension")
use_repo(android_ndk_repository_extension, "androidndk")
register_toolchains("@androidndk//:all")
# SDK
bazel_dep(name = "rules_android", version = "0.6.6")
register_toolchains(
"@rules_android//toolchains/android:android_default_toolchain",
"@rules_android//toolchains/android_sdk:android_sdk_tools",
)
android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
use_repo(android_sdk_repository_extension, "androidsdk")
register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all")
Chế độ thiết lập WORKSPACE cũ
Thêm đoạn mã sau vào WORKSPACE
:
load("@rules_android//rules:rules.bzl", "android_sdk_repository")
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.
)
load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
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.
)
Lưu ý về khả năng tương thích đối với WORKSPACE:
- Cả hai quy tắc
rules_android
vàrules_android_ndk
đều yêu cầu thêm phần nội dung tiêu chuẩn không được mô tả trong đoạn mã WORKSPACE ở trên. Để biết một đoạn mã khởi tạo đầy đủ và mới nhất, hãy xem tệp WORKSPACE của ứng dụng mẫu cơ bảnrules_android_ndk
.
Để biết thêm thông tin về quy tắc android_ndk_repository
, hãy xem chuỗi tài liệu của quy tắc này.
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ụ: giả sử tệp BUILD
sau đây cho một ứng dụng Android:
# In <project>/app/src/main/BUILD.bazel
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_android//rules:rules.bzl", "android_binary", "android_library")
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 sẽ tạo ra biểu đồ đích sau:
Hình 1. Tạo biểu đồ dự án Android với 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 --android_platforms=<your platform>
Xin lưu ý rằng nếu bạn không chỉ định --android_platforms
, bản dựng của bạn sẽ gặp lỗi về việc thiếu tiêu đề JNI.
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 duy nhất (.so
), nhắm đến các cấu trúc được chỉ định theo --android_platforms
.
Hãy xem phần về định cấu hình ABI mục tiêu để biết thêm thông tin.
Ví dụ về cách thiết lập
Ví dụ này có trong kho lưu trữ ví dụ về Bazel.
Trong tệp BUILD.bazel
, 3 mục tiêu được xác định bằng các quy tắc android_binary
, android_library
và cc_library
.
Mục tiêu cấp cao nhất android_binary
sẽ tạo APK.
Mục tiêu cc_library
chứa một tệp nguồn C++ duy nhất có một 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
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 ABI mục tiêu
Để định cấu hình ABI mục tiêu, hãy dùng cờ --android_platforms
như sau:
bazel build //:app --android_platforms=comma-separated list of platforms
Giống như cờ --platforms
, các giá trị được truyền đến --android_platforms
là nhãn của các mục tiêu platform
, sử dụng các giá trị ràng buộc tiêu chuẩn để mô tả thiết bị của bạn.
Ví dụ: đối với một thiết bị Android có bộ xử lý ARM 64 bit, bạn sẽ xác định nền tảng của mình như sau:
platform(
name = "android_arm64",
constraint_values = [
"@platforms//os:android",
"@platforms//cpu:arm64",
],
)
Mọi platform
Android đều phải sử dụng ràng buộc hệ điều hành @platforms//os:android
. Để di chuyển ràng buộc về CPU, hãy xem biểu đồ này:
Giá trị CPU | Nền tảng |
---|---|
armeabi-v7a |
@platforms//cpu:armv7 |
arm64-v8a |
@platforms//cpu:arm64 |
x86 |
@platforms//cpu:x86_32 |
x86_64 |
@platforms//cpu:x86_64 |
Và tất nhiên, đối với một APK đa kiến trúc, bạn sẽ truyền nhiều nhãn, ví dụ: --android_platforms=//:arm64,//:x86_64
(giả sử bạn đã xác định các nhãn đó trong tệp BUILD.bazel
cấp cao nhất).
Bazel không thể chọn nền tảng Android mặc định, vì vậy, bạn phải xác định và chỉ định một nền tảng bằng --android_platforms
.
Tuỳ thuộc vào bản sửa đổi NDK và cấp độ API Android, bạn có thể sử dụng các ABI sau:
Bản sửa đổi NDK | ABI |
---|---|
16 tuổi trở xuống | armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64 |
17 tuổi trở lên | armeabi-v7a, arm64-v8a, x86, x86_64 |
Hãy 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 dùng APK lớn có nhiều ABI cho các bản dựng phát hành vì chúng làm tăng kích thước của APK, nhưng có thể hữu ích cho các bản dựng phát triển và QA.
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++98 | Mặc định, không cần cờ |
C++11 | --cxxopt=-std=c++11 |
C++14 | --cxxopt=-std=c++14 |
C++17 | --cxxopt=-std=c++17 |
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ờ của trình biên dịch và trình liên kết dưới dạng các 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ạo cc_library
cho Android mà không cần dùng android_binary
Để tạo một cc_binary
hoặc cc_library
độc lập cho Android mà không cần dùng android_binary
, hãy sử dụng cờ --platforms
.
Ví dụ: giả sử bạn đã xác định các nền tảng Android trong my/platforms/BUILD
:
bazel build //my/cc/jni:target \
--platforms=//my/platforms:x86_64
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ỗi cờ cho một ABI), trong project/.bazelrc
:
common:android_x86 --platforms=//my/platforms:x86
common:android_armeabi-v7a --platforms=//my/platforms:armeabi-v7a
# In general
common:android_<abi> --platforms=//my/platforms:<abi>
Sau đó, để tạo một cc_library
cho x86
, chẳng hạn như, hãy chạy:
bazel build //my/cc/jni:target --config=android_x86
Nói chung, hãy sử dụng phương thức này cho các mục tiêu cấp thấp (chẳng hạn như cc_library
) hoặc khi bạn biết chính xác những gì mình đang xây dựng; hãy dựa vào các quá trình 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 dự kiến sẽ xây dựng nhiều mục tiêu mà bạn không kiểm soát.