Menggunakan Android Native Development Kit dengan Bazel

Laporkan masalah Lihat sumber Per Malam · 7,4 kami. 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Jika Anda baru mengenal Bazel, mulailah dengan Membangun Android dengan Bazel.

Ringkasan

Bazel dapat berjalan di berbagai konfigurasi build yang berbeda, termasuk beberapa yang menggunakan toolchain Android Native Development Kit (NDK). Artinya, aturan cc_library dan cc_binary normal dapat dikompilasi untuk Android langsung dalam Bazel. Bazel melakukannya dengan menggunakan repositori android_ndk_repository aturan.

Prasyarat

Pastikan Anda telah menginstal Android SDK dan NDK.

Untuk menyiapkan SDK dan NDK, tambahkan cuplikan berikut ke WORKSPACE Anda:

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

Untuk informasi selengkapnya tentang aturan android_ndk_repository, lihat dokumen Build Entri Ensiklopedia.

Jika Anda menggunakan Android NDK versi terbaru (r22 dan yang lebih baru), gunakan implementasi Starlark android_ndk_repository. Ikuti petunjuk di README-nya.

Mulai cepat

Untuk mem-build C++ untuk Android, cukup tambahkan dependensi cc_library ke aturan android_binary atau android_library Anda.

Misalnya, dengan file BUILD berikut untuk aplikasi 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",
)

File BUILD ini menghasilkan grafik target berikut:

Hasil contoh

Gambar 1. Membuat grafik project Android dengan dependensi cc_library.

Untuk membuat aplikasi, cukup jalankan:

bazel build //app/src/main:app

Perintah bazel build mengompilasi file Java, file resource Android, dan aturan cc_library, serta memaketkan semuanya ke dalam 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 mengompilasi semua cc_libraries menjadi satu file objek bersama (.so), yang ditargetkan untuk ABI armeabi-v7a secara default. Untuk mengubahnya atau membangun untuk beberapa ABI secara bersamaan, lihat bagian tentang mengonfigurasi target ABI lebih lanjut.

Contoh penyiapan

Contoh ini tersedia dalam contoh Bazel repositori Anda.

Dalam file BUILD.bazel, tiga target ditentukan dengan aturan android_binary, android_library, dan cc_library.

Target tingkat atas android_binary membuat APK.

Target cc_library berisi satu file sumber C++ dengan implementasi fungsi JDK:

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

Target android_library menentukan sumber Java, file resource, dan dependensi pada target cc_library. Untuk contoh ini, MainActivity.java memuat file objek bersama libapp.so, dan menentukan tanda tangan metode untuk JNI {i>function<i}:

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromJNI();

}

Mengkonfigurasi STL

Untuk mengonfigurasi STL C++, gunakan flag --android_crosstool_top.

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

STL yang tersedia di @androidndk adalah:

STL Label target
STLport @androidndk//:toolchain-stlport
libc++ @androidndk//:toolchain-libcpp
gnustl @androidndk//:toolchain-gnu-libstdcpp

Untuk r16 dan yang lebih lama, STL default-nya adalah gnustl. Untuk r17 dan yang lebih baru, nilainya adalah libc++. Untuk memudahkan, @androidndk//:default_crosstool target akan diberi alias ke STL default masing-masing.

Perlu diketahui bahwa mulai r18 dan seterusnya, STLport dan gnustl akan dihapus, menjadikan libc++ satu-satunya STL di NDK.

Lihat dokumentasi NDK untuk mengetahui informasi selengkapnya tentang STL ini.

Mengonfigurasi ABI target

Untuk mengonfigurasi ABI target, gunakan flag --fat_apk_cpu sebagai berikut:

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

Secara default, Bazel mem-build kode Android native untuk armeabi-v7a. Untuk membangun aplikasi x86 (misalnya untuk emulator), teruskan --fat_apk_cpu=x86. Untuk membuat APK gemuk untuk beberapa Anda dapat menentukan beberapa CPU sekaligus: --fat_apk_cpu=armeabi-v7a,x86.

Jika lebih dari satu ABI ditentukan, Bazel akan membangun APK yang berisi untuk setiap ABI.

Bergantung pada revisi NDK dan level API Android, ABI berikut adalah tersedia:

Revisi NDK ABI
16 tahun ke bawah armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 ke atas armeabi-v7a, arm64-v8a, x86, x86_64

Lihat dokumen NDK untuk mengetahui informasi selengkapnya tentang ABI ini.

Fat APK Multi-ABI tidak direkomendasikan untuk build rilis karena meningkatkan ukuran APK, tetapi dapat berguna untuk build pengembangan dan QA.

Memilih standar C++

Gunakan flag berikut untuk membangun sesuai dengan standar C++:

Standar C++ Bendera
C++98 Default, tidak perlu tanda
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14

Contoh:

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

Baca selengkapnya tentang meneruskan tanda compiler dan penaut dengan --cxxopt, --copt, dan --linkopt di Panduan Pengguna.

Tanda compiler dan penaut juga dapat ditentukan sebagai atribut di cc_library menggunakan copts dan linkopts. Contoh:

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

Integrasi dengan platform dan toolchain

Model konfigurasi Bazel bergerak menuju platform dan Rantai Konten. Jika build Anda menggunakan flag --platforms untuk memilih arsitektur atau sistem operasi yang akan di-build, Anda harus meneruskan flag --extra_toolchains ke Bazel untuk menggunakan NDK.

Misalnya, untuk berintegrasi dengan toolchain android_arm64_cgo yang disediakan oleh aturan Go, teruskan --extra_toolchains=@androidndk//:all selain tanda --platforms.

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

Anda juga dapat mendaftarkannya langsung di file WORKSPACE:

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

Dengan mendaftarkan toolchain ini, Bazel akan diberi tahu untuk mencarinya di BUILD NDK (untuk NDK 20) saat me-resolve arsitektur dan batasan sistem operasi:

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

Cara kerjanya: memperkenalkan transisi konfigurasi Android

Aturan android_binary dapat secara eksplisit meminta Bazel untuk mem-build dependensinya dalam konfigurasi yang kompatibel dengan Android sehingga build Bazel langsung berfungsi tanpa flag khusus, kecuali untuk --fat_apk_cpu dan --android_crosstool_top untuk konfigurasi ABI dan STL.

Di balik layar, konfigurasi otomatis ini menggunakan konfigurasi Android ini.

Aturan yang kompatibel, seperti android_binary, secara otomatis mengubah konfigurasi dependensinya menjadi konfigurasi Android, sehingga hanya subtree khusus Android dari build yang terpengaruh. Bagian build lainnya grafik diproses menggunakan konfigurasi target tingkat teratas. Bahkan, build ini dapat memproses satu target di kedua konfigurasi, jika ada jalur melalui grafik build untuk mendukungnya.

Setelah Bazel berada dalam konfigurasi yang kompatibel dengan Android, tingkat teratas atau karena titik transisi tingkat yang lebih tinggi, transisi tambahan titik yang ditemui tidak mengubah konfigurasi lebih lanjut.

Satu-satunya lokasi bawaan yang memicu transisi ke Android konfigurasi adalah atribut deps dari android_binary.

Misalnya, jika Anda mencoba membuat target android_library dengan cc_library dependensi tanpa tanda apa pun, Anda mungkin akan mengalami error tentang JNI yang tidak ada {i>header<i}:

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.

Idealnya, transisi otomatis ini harus membuat Bazel melakukan hal yang benar dalam kebanyakan kasus. Namun, jika target pada baris perintah Bazel sudah di bawah salah satu aturan transisi ini, seperti developer C++ yang menguji cc_library, maka --crosstool_top kustom harus digunakan.

Membuat cc_library untuk Android tanpa menggunakan android_binary

Untuk membuat cc_binary atau cc_library mandiri bagi Android tanpa menggunakan elemen android_binary, gunakan --crosstool_top, --cpu, dan --host_crosstool_top penanda.

Contoh:

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

Dalam contoh ini, target cc_library dan cc_binary level teratas dibuat menggunakan toolchain NDK. Namun, hal ini menyebabkan alat {i>host<i} Bazel dibuat dengan toolchain NDK (dan juga untuk Android), karena toolchain host yang disalin dari toolchain target. Untuk mengatasi hal ini, tentukan nilai --host_crosstool_top menjadi @bazel_tools//tools/cpp:toolchain untuk menetapkan toolchain C++ host secara eksplisit.

Dengan pendekatan ini, seluruh hierarki build akan terpengaruh.

Flag ini dapat dimasukkan ke dalam konfigurasi bazelrc (satu untuk setiap ABI), di 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

Kemudian, untuk membangun cc_library untuk x86, misalnya, jalankan:

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

Secara umum, gunakan metode ini untuk target tingkat rendah (seperti cc_library) atau saat Anda mengetahui dengan tepat apa yang Anda build; andalkan transisi konfigurasi otomatis dari android_binary untuk target tingkat tinggi tempat Anda mengharapkan untuk mem-build banyak target yang tidak Anda kontrol.