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

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

खास जानकारी

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

Android के सामान्य कंपाइलेशन के लिए, rules_android का इस्तेमाल करें. इस ट्यूटोरियल में, Android ऐप्लिकेशन में C++ लाइब्रेरी की डिपेंडेंसी इंटिग्रेट करने का तरीका बताया गया है. साथ ही, NDK टूलचेन की खोज और रजिस्ट्रेशन के लिए, rules_android_ndk का इस्तेमाल किया गया है.

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

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

NDK और SDK टूल सेटअप

बाहरी रिपॉज़िटरी का सेटअप इस बात पर निर्भर करता है कि आपने WORKSPACE या bzlmod (MODULE.bazel) का इस्तेमाल किया है. Bazel 7 या इसके बाद के वर्शन के लिए, bzlmod को प्राथमिकता दी जाती है. ध्यान दें कि MODULE.bazel और WORKSPACE सेटअप स्टैंज़ा एक-दूसरे से अलग होते हैं. अगर डिपेंडेंसी मैनेजमेंट के किसी एक समाधान का इस्तेमाल किया जा रहा है, तो आपको दूसरे के लिए बॉयलरप्लेट जोड़ने की ज़रूरत नहीं है.

Bzlmod MODULE.bazel सेटअप

अपने MODULE.bazel में यह स्निपेट जोड़ें:

# NDK
bazel_dep(name = "rules_android_ndk", version = "0.1.3")
android_ndk_repository_extension = use_extension("@rules_android_ndk//:extension.bzl", "android_ndk_repository_extension")
use_repo(android_ndk_repository_extension, "androidndk")
register_toolchains("@androidndk//:all")

# SDK
bazel_dep(name = "rules_android", version = "0.6.6")
register_toolchains(
    "@rules_android//toolchains/android:android_default_toolchain",
    "@rules_android//toolchains/android_sdk:android_sdk_tools",
)
android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension")
use_repo(android_sdk_repository_extension, "androidsdk")
register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all")

लेगसी WORKSPACE सेटअप

अपने WORKSPACE में यह स्निपेट जोड़ें:

load("@rules_android//rules:rules.bzl", "android_sdk_repository")
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.
)

load("@rules_android_ndk//:rules.bzl", "android_ndk_repository")
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.
)

WORKSPACE के लिए कंपैटिबिलिटी से जुड़े नोट:

  • rules_android और rules_android_ndk, दोनों नियमों के लिए, अतिरिक्त बॉयलरप्लेट की ज़रूरत होती है. इसे ऊपर दिए गए WORKSPACE स्निपेट में नहीं दिखाया गया है. अप-टू-डेट और पूरी तरह से तैयार इंस्टैंशिएशन स्टैंज़ा के लिए, WORKSPACE फ़ाइल देखें rules_android_ndk'के बुनियादी उदाहरण वाले ऐप्लिकेशन की.

android_ndk_repository नियम के बारे में ज़्यादा जानने के लिए, इसका डॉकस्ट्रिंग देखें.

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

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

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

# In <project>/app/src/main/BUILD.bazel
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_android//rules:rules.bzl", "android_binary", "android_library")

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 --android_platforms=<your platform>

ध्यान दें कि अगर --android_platforms की जानकारी नहीं दी जाती है, तो JNI हेडर न होने से जुड़े गड़बड़ी के मैसेज के साथ आपका बिल्ड पूरा नहीं हो पाएगा.

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) फ़ाइल में कंपाइल करता है. इसे --android_platforms से तय किए गए आर्किटेक्चर के लिए टारगेट किया जाता है. ज़्यादा जानकारी के लिए, टारगेट 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();

}

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

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

bazel build //:app --android_platforms=comma-separated list of platforms

--platforms फ़्लैग की तरह, --android_platforms को पास की गई वैल्यू, platform टारगेट के लेबल होती हैं. इनमें आपके डिवाइस के बारे में बताने के लिए, स्टैंडर्ड कंस्ट्रेंट वैल्यू का इस्तेमाल किया जाता है.

उदाहरण के लिए, 64-बिट ARM प्रोसेसर वाले Android डिवाइस के लिए, अपने प्लैटफ़ॉर्म को इस तरह तय करें:

platform(
    name = "android_arm64",
    constraint_values = [
        "@platforms//os:android",
        "@platforms//cpu:arm64",
    ],
)

हर Android platform को @platforms//os:android ओएस कंस्ट्रेंट का इस्तेमाल करना चाहिए. सीपीयू कंस्ट्रेंट माइग्रेट करने के लिए, यह चार्ट देखें:

सीपीयू वैल्यू प्लैटफ़ॉर्म
armeabi-v7a @platforms//cpu:armv7
arm64-v8a @platforms//cpu:arm64
x86 @platforms//cpu:x86_32
x86_64 @platforms//cpu:x86_64

मल्टी-आर्किटेक्चर APK के लिए, एक से ज़्यादा लेबल पास करें. उदाहरण के लिए: --android_platforms=//:arm64,//:x86_64. हालांकि, यह मान लिया जाता है कि आपने इन्हें अपने टॉप-लेवल BUILD.bazel फ़ाइल में तय किया है.

Bazel, डिफ़ॉल्ट Android प्लैटफ़ॉर्म नहीं चुन सकता. इसलिए, एक प्लैटफ़ॉर्म तय करना और --android_platforms के साथ उसकी जानकारी देना ज़रूरी है.

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 का साइज़ बढ़ जाता है. हालांकि, डेवलपमेंट और क्यूए बिल्ड के लिए यह काम का हो सकता है.

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

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

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

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

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
)

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

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

उदाहरण के लिए, मान लें कि आपने my/platforms/BUILD में Android प्लैटफ़ॉर्म तय किए हैं:

bazel build //my/cc/jni:target \
      --platforms=//my/platforms:x86_64

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

इन फ़्लैग को bazelrc कॉन्फ़िगरेशन (हर ABI के लिए एक) में, project/.bazelrc में रखा जा सकता है:

common:android_x86 --platforms=//my/platforms:x86

common:android_armeabi-v7a --platforms=//my/platforms:armeabi-v7a

# In general
common:android_<abi> --platforms=//my/platforms:<abi>

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

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

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