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

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

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

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

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

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

अब इस्तेमाल नहीं होने की नीति

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

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

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

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

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

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

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

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

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

एक्ज़ीक्यूशन का चरण

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

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

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

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

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

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

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

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

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

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

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

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

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

एजीपी, 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 सैंपल भी देखे जा सकते हैं. यहां प्लगिन कोड से कॉलबैक रजिस्टर करने का एक उदाहरण दिया गया है:

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 रेसिपी देखें.

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

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 रेसिपी देखें.

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

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

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

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

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

FileSystemLocation टाइप

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

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

हर Artifact क्लास, यह बताने के लिए कि वह कौनसी कार्रवाइयों के लिए उपलब्ध है, इनमें से कोई भी इंटरफ़ेस लागू कर सकती है:

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

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

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

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

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

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

आखिर में, 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 बिल्ड सिस्टम के मैन्युअल के ये सेक्शन पढ़ें: