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

Bu doküman, eklenti yazarlarına Kotlin Multiplatform (KMP) kurulumunu doğru şekilde algılama, kurulumla etkileşim kurma ve kurulumu yapılandırma konusunda rehberlik eder. KMP projesindeki Android hedefleriyle entegrasyona özellikle odaklanılır. KMP gelişmeye devam ederken KotlinMultiplatformExtension, KotlinTarget türleri ve Android'e özgü entegrasyon arayüzleri gibi uygun kancaları ve API'leri anlamak, çok platformlu bir projede tanımlanan tüm platformlarda sorunsuz bir şekilde çalışan sağlam ve geleceğe hazır araçlar oluşturmak için çok önemlidir.

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 hemen tepki vermek yerine plugins.withId() kullanmak en iyi uygulamadır. Bu reaktif yaklaşım, eklentinizin kullanıcının derleme komut dosyalarında eklentilerin uygulanma sırasına göre kırılgan olmasını engeller.

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 mantığı yalnızca belirli platform türlerine 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 geniş kapsamlı bir şekilde ayrım yapması gerekiyorsa (ör. yalnızca JVM benzeri hedeflerde çalışması) 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 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'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, com.android.kotlin.multiplatform.library kullanan projeler için KotlinMultiplatformAndroidLibraryTarget.

KotlinMultiplatformAndroidLibraryTarget API'si AGP 8.8.0'da eklendi. Bu nedenle, eklentinizin tüketicileri daha eski bir AGP sürümünde çalışı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() işaretini kontrol edin. AndroidPluginVersion.getCurrent() için AGP 7.1 veya sonraki bir sürümün gerektiğini unutmayın.

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

Eklentiniz, öncelikle KMP Android hedefi için Kotlin Multiplatform eklentisi tarafından sağlanan Kotlin uzantısı ve AGP tarafından sağlanan Android uzantısıyla etkileşimde bulunur. KMP projesindeki Kotlin uzantısındaki android {} bloğu, KotlinMultiplatformAndroidLibraryExtension'yi de genişleten KotlinMultiplatformAndroidLibraryTarget arayüzüyle temsil edilir. Bu sayede, hem hedefe özgü hem de Android'e özgü 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
            }
        }
    }
}

Diğer Android eklentilerinin (ör. com.android.library veya com.android.application) aksine, KMP Android eklentisi ana DSL uzantısını proje düzeyinde kaydetmez. Bu özellik, yalnızca çok platformlu kurulumunuzda tanımlanan belirli Android hedefi için geçerli olduğundan emin olmak amacıyla KMP hedef hiyerarşisi içinde yer alır.

Derlemeleri ve kaynak kümelerini işleme

Eklentilerin genellikle yalnızca hedefle değil, daha ayrıntılı bir düzeyde, özellikle de derleme düzeyinde çalışması gerekir. KotlinMultiplatformAndroidLibraryTarget, KotlinMultiplatformAndroidCompilation örnekleri (ör. main, hostTest, deviceTest) içerir. Her derleme, Kotlin kaynak kümeleriyle ilişkilendirilir. Eklentiler, kaynak eklemek, bağımlılıkları yönetmek veya derleme görevlerini yapılandırmak için bunlarla etkileşimde bulunabilir.

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

                    }
                }
            }
        }
    }
}

Test derlemelerini kural eklentilerinde yapılandırma

Bir kural eklentisinde test derlemeleri (ör. targetSdk enstrümanlı testler için) için varsayılan değerleri yapılandırırken withDeviceTest { } veya withHostTest { } gibi etkinleştirici yöntemleri kullanmaktan kaçınmalısınız. Bu yöntemlerin erken çağrılması, kural eklentisini uygulayan her modül için karşılık gelen Android test varyantlarının ve derlemelerinin oluşturulmasını tetikler. Bu durum uygun olmayabilir. Ayrıca, bu yöntemler ayarları hassaslaştırmak için belirli bir modülde ikinci kez çağrılamaz. Aksi takdirde, derlemenin zaten oluşturulduğunu belirten bir hata verilir.

Bunun yerine, derleme kapsayıcısında reaktif bir configureEach blok kullanmanızı öneririz. Bu, yalnızca bir modül test derlemesini açıkça etkinleştirdiğinde geçerli olan varsayılan yapılandırmalar sağlamanıza olanak tanır:

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

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

Variant API ile etkileşim kurma

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

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

Son varyant nesnesine (KotlinMultiplatformAndroidVariant) erişmek için onVariants kullanın. Burada, çözümlenmiş özellikleri inceleyebilir veya birleştirilmiş manifest ya da kitaplık sınıfları gibi yapılarda 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.variant.SingleArtifact.MERGED_MANIFEST)
            }
        }
    }
}