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

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

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

गड़बड़ियों से बचने और यह पक्का करने के लिए कि आपका प्लगिन सिर्फ़ तब काम करे, जब केएमपी मौजूद हो, आपको यह देखना होगा कि प्रोजेक्ट में केएमपी प्लगिन का इस्तेमाल किया गया है या नहीं. केएमपी प्लगिन के लागू होने पर, तुरंत उसकी जांच करने के बजाय, 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.
        }
    }
}

मॉडल का ऐक्सेस पाना

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

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 प्रॉपर्टी की जांच करना एक आम तरीका है. यह एक एनम है, जो टारगेट को मोटे तौर पर कैटगरी में बांटता है.

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

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 केएमपी एक्सटेंशन और उसकी प्रॉपर्टी का ऐक्सेस पाना

आपका प्लगिन, मुख्य तौर पर Kotlin Multiplatform प्लगिन से मिले Kotlin एक्सटेंशन और केएमपी 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) के उलट, केएमपी Android प्लगिन, प्रोजेक्ट लेवल पर अपना मुख्य डीएसएल एक्सटेंशन रजिस्टर नहीं करता. यह केएमपी टारगेट के क्रम में मौजूद होता है, ताकि यह पक्का किया जा सके कि यह सिर्फ़ आपके मल्टीप्लैटफ़ॉर्म सेटअप में तय किए गए 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 { } जैसे एनबलर तरीकों का इस्तेमाल नहीं करना चाहिए. इन तरीकों को तुरंत कॉल करने से, हर उस मॉड्यूल के लिए, 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 { } को कॉल करके, अपने टेस्ट को डिफ़ॉल्ट सेटिंग से अलग, चालू और पसंद के मुताबिक बना सकें.

वैरिएंट एपीआई के साथ इंटरैक्ट करना

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

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

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

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)
            }
        }
    }
}