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

संग्रह की मदद से व्यवस्थित रहें अपनी प्राथमिकताओं के आधार पर, कॉन्टेंट को सेव करें और कैटगरी में बांटें.
किसी समस्या की शिकायत करें स्रोत देखें

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

खास जानकारी

Bazel कई बिल्ड कॉन्फ़िगरेशन में काम कर सकता है. इनमें कई ऐसे कॉन्फ़िगरेशन भी शामिल हैं जो Android नेटिव डेवलपमेंट किट (एनडीके) टूलचेन का इस्तेमाल करते हैं. इसका मतलब है कि Android के लिए, सामान्य cc_library और cc_binary नियमों को सीधे बेज़ल में कंपाइल किया जा सकता है. बैज़ल ऐसा करने के लिए 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 नियम के बारे में ज़्यादा जानकारी के लिए, एन्साइक्लोपीडिया बनाएं देखें.

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

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

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

यह उदाहरण 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();

}

STL को कॉन्फ़िगर करना

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

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

@androidndk में उपलब्ध एसटीएल हैं:

एसटीएल टारगेट लेबल
स्टेलपोर्ट @androidndk//:toolchain-stlport
libc+ @androidndk//:toolchain-libcpp
gnustl @androidndk//:toolchain-gnu-libstdcpp

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

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

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

टारगेट एबीआई को कॉन्फ़िगर करना

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

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

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

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

एनडीके में किए गए बदलाव और Android एपीआई के लेवल के आधार पर, ये एबीआई उपलब्ध होते हैं:

NDK वर्शन एबीआई
16 और उससे कम उपलब्ध है,
17 साल और उससे ज़्यादा आर्मेबी-v7a, आर्म64-v8a, x86, x86_64

इन एबीआई के बारे में ज़्यादा जानने के लिए, एनडीके दस्तावेज़ देखें.

एक से ज़्यादा एबीआई वाले फ़ैट APKs को, रिलीज़ के बिल्ड में शामिल करने का सुझाव नहीं दिया जाता. ऐसा इसलिए, क्योंकि ये 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 के साथ कंपाइलर और लिंकर फ़्लैग पास करने के बारे में ज़्यादा जानने के लिए, उपयोगकर्ता मैन्युअल पर जाएं.

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

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

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

Bazel का कॉन्फ़िगरेशन मॉडल प्लैटफ़ॉर्म और टूलचेन की तरफ़ बढ़ रहा है. अगर आपका बिल्डर आर्किटेक्चर या ऑपरेटिंग सिस्टम चुनने के लिए --platforms फ़्लैग का इस्तेमाल करता है, तो एनडीके का इस्तेमाल करने के लिए आपको बेज़ल को --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 का नियम, खास तौर पर बेज़ल से, Android के साथ काम करने वाले कॉन्फ़िगरेशन में अपनी डिपेंडेंसी बनाने के लिए कह सकता है. ऐसा इसलिए किया जाता है, ताकि Bazel बिल्ड को सिर्फ़ काम कर सके. इसमें एबीआई और एसटीएल कॉन्फ़िगरेशन के लिए, --fat_apk_cpu और --android_crosstool_top को छोड़कर, कोई खास फ़्लैग शामिल नहीं किया जाता है.

पर्दे के पीछे, यह अपने-आप काम करने वाला कॉन्फ़िगरेशन Android कॉन्फ़िगरेशन में बदलाव करना इस्तेमाल करता है.

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

जब बेज़ल, Android के साथ काम करने वाले किसी कॉन्फ़िगरेशन में होता है, तो ऊपर के लेवल पर बताई गई या हाई-लेवल ट्रांज़िशन पॉइंट की वजह से, ट्रांज़िशन पॉइंट में कॉन्फ़िगरेशन में और बदलाव नहीं होता.

Android की कॉन्फ़िगरेशन में ट्रांज़िशन को ट्रिगर करने वाली इकलौती जगह android_binarydeps की विशेषता है.

उदाहरण के लिए, अगर आप android_library फ़्लैग का इस्तेमाल किए बिना, cc_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 कमांड लाइन का टारगेट पहले से ही इनमें से किसी भी ट्रांज़िशन नियम के नीचे है, जैसे कि किसी खास cc_library को टेस्ट करने वाले C++ डेवलपर, तो कस्टम --crosstool_top का इस्तेमाल करना चाहिए.

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 टूलचेन (और Android के लिए) के साथ बनाए जा सकते हैं. इसकी वजह यह है कि होस्ट टूलचेन को टारगेट टूलचेन से कॉपी किया जाता है. इस पर काम करने के लिए, --host_crosstool_top का मान @bazel_tools//tools/cpp:toolchain के तौर पर बताएं, ताकि साफ़ तौर पर, होस्ट का C++ टूल चेन सेट किया जा सके.

इस तरीके से पूरा बिल्ड ट्री प्रभावित होता है.

इन फ़्लैग को project/.bazelrc में, 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 से अपने-आप कॉन्फ़िगरेशन होने की सुविधा पर भरोसा करें. यहां पर ऐसे बहुत से टारगेट बनाने की उम्मीद की जाती है जिन्हें आप कंट्रोल नहीं करते.