이 문서에서는 플러그인 작성자를 위해 Kotlin 멀티플랫폼 (KMP) 설정을 올바르게 감지하고, 상호작용하고, 구성하는 방법을 안내하며, KMP 프로젝트 내에서 Android 타겟과 통합하는 데 중점을 둡니다. KMP가 계속 발전함에 따라 KotlinMultiplatformExtension, KotlinTarget 유형, Android 전용 통합 인터페이스와 같은 적절한 후크와 API를 이해하는 것은 멀티 플랫폼 프로젝트에 정의된 모든 플랫폼에서 원활하게 작동하는 강력하고 미래 지향적인 도구를 빌드하는 데 필수적입니다.
프로젝트에서 Kotlin 멀티플랫폼 플러그인을 사용하는지 확인
오류를 방지하고 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.
}
}
}
모델 액세스
모든 Kotlin 멀티플랫폼 구성의 진입점은 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)
}
}
}
Kotlin 멀티플랫폼 타겟에 반응
targets 컨테이너를 사용하여 사용자가 추가하는 각 타겟에 대해 반응형으로 플러그인을 구성합니다.
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) */ }
}
}
}
}
}
Android 관련 세부정보
모든 Android 타겟에는 platformType.androidJvm 표시기가 있지만 KMP에는 사용된 Android Gradle 플러그인에 따라 두 가지 고유한 통합 지점이 있습니다. com.android.library 또는 com.android.application를 사용하는 프로젝트의 경우 KotlinAndroidTarget, com.android.kotlin.multiplatform.library를 사용하는 프로젝트의 경우 KotlinMultiplatformAndroidLibraryTarget입니다.
KotlinMultiplatformAndroidLibraryTarget API는 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
}
}
}
}
}
Android KMP 확장 프로그램 및 속성에 액세스
플러그인은 주로 Kotlin 멀티플랫폼 플러그인에서 제공하는 Kotlin 확장 프로그램과 KMP Android 타겟용 AGP에서 제공하는 Android 확장 프로그램과 상호작용합니다. KMP 프로젝트의 Kotlin 확장 프로그램 내 android {} 블록은 KotlinMultiplatformAndroidLibraryExtension도 확장하는 KotlinMultiplatformAndroidLibraryTarget 인터페이스로 표현됩니다.
즉, 이 단일 객체를 통해 타겟별 DSL 속성과 Android별 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
}
}
}
}
다른 Android 플러그인 (예: com.android.library 또는 com.android.application)과 달리 KMP Android 플러그인은 프로젝트 수준에서 기본 DSL 확장 프로그램을 등록하지 않습니다. 멀티플랫폼 설정에 정의된 특정 Android 타겟에만 적용되도록 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 { }와 같은 지원 메서드를 사용하지 않아야 합니다. 이러한 메서드를 적극적으로 호출하면 규칙 플러그인을 적용하는 모든 모듈에 해당하는 Android 테스트 변형과 컴파일이 트리거되므로 적합하지 않을 수 있습니다. 또한 이러한 메서드는 설정을 수정하기 위해 특정 모듈에서 두 번 호출할 수 없습니다. 이렇게 하면 컴파일이 이미 생성되었다는 오류가 발생하기 때문입니다.
대신 컴파일 컨테이너에서 반응형 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 { }를 호출하여 기본값과 충돌하지 않고 테스트를 사용 설정하고 추가로 맞춤설정할 수 있습니다.
Variant API와 상호작용
후반 단계 구성, 아티팩트 액세스 (예: 매니페스트 또는 바이트 코드), 특정 구성요소를 사용 설정 또는 중지하는 기능이 필요한 작업에는 Android Variant API를 사용해야 합니다. KMP 프로젝트에서 확장 프로그램은 KotlinMultiplatformAndroidComponentsExtension 유형입니다.
KMP Android 플러그인이 적용되면 확장 프로그램이 프로젝트 수준에서 등록됩니다.
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)
}
}
}
}
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 환경 설정
- 프로젝트에 KMP 모듈 추가
- KMP용 Android Gradle 라이브러리 플러그인 설정