Sử dụng Bộ phát triển gốc Android với Bazel

Báo cáo vấn đề Xem nguồn Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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_librarycc_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.

Đ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 SDK và NDK, hãy thêm đoạn mã sau vào 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.
)

Để biết thêm thông tin về quy tắc android_ndk_repository, hãy xem mục Build Encyclopedia.

Nếu bạn đang sử dụng phiên bản gần đây của Android NDK (r22 trở lên), hãy sử dụng chế độ triển khai Starlark của android_ndk_repository. Làm theo hướng dẫn trong README của công 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ử bạn có 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 sẽ tạo ra biểu đồ đích sau:

Kết quả mẫu

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

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), theo mặc định nhắm đến ABI armeabi-v7a. Để thay đổi chế độ này hoặc tạo cho nhiều ABI cùng một lúc, hãy xem phần định cấu hình ABI mục tiêu.

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_librarycc_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, các ABI sau đây sẽ có sẵn:

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--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 coptslinkopts. 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ột cờ cho mỗi 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.