Bazel ile Android Yerel Geliştirme Kiti'ni kullanma

7.3 · 7.2 · 7.1 · 7.0 · 6.5

Bazel'i kullanmaya yeni başladıysanız lütfen Bazel ile Android geliştirme eğitimiyle başlayın.

Genel Bakış

Bazel, Android Yerel Geliştirme Kiti (NDK) araç zincirini kullanan birkaç yapılandırma da dahil olmak üzere birçok farklı derleme yapılandırmasında çalışabilir. Bu, normal cc_library ve cc_binary kurallarının Android için doğrudan Bazel'da derlenebileceği anlamına gelir. Bazel, bunu android_ndk_repository depo kuralını kullanarak yapar.

Ön koşullar

Lütfen Android SDK'sını ve NDK'yı yüklediğinizden emin olun.

SDK'yı ve NDK'yı ayarlamak için WORKSPACE dosyanıza aşağıdaki snippet'i ekleyin:

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 kuralı hakkında daha fazla bilgi için Build Ansiklopedisi girişine bakın.

Hızlı başlangıç

Android için C++ geliştirmek üzere android_binary veya android_library kurallarınıza cc_library bağımlılıkları eklemeniz yeterlidir.

Örneğin, bir Android uygulaması için aşağıdaki BUILD dosyası verilmiştir:

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

Bu BUILD dosyası aşağıdaki hedef grafiği oluşturur:

Örnek sonuçlar

Şekil 1. cc_library bağımlılıkları olan Android projesinin grafiğini oluşturun.

Uygulamayı derlemek için şunları çalıştırmanız yeterlidir:

bazel build //app/src/main:app

bazel build komutu, Java dosyalarını, Android kaynak dosyalarını ve cc_library kurallarını derleyip her şeyi bir APK'ya paketler:

$ 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, tüm cc_libraries dosyalarını varsayılan olarak armeabi-v7a ABI'yi hedefleyen tek bir paylaşılan nesne (.so) dosyasında derleyebilir. Bunu değiştirmek veya aynı anda birden fazla ABI için derleme yapmak istiyorsanız hedef ABI'yi yapılandırma bölümüne bakın.

Örnek kurulum

Bu örnek, Bazel örnekleri deposunda bulunmaktadır.

BUILD.bazel dosyasında android_binary, android_library ve cc_library kurallarıyla üç hedef tanımlanmıştır.

android_binary üst düzey hedefi APK'yı oluşturur.

cc_library hedefi, JNI işlev uygulamasını içeren tek bir C++ kaynak dosyası içerir:

#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 hedefi; Java kaynaklarını, kaynak dosyalarını ve bir cc_library hedefine bağımlılığı belirtir. Bu örnekte MainActivity.java, libapp.so paylaşılan nesne dosyasını yükler ve JNI işlevi için yöntem imzasını tanımlar:

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromJNI();

}

STL'yi yapılandırma

C++ STL'yi yapılandırmak için --android_crosstool_top işaretini kullanın.

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

@androidndk'te kullanılabilen STL'ler şunlardır:

STL Hedef etiket
STLport @androidndk//:toolchain-stlport
libc++ @androidndk//:toolchain-libcpp
gnustl @androidndk//:toolchain-gnu-libstdcpp

r16 ve önceki sürümlerde varsayılan STL gnustl'tür. r17 ve sonraki sürümler için libc++ değerini kullanın. Kolaylık sağlamak için hedef @androidndk//:default_crosstool, ilgili varsayılan STL'lere takma ad olarak atanır.

r18'den itibaren STLport ve gnustl'nin kaldırılacağını, bu nedenle NDK'da yalnızca libc++ STL'sinin kullanılabileceğini lütfen unutmayın.

Bu STL'ler hakkında daha fazla bilgi için NDK belgelerini inceleyin.

Hedef ABI'yi yapılandırma

Hedef ABI'yi yapılandırmak için --fat_apk_cpu işaretini aşağıdaki gibi kullanın:

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

Bazel, varsayılan olarak armeabi-v7a için yerel Android kodu oluşturur. x86 için derlemek (örneğin emülatörlerde) için --fat_apk_cpu=x86 değerini iletin. Birden fazla mimari için büyük bir APK oluşturmak istiyorsanız birden fazla CPU belirtebilirsiniz: --fat_apk_cpu=armeabi-v7a,x86.

Birden fazla ABI belirtilirse Bazel, her ABI için paylaşılan nesne içeren bir APK oluşturur.

NDK revizyonuna ve Android API düzeyine bağlı olarak aşağıdaki ABI'ler kullanılabilir:

NDK sürümü ABI'lar
16 ve altı armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 yaş ve üstü armeabi-v7a, arm64-v8a, x86, x86_64

Bu ABI'ler hakkında daha fazla bilgi için NDK dokümanlarına bakın.

Çoklu ABI'li büyük APK'lar, APK'nın boyutunu artırdığından sürüm derlemeleri için önerilmez ancak geliştirme ve kalite kontrol derlemeleri için yararlı olabilir.

C++ standardı seçme

C++ standardına göre derlemek için aşağıdaki işaretleri kullanın:

C++ Standard İşaret
C++98 Varsayılan, işaret gerekmez
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14

Örneğin:

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

Derleyici ve bağlayıcı işaretlerini --cxxopt, --copt ve --linkopt ile iletme hakkında daha fazla bilgiyi Kullanıcı Kılavuzu'nda bulabilirsiniz.

Derleyici ve bağlayıcı işaretleri, copts ve linkopts kullanılarak cc_library içinde özellik olarak da belirtilebilir. Örneğin:

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

Platformlar ve araç zincirleriyle entegrasyon

Bazel'in yapılandırma modeli platformlar ve araç zincirlerine taşınıyor. Derlemeniz, derleme için mimariyi veya işletim sistemini seçmek üzere --platforms işaretini kullanıyorsa NDK'yı kullanmak için --extra_toolchains işaretini Bazel'e iletmeniz gerekir.

Örneğin, Go kuralları tarafından sağlanan android_arm64_cgo araç setiyle entegrasyon yapmak için --platforms işaretçisine ek olarak --extra_toolchains=@androidndk//:all işaretçisini iletin.

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

Bunları doğrudan WORKSPACE dosyasına da kaydedebilirsiniz:

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

Bu araç zincirlerini kaydettiğinizde Bazel, mimari ve işletim sistemi kısıtlamalarını çözerken bunları NDK BUILDdosyasında (NDK 20 için) arayacaktır:

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

İşleyiş şekli: Android yapılandırma geçişlerini tanıtıyoruz

android_binary kuralı, Bazel'den bağımlılıklarını Android uyumlu bir yapılandırmada derlemesini açıkça isteyebilir. Böylece Bazel derlemesi, ABI ve STL yapılandırması için --fat_apk_cpu ve --android_crosstool_top hariç herhangi bir özel işaret olmadan sadece çalışır.

Bu otomatik yapılandırma, perde arkasında Android yapılandırma geçişlerini kullanır.

android_binary gibi uyumlu bir kural, bağımlılıkların yapılandırmasını otomatik olarak bir Android yapılandırmasına değiştirir. Bu nedenle, derlemenin yalnızca Android'e özgü alt ağaçları etkilenir. Derleme grafiğinin diğer bölümleri, üst düzey hedef yapılandırması kullanılarak işlenir. Oluşturma grafiğinde bunu destekleyen yollar varsa her iki yapılandırmada da tek bir hedefi işleyebilir.

Bazel, üst düzeyde veya daha üst düzey bir geçiş noktası nedeniyle Android uyumlu bir yapılandırmada olduğunda, karşılaşılan ek geçiş noktaları yapılandırmada başka bir değişiklik yapmaz.

Android yapılandırmasına geçişi tetikleyen tek yerleşik konum, android_binary'ın deps özelliğidir.

Örneğin, herhangi bir işaret olmadan cc_library bağımlılığına sahip bir android_library hedefi oluşturmaya çalışırsanız eksik bir JNI üstbilgisiyle ilgili hatayla karşılaşabilirsiniz:

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.

İdeal olarak bu otomatik geçişler, Bazel'in çoğu durumda doğru şeyi yapmasını sağlar. Ancak Bazel komut satırındaki hedef, bu geçiş kurallarının herhangi birinin altındaysa (ör. C++ geliştiricileri belirli bir cc_library'ü test ediyorsa) özel bir --crosstool_top kullanılmalıdır.

android_binary kullanmadan Android için cc_library oluşturma

android_binary kullanmadan Android için bağımsız bir cc_binary veya cc_library oluşturmak isterseniz --crosstool_top, --cpu ve --host_crosstool_top işaretlerini kullanın.

Örneğin:

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

Bu örnekte, üst düzey cc_library ve cc_binary hedefleri NDK araç zinciri kullanılarak oluşturulur. Ancak bu, ana makine araç zinciri hedef araç zincirinden kopyalandığı için Bazel'in kendi ana makine araçlarının NDK araç zinciriyle (ve dolayısıyla Android için) oluşturulmasına neden olur. Bu sorunu gidermek için barındırıcının C++ araç zincirini açıkça ayarlamak üzere --host_crosstool_top değerini @bazel_tools//tools/cpp:toolchain olarak belirtin.

Bu yaklaşımda derleme ağacının tamamı etkilenir.

Bu işaretler, bazelrc yapılandırmasına (her ABI için bir tane) project/.bazelrc aşağıdaki şekilde eklenebilir:

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

Ardından, örneğin x86 için bir cc_library oluşturmak üzere şu komutu çalıştırın:

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

Genel olarak, bu yöntemi düşük düzey hedefler (cc_library gibi) için veya tam olarak ne oluşturduğunuzu bildiğiniz durumlarda kullanın. Kontrol etmediğiniz çok sayıda hedef oluşturmayı beklediğiniz yüksek düzey hedefler için android_binary'ten otomatik yapılandırma geçişlerine güvenin.