Android KMP के लिए, अपने हिसाब से Gradle प्लगिन बनाना

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

देखें कि किसी प्रोजेक्ट में Kotlin Multiplatform प्लगिन का इस्तेमाल किया जा रहा है या नहीं

गड़बड़ियों से बचने और यह पक्का करने के लिए कि आपका प्लगिन सिर्फ़ तब काम करे, जब KMP मौजूद हो, आपको यह जांच करनी होगी कि प्रोजेक्ट में KMP प्लगिन का इस्तेमाल किया जा रहा है या नहीं. सबसे सही तरीका यह है कि KMP प्लगिन लागू होने पर प्रतिक्रिया देने के लिए, plugins.withId() का इस्तेमाल किया जाए. इसके बजाय, इसे तुरंत जांचने के लिए इस्तेमाल न करें. इस तरीके से, आपका प्लगिन इस बात से सुरक्षित रहता है कि उपयोगकर्ता की बिल्ड स्क्रिप्ट में प्लगिन किस क्रम में लागू किए गए हैं.

import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
            // The KMP plugin is applied, you can now configure your KMP integration.
        }
    }
}

मॉडल को ऐक्सेस करना

सभी Kotlin Multiplatform कॉन्फ़िगरेशन के लिए एंट्री पॉइंट, KotlinMultiplatformExtension एक्सटेंशन है.

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
            val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
        }
    }
}

Kotlin Multiplatform टारगेट पर प्रतिक्रिया करना

targets कंटेनर का इस्तेमाल करके, उपयोगकर्ता के जोड़े गए हर टारगेट के लिए अपने प्लग इन को कॉन्फ़िगर करें.

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
            val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
            kmpExtension.targets.configureEach { target ->
                // 'target' is an instance of KotlinTarget
                val targetName = target.name // for example, "android", "iosX64", "jvm"
                val platformType = target.platformType // for example, androidJvm, jvm, native, js
            }
        }
    }
}

टारगेट के हिसाब से लॉजिक लागू करना

अगर आपके प्लगिन को लॉजिक सिर्फ़ कुछ प्लैटफ़ॉर्म पर लागू करना है, तो platformType प्रॉपर्टी की जांच करना एक सामान्य तरीका है. यह एक इनम है, जो टारगेट को मोटे तौर पर कैटगरी में बांटता है.

उदाहरण के लिए, इसका इस्तेमाल तब करें, जब आपके प्लगिन को सिर्फ़ सामान्य तौर पर अंतर करना हो (उदाहरण के लिए, सिर्फ़ JVM जैसे टारगेट पर चलाना हो):

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
            val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
            kmpExtension.targets.configureEach { target ->
                when (target.platformType) {
                    KotlinPlatformType.jvm -> { /* Standard JVM or Android */ }
                    KotlinPlatformType.androidJvm -> { /* Android */ }
                    KotlinPlatformType.js -> { /* JavaScript */ }
                    KotlinPlatformType.native -> { /* Any Native (iOS, Linux, Windows, etc.) */ }
                    KotlinPlatformType.wasm -> { /* WebAssembly */ }
                    KotlinPlatformType.common -> { /* Metadata target (rarely needs direct plugin interaction) */ }
                }
            }
        }
    }
}

Android के लिए खास जानकारी

सभी Android टारगेट में platformType.androidJvm इंडिकेटर होता है. हालांकि, केएमपी में इस्तेमाल किए गए Android Gradle प्लगिन के आधार पर, दो अलग-अलग इंटिग्रेशन पॉइंट होते हैं: com.android.library या com.android.application का इस्तेमाल करने वाले प्रोजेक्ट के लिए KotlinAndroidTarget और com.android.kotlin.multiplatform.library का इस्तेमाल करने वाले प्रोजेक्ट के लिए KotlinMultiplatformAndroidLibraryTarget.

KotlinMultiplatformAndroidLibraryTarget एपीआई को AGP 8.8.0 में जोड़ा गया था. इसलिए, अगर आपके प्लगिन का इस्तेमाल करने वाले लोग AGP के पुराने वर्शन का इस्तेमाल कर रहे हैं, तो target is KotlinMultiplatformAndroidLibraryTarget की जांच करने पर ClassNotFoundException मिल सकता है. इसे सुरक्षित बनाने के लिए, टारगेट टाइप की जांच करने से पहले AndroidPluginVersion.getCurrent() की जांच करें. ध्यान दें कि AndroidPluginVersion.getCurrent() के लिए, AGP 7.1 या इसके बाद का वर्शन ज़रूरी है.

import com.android.build.api.AndroidPluginVersion
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.kotlin.multiplatform.library") {
            val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
            kmpExtension.targets.configureEach { target ->
                if (target is KotlinAndroidTarget) {
                    // Old kmp android integration using com.android.library or com.android.application
                }
                if (AndroidPluginVersion.getCurrent() >= AndroidPluginVersion(8, 8) &&
                    target is KotlinMultiplatformAndroidLibraryTarget
                ) {
                    // New kmp android integration using com.android.kotlin.multiplatform.library
                }
            }
        }
    }
}

Android KMP एक्सटेंशन और उसकी प्रॉपर्टी ऐक्सेस करना

आपका प्लगिन मुख्य रूप से, Kotlin Multiplatform प्लगिन से मिले Kotlin एक्सटेंशन और KMP Android टारगेट के लिए AGP से मिले Android एक्सटेंशन के साथ इंटरैक्ट करेगा. केएमपी प्रोजेक्ट में Kotlin एक्सटेंशन के अंदर मौजूद android {} ब्लॉक को KotlinMultiplatformAndroidLibraryTarget इंटरफ़ेस से दिखाया जाता है. यह KotlinMultiplatformAndroidLibraryExtension को भी बढ़ाता है. इसका मतलब है कि इस एक ऑब्जेक्ट के ज़रिए, टारगेट और Android, दोनों के हिसाब से डीएसएल प्रॉपर्टी ऐक्सेस की जा सकती हैं.

import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.kotlin.multiplatform.library") {
            val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)

            // Access the Android target, which also serves as the Android-specific DSL extension
            kmpExtension.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java).configureEach { androidTarget ->

                // You can now access properties and methods from both
                // KotlinMultiplatformAndroidLibraryTarget and KotlinMultiplatformAndroidLibraryExtension
                androidTarget.compileSdk = 34
                androidTarget.namespace = "com.example.myplugin.library"
                androidTarget.withJava() // enable Java sources
            }
        }
    }
}

अन्य Android प्लगिन (जैसे, com.android.library या com.android.application) के उलट, KMP Android प्लगिन, प्रोजेक्ट लेवल पर अपने मुख्य डीएसएल एक्सटेंशन को रजिस्टर नहीं करता. यह KMP टारगेट के क्रम में मौजूद होता है, ताकि यह सिर्फ़ उस Android टारगेट पर लागू हो जिसे आपने मल्टीप्लेटफ़ॉर्म सेटअप में तय किया है.

संकलन और सोर्स सेट मैनेज करना

अक्सर, प्लगिन को सिर्फ़ टारगेट से ज़्यादा बारीक लेवल पर काम करने की ज़रूरत होती है. खास तौर पर, उन्हें कंपाइलेशन लेवल पर काम करने की ज़रूरत होती है. KotlinMultiplatformAndroidLibraryTarget में KotlinMultiplatformAndroidCompilation इंस्टेंस होते हैं. उदाहरण के लिए, main, hostTest, deviceTest. हर कंपाइलेशन, Kotlin सोर्स सेट से जुड़ा होता है. प्लगिन, सोर्स और डिपेंडेंसी जोड़ने या कंपाइल करने के टास्क कॉन्फ़िगर करने के लिए, इनके साथ इंटरैक्ट कर सकते हैं.

import com.android.build.api.dsl.KotlinMultiplatformAndroidCompilation
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.kotlin.multiplatform.library") {
            val kmpExtension = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
            kmpExtension.targets.configureEach { target ->
                target.compilations.configureEach { compilation ->
                    // standard compilations are usually 'main' and 'test'
                    // android target has 'main', 'hostTest', 'deviceTest'
                    val compilationName = compilation.name

                    // Access the default source set for this compilation
                    val defaultSourceSet = compilation.defaultSourceSet

                    // Access the Android-specific compilation DSL
                    if (compilation is KotlinMultiplatformAndroidCompilation) {

                    }

                    // Access and configure the Kotlin compilation task
                    compilation.compileTaskProvider.configure { compileTask ->

                    }
                }
            }
        }
    }
}

कन्वेंशन प्लगिन में टेस्ट कंपाइलेशन कॉन्फ़िगर करना

कन्वेंशन प्लगिन में, टेस्ट कंपाइलेशन (जैसे कि इंस्ट्रुमेंट किए गए टेस्ट के लिए targetSdk) के लिए डिफ़ॉल्ट वैल्यू कॉन्फ़िगर करते समय, आपको withDeviceTest { } या withHostTest { } जैसे एनबलर तरीकों का इस्तेमाल नहीं करना चाहिए. इन तरीकों को कॉल करने से, convention प्लगिन लागू करने वाले हर मॉड्यूल के लिए, Android टेस्ट के वैरिएंट और कंपाइलेशन तुरंत ट्रिगर हो जाते हैं. हालांकि, ऐसा हो सकता है कि यह तरीका सही न हो. इसके अलावा, इन तरीकों को किसी खास मॉड्यूल में दूसरी बार कॉल नहीं किया जा सकता, ताकि सेटिंग को बेहतर बनाया जा सके. ऐसा करने पर, एक गड़बड़ी दिखेगी. इसमें बताया जाएगा कि कंपाइलेशन पहले ही बनाया जा चुका है.

इसके बजाय, हमारा सुझाव है कि आप कंपाइलेशन कंटेनर पर, रिऐक्टिव configureEach ब्लॉक का इस्तेमाल करें. इससे डिफ़ॉल्ट कॉन्फ़िगरेशन दिए जा सकते हैं. ये कॉन्फ़िगरेशन सिर्फ़ तब लागू होते हैं, जब कोई मॉड्यूल टेस्ट कंपाइलेशन को साफ़ तौर पर चालू करता है:

import com.android.build.api.dsl.KotlinMultiplatformAndroidDeviceTestCompilation
import com.android.build.api.dsl.KotlinMultiplatformAndroidLibraryTarget
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.kotlin.multiplatform.library") {
            val kmpExtension =
                project.extensions.getByType(KotlinMultiplatformExtension::class.java)
            kmpExtension.targets.withType(KotlinMultiplatformAndroidLibraryTarget::class.java)
                .configureEach { androidTarget ->
                    androidTarget.compilations.withType(
                        KotlinMultiplatformAndroidDeviceTestCompilation::class.java
                    ).configureEach {
                        targetSdk { version = release(34) }
                    }
                }
        }
    }
}

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

Variant API के साथ इंटरैक्ट करना

ऐसे टास्क के लिए Android Variant API का इस्तेमाल करना ज़रूरी है जिनके लिए कॉन्फ़िगरेशन बाद में करना होता है, आर्टफ़ैक्ट का ऐक्सेस (जैसे कि मेनिफ़ेस्ट या बाइट-कोड) ज़रूरी होता है या कुछ कॉम्पोनेंट को चालू या बंद करने की सुविधा ज़रूरी होती है. KMP प्रोजेक्ट में, एक्सटेंशन KotlinMultiplatformAndroidComponentsExtension टाइप का होता है.

KMP Android प्लगिन लागू होने पर, एक्सटेंशन को प्रोजेक्ट लेवल पर रजिस्टर किया जाता है.

beforeVariants का इस्तेमाल करके, वैरिएंट बनाने या उनके नेस्ट किए गए टेस्ट कॉम्पोनेंट (hostTests और deviceTests) को कंट्रोल करें. प्रोग्राम के हिसाब से टेस्ट बंद करने या डीएसएल प्रॉपर्टी की वैल्यू बदलने के लिए, यह सही जगह है.

import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.kotlin.multiplatform.library") {
            val androidComponents = project.extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)
            androidComponents?.beforeVariants { variantBuilder ->
                // Disable all tests for this module
                variantBuilder.hostTests.values.forEach { it.enable = false }
                variantBuilder.deviceTests.values.forEach { it.enable = false }
            }
        }
    }
}

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

import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.kotlin.multiplatform.library") {
            val androidComponents = project.extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)
            androidComponents?.onVariants { variant ->
                // 'variant' is a KotlinMultiplatformAndroidVariant
                val variantName = variant.name

                // Access the artifacts API
                val manifest = variant.artifacts.get(com.android.build.api.variant.SingleArtifact.MERGED_MANIFEST)
            }
        }
    }
}