Bazel ile Android Yerel Geliştirme Kiti'ni kullanma

Bazel'i kullanmaya yeni başladıysanız lütfen Bazel ile Android oluşturma eğiticisiyle 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 deposu kuralını kullanarak gerçekleştirir.

Ö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 aşağıdaki snippet'i WORKSPACE cihazınıza 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 Ansiklopedi Oluşturma girişi'ne bakın.

Hızlı başlangıç

Android için C++ oluşturmak istiyorsanız 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ı göz önünde bulundurulduğunda:

# 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ı şu hedef grafiğinle sonuçlanıyor:

Örnek sonuçlar

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

Uygulamayı oluşturmak için şu komutu ç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'da 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 öğelerini varsayılan olarak armeabi-v7a ABI'yı hedefleyen tek bir paylaşılan nesne (.so) dosyasında derler. Bunu değiştirmek veya aynı anda birden çok ABI oluşturmak için hedef ABI'yi yapılandırma bölümüne bakın.

Örnek kurulum

Bu örneği Bazel örnekleri deposunda bulabilirsiniz.

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

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

cc_library hedefi, JNI işlevi uygulamasına sahip 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 bölgesinde kullanılabilen STL'ler şunlardır:

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

r16 ve altı için varsayılan STL gnustl'dir. r17 ve sonraki sürümler için bu değer libc++ şeklindedir. Kolaylık sağlamak amacıyla @androidndk//:default_crosstool hedefi, ilgili varsayılan STL'ler ile tasfiye edilir.

r18'den itibaren STLport ve gnustl'ın kaldırılacağını ve böylece libc++'nin NDK'daki tek STL'yi oluşturacağını lütfen unutmayın.

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

Hedef ABI'yı 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

Varsayılan olarak Bazel, armeabi-v7a için yerel Android kodu oluşturur. x86 için derlemek amacıyla (ör. emülatörler için) --fat_apk_cpu=x86 aktarın. Birden fazla mimari için yağ APK oluşturmak isterseniz 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 düzeltmesine ve Android API düzeyine bağlı olarak aşağıdaki ABI'ler kullanılabilir:

NDK düzeltmesi ABI'lar
16 ve altı armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 ve üzeri armeabi-v7a, arm64-v8a, x86, x86_64

Bu ABI'ler hakkında daha fazla bilgi için NDK belgelerine bakın.

Multi-ABI Fat APK'ları, APK'nın boyutunu büyüttükleri için sürüm derlemeleri için önerilmez, ancak geliştirme ve KG derlemeleri için yararlı olabilirler.

C++ standardı seçme

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

C++ Standart İş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

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

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, platformlara ve araç zincirlerine doğru hareket etmektedir. Derlemeniz, geliştirme yapılacak mimari veya işletim sistemi için seçim yapmak üzere --platforms işaretini kullanıyorsa NDK'yı kullanabilmek için --extra_toolchains işaretini Bazel'e iletmeniz gerekir.

Örneğin, Go kuralları tarafından sağlanan android_arm64_cgo araç zinciriyle entegrasyon yapmak için --platforms işaretinin yanı sıra --extra_toolchains=@androidndk//:all değerini de geçirin.

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

Dilerseniz doğrudan WORKSPACE dosyasına da kaydedebilirsiniz:

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

Bu araç zincirlerinin kaydedilmesi, Bazel'e mimari ve işletim sistemi kısıtlamalarını çözümlerken bunları NDK BUILD dosyasında (NDK 20 için) aramasını bildirir:

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 kullanıma sunuyoruz

android_binary kuralı, Bazel'dan bağımlılıklarını Android uyumlu bir yapılandırmada oluşturmasını açıkça isteyebilir. Böylece Bazel derlemesi, ABI ve STL yapılandırması için --fat_apk_cpu ve --android_crosstool_top dışında herhangi bir özel işaret olmadan tamamen ç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ın yapılandırmasını otomatik olarak Android yapılandırmasıyla değiştirir. Bu nedenle, derlemenin yalnızca Android'e özel 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 destekleyecek yollar varsa her iki yapılandırmada da tek bir hedefi işleyebilir.

Bazel en üst düzeyde belirtilmiş veya daha üst seviyedeki bir geçiş noktası nedeniyle Android uyumlu bir yapılandırmaya ulaştığında, karşılaşılan ek geçiş noktaları yapılandırmayı değiştirmez.

Android yapılandırmasına geçişi tetikleyen tek yerleşik konum, android_binary uygulamasını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 JNI başlığıyla ilgili bir 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şlerin Bazel'in vakaların çoğunda doğru olanı yapmasını sağlaması gerekir. Ancak Bazel komut satırındaki hedef bu geçiş kurallarından herhangi birinin zaten altındaysa (örneğin, belirli bir cc_library öğesini test eden C++ geliştiricilerinin) ö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 istiyorsanız --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şturulmuştur. Ancak bu, ana makine araç zincirinin hedef araç zincirinden kopyalanması nedeniyle, 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 ana makinenin C++ araç zincirini açıkça ayarlamak üzere --host_crosstool_top değerini @bazel_tools//tools/cpp:toolchain olacak şekilde belirtin.

Bu yaklaşımda tüm yapı ağacı etkilenir.

Bu işaretler, project/.bazelrc içinde bir bazelrc yapılandırmasına (her ABI için bir tane) 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 seviyeli hedefler (cc_library gibi) için veya ne geliştirdiğinizi tam olarak biliyorsanız kullanın. Kontrol etmediğiniz çok sayıda hedef oluşturmayı beklediğiniz üst düzey hedefler için android_binary ürününden yapılan otomatik yapılandırma geçişlerini kullanın.