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

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

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

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

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

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 */ }
                    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() के लिए, एजीपी 8.3.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.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget

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 ->
                if (target is KotlinAndroidTarget) {
                    // Old kmp android integration using com.android.library or com.android.application
                } else if (
                    // Check platformType first, so AGP classes load only for Android targets
                    target.platformType == KotlinPlatformType.androidJvm &&
                    AndroidPluginVersion.getCurrent() >= AndroidPluginVersion(8, 8) &&
                    target is KotlinMultiplatformAndroidLibraryTarget
                ) {
                    // New kmp android integration using com.android.kotlin.multiplatform.library
                }
            }
        }
    }
}

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

आपका प्लगिन मुख्य रूप से, Kotlin Multiplatform प्लगिन से मिले Kotlin एक्सटेंशन और केएमपी Android टारगेट के लिए एजीपी से मिले 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
            }
        }
    }
}

com.android.library या com.android.application जैसे अन्य Android प्लग इन के उलट, केएमपी 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 { } जैसे एनेबलर तरीकों का इस्तेमाल करने से बचें. इन तरीकों को तुरंत कॉल करने से, 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 {
                        it.targetSdk { version = release(34) }
                    }
                }
        }
    }
}

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

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

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

KMP 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.artifact.SingleArtifact.MERGED_MANIFEST)
            }
        }
    }
}