Android KMP için özel Gradle eklentileri oluşturma

Bu kılavuzda, özel bir Gradle eklentisinden Kotlin Multiplatform (KMP) projesindeki Android hedefini nasıl algılayacağınız, yapılandıracağınız ve bu hedefle nasıl etkileşim kuracağınız açıklanmaktadır. Derlemeniz için kural eklentileri yazarken veya KMP projeleri için Gradle eklentileri yayınlarken bu API'leri kullanın.

Bir projenin Kotlin Multiplatform eklentisini kullanıp kullanmadığını kontrol etme

Hataları önlemek ve eklentinizin yalnızca KMP mevcut olduğunda çalışmasını sağlamak için projenin KMP eklentisini kullanıp kullanmadığını kontrol etmeniz gerekir. KMP eklentisinin uygulanmasına tepki vermek için eklentiyi hemen kontrol etmek yerine plugins.withId() kullanmak en iyi uygulamadır. Bu reaktif yaklaşım, eklentinizin kullanıcının derleme komut dosyalarında eklentilerin uygulandığı sıraya karşı hassas olmasını önler.

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

Modele erişme

Tüm Kotlin Multiplatform yapılandırmalarının giriş noktası KotlinMultiplatformExtension uzantısıdır.

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 hedeflerine tepki verme

Kullanıcının eklediği her hedef için eklentinizi reaktif olarak yapılandırmak üzere targets kapsayıcısını kullanın.

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

Hedefe özel mantık uygulama

Eklentinizin yalnızca belirli platform türlerine mantık uygulaması gerekiyorsa yaygın bir yaklaşım, platformType özelliğini kontrol etmektir. Bu, hedefi genel olarak kategorize eden bir enum'dur.

Örneğin, eklentinizin yalnızca genel olarak ayırt etmesi gerekiyorsa (yalnızca JVM benzeri hedeflerde çalışmak gibi) bunu kullanın:

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'e özgü ayrıntılar

Tüm Android hedeflerinde platformType.androidJvm göstergesi bulunurken KMP'de, kullanılan Android Gradle eklentisine bağlı olarak iki farklı entegrasyon noktası vardır: com.android.library veya com.android.application kullanan projeler için KotlinAndroidTarget ve com.android.kotlin.multiplatform.library kullanan projeler için KotlinMultiplatformAndroidLibraryTarget.

KotlinMultiplatformAndroidLibraryTarget API'si AGP 8.8.0'da eklendi. Bu nedenle, eklentinizin kullanıcıları AGP'nin daha eski bir sürümünü kullanıyorsa target is KotlinMultiplatformAndroidLibraryTarget kontrolü ClassNotFoundException ile sonuçlanabilir. Bunu güvenli hale getirmek için hedef türünü kontrol etmeden önce AndroidPluginVersion.getCurrent() değerini kontrol edin. AndroidPluginVersion.getCurrent() için AGP 8.3.1 veya sonraki sürümler gerekir.

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 uzantısına ve özelliklerine erişme

Eklentiniz, Kotlin Multiplatform eklentisi tarafından sağlanan Kotlin uzantısıyla ve KMP Android hedefi için AGP tarafından sağlanan Android uzantısıyla etkileşim kurar. KMP projesindeki Kotlin uzantısının içindeki android {} bloğu, KotlinMultiplatformAndroidLibraryExtension'yi de genişleten KotlinMultiplatformAndroidLibraryTarget arayüzüyle temsil edilir. Bu sayede, hem hedefe özel hem de Android'e özel DSL özelliklerine bu tek nesne üzerinden erişebilirsiniz.

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

KMP Android eklentisi, diğer Android eklentilerinin (ör. com.android.library veya com.android.application) aksine ana DSL uzantısını proje düzeyinde kaydetmez. Bu, çoklu platform kurulumunuzda tanımlanan belirli Android hedefine uygulandığından emin olmak için KMP hedef hiyerarşisinde yer alır.

Derlemeleri ve kaynak gruplarını işleme

Eklentilerin genellikle yalnızca hedef düzeyinde değil, daha ayrıntılı bir düzeyde, özellikle de derleme düzeyinde çalışması gerekir. KotlinMultiplatformAndroidLibraryTarget, KotlinMultiplatformAndroidCompilation örnek (ör. main, hostTest, deviceTest) içerir. Her derleme, Kotlin kaynak gruplarıyla ilişkilendirilir. Eklentiler, kaynak ve bağımlılık eklemek veya derleme görevlerini yapılandırmak için bunlarla etkileşim kurabilir.

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

                    }
                }
            }
        }
    }
}

Kural eklentilerinde test derlemelerini yapılandırma

Bir kural eklentisinde test derlemeleri için varsayılan değerleri (ör. enstrümantasyon testleri için targetSdk) yapılandırırken withDeviceTest { } veya withHostTest { } gibi etkinleştirici yöntemleri kullanmaktan kaçının. Bu yöntemleri çağırmak, kural eklentisini uygulayan her modül için ilgili Android test varyantlarının ve derlemelerinin oluşturulmasını tetikler. Bu durum uygun olmayabilir. Bu yöntemler, ayarları hassaslaştırmak için belirli bir modülde ikinci kez çağrılamaz. Bunun yapılması, derlemenin zaten oluşturulduğunu belirten bir hataya neden olur.

Bunun yerine, derleme kapsayıcısında reaktif bir configureEach bloğu kullanın. Bu sayede, yalnızca bir modül test derlemesini açıkça etkinleştirdiğinde geçerli olan varsayılan yapılandırmalar sağlayabilirsiniz:

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

Bu kalıp, kural eklentinizin geç yüklenmesini sağlar ve her modülün, varsayılanlarla çakışmadan testlerini etkinleştirmek ve daha fazla özelleştirmek için withDeviceTest { } yöntemini çağırmasına olanak tanır.

Variant API ile etkileşim kurma

Geç aşama yapılandırması, yapı erişimi (ör. manifest dosyaları veya bayt kodu) ya da belirli bileşenleri etkinleştirme veya devre dışı bırakma özelliği gerektiren görevler için Android Variant API'yi kullanmanız gerekir. KMP projelerinde uzantı, KotlinMultiplatformAndroidComponentsExtension türündedir.

KMP Android eklentisi uygulandığında uzantı, proje düzeyinde kaydedilir.

Varyantların veya iç içe yerleştirilmiş test bileşenlerinin (hostTests ve deviceTests) oluşturulmasını kontrol etmek için beforeVariants kullanın. Testleri programatik olarak devre dışı bırakmak veya DSL özelliklerinin değerlerini değiştirmek için doğru yer burasıdır.

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

Nihai değişken nesnesine (KotlinMultiplatformAndroidVariant) erişmek için onVariants kullanın. Çözümlenen özellikleri inceleyebilir veya birleştirilmiş manifest ya da kitaplık sınıfları gibi yapıtlarda dönüşümleri kaydedebilirsiniz.

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