Este documento fornece um guia para autores de plug-ins sobre como detectar, interagir e configurar corretamente a configuração do Kotlin Multiplatform (KMP), com foco específico na integração com as metas do Android em um projeto KMP. À medida que o KMP continua evoluindo, entender os hooks e as APIs adequados, como KotlinMultiplatformExtension, tipos KotlinTarget e as interfaces de integração específicas do Android, é essencial para criar ferramentas robustas e preparadas para o futuro que funcionam perfeitamente em todas as plataformas definidas em um projeto multiplataforma.
Verificar se um projeto usa o plug-in Kotlin Multiplatform
Para evitar erros e garantir que o plug-in só seja executado quando a KMP estiver presente,
verifique se o projeto usa o plug-in KMP. A prática recomendada é usar
plugins.withId() para reagir à aplicação do plug-in KMP, em vez de
verificar imediatamente. Essa abordagem reativa evita que seu plug-in seja frágil à ordem em que os plug-ins são aplicados nos scripts de build do usuário.
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.
}
}
}
Acessar o modelo
O ponto de entrada para todas as configurações do Kotlin Multiplatform é a
extensão 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)
}
}
}
Reagir a destinos do Kotlin Multiplatform
Use o contêiner targets para configurar de forma reativa seu plug-in para cada destino
adicionado pelo usuário.
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
}
}
}
}
Aplicar lógica específica do destino
Se o plug-in precisar aplicar lógica apenas a determinados tipos de plataformas, uma abordagem
comum é verificar a propriedade platformType. É uma enumeração que categoriza o destino de forma geral.
Por exemplo, use isso se o plug-in precisar apenas diferenciar de maneira geral (por exemplo, executar apenas em destinos semelhantes à 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) */ }
}
}
}
}
}
Detalhes específicos do Android
Embora todos os destinos do Android tenham o indicador platformType.androidJvm, a KMP tem
dois pontos de integração distintos, dependendo do Plug-in do Android para Gradle usado:
KotlinAndroidTarget para projetos que usam com.android.library ou
com.android.application e KotlinMultiplatformAndroidLibraryTarget para
projetos que usam com.android.kotlin.multiplatform.library.
A API KotlinMultiplatformAndroidLibraryTarget foi adicionada no AGP 8.8.0. Portanto, se
os consumidores do seu plug-in estiverem usando uma versão mais antiga do AGP, a verificação
de target is KotlinMultiplatformAndroidLibraryTarget poderá resultar em um
ClassNotFoundException. Para garantir a segurança, verifique AndroidPluginVersion.getCurrent() antes de verificar o tipo de destino.
O AndroidPluginVersion.getCurrent() requer o AGP 7.1 ou versões mais recentes.
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
}
}
}
}
}
Acessar a extensão KMP do Android e as propriedades dela
Seu plug-in vai interagir principalmente com a extensão Kotlin fornecida
pelo plug-in Kotlin Multiplatform e a extensão Android fornecida pelo AGP
para o destino Android do KMP. O bloco android {} na extensão Kotlin
em um projeto KMP é representado pela interface KotlinMultiplatformAndroidLibraryTarget, que também estende KotlinMultiplatformAndroidLibraryExtension.
Isso significa que você pode acessar propriedades de DSL específicas do destino e do Android usando esse ú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
}
}
}
}
Ao contrário de outros plug-ins do Android (como com.android.library ou
com.android.application), o plug-in do Android KMP não registra a extensão principal
DSL no nível do projeto. Ele fica na hierarquia de destino do KMP
para garantir que só se aplique ao destino específico do Android definido na sua
configuração multiplataforma.
Processar compilações e conjuntos de origem
Muitas vezes, os plug-ins precisam trabalhar em um nível mais granular do que apenas o
destino. Especificamente, eles precisam trabalhar no nível de compilação. O KotlinMultiplatformAndroidLibraryTarget contém instâncias de KotlinMultiplatformAndroidCompilation (por exemplo, main, hostTest, deviceTest). Cada compilação está associada a conjuntos de origem do Kotlin. Os plug-ins podem interagir com eles para adicionar fontes, dependências ou configurar
tarefas de compilação.
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 ->
}
}
}
}
}
}
Configurar compilações de teste em plug-ins de convenção
Ao configurar valores padrão para compilações de teste (como targetSdk para
testes instrumentados) em um plug-in de convenção, evite usar métodos
de ativação como withDeviceTest { } ou withHostTest { }. Chamar esses métodos
aciona a criação das variantes de teste e
compilações correspondentes do Android para cada módulo que aplica o plug-in de convenção, o que pode
não ser adequado. Além disso, esses métodos não podem ser chamados uma segunda vez em
um módulo específico para refinar as configurações, porque isso vai gerar um erro
informando que a compilação já foi criada.
Em vez disso, recomendamos usar um bloco configureEach reativo
no contêiner de compilações. Isso permite fornecer configurações padrão
que só se aplicam se e quando um módulo ativa explicitamente a compilação de teste:
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) }
}
}
}
}
}
Esse padrão garante que o plug-in de convenção permaneça lento e permite que
módulos individuais chamem withDeviceTest { } para ativar e personalizar ainda mais
os testes sem conflitar com os padrões.
Interagir com a API Variant
Para tarefas que exigem configuração em estágio avançado, acesso a artefatos (como
manifestos ou bytecode) ou a capacidade de ativar ou desativar componentes
específicos, use a API Android Variant. Em projetos do KMP, a
extensão é do tipo KotlinMultiplatformAndroidComponentsExtension.
A extensão é registrada no nível do projeto quando o plug-in do Android KMP é aplicado.
Use beforeVariants para controlar a criação de variantes ou os componentes de teste aninhados (hostTests e deviceTests). Este é o lugar certo para desativar testes ou mudar os valores das propriedades da DSL de maneira programática.
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 }
}
}
}
}
Use onVariants para acessar o objeto de variante final (KotlinMultiplatformAndroidVariant). É aqui que você pode inspecionar propriedades resolvidas ou registrar transformações em artefatos, como o manifesto mesclado ou classes 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)
}
}
}
}
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Configurar o ambiente
- Adicionar um módulo KMP a um projeto
- Configurar o plug-in da biblioteca do Android Gradle para KMP