ساخت افزونه‌های سفارشی Gradle برای اندروید KMP

این سند راهنمایی برای نویسندگان افزونه در مورد نحوه تشخیص صحیح، تعامل با و پیکربندی تنظیمات Kotlin Multiplatform (KMP) ارائه می‌دهد، با تمرکز ویژه بر ادغام با اهداف اندروید در یک پروژه KMP. با ادامه تکامل KMP، درک hookها و APIهای مناسب - مانند KotlinMultiplatformExtension ، انواع KotlinTarget و رابط‌های یکپارچه‌سازی مخصوص اندروید - برای ساخت ابزارهای قوی و آینده‌نگر که به طور یکپارچه در تمام پلتفرم‌های تعریف شده در یک پروژه چند پلتفرمی کار می‌کنند، ضروری است.

بررسی کنید که آیا یک پروژه از افزونه Kotlin Multiplatform استفاده می‌کند یا خیر

برای جلوگیری از خطا و اطمینان از اینکه افزونه شما فقط زمانی اجرا می‌شود که KMP وجود داشته باشد، باید بررسی کنید که آیا پروژه از افزونه KMP استفاده می‌کند یا خیر. بهتر است از plugins.withId() برای واکنش به افزونه KMP در حال اعمال استفاده کنید، نه اینکه فوراً آن را بررسی کنید. این رویکرد واکنشی مانع از آن می‌شود که افزونه شما نسبت به ترتیب اعمال افزونه‌ها در اسکریپت‌های ساخت کاربر، شکننده باشد.

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 است.

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

واکنش به اهداف چند پلتفرمی کاتلین

از کانتینر targets برای پیکربندی واکنشی افزونه خود برای هر target که کاربر اضافه می‌کند، استفاده کنید.

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

جزئیات مخصوص اندروید

در حالی که همه تارگت‌های اندروید دارای نشانگر platformType.androidJvm هستند، KMP بسته به افزونه Android Gradle مورد استفاده، دو نقطه ادغام مجزا دارد: KotlinAndroidTarget برای پروژه‌هایی که از com.android.library یا com.android.application استفاده می‌کنند، و KotlinMultiplatformAndroidLibraryTarget برای پروژه‌هایی که از com.android.kotlin.multiplatform.library استفاده می‌کنند.

رابط برنامه‌نویسی کاربردی 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
                }
            }
        }
    }
}

دسترسی به افزونه‌ی KMP اندروید و ویژگی‌های آن

افزونه شما در درجه اول با افزونه Kotlin ارائه شده توسط افزونه Kotlin Multiplatform و افزونه Android ارائه شده توسط AGP برای هدف KMP Android تعامل خواهد داشت. بلوک android {} در افزونه Kotlin در یک پروژه KMP توسط رابط KotlinMultiplatformAndroidLibraryTarget نمایش داده می‌شود که KotlinMultiplatformAndroidLibraryExtension را نیز بسط می‌دهد. این بدان معناست که می‌توانید از طریق این شیء واحد به هر دو ویژگی 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
            }
        }
    }
}

برخلاف سایر افزونه‌های اندروید (مانند com.android.library یا com.android.application )، افزونه اندروید KMP افزونه DSL اصلی خود را در سطح پروژه ثبت نمی‌کند. این افزونه در سلسله مراتب هدف KMP قرار دارد تا مطمئن شود که فقط روی هدف خاص اندروید تعریف شده در تنظیمات چند پلتفرمی شما اعمال می‌شود.

مدیریت کامپایل‌ها و مجموعه‌های منبع

اغلب، افزونه‌ها باید در سطح جزئی‌تری نسبت به هدف کار کنند - به طور خاص، آنها باید در سطح کامپایل کار کنند. 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 { } خودداری کنید. فراخوانی مشتاقانه این متدها باعث ایجاد انواع تست اندروید و کامپایل‌های مربوطه برای هر ماژولی می‌شود که افزونه قراردادی را اعمال می‌کند، که ممکن است مناسب نباشد. علاوه بر این، این متدها را نمی‌توان بار دوم در یک ماژول خاص برای اصلاح تنظیمات فراخوانی کرد، زیرا انجام این کار خطایی مبنی بر ایجاد کامپایل از قبل ایجاد شده، ایجاد می‌کند.

در عوض، توصیه می‌کنیم از یک بلوک reactive 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 { } را فراخوانی کنند تا تست‌های خود را بدون تداخل با پیش‌فرض‌ها فعال و سفارشی‌سازی کنند.

تعامل با API نوع (Variant API)

برای کارهایی که نیاز به پیکربندی در مراحل پایانی، دسترسی به مصنوعات (مانند مانیفست یا بایت کد) یا امکان فعال یا غیرفعال کردن اجزای خاص دارند، باید از API نوع Android Variant استفاده کنید. در پروژه‌های KMP، این افزونه از نوع KotlinMultiplatformAndroidComponentsExtension است.

این افزونه هنگام اعمال افزونه اندروید KMP در سطح پروژه ثبت می‌شود.

از beforeVariants برای کنترل ایجاد گونه‌ها یا اجزای تست تو در تو ( hostTests و deviceTests ) استفاده کنید. این مکان صحیح برای غیرفعال کردن تست‌ها یا تغییر مقادیر ویژگی‌های 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.variant.SingleArtifact.MERGED_MANIFEST)
            }
        }
    }
}
{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}