Bazel के साथ Android नेटिव डेवलपमेंट किट का इस्तेमाल करना

अगर आपने Bazel का इस्तेमाल पहली बार किया है, तो कृपया Bazel की मदद से Android ऐप्लिकेशन बनाने का ट्यूटोरियल देखें.

खास जानकारी

Bazel को कई अलग-अलग बिल्ड कॉन्फ़िगरेशन में चलाया जा सकता है. इनमें से कुछ कॉन्फ़िगरेशन, Android Native Development Kit (NDK) टूलचेन का इस्तेमाल करते हैं. इसका मतलब है कि सामान्य cc_library और cc_binary नियमों को सीधे Bazel में Android के लिए कंपाइल किया जा सकता है. Bazel, android_ndk_repository रिपॉज़िटरी नियम का इस्तेमाल करके ऐसा करता है.

ज़रूरी शर्तें

कृपया पक्का करें कि आपने Android SDK और NDK इंस्टॉल किया हो.

SDK और NDK सेट अप करने के लिए, अपने 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.
)

android_ndk_repository नियम के बारे में ज़्यादा जानने के लिए, Build Encyclopedia में मौजूद एंट्री देखें.

अगर आपने Android NDK (r22 और इसके बाद के वर्शन) का नया वर्शन इस्तेमाल किया है, तो android_ndk_repository के Starlark लागू करने की सुविधा का इस्तेमाल करें. इसके README में दिए गए निर्देशों का पालन करें.

तुरंत शुरू करना

Android के लिए C++ बनाने के लिए, अपने android_binary या android_library नियमों में cc_library डिपेंडेंसी जोड़ें.

उदाहरण के लिए, Android ऐप्लिकेशन के लिए यहां दी गई BUILD फ़ाइल देखें:

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

इस BUILD फ़ाइल से, टारगेट का यह ग्राफ़ बनता है:

परिणामों के उदाहरण

पहली इमेज. cc_library डिपेंडेंसी वाले Android प्रोजेक्ट का बिल्ड ग्राफ़.

ऐप्लिकेशन बनाने के लिए, यह कमांड चलाएं:

bazel build //app/src/main:app

bazel build कमांड, Java फ़ाइलें, Android रिसॉर्स फ़ाइलें, और cc_library नियमों को कंपाइल करता है. साथ ही, इन सभी को एक 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, सभी cc_libraries को एक शेयर किए गए ऑब्जेक्ट (.so) फ़ाइल में कंपाइल करता है. यह डिफ़ॉल्ट रूप से armeabi-v7a ABI के लिए टारगेट की जाती है. इसे बदलने या एक साथ कई ABI के लिए बिल्ड करने के लिए, टारगेट ABI को कॉन्फ़िगर करने से जुड़ा सेक्शन देखें.

सेटअप का उदाहरण

यह उदाहरण, Bazel के उदाहरणों की रिपॉज़िटरी में उपलब्ध है.

BUILD.bazel फ़ाइल में, android_binary, android_library, और cc_library नियमों के साथ तीन टारगेट तय किए गए हैं.

टॉप-लेवल android_binary टारगेट, APK बनाता है.

cc_library टारगेट में, JNI फ़ंक्शन को लागू करने वाली एक C++ सोर्स फ़ाइल शामिल होती है:

#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 टारगेट, Java सोर्स, रिसॉर्स फ़ाइलें, और cc_library टारगेट पर डिपेंडेंसी तय करता है. इस उदाहरण के लिए, MainActivity.java, शेयर किए गए ऑब्जेक्ट फ़ाइल libapp.so को लोड करता है. साथ ही, JNI फ़ंक्शन के लिए, तरीके के सिग्नेचर को तय करता है:

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromJNI();

}

एसटीएल को कॉन्फ़िगर करना

C++ STL को कॉन्फ़िगर करने के लिए, --android_crosstool_top फ़्लैग का इस्तेमाल करें.

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

@androidndk में ये STL उपलब्ध हैं:

STL टारगेट लेबल
STLport @androidndk//:toolchain-stlport
libc++ @androidndk//:toolchain-libcpp
gnustl @androidndk//:toolchain-gnu-libstdcpp

r16 और इससे पहले के वर्शन के लिए, डिफ़ॉल्ट STL, gnustl है. r17 और इसके बाद के वर्शन के लिए, यह libc++ है. सुविधा के लिए, टारगेट @androidndk//:default_crosstool को, डिफ़ॉल्ट STL के तौर पर एलियास किया जाता है.

कृपया ध्यान दें कि r18 के बाद, STLport और gnustl को हटा दिया जाएगा. इसके बाद, NDK में सिर्फ़ libc++ STL उपलब्ध होगा.

इन STL के बारे में ज़्यादा जानने के लिए, NDK दस्तावेज़ देखें.

टारगेट ABI को कॉन्फ़िगर करना

टारगेट ABI को कॉन्फ़िगर करने के लिए, --fat_apk_cpu फ़्लैग का इस्तेमाल इस तरह करें:

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

डिफ़ॉल्ट रूप से, Bazel, Android के नेटिव कोड को armeabi-v7a के लिए बनाता है. x86 के लिए बिल्ड करने के लिए (जैसे, एम्युलेटर के लिए), --fat_apk_cpu=x86 पास करें. कई आर्किटेक्चर के लिए फ़ैट APK बनाने के लिए, एक से ज़्यादा सीपीयू तय किए जा सकते हैं: --fat_apk_cpu=armeabi-v7a,x86.

अगर एक से ज़्यादा ABI तय किए जाते हैं, तो Bazel एक ऐसा APK बनाएगा जिसमें हर ABI के लिए, शेयर किया गया एक ऑब्जेक्ट होगा.

NDK के वर्शन और Android एपीआई लेवल के हिसाब से, ये ABI उपलब्ध हैं:

NDK का वर्शन एबीआई
16 और इससे पहले के वर्शन armeabi, armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 और इसके बाद के वर्शन armeabi-v7a, arm64-v8a, x86, x86_64

इन ABI के बारे में ज़्यादा जानने के लिए, NDK के दस्तावेज़ देखें.

रिलीज़ बिल्ड के लिए, मल्टी-ABI फ़ैट APK का इस्तेमाल करने का सुझाव नहीं दिया जाता. इसकी वजह यह है कि इससे APK का साइज़ बढ़ जाता है. हालांकि, डेवलपमेंट और QA बिल्ड के लिए यह काम का हो सकता है.

C++ स्टैंडर्ड चुनना

किसी C++ स्टैंडर्ड के मुताबिक बिल्ड करने के लिए, इन फ़्लैग का इस्तेमाल करें:

C++ स्टैंडर्ड फ़्लैग
C++98 डिफ़ॉल्ट, किसी फ़्लैग की ज़रूरत नहीं है
C++11 --cxxopt=-std=c++11
C++14 --cxxopt=-std=c++14

उदाहरण के लिए:

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

यूज़र मैन्युअल में, --cxxopt, --copt, और --linkopt की मदद से कंपाइलर और लिंकर फ़्लैग पास करने के बारे में ज़्यादा पढ़ें.

कंपाइलर और लिंकर फ़्लैग को, cc_library में एट्रिब्यूट के तौर पर भी तय किया जा सकता है. इसके लिए, copts और linkopts का इस्तेमाल करें. उदाहरण के लिए:

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

प्लैटफ़ॉर्म और टूलचेन के साथ इंटिग्रेशन

Bazel का कॉन्फ़िगरेशन मॉडल, प्लैटफ़ॉर्म और टूलचेन की ओर बढ़ रहा है. अगर आपका बिल्ड, आर्किटेक्चर या ऑपरेटिंग सिस्टम को चुनने के लिए --platforms फ़्लैग का इस्तेमाल करता है, तो NDK का इस्तेमाल करने के लिए, आपको Bazel को --extra_toolchains फ़्लैग पास करना होगा.

उदाहरण के लिए, Go के नियमों से दिए गए android_arm64_cgo टूलचेन के साथ इंटिग्रेट करने के लिए, --platforms फ़्लैग के अलावा, --extra_toolchains=@androidndk//:all पास करें.

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

इन्हें सीधे WORKSPACE फ़ाइल में भी रजिस्टर किया जा सकता है:

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

इन टूलचेन को रजिस्टर करने से, Bazel को आर्किटेक्चर और ऑपरेटिंग सिस्टम की पाबंदियों को हल करते समय, NDK की BUILD फ़ाइल (NDK 20 के लिए) में इन्हें ढूंढने का निर्देश मिलता है:

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

यह कैसे काम करता है: Android कॉन्फ़िगरेशन ट्रांज़िशन के बारे में जानकारी

android_binary नियम, Bazel से साफ़ तौर पर कह सकता है कि वह अपनी डिपेंडेंसी को Android के साथ काम करने वाले कॉन्फ़िगरेशन में बनाए. इससे Bazel बिल्ड, किसी खास फ़्लैग के बिना काम करता है. हालांकि, ABI और STL कॉन्फ़िगरेशन के लिए, --fat_apk_cpu और --android_crosstool_top फ़्लैग का इस्तेमाल किया जा सकता है.

बैकग्राउंड में, इस ऑटोमैटिक कॉन्फ़िगरेशन में Android कॉन्फ़िगरेशन ट्रांज़िशन का इस्तेमाल किया जाता है.

android_binary जैसे काम करने वाले नियम, अपनी डिपेंडेंसी के कॉन्फ़िगरेशन को अपने-आप Android कॉन्फ़िगरेशन में बदल देते हैं. इसलिए, बिल्ड के सिर्फ़ Android के लिए बने सबट्री पर असर पड़ता है. बिल्ड ग्राफ़ के अन्य हिस्सों को, टॉप-लेवल टारगेट कॉन्फ़िगरेशन का इस्तेमाल करके प्रोसेस किया जाता है. अगर बिल्ड ग्राफ़ में ऐसे पाथ मौजूद हैं जो किसी एक टारगेट को दोनों कॉन्फ़िगरेशन में प्रोसेस कर सकते हैं, तो ऐसा भी हो सकता है.

Bazel, Android के साथ काम करने वाले कॉन्फ़िगरेशन में होने पर, टॉप लेवल पर तय किए गए या किसी बड़े ट्रांज़िशन पॉइंट की वजह से, ट्रांज़िशन के अन्य पॉइंट मिलने पर भी कॉन्फ़िगरेशन में कोई बदलाव नहीं होता.

Android कॉन्फ़िगरेशन में ट्रांज़िशन को ट्रिगर करने वाली, सिर्फ़ एक जगह है. यह android_binary का deps एट्रिब्यूट है.

उदाहरण के लिए, अगर आपने किसी फ़्लैग के बिना, cc_library डिपेंडेंसी वाले android_library टारगेट को बिल्ड करने की कोशिश की, तो आपको JNI हेडर के न होने की गड़बड़ी मिल सकती है:

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.

आदर्श तौर पर, इन ऑटोमैटिक ट्रांज़िशन की मदद से, ज़्यादातर मामलों में Bazel सही तरीके से काम करेगा. हालांकि, अगर Bazel की कमांड लाइन पर मौजूद टारगेट, इनमें से किसी भी ट्रांज़िशन नियम के तहत आता है, तो कस्टम --crosstool_top का इस्तेमाल करना होगा. जैसे, C++ डेवलपर किसी खास cc_library की जांच कर रहे हैं.

android_binary का इस्तेमाल किए बिना, Android के लिए cc_library बनाना

android_binary का इस्तेमाल किए बिना, Android के लिए स्टैंडअलोन cc_binary या cc_library बनाने के लिए, --crosstool_top, --cpu, और --host_crosstool_top फ़्लैग का इस्तेमाल करें.

उदाहरण के लिए:

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

इस उदाहरण में, टॉप-लेवल cc_library और cc_binary टारगेट, NDK टूलचेन का इस्तेमाल करके बनाए जाते हैं. हालांकि, इससे Bazel के अपने होस्ट टूल, NDK टूलचेन (और इस तरह Android के लिए) के साथ बनाए जाते हैं. इसकी वजह यह है कि होस्ट टूलचेन को टारगेट टूलचेन से कॉपी किया जाता है. इससे बचने के लिए, होस्ट के C++ टूलचेन को साफ़ तौर पर सेट करने के लिए, --host_crosstool_top की वैल्यू को @bazel_tools//tools/cpp:toolchain पर सेट करें.

इस तरीके से, पूरे बिल्ड ट्री पर असर पड़ता है.

इन फ़्लैग को bazelrc कॉन्फ़िगरेशन (हर ABI के लिए एक) में, 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

इसके बाद, उदाहरण के लिए, x86 के लिए cc_library बनाने के लिए, यह कमांड चलाएं:

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

आम तौर पर, इस तरीके का इस्तेमाल लो-लेवल टारगेट (जैसे, cc_library) के लिए या तब करें, जब आपको पता हो कि आपको क्या बनाना है. हाई-लेवल टारगेट के लिए, android_binary से मिलने वाले ऑटोमैटिक कॉन्फ़िगरेशन ट्रांज़िशन पर भरोसा करें. ऐसा तब करें, जब आपको ऐसे कई टारगेट बनाने हों जिन्हें आप कंट्रोल नहीं करते.