En este documento, se proporciona una guía para los autores de complementos sobre cómo detectar, interactuar y configurar correctamente la configuración de Kotlin Multiplatform (KMP), con un enfoque específico en la integración con los destinos de Android dentro de un proyecto de KMP. A medida que KMP sigue evolucionando, es fundamental comprender los hooks y las APIs adecuados, como los tipos KotlinMultiplatformExtension y KotlinTarget, y las interfaces de integración específicas de Android, para compilar herramientas sólidas y preparadas para el futuro que funcionen sin problemas en todas las plataformas definidas en un proyecto multiplataforma.
Cómo verificar si un proyecto usa el complemento de Kotlin Multiplatform
Para evitar errores y asegurarte de que tu complemento solo se ejecute cuando KMP esté presente, debes verificar si el proyecto usa el complemento de KMP. Se recomienda usar plugins.withId() para reaccionar a la aplicación del complemento de KMP, en lugar de verificarlo de inmediato. Este enfoque reactivo evita que tu complemento sea frágil al orden en que se aplican los complementos en los secuencias de comandos de compilación del usuario.
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.
}
}
}
Accede al modelo
El punto de entrada para todas las configuraciones de Kotlin Multiplatform es la extensión 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)
}
}
}
Cómo reaccionar a los destinos de Kotlin Multiplatform
Usa el contenedor targets para configurar de forma reactiva tu complemento para cada destino que agregue el usuario.
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
}
}
}
}
Aplica lógica específica del destino
Si tu complemento necesita aplicar lógica solo a ciertos tipos de plataformas, un enfoque común es verificar la propiedad platformType. Es una enumeración que categoriza de forma general el objetivo.
Por ejemplo, usa esta opción si tu complemento solo necesita diferenciar de forma general (por ejemplo, ejecutarse solo en destinos similares a la 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) */ }
}
}
}
}
}
Detalles específicos de Android
Si bien todos los destinos de Android tienen el indicador platformType.androidJvm, KMP tiene dos puntos de integración distintos según el complemento de Gradle para Android que se use: KotlinAndroidTarget para proyectos que usan com.android.library o com.android.application, y KotlinMultiplatformAndroidLibraryTarget para proyectos que usan com.android.kotlin.multiplatform.library.
La API de KotlinMultiplatformAndroidLibraryTarget se agregó en AGP 8.8.0, por lo que, si los consumidores de tu complemento ejecutan una versión anterior de AGP, verificar target is KotlinMultiplatformAndroidLibraryTarget podría generar un ClassNotFoundException. Para que esto sea seguro, verifica AndroidPluginVersion.getCurrent() antes de verificar el tipo de destino.
Ten en cuenta que AndroidPluginVersion.getCurrent() requiere AGP 7.1 o una versión posterior.
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
}
}
}
}
}
Cómo acceder a la extensión de KMP de Android y a sus propiedades
Tu complemento interactuará principalmente con la extensión de Kotlin que proporciona el complemento de Kotlin Multiplatform y la extensión de Android que proporciona AGP para el destino de Android de KMP. El bloque android {} dentro de la extensión de Kotlin en un proyecto de KMP se representa con la interfaz KotlinMultiplatformAndroidLibraryTarget, que también extiende KotlinMultiplatformAndroidLibraryExtension.
Esto significa que puedes acceder a las propiedades del DSL específicas del destino y de Android a través de este único objeto.
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
}
}
}
}
A diferencia de otros complementos de Android (como com.android.library o com.android.application), el complemento de Android de KMP no registra su extensión principal del DSL a nivel del proyecto. Se encuentra dentro de la jerarquía de destino de KMP para garantizar que solo se aplique al destino específico de Android definido en tu configuración multiplataforma.
Cómo controlar las compilaciones y los conjuntos de fuentes
A menudo, los complementos deben funcionar a un nivel más detallado que solo el objetivo; específicamente, deben funcionar a nivel de compilación. El KotlinMultiplatformAndroidLibraryTarget contiene instancias de KotlinMultiplatformAndroidCompilation (por ejemplo, main, hostTest, deviceTest). Cada compilación está asociada con conjuntos de fuentes de Kotlin. Los complementos pueden interactuar con estos para agregar fuentes, dependencias o configurar tareas de compilación.
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 ->
}
}
}
}
}
}
Configura compilaciones de prueba en complementos basados en convenciones
Cuando configures valores predeterminados para las compilaciones de prueba (como targetSdk para las pruebas instrumentadas) en un complemento de convenciones, debes evitar usar métodos de habilitación como withDeviceTest { } o withHostTest { }. Llamar a estos métodos de forma anticipada activa la creación de las variantes de prueba y las compilaciones de Android correspondientes para cada módulo que aplica el complemento de convenciones, lo que podría no ser adecuado. Además, estos métodos no se pueden llamar una segunda vez en un módulo específico para ajustar la configuración, ya que, si lo haces, se arrojará un error que indica que ya se creó la compilación.
En cambio, te recomendamos que uses un bloque configureEach reactivo en el contenedor de compilaciones. Esto te permite proporcionar configuraciones predeterminadas que solo se aplican si un módulo habilita explícitamente la compilación de pruebas:
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) }
}
}
}
}
}
Este patrón garantiza que el complemento de convenciones permanezca inactivo y permite que los módulos individuales llamen a withDeviceTest { } para habilitar y personalizar aún más sus pruebas sin entrar en conflicto con los valores predeterminados.
Interactúa con la API de Variant
Para las tareas que requieren configuración en etapas avanzadas, acceso a artefactos (como manifiestos o código de bytes) o la capacidad de habilitar o inhabilitar componentes específicos, debes usar la API de Android Variant. En los proyectos de KMP, la extensión es de tipo KotlinMultiplatformAndroidComponentsExtension.
La extensión se registra a nivel del proyecto cuando se aplica el complemento para Android de KMP.
Usa beforeVariants para controlar la creación de variantes o sus componentes de prueba anidados (hostTests y deviceTests). Este es el lugar correcto para inhabilitar pruebas de forma programática o cambiar los valores de las propiedades del 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 }
}
}
}
}
Usa onVariants para acceder al objeto de variante final (KotlinMultiplatformAndroidVariant). Aquí puedes inspeccionar las propiedades resueltas o registrar transformaciones en artefactos, como el manifiesto combinado o las clases de biblioteca.
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)
}
}
}
}
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Configura tu entorno
- Agrega un módulo de KMP a un proyecto
- Configura el complemento de la biblioteca de Android Gradle para KMP