Android KMP-এর জন্য কাস্টম Gradle প্লাগ-ইন তৈরি করা

এই নির্দেশিকা ব্যাখ্যা করে যে কীভাবে কাস্টম Gradle প্লাগইন থেকে Kotlin Multiplatform (KMP) প্রোজেক্টে Android টার্গেট শনাক্ত, ইন্টার‍্যাক্ট এবং কনফিগার করতে হয়। আপনার বিল্ডের জন্য কনভেনশন প্লাগ-ইন লেখার সময় বা KMP প্রোজেক্টের জন্য Gradle প্লাগ-ইন প্রকাশ করার সময় এইসব API ব্যবহার করুন।

কোনও প্রোজেক্ট 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 প্রপার্টি চেক করা। এটি একটি enum যা টার্গেটকে মোটামুটিভাবে শ্রেণীবদ্ধ করে।

যেমন, আপনার প্লাগ-ইনকে যদি শুধু বড় আকারে পার্থক্য করতে হয় (যেমন, শুধু 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 */ }
                    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 ইন্ডিকেটর থাকলেও, KMP-তে দুটি আলাদা ইন্টিগ্রেশন পয়েন্ট আছে যা ব্যবহৃত Android Gradle প্লাগ-ইনের উপর নির্ভর করে: KotlinAndroidTarget com.android.library বা com.android.application ব্যবহার করা প্রোজেক্টের জন্য এবং KotlinMultiplatformAndroidLibraryTarget com.android.kotlin.multiplatform.library ব্যবহার করা প্রোজেক্টের জন্য।

KotlinMultiplatformAndroidLibraryTarget API-কে AGP 8.8.0-তে যোগ করা হয়েছে, তাই আপনার প্লাগ-ইনের গ্রাহকরা AGP-এর কোনও পুরনো ভার্সন ব্যবহার করলে, target is KotlinMultiplatformAndroidLibraryTarget চেক করলে ClassNotFoundException হতে পারে। এটি নিরাপদ করতে, টার্গেট টাইপ চেক করার আগে AndroidPluginVersion.getCurrent() চেক করুন। AndroidPluginVersion.getCurrent()-এর জন্য AGP 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 KMP এক্সটেনশন ও এর প্রপার্টি অ্যাক্সেস করা

আপনার প্লাগ-ইন মূলত Kotlin Multiplatform প্লাগ-ইন দ্বারা প্রদত্ত Kotlin এক্সটেনশন এবং KMP Android টার্গেটের জন্য AGP দ্বারা প্রদত্ত Android এক্সটেনশনের সাথে ইন্টার‍্যাক্ট করে। KMP প্রোজেক্টে Kotlin এক্সটেনশনের মধ্যে android {} ব্লক KotlinMultiplatformAndroidLibraryTarget ইন্টারফেসের মাধ্যমে দেখানো হয়, যা KotlinMultiplatformAndroidLibraryExtension-কেও এক্সটেন্ড করে। এর অর্থ হল, আপনি এই একটি অবজেক্টের মাধ্যমে টার্গেট-নির্দিষ্ট এবং Android-নির্দিষ্ট DSL প্রপার্টি অ্যাক্সেস করতে পারবেন।

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 প্লাগ-ইন প্রোজেক্ট লেভেলে এর প্রধান DSL এক্সটেনশন রেজিস্টার করে না। এটি 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 Variant API ব্যবহার করতে হবে। KMP প্রোজেক্টে, এক্সটেনশনটি KotlinMultiplatformAndroidComponentsExtension ধরনের।

KMP Android প্লাগ-ইন প্রয়োগ করা হলে, প্রোজেক্ট লেভেলে এক্সটেনশন রেজিস্টার করা হয়।

ভেরিয়েন্ট বা তাদের নেস্টেড টেস্ট কম্পোনেন্ট (hostTests এবং deviceTests) তৈরি করা নিয়ন্ত্রণ করতে beforeVariants ব্যবহার করুন। প্রোগ্রাম্যাটিক্যালি টেস্ট বন্ধ করতে বা DSL প্রপার্টির ভ্যালু পরিবর্তন করতে এটিই সঠিক জায়গা।

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

চূড়ান্ত ভ্যারিয়েন্ট অবজেক্ট অ্যাক্সেস করতে onVariants ব্যবহার করুন (KotlinMultiplatformAndroidVariant)। এখানে আপনি সমাধান করা প্রপার্টি পরীক্ষা করতে পারবেন অথবা মার্জ করা ম্যানিফেস্ট বা লাইব্রেরি ক্লাসের মতো আর্টিফ্যাক্টে ট্রান্সফর্মেশন রেজিস্টার করতে পারবেন।

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