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

समस्या की शिकायत करें स्रोत देखें

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

खास जानकारी

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

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

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

SDK टूल और एनडीके सेट अप करने के लिए, 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 एनडीके (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 एबीआई के लिए टारगेट किया गया है. इसे बदलने या एक साथ कई एबीआई बनाने के लिए, टारगेट एबीआई को कॉन्फ़िगर करने से जुड़ा सेक्शन देखें.

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

यह उदाहरण 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 को लोड करता है और जेएनआई फ़ंक्शन के लिए मेथड सिग्नेचर के बारे में बताता है:

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 टारगेट लेबल
STLport @androidndk//:toolchain-stlport
Libc++ @androidndk//:toolchain-libcpp
गनस्टल @androidndk//:toolchain-gnu-libstdcpp

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

कृपया ध्यान दें कि r18 के बाद से, STLport और gnusl को हटा दिया जाएगा, जिससे एनडीके का सिर्फ़ libc++ एसटीएल बन जाएगा.

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

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

टारगेट एबीआई को कॉन्फ़िगर करने के लिए, --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 और उससे कम Armeabi, Armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 साल और उससे ज़्यादा उम्र Armeabi-v7a, arm64-v8a, x86, x86_64

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

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

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 फ़्लैग का इस्तेमाल करता है, तो आपको एनडीके का इस्तेमाल करने के लिए, 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 को एनडीके BUILD फ़ाइल (एनडीके 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 बिल्ड बिना किसी खास फ़्लैग के काम करे. इसमें एबीआई और एसटीएल कॉन्फ़िगरेशन के लिए --fat_apk_cpu और --android_crosstool_top शामिल नहीं हैं.

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

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

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

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

उदाहरण के लिए, बिना किसी फ़्लैग के cc_library डिपेंडेंसी के साथ android_library टारगेट बनाने की कोशिश करने पर, आपको जेएनआई हेडर मौजूद न होने से जुड़ी गड़बड़ी दिख सकती है:

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

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

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