Gradle प्लग इन लिखना

Android Gradle प्लगिन (एजीपी), Android ऐप्लिकेशन के लिए आधिकारिक बिल्ड सिस्टम है. इसमें कई तरह के सोर्स को कंपाइल करने और उन्हें एक ऐप्लिकेशन में लिंक करने की सुविधा शामिल है. इस ऐप्लिकेशन को किसी Android डिवाइस या एम्युलेटर पर चलाया जा सकता है.

एजीपी में प्लग इन के लिए एक्सटेंशन पॉइंट होते हैं. इनकी मदद से, बिल्ड इनपुट को कंट्रोल किया जा सकता है. साथ ही, नए चरणों के ज़रिए इसकी सुविधाओं को बढ़ाया जा सकता है. इन चरणों को स्टैंडर्ड बिल्ड टास्क के साथ इंटिग्रेट किया जा सकता है. एजीपी के पिछले वर्शन में, आधिकारिक एपीआई को इंटरनल इंप्लिमेंटेशन से साफ़ तौर पर अलग नहीं किया गया था. वर्शन 7.0 से, AGP में आधिकारिक और स्टेबल एपीआई का एक सेट होता है. इस पर भरोसा किया जा सकता है.

एजीपी एपीआई की लाइफ़साइकल

AGP, अपने एपीआई की स्थिति तय करने के लिए, Gradle की सुविधा की लाइफ़साइकल का पालन करता है:

  • इंटरनल: इसे सार्वजनिक तौर पर इस्तेमाल नहीं किया जा सकता
  • इनक्यूबेटिंग: ये सार्वजनिक तौर पर इस्तेमाल के लिए उपलब्ध हैं, लेकिन फ़ाइनल नहीं हैं. इसका मतलब है कि हो सकता है कि ये फ़ाइनल वर्शन में, पुराने सिस्टम के साथ काम करने की सुविधा न दें
  • सार्वजनिक: सार्वजनिक तौर पर इस्तेमाल के लिए उपलब्ध और स्टेबल
  • अब इस्तेमाल नहीं किया जाता: अब यह काम नहीं करता और इसकी जगह नए एपीआई इस्तेमाल किए जाते हैं

समर्थन नहीं होने की नीति

एजीपी में लगातार बदलाव हो रहे हैं. इसके तहत, पुराने एपीआई को हटाकर उनकी जगह नए और स्टेबल एपीआई लाए जा रहे हैं. साथ ही, एक नई डोमेन स्पेसिफ़िक लैंग्वेज (डीएसएल) लाई जा रही है. यह बदलाव, एजीपी की कई रिलीज़ में किया जाएगा. इसके बारे में ज़्यादा जानने के लिए, एजीपी एपीआई/डीएसएल माइग्रेशन की टाइमलाइन पर जाएं.

जब एजीपी एपीआई को बंद कर दिया जाता है, तो वे मौजूदा मेजर रिलीज़ में उपलब्ध रहेंगे. हालांकि, वे चेतावनियां जनरेट करेंगे. ऐसा इस माइग्रेशन या किसी अन्य वजह से हो सकता है. अगली बड़ी रिलीज़ में, AGP से उन एपीआई को पूरी तरह से हटा दिया जाएगा जो अब काम नहीं करते. उदाहरण के लिए, अगर AGP 7.0 में किसी एपीआई को बंद कर दिया जाता है, तो वह उस वर्शन में उपलब्ध होगा और चेतावनियां जनरेट करेगा. यह एपीआई, AGP 8.0 में उपलब्ध नहीं होगा.

सामान्य बिल्ड कस्टमाइज़ेशन में इस्तेमाल किए गए नए एपीआई के उदाहरण देखने के लिए, Android Gradle प्लग इन रेसिपी देखें. इनमें, बिल्ड को पसंद के मुताबिक बनाने के सामान्य उदाहरण दिए गए हैं. नए एपीआई के बारे में ज़्यादा जानकारी, हमारे रेफ़रंस दस्तावेज़ में भी मिल सकती है.

एजीपी एपीआई आर्टफ़ैक्ट के हिसाब से कंपाइल करना

कस्टम Gradle प्लग इन या बिल्ड लॉजिक (जैसे, buildSrc डायरेक्ट्री, कन्वेंशन प्लग इन मॉड्यूल या स्टैंडअलोन प्लग इन प्रोजेक्ट में) लिखने के लिए, जो एजीपी को एक्सटेंड करता है, पूरे प्लग इन को लागू करने वाले आर्टफ़ैक्ट के बजाय, एजीपी के gradle-api आर्टफ़ैक्ट के साथ कंपाइल करें.

gradle-api आर्टफ़ैक्ट के लिए कंपाइल क्यों करें?

एजीपी, दो मुख्य आर्टफ़ैक्ट पब्लिश करता है:

  • com.android.tools.build:gradle-api: इसमें आधिकारिक एजीपी एपीआई शामिल हैं. इनमें स्टेबल एपीआई (जैसे, AndroidComponentsExtension, Variant, और सार्वजनिक डीएसएल इंटरफ़ेस), इनक्यूबेटिंग एपीआई (@Incubating के तौर पर मार्क किए गए), और अब काम न करने वाले एपीआई (@Deprecated के तौर पर मार्क किए गए) शामिल हैं.
  • com.android.tools.build:gradle: इसमें एजीपी रनटाइम का पूरा सेटअप शामिल होता है. इसमें इंटरनल क्लास, बिल्ड टास्क, और कंपाइलर इंटिग्रेशन शामिल हैं.

gradle-api आर्टफ़ैक्ट के हिसाब से कंपाइल करने से कई अहम फ़ायदे मिलते हैं:

  • इंटरनल एपीआई का इस्तेमाल न करें: इससे यह पक्का होता है कि आपका प्लगिन कोड सिर्फ़ आधिकारिक एपीआई को ऐक्सेस करे. इससे, इंटरनल इंप्लिमेंटेशन क्लास (com.android.build.gradle.internal.*) पर अनजाने में होने वाली डिपेंडेंसी को रोका जा सकता है. ये क्लास, एजीपी के अलग-अलग वर्शन में बिना किसी चेतावनी के बदली या हटाई जा सकती हैं.
  • तेज़ी से बिल्ड बनाना और कम स्टोरेज का इस्तेमाल करना: gradle-api आर्टफ़ैक्ट का डिपेंडेंसी ट्री, पूरे gradle आर्टफ़ैक्ट की तुलना में काफ़ी छोटा होता है. इससे डाउनलोड किए जाने वाले डेटा का साइज़ कम हो जाता है, क्लासपाथ में गड़बड़ी नहीं होती, और आपके बिल्ड लॉजिक को कंपाइल करने की प्रोसेस तेज़ हो जाती है.
  • रनटाइम आइसोलेशन: कस्टम प्लग इन को कंपाइलेशन के दौरान सिर्फ़ एपीआई डेफ़िनिशन की ज़रूरत होती है. रनटाइम के दौरान, Android ऐप्लिकेशन या लाइब्रेरी प्लग इन लागू होने पर, Android बिल्ड से एजीपी को पूरी तरह से लागू किया जाता है.

डिपेंडेंसी जोड़ें

अपने प्लगिन प्रोजेक्ट या buildSrc डायरेक्ट्री की बिल्ड फ़ाइल में, gradle-api आर्टफ़ैक्ट को डिपेंडेंसी के तौर पर जोड़ें. gradle-api आर्टफ़ैक्ट को Google की Maven रिपॉज़िटरी पर पब्लिश किया गया है. आर्टफ़ैक्ट को ठीक करने के लिए, पक्का करें कि आपके बिल्ड में google() डेटाबेस शामिल हो. उदाहरण के लिए, इसे settings.gradle या settings.gradle.kts फ़ाइल के dependencyResolutionManagement.repositories ब्लॉक में या अपने प्लग इन की बिल्ड फ़ाइल या buildSrc डायरेक्ट्री की बिल्ड फ़ाइल के repositories ब्लॉक में कॉन्फ़िगर करें.

वर्शन कैटलॉग के साथ

अगर आपका प्रोजेक्ट वर्शन कैटलॉग का इस्तेमाल करता है, तो gradle/libs.versions.toml फ़ाइल में gradle-api डिपेंडेंसी तय करें:

[versions]
androidGradlePlugin = "9.4.0"

[libraries]
android-gradle-plugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" }

android-gradle-plugin-api कैटलॉग का उपनाम, टाइप-सेफ़ ऐक्सेसर libs.android.gradle.plugin.api जनरेट करता है.

इसके बाद, अपने प्लगिन की बिल्ड फ़ाइल के dependencies ब्लॉक में libs.android.gradle.plugin.api जोड़ें:

Kotlin

dependencies {
    compileOnly(libs.android.gradle.plugin.api)
}

Groovy

dependencies {
    compileOnly libs.android.gradle.plugin.api
}

वर्शन कैटलॉग के बिना

अगर आपका प्रोजेक्ट वर्शन कैटलॉग का इस्तेमाल नहीं करता है, तो डिपेंडेंसी का एलान सीधे तौर पर अपने प्लग इन की बिल्ड फ़ाइल में करें:

Kotlin

dependencies {
    compileOnly("com.android.tools.build:gradle-api:9.4.0")
}

Groovy

dependencies {
    compileOnly 'com.android.tools.build:gradle-api:9.4.0'
}

पब्लिश किए जाने वाले स्टैंडअलोन प्लग इन के लिए, डिफ़ॉल्ट तौर पर compileOnly कॉन्फ़िगरेशन विकल्प का इस्तेमाल करें. इस्तेमाल करने वाला प्रोजेक्ट, एजीपी लागू करता है और बिल्डस्क्रिप्ट क्लासपाथ पर अपनी रनटाइम क्लास उपलब्ध कराता है. compileOnly कॉन्फ़िगरेशन का इस्तेमाल करने से, पब्लिश किए गए प्लग इन को किसी खास एजीपी वर्शन पर रनटाइम डिपेंडेंसी लीक होने से रोका जा सकता है. साथ ही, उपभोक्ताओं के लिए वर्शन से जुड़ी समस्याओं से बचा जा सकता है. अगर आपको इंटरनल बिल्ड लॉजिक डेवलप करना है, जैसे कि buildSrc डायरेक्ट्री में मौजूद प्लग इन या कंपोज़िट बिल्ड में मौजूद कन्वेंशन प्लग इन, तो इसके बजाय implementation कॉन्फ़िगरेशन विकल्प का इस्तेमाल करें. ये प्लग इन, बिल्ड के अंदर चलते हैं और इन्हें रनटाइम क्लासपाथ पर एजीपी की ज़रूरत होती है.

Gradle बिल्ड की बुनियादी बातें

इस गाइड में, Gradle बिल्ड सिस्टम के बारे में पूरी जानकारी नहीं दी गई है. हालांकि, इसमें हमारे एपीआई के साथ इंटिग्रेट करने में आपकी मदद करने के लिए, ज़रूरी कॉन्सेप्ट का कम से कम सेट शामिल है. साथ ही, ज़्यादा जानकारी के लिए Gradle के मुख्य दस्तावेज़ के लिंक भी दिए गए हैं.

हम यह मानकर चलते हैं कि आपको Gradle के काम करने के तरीके के बारे में बुनियादी जानकारी है. इसमें प्रोजेक्ट कॉन्फ़िगर करने, बिल्ड फ़ाइलों में बदलाव करने, प्लगिन लागू करने, और टास्क चलाने का तरीका शामिल है. एजीपी के हिसाब से Gradle की बुनियादी बातों के बारे में जानने के लिए, हमारा सुझाव है कि आप अपना बिल्ड कॉन्फ़िगर करना लेख पढ़ें. Gradle प्लगिन को पसंद के मुताबिक बनाने के लिए, सामान्य फ़्रेमवर्क के बारे में जानने के लिए, कस्टम Gradle प्लगिन डेवलप करना लेख पढ़ें.

Gradle लेज़ी टाइप की शब्दावली

Gradle कई तरह के टाइप उपलब्ध कराता है, जो "लेज़ी" तरीके से काम करते हैं या भारी कंप्यूटेशन या Task क्रिएशन को बिल्ड के बाद के चरणों में टालने में मदद करते हैं. ये टाइप, कई Gradle और एजीपी एपीआई के मुख्य हिस्से होते हैं. यहां दी गई सूची में, लेज़ी एक्ज़ीक्यूशन में शामिल मुख्य Gradle टाइप और उनके मुख्य तरीके शामिल हैं.

Provider<T>
यह T टाइप की वैल्यू देता है (जहां "T" का मतलब किसी भी टाइप से है), जिसे एक्ज़ीक्यूशन फ़ेज़ के दौरान get() का इस्तेमाल करके पढ़ा जा सकता है या map(), flatMap(), और zip() तरीकों का इस्तेमाल करके, नए Provider<S> (जहां "S" का मतलब किसी अन्य टाइप से है) में बदला जा सकता है. ध्यान दें कि कॉन्फ़िगरेशन के दौरान, get() को कभी भी कॉल नहीं किया जाना चाहिए.
  • map(): यह lambda को स्वीकार करता है और S टाइप का Provider जनरेट करता है, Provider<S>. map() के लिए lambda आर्ग्युमेंट, T वैल्यू लेता है और S वैल्यू जनरेट करता है. लैम्ब्डा को तुरंत लागू नहीं किया जाता. इसके बजाय, इसे तब लागू किया जाता है, जब नतीजे के तौर पर मिलने वाले Provider<S> पर get() को कॉल किया जाता है. इससे पूरी चेन लेज़ी हो जाती है.
  • flatMap(): यह लैम्ब्डा को भी स्वीकार करता है और Provider<S> जनरेट करता है. हालांकि, लैम्ब्डा T वैल्यू लेता है और Provider<S> जनरेट करता है. यह सीधे तौर पर S वैल्यू जनरेट नहीं करता. flatMap() का इस्तेमाल तब करें, जब कॉन्फ़िगरेशन के समय S का पता न लगाया जा सके और आपको सिर्फ़ Provider<S> मिल सके. अगर आपने map() का इस्तेमाल किया और आपको Provider<Provider<S>> नतीजे मिले, तो इसका मतलब है कि आपको flatMap() का इस्तेमाल करना चाहिए था.
  • zip(): इसकी मदद से, दो Provider इंस्टेंस को जोड़कर एक नया Provider बनाया जा सकता है. इसकी वैल्यू, दो इनपुट Providers इंस्टेंस की वैल्यू को जोड़ने वाले फ़ंक्शन का इस्तेमाल करके कैलकुलेट की जाती है.
Property<T>
Provider<T> को लागू करता है, इसलिए यह T टाइप की वैल्यू भी देता है. Provider<T> के लिए सिर्फ़ वैल्यू पढ़ी जा सकती है. हालांकि, Property<T> के लिए वैल्यू सेट भी की जा सकती है. ऐसा करने के दो तरीके हैं:
  • जब T टाइप की वैल्यू उपलब्ध हो, तो उसे सीधे सेट करें. इसके लिए, बाद में होने वाली कंप्यूटेशन की ज़रूरत नहीं है.
  • Property<T> की वैल्यू के सोर्स के तौर पर, कोई दूसरा Provider<T> सेट करें. इस मामले में, T की वैल्यू सिर्फ़ तब दिखती है, जब Property.get() को कॉल किया जाता है.
TaskProvider
Provider<Task> को लागू करता है. TaskProvider जनरेट करने के लिए, tasks.register() का इस्तेमाल करें, न कि tasks.create() का. इससे यह पक्का किया जा सकेगा कि टास्क सिर्फ़ ज़रूरत पड़ने पर ही इंस्टैंशिएट किए जाएं. Task के बनने से पहले, उसके Task आउटपुट को ऐक्सेस करने के लिए, flatMap() का इस्तेमाल किया जा सकता है. यह तब काम आ सकता है, जब आपको आउटपुट को अन्य Task इंस्टेंस के इनपुट के तौर पर इस्तेमाल करना हो.

टास्क के इनपुट और आउटपुट को लेज़ी तरीके से सेट अप करने के लिए, प्रोवाइडर और उनके ट्रांसफ़ॉर्मेशन के तरीके ज़रूरी हैं. इसका मतलब है कि सभी टास्क को पहले से बनाने और वैल्यू को रिज़ॉल्व करने की ज़रूरत नहीं है.

प्रोवाइडर, टास्क की डिपेंडेंसी की जानकारी भी देते हैं. Task आउटपुट को बदलकर Provider बनाने पर, वह Task, Provider पर निर्भर हो जाता है. साथ ही, Provider की वैल्यू रिज़ॉल्व होने पर, उसे बनाया और चलाया जाएगा. जैसे, जब किसी दूसरे Task को इसकी ज़रूरत हो.

यहां दो टास्क, GitVersionTask और ManifestProducerTask को रजिस्टर करने का उदाहरण दिया गया है. इसमें Task इंस्टेंस बनाने की प्रोसेस को तब तक के लिए रोक दिया गया है, जब तक कि उनकी ज़रूरत न हो. ManifestProducerTask इनपुट वैल्यू को GitVersionTask के आउटपुट से मिली Provider पर सेट किया गया है. इसलिए, ManifestProducerTask पूरी तरह से GitVersionTask पर निर्भर करता है.

// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
    project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
        it.gitVersionOutputFile.set(
            File(project.buildDir, "intermediates/gitVersionProvider/output")
        )
    }

...

/**
 * Register another task in the configuration block (also executed lazily,
 * only if the task is required).
 */
val manifestProducer =
    project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
        /**
         * Connect this task's input (gitInfoFile) to the output of
         * gitVersionProvider.
         */
        it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
    }

ये दोनों टास्क सिर्फ़ तब ही पूरे होंगे, जब इनके लिए साफ़ तौर पर अनुरोध किया जाएगा. ऐसा Gradle को शुरू करने के दौरान हो सकता है. उदाहरण के लिए, अगर आपने ./gradlew debugManifestProducer को चलाया है या ManifestProducerTask का आउटपुट किसी अन्य टास्क से कनेक्ट है और उसकी वैल्यू ज़रूरी है.

कस्टम टास्क लिखने के लिए, इनपुट और/या आउटपुट का इस्तेमाल किया जाता है. हालांकि, एजीपी अपने टास्क को सीधे तौर पर सार्वजनिक तौर पर ऐक्सेस करने की सुविधा नहीं देता. ये लागू करने से जुड़ी जानकारी होती है, जो हर वर्शन के हिसाब से बदल सकती है. इसके बजाय, एजीपी, वैरिएंट एपीआई और उसके टास्क के आउटपुट या बिल्ड आर्टफ़ैक्ट का ऐक्सेस देता है. इन्हें पढ़ा और बदला जा सकता है. ज़्यादा जानकारी के लिए, इस दस्तावेज़ में वैरिएंट एपीआई, आर्टफ़ैक्ट, और टास्क देखें.

Gradle बिल्ड के फ़ेज़

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

इनमें से कुछ ऑप्टिमाइज़ेशन लागू करने के लिए, Gradle स्क्रिप्ट और प्लगिन को Gradle बिल्ड के हर चरण के दौरान सख्त नियमों का पालन करना होगा: शुरू करना, कॉन्फ़िगर करना, और लागू करना. इस गाइड में, हम कॉन्फ़िगरेशन और एक्ज़ीक्यूशन के चरणों पर फ़ोकस करेंगे. Gradle बिल्ड लाइफ़साइकल गाइड में, सभी चरणों के बारे में ज़्यादा जानकारी मिल सकती है.

कॉन्फ़िगरेशन का चरण

कॉन्फ़िगरेशन के दौरान, बिल्ड में शामिल सभी प्रोजेक्ट के लिए बिल्ड स्क्रिप्ट का आकलन किया जाता है. साथ ही, प्लग इन लागू किए जाते हैं और बिल्ड डिपेंडेंसी को हल किया जाता है. इस फ़ेज़ का इस्तेमाल, डीएसएल ऑब्जेक्ट का इस्तेमाल करके बिल्ड को कॉन्फ़िगर करने और टास्क और उनके इनपुट को लेज़ी तरीके से रजिस्टर करने के लिए किया जाना चाहिए.

कॉन्फ़िगरेशन फ़ेज़ हमेशा चलता है, भले ही किसी भी टास्क को चलाने का अनुरोध किया गया हो. इसलिए, इसे छोटा रखना और किसी भी कंप्यूटेशन को बिल्ड स्क्रिप्ट के अलावा अन्य इनपुट पर निर्भर होने से रोकना ज़रूरी है. इसका मतलब है कि आपको बाहरी प्रोग्राम नहीं चलाने चाहिए या नेटवर्क से डेटा नहीं पढ़ना चाहिए. इसके अलावा, आपको ऐसी लंबी कंप्यूटेशन नहीं करनी चाहिए जिन्हें सही Task इंस्टेंस के तौर पर, एक्ज़ीक्यूशन फ़ेज़ तक टाला जा सकता है.

लागू करने का चरण

एक्ज़ीक्यूशन फ़ेज़ में, अनुरोध किए गए टास्क और उन पर निर्भर टास्क को एक्ज़ीक्यूट किया जाता है. खास तौर पर, @TaskAction से मार्क किए गए Task क्लास के तरीके लागू किए जाते हैं. टास्क को पूरा करने के दौरान, आपको इनपुट (जैसे कि फ़ाइलें) पढ़ने और Provider<T>.get() को कॉल करके लेज़ी प्रोवाइडर को रिज़ॉल्व करने की अनुमति है. इस तरह से लेज़ी प्रोवाइडर को रिज़ॉल्व करने से, map() या flatMap() कॉल का क्रम शुरू होता है. ये कॉल, प्रोवाइडर में मौजूद टास्क डिपेंडेंसी की जानकारी का पालन करते हैं. ज़रूरी वैल्यू को लागू करने के लिए, टास्क को लेज़ी तरीके से चलाया जाता है.

वैरिएंट एपीआई, आर्टफ़ैक्ट, और टास्क

वैरिएंट एपीआई, Android Gradle प्लगिन में एक एक्सटेंशन मैकेनिज़्म है. इसकी मदद से, अलग-अलग विकल्पों में बदलाव किया जा सकता है. आम तौर पर, ये विकल्प बिल्ड कॉन्फ़िगरेशन फ़ाइलों में डीएसएल का इस्तेमाल करके सेट किए जाते हैं. ये विकल्प, Android बिल्ड पर असर डालते हैं. वैरिएंट एपीआई की मदद से, बिल्ड से बनाए गए इंटरमीडिएट और फ़ाइनल आर्टफ़ैक्ट भी ऐक्सेस किए जा सकते हैं. जैसे, क्लास फ़ाइलें, आपस में मिले हुए मेनिफ़ेस्ट या APK/AAB फ़ाइलें.

Android बिल्ड फ़्लो और एक्सटेंशन पॉइंट

एजीपी के साथ इंटरैक्ट करते समय, सामान्य Gradle लाइफ़साइकल कॉलबैक (जैसे, afterEvaluate()) को रजिस्टर करने या साफ़ तौर पर Task डिपेंडेंसी सेट अप करने के बजाय, खास तौर पर बनाए गए एक्सटेंशन पॉइंट का इस्तेमाल करें. एजीपी से बनाए गए टास्क, लागू करने से जुड़ी जानकारी माने जाते हैं. इन्हें सार्वजनिक एपीआई के तौर पर नहीं दिखाया जाता. आपको Task ऑब्जेक्ट के इंस्टेंस पाने या Task के नामों का अनुमान लगाने से बचना चाहिए. साथ ही, उन Task ऑब्जेक्ट में सीधे तौर पर कॉलबैक या डिपेंडेंसी जोड़ने से बचना चाहिए.

AGP, Task के इंस्टेंस बनाने और उन्हें लागू करने के लिए, यहां दिया गया तरीका अपनाता है. इससे बिल्ड आर्टफ़ैक्ट बनते हैं. Variant ऑब्जेक्ट बनाने के मुख्य चरणों के बाद, कॉलबैक होते हैं. इनकी मदद से, बिल्ड के हिस्से के तौर पर बनाए गए कुछ ऑब्जेक्ट में बदलाव किए जा सकते हैं. यह ध्यान रखना ज़रूरी है कि सभी कॉलबैक, कॉन्फ़िगरेशन फ़ेज़ के दौरान होते हैं. इस फ़ेज़ के बारे में इस पेज पर बताया गया है. साथ ही, कॉलबैक तेज़ी से होने चाहिए. इसके लिए, किसी भी मुश्किल काम को एक्ज़ीक्यूशन फ़ेज़ के दौरान सही Task इंस्टेंस के लिए टाल दें.

  1. डीएसएल पार्सिंग: इस दौरान, बिल्ड स्क्रिप्ट का आकलन किया जाता है. साथ ही, android ब्लॉक से Android डीएसएल ऑब्जेक्ट की अलग-अलग प्रॉपर्टी बनाई और सेट की जाती हैं. इस फ़ेज़ के दौरान, वैरिएंट एपीआई कॉलबैक भी रजिस्टर किए जाते हैं. इनके बारे में यहां दिए गए सेक्शन में बताया गया है.
  2. finalizeDsl(): यह कॉलबैक, कॉम्पोनेंट (वैरिएंट) बनाने के लिए लॉक किए जाने से पहले, डीएसएल ऑब्जेक्ट में बदलाव करने की सुविधा देता है. VariantBuilder ऑब्जेक्ट, डीएसएल ऑब्जेक्ट में मौजूद डेटा के आधार पर बनाए जाते हैं.

  3. डीएसएल लॉक करना: डीएसएल अब लॉक हो गया है और इसमें बदलाव नहीं किए जा सकते.

  4. beforeVariants(): यह कॉलबैक, VariantBuilder के ज़रिए यह तय कर सकता है कि कौनसे कॉम्पोनेंट बनाए जाएं और उनकी कुछ प्रॉपर्टी क्या हों. हालांकि, इससे बिल्ड फ़्लो और जनरेट किए गए आर्टफ़ैक्ट में बदलाव किए जा सकते हैं.

  5. वैरिएंट बनाना: कॉम्पोनेंट और आर्टफ़ैक्ट की सूची अब फ़ाइनल हो गई है और इसमें बदलाव नहीं किया जा सकता.

  6. onVariants(): इस कॉलबैक में, आपको बनाए गए Variant ऑब्जेक्ट का ऐक्सेस मिलता है. साथ ही, इनमें मौजूद Property वैल्यू के लिए, वैल्यू या प्रोवाइडर सेट किए जा सकते हैं, ताकि उन्हें लेज़ी तरीके से कंप्यूट किया जा सके.

  7. वैरिएंट लॉक करना: वैरिएंट ऑब्जेक्ट अब लॉक हो गए हैं और उनमें बदलाव नहीं किया जा सकता.

  8. टास्क बनाए गए: Variant ऑब्जेक्ट और उनकी Property वैल्यू का इस्तेमाल, Task इंस्टेंस बनाने के लिए किया जाता है. ये इंस्टेंस, बिल्ड प्रोसेस को पूरा करने के लिए ज़रूरी होते हैं.

AGP में एक AndroidComponentsExtension शामिल किया गया है. इसकी मदद से, finalizeDsl(), beforeVariants(), और onVariants() के लिए कॉलबैक रजिस्टर किए जा सकते हैं. यह एक्सटेंशन, androidComponents ब्लॉक की मदद से बिल्ड स्क्रिप्ट में उपलब्ध है:

// This is used only for configuring the Android build through DSL.
android { ... }

// The androidComponents block is separate from the DSL.
androidComponents {
   finalizeDsl { extension ->
      ...
   }
}

हालांकि, हमारा सुझाव है कि बिल्ड स्क्रिप्ट को सिर्फ़ डिक्लेरेटिव कॉन्फ़िगरेशन के लिए इस्तेमाल करें. इसके लिए, Android ब्लॉक के डीएसएल का इस्तेमाल करें और किसी भी कस्टम इंपरेटिव लॉजिक को buildSrc डायरेक्ट्री या बाहरी प्लग इन में ले जाएं. अपने प्रोजेक्ट में प्लग इन बनाने का तरीका जानने के लिए, Gradle रेसिपी के GitHub रिपॉज़िटरी में मौजूद buildSrc सैंपल भी देखे जा सकते हैं. पक्का करें कि आपकी बिल्ड फ़ाइल में, आपका प्लग इन gradle-api आर्टफ़ैक्ट के साथ कंपाइल हो. यहां प्लगिन कोड से कॉलबैक रजिस्टर करने का एक उदाहरण दिया गया है:

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            ...
        }
    }
}

आइए, उपलब्ध कॉलबैक और इस्तेमाल के मामलों के टाइप पर नज़र डालते हैं. इनमें से हर एक में आपका प्लग इन काम कर सकता है:

finalizeDsl(callback: (DslExtensionT) -> Unit)

इस कॉलबैक में, उन डीएसएल ऑब्जेक्ट को ऐक्सेस किया जा सकता है और उनमें बदलाव किया जा सकता है जो बिल्ड फ़ाइलों में android ब्लॉक से जानकारी को पार्स करके बनाए गए थे. इन डीएसएल ऑब्जेक्ट का इस्तेमाल, बिल्ड के बाद के चरणों में वैरिएंट को शुरू और कॉन्फ़िगर करने के लिए किया जाएगा. उदाहरण के लिए, प्रोग्राम के हिसाब से नए कॉन्फ़िगरेशन बनाए जा सकते हैं या प्रॉपर्टी को बदला जा सकता है. हालांकि, ध्यान रखें कि कॉन्फ़िगरेशन के समय सभी वैल्यू को रिज़ॉल्व करना ज़रूरी है. इसलिए, उन्हें किसी बाहरी इनपुट पर निर्भर नहीं होना चाहिए. इस कॉलबैक के पूरा होने के बाद, डीएसएल ऑब्जेक्ट काम के नहीं रह जाते. साथ ही, आपको उनके रेफ़रंस सेव नहीं रखने चाहिए या उनकी वैल्यू में बदलाव नहीं करना चाहिए.

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            extension.buildTypes.create("extra").let {
                it.isJniDebuggable = true
            }
        }
    }
}

beforeVariants()

बिल्ड के इस चरण में, आपको VariantBuilder ऑब्जेक्ट का ऐक्सेस मिलता है. इनसे यह तय होता है कि कौनसे वैरिएंट बनाए जाएंगे और उनकी प्रॉपर्टी क्या होंगी. उदाहरण के लिए, प्रोग्राम के हिसाब से कुछ वैरिएंट और उनके टेस्ट बंद किए जा सकते हैं. इसके अलावा, किसी प्रॉपर्टी की वैल्यू (उदाहरण के लिए, minSdk) को सिर्फ़ चुने गए वैरिएंट के लिए बदला जा सकता है. finalizeDsl() की तरह ही, आपकी दी गई सभी वैल्यू को कॉन्फ़िगरेशन के समय ही रिज़ॉल्व किया जाना चाहिए. साथ ही, ये वैल्यू बाहरी इनपुट पर निर्भर नहीं होनी चाहिए. beforeVariants() कॉलबैक के पूरा होने के बाद, VariantBuilder ऑब्जेक्ट में बदलाव नहीं किया जाना चाहिए.

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

beforeVariants() कॉलबैक फ़ंक्शन, वैकल्पिक तौर पर VariantSelector लेता है. इसे androidComponentsExtension पर selector() वाले तरीके से हासिल किया जा सकता है. इसका इस्तेमाल, कॉलबैक को शुरू करने वाले कॉम्पोनेंट को उनके नाम, बिल्ड टाइप या ऐप्लिकेशन टाइप के आधार पर फ़िल्टर करने के लिए किया जा सकता है.

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

onVariants() को कॉल करने तक, एजीपी से बनाए जाने वाले सभी आर्टफ़ैक्ट पहले ही तय हो जाते हैं. इसलिए, अब उन्हें बंद नहीं किया जा सकता. हालांकि, टास्क के लिए इस्तेमाल की जाने वाली कुछ वैल्यू में बदलाव किया जा सकता है. इसके लिए, उन्हें Variant ऑब्जेक्ट में Property एट्रिब्यूट के लिए सेट करें. Property वैल्यू सिर्फ़ तब रिज़ॉल्व होंगी, जब एजीपी के टास्क पूरे हो जाएंगे. इसलिए, उन्हें अपने कस्टम टास्क से प्रोवाइडर को सुरक्षित तरीके से वायर किया जा सकता है. ये टास्क, ज़रूरी कंप्यूटेशन करेंगे. इनमें फ़ाइलों या नेटवर्क जैसे बाहरी इनपुट से डेटा पढ़ना शामिल है.

// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
    // Gather the output when we are in single mode (no multi-apk).
    val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

    // Create version code generating task
    val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
        it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
    }
    /**
     * Wire version code from the task output.
     * map() will create a lazy provider that:
     * 1. Runs just before the consumer(s), ensuring that the producer
     * (VersionCodeTask) has run and therefore the file is created.
     * 2. Contains task dependency information so that the consumer(s) run after
     * the producer.
     */
    mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}

जनरेट किए गए सोर्स को बिल्ड में शामिल करना

आपका प्लग इन, जनरेट किए गए कुछ तरह के सोर्स में योगदान दे सकता है. जैसे:

जोड़े जा सकने वाले सोर्स की पूरी सूची देखने के लिए, Sources API देखें.

इस कोड स्निपेट में, addStaticSourceDirectory() फ़ंक्शन का इस्तेमाल करके, Java सोर्स सेट में ${variant.name} नाम का कस्टम सोर्स फ़ोल्डर जोड़ने का तरीका बताया गया है. इसके बाद, Android टूलचेन इस फ़ोल्डर को प्रोसेस करता है.

onVariants { variant ->
    variant.sources.java?.let { java ->
        java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
    }
}

ज़्यादा जानकारी के लिए, addJavaSource रेसिपी देखें.

इस कोड स्निपेट में, कस्टम टास्क से जनरेट किए गए Android रिसॉर्स वाली डायरेक्ट्री को res सोर्स सेट में जोड़ने का तरीका बताया गया है. अन्य सोर्स टाइप के लिए भी यह प्रोसेस एक जैसी है.

onVariants(selector().withBuildType("release")) { variant ->
    // Step 1. Register the task.
    val resCreationTask =
       project.tasks.register<ResCreatorTask>("create${variant.name}Res")

    // Step 2. Register the task output to the variant-generated source directory.
    variant.sources.res?.addGeneratedSourceDirectory(
       resCreationTask,
       ResCreatorTask::outputDirectory)
    }

...

// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
   @get:OutputFiles
   abstract val outputDirectory: DirectoryProperty

   @TaskAction
   fun taskAction() {
      // Step 4. Generate your resources.
      ...
   }
}

ज़्यादा जानकारी के लिए, addCustomAsset रेसिपी देखें.

आर्टफ़ैक्ट ऐक्सेस करना और उनमें बदलाव करना

AGP की मदद से, Variant ऑब्जेक्ट की सामान्य प्रॉपर्टी में बदलाव किया जा सकता है. साथ ही, इसमें एक एक्सटेंशन मैकेनिज़्म भी होता है. इसकी मदद से, बिल्ड के दौरान जनरेट हुए इंटरमीडिएट और फ़ाइनल आर्टफ़ैक्ट को पढ़ा जा सकता है या उनमें बदलाव किया जा सकता है. उदाहरण के लिए, मर्ज की गई फ़ाइनल AndroidManifest.xml फ़ाइल के कॉन्टेंट को कस्टम Task में पढ़ा जा सकता है, ताकि उसका विश्लेषण किया जा सके. इसके अलावा, इसके कॉन्टेंट को कस्टम Task से जनरेट की गई मेनिफ़ेस्ट फ़ाइल के कॉन्टेंट से पूरी तरह बदला जा सकता है.

फ़िलहाल, इस्तेमाल किए जा सकने वाले आर्टफ़ैक्ट की सूची, Artifact क्लास के रेफ़रंस दस्तावेज़ में देखी जा सकती है. हर तरह के आर्टफ़ैक्ट की कुछ ऐसी प्रॉपर्टी होती हैं जिनके बारे में जानना ज़रूरी है:

एलिमेंट की संख्या

Artifact के एलिमेंट की संख्या से, उसके FileSystemLocation इंस्टेंस की संख्या या आर्टफ़ैक्ट टाइप की फ़ाइलों या डायरेक्ट्री की संख्या का पता चलता है. किसी आर्टफ़ैक्ट में मौजूद एलिमेंट की संख्या के बारे में जानकारी पाने के लिए, उसकी पैरंट क्लास देखें: एक FileSystemLocation वाले आर्टफ़ैक्ट, Artifact.Single की सबक्लास होंगे. वहीं, FileSystemLocation के कई इंस्टेंस वाले आर्टफ़ैक्ट, Artifact.Multiple की सबक्लास होंगे.

FileSystemLocation तरह का

यह देखा जा सकता है कि कोई Artifact, फ़ाइलों या डायरेक्ट्री को दिखाता है या नहीं. इसके लिए, उसके पैरामीटर वाले FileSystemLocation टाइप को देखें. यह RegularFile या Directory हो सकता है.

इन कार्रवाइयों के लिए यह सुविधा उपलब्ध है

हर Artifact क्लास, इनमें से किसी भी इंटरफ़ेस को लागू कर सकती है, ताकि यह बताया जा सके कि वह किन कार्रवाइयों के साथ काम करती है:

  • Transformable: इससे Artifact को Task के इनपुट के तौर पर इस्तेमाल किया जा सकता है. यह Task, Artifact में अपनी मर्ज़ी से बदलाव करता है और उसका नया वर्शन आउटपुट के तौर पर देता है.
  • Appendable: यह सिर्फ़ उन आर्टफ़ैक्ट पर लागू होता है जो Artifact.Multiple की सबक्लास हैं. इसका मतलब है कि Artifact में और वैल्यू जोड़ी जा सकती हैं. यानी, कस्टम Task इस Artifact टाइप के नए इंस्टेंस बना सकता है. इन्हें मौजूदा सूची में जोड़ दिया जाएगा.
  • Replaceable: यह सिर्फ़ उन आर्टफ़ैक्ट पर लागू होता है जो Artifact.Single की सबक्लास हैं. बदले जा सकने वाले Artifact को पूरी तरह से नए इंस्टेंस से बदला जा सकता है. यह इंस्टेंस, Task के आउटपुट के तौर पर जनरेट होता है.

आर्टफ़ैक्ट में बदलाव करने वाले तीन ऑपरेशन के अलावा, हर आर्टफ़ैक्ट में get() (या getAll()) ऑपरेशन भी काम करता है. यह आर्टफ़ैक्ट के फ़ाइनल वर्शन के साथ Provider दिखाता है. ऐसा तब होता है, जब उस पर सभी ऑपरेशन पूरे हो जाते हैं.

एक से ज़्यादा प्लग इन, onVariants() कॉलबैक से आर्टफ़ैक्ट पर किसी भी संख्या में कार्रवाइयां जोड़ सकते हैं. साथ ही, एजीपी यह पक्का करेगा कि वे सही तरीके से चेन किए गए हों, ताकि सभी टास्क सही समय पर चलें और आर्टफ़ैक्ट सही तरीके से जनरेट और अपडेट हों. इसका मतलब है कि जब कोई कार्रवाई किसी आउटपुट को जोड़कर, बदलकर या ट्रांसफ़ॉर्म करके बदलती है, तो अगली कार्रवाई में इन आर्टफ़ैक्ट का अपडेट किया गया वर्शन इनपुट के तौर पर दिखेगा. इसी तरह, आगे भी ऐसा ही होगा.

ऑपरेशन रजिस्टर करने के लिए, एंट्री पॉइंट Artifacts क्लास है. यहां दिए गए कोड स्निपेट में बताया गया है कि onVariants() कॉलबैक में Variant ऑब्जेक्ट की प्रॉपर्टी से, Artifacts के इंस्टेंस का ऐक्सेस कैसे पाया जा सकता है.

इसके बाद, TaskBasedOperation ऑब्जेक्ट (1) पाने के लिए, अपना कस्टम TaskProvider पास किया जा सकता है. साथ ही, wiredWith* तरीकों (2) में से किसी एक का इस्तेमाल करके, इसके इनपुट और आउटपुट को कनेक्ट किया जा सकता है.

आपको कौनसा तरीका चुनना है, यह एलिमेंट की संख्या और FileSystemLocation टाइप पर निर्भर करता है. यह टाइप, उस Artifact ने लागू किया है जिसे आपको बदलना है.

आखिर में, Artifact टाइप को उस तरीके में पास किया जाता है जो बदले में मिलने वाले *OperationRequest ऑब्जेक्ट पर चुनी गई कार्रवाई को दिखाता है. उदाहरण के लिए, toAppendTo(), toTransform() या toCreate() (3).

androidComponents.onVariants { variant ->
    val manifestUpdater = // Custom task that will be used for the transform.
            project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
                it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
            }
    // (1) Register the TaskProvider w.
    val variant.artifacts.use(manifestUpdater)
         // (2) Connect the input and output files.
        .wiredWithFiles(
            ManifestTransformerTask::mergedManifest,
            ManifestTransformerTask::updatedManifest)
        // (3) Indicate the artifact and operation type.
        .toTransform(SingleArtifact.MERGED_MANIFEST)
}

इस उदाहरण में, MERGED_MANIFEST एक SingleArtifact है और यह एक RegularFile है. इस वजह से, हमें wiredWithFiles तरीके का इस्तेमाल करना होगा. यह तरीका, इनपुट के लिए एक RegularFileProperty रेफ़रंस और आउटपुट के लिए एक RegularFileProperty रेफ़रंस स्वीकार करता है. TaskBasedOperation क्लास में wiredWith* के अन्य तरीके भी हैं. ये तरीके, Artifact एलिमेंट की संख्या और FileSystemLocation टाइप के अन्य कॉम्बिनेशन के लिए काम करेंगे.

एजीपी को एक्सटेंड करने के बारे में ज़्यादा जानने के लिए, हमारा सुझाव है कि आप Gradle बिल्ड सिस्टम मैन्युअल के ये सेक्शन पढ़ें: