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

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

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

खास जानकारी

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

Basel की सभी cc_लाइब्रेरी को, शेयर किए गए एक ऑब्जेक्ट (.so) फ़ाइल में इकट्ठा किया जाता है. यह डिफ़ॉल्ट रूप से, armeabi-v7a एबीआई के लिए टारगेट होती है. इसमें बदलाव करने या एक साथ कई एबीआई बनाने के लिए, टारगेट एबीआई को कॉन्फ़िगर करने से जुड़ा सेक्शन देखें.

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

यह उदाहरण, Bazz के उदाहरण वाले डेटा स्टोर करने की जगह में उपलब्ध है.

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

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

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

#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 में उपलब्ध एसटीएल ये हैं:

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

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

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

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

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

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

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

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

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

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

एनडीके (NDK) में बदलाव एबीआई
16 और उससे कम Armeabi, Armeabi-v7a, arm64-v8a, mips, mips64, x86, x86_64
17 साल और इससे ज़्यादा Armeabi-v7a, arm64-v8a, x86, x86_64

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

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

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

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

C++ स्टैंडर्ड झंडा
सी++98 डिफ़ॉल्ट, फ़्लैग ज़रूरी नहीं है
सी++11 --cxxopt=-std=c++11
सी++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
)

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

बेज़ल के कॉन्फ़िगरेशन मॉडल का इस्तेमाल, प्लैटफ़ॉर्म और टूलचेन से किया जा रहा है. अगर आपका बिल्ड, आर्किटेक्चर या ऑपरेटिंग सिस्टम को चुनने के लिए --platforms फ़्लैग का इस्तेमाल करता है, तो आपको एनडीके (NDK) का इस्तेमाल करने के लिए, Basel को --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")

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

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

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

जब Basel, 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.

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