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

किसी समस्या की शिकायत करें सोर्स देखें Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

खास जानकारी

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

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

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

यह उदाहरण, 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 को संबंधित डिफ़ॉल्ट एसटीएल के नाम से जाना जाता है.

कृपया ध्यान दें कि r18 से, STLport और gnustl को हटा दिया जाएगा. इससे NDK में libc++ ही एक 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 बनाएगा.

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

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

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

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

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

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

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

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