All skills
kotlin avatar

/kotlin-tooling-agp9-migration

@43f4771 official
by kotlinkotlin/kotlin-agent-skills1.1k stars
42

Migrates Kotlin Multiplatform (KMP) projects to Android Gradle Plugin 9.0+. Handles plugin replacement (com.android.kotlin.multiplatform.library), module splitting, DSL migration, and the new default project structure. Use when upgrading AGP, when build fails due to KMP+AGP incompatibility, or when the user mentions AGP 9.0, android multiplatform plugin, KMP migration, or com.android.kotlin.multiplatform.library.

Use this Skill: https://skilld.dev/gh/kotlin/kotlin-agent-skills/kotlin-tooling-agp9-migration

This session only. Nothing lands on disk.

referencesMIGRATION-LIBRARY.md

≈3.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Migrating a KMP Library Module to AGP 9.0

This reference covers the full migration of a Kotlin Multiplatform library module from com.android.library (AGP 8.x) to com.android.kotlin.multiplatform.library (AGP 9.x).


build.gradle.kts -- Before (AGP 8.x)

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidLibrary)
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions { jvmTarget = "11" }
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        commonMain.dependencies {
            implementation(libs.kotlinx.coroutines.core)
        }
        androidMain.dependencies {
            implementation(libs.androidx.appcompat)
        }
    }
}

android {
    namespace = "com.example.shared"
    compileSdk = 34
    defaultConfig { minSdk = 24 }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
}

dependencies {
    debugImplementation(libs.compose.ui.tooling)
}

build.gradle.kts -- After (AGP 9.x)

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidKmpLibrary)
}

kotlin {
    android {
        namespace = "com.example.shared"
        compileSdk = 35
        minSdk = 24

        compilerOptions {
            jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
        }

        androidResources { enable = true }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        commonMain.dependencies {
            implementation(libs.kotlinx.coroutines.core)
        }
        androidMain.dependencies {
            implementation(libs.androidx.appcompat)
        }
    }
}

dependencies {
    androidRuntimeClasspath(libs.compose.ui.tooling)
}

Version and Plugin Changes

With Version Catalog (gradle/libs.versions.toml)

Before:

[versions]
agp = "8.7.3"
kotlin = "2.1.0"

[plugins]
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }

After:

[versions]
agp = "9.0.1"
kotlin = "2.3.20"

[plugins]
androidKmpLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }

Without Version Catalog

If versions are declared directly in build files, update the plugin IDs and versions in place:

Before (root build.gradle.kts):

plugins {
    id("com.android.library") version "8.7.3" apply false
    id("org.jetbrains.kotlin.multiplatform") version "2.1.0" apply false
}

After (root build.gradle.kts):

plugins {
    id("com.android.kotlin.multiplatform.library") version "9.0.1" apply false
    id("org.jetbrains.kotlin.multiplatform") version "2.3.20" apply false
}

Before (module build.gradle.kts):

plugins {
    id("org.jetbrains.kotlin.multiplatform")
    id("com.android.library")
}

After (module build.gradle.kts):

plugins {
    id("org.jetbrains.kotlin.multiplatform")
    id("com.android.kotlin.multiplatform.library")
}

Key changes:

  • The plugin ID changes from com.android.library to com.android.kotlin.multiplatform.library.
  • AGP version must be 9.0.0+, Gradle 9.1.0+, KGP 2.0.0+ (2.3.0+ recommended).

Root build.gradle.kts Changes

With Version Catalog

Before:

plugins {
    alias(libs.plugins.androidLibrary) apply false
    alias(libs.plugins.kotlinMultiplatform) apply false
}

After:

plugins {
    alias(libs.plugins.androidKmpLibrary) apply false
    alias(libs.plugins.kotlinMultiplatform) apply false
}

Without Version Catalog

Before:

plugins {
    id("com.android.library") version "8.7.3" apply false
    id("org.jetbrains.kotlin.multiplatform") version "2.1.0" apply false
}

After:

plugins {
    id("com.android.kotlin.multiplatform.library") version "9.0.1" apply false
    id("org.jetbrains.kotlin.multiplatform") version "2.3.20" apply false
}

No other root-level changes are required unless you have convention plugins that reference the old plugin ID (see convention plugin section below).


Source Directory Renames

The new KMP-integrated plugin does NOT change the expected source directory layout. The standard KMP source sets still apply:

Source Set Directory
commonMain src/commonMain/kotlin/
androidMain src/androidMain/kotlin/
androidMain resources src/androidMain/res/
iosMain src/iosMain/kotlin/

No renames are required if you already use the standard KMP layout. If your module previously used the classic Android layout (src/main/java/, src/main/res/), you must migrate to the KMP layout:

Old (Android layout) New (KMP layout)
src/main/java/ src/androidMain/kotlin/
src/main/res/ src/androidMain/res/
src/main/AndroidManifest.xml src/androidMain/AndroidManifest.xml
src/test/java/ src/androidHostTest/kotlin/
src/androidTest/java/ src/androidDeviceTest/kotlin/

Test Configuration

The new plugin uses explicit opt-in for test source sets.

Host Tests (Unit Tests)

kotlin {
    android {
        // Enable unit tests (JVM-based, run on host machine)
        withHostTest {
            // Optional: configure the host test compilation
        }
    }
}

This creates the androidHostTest source set. The previous name androidUnitTest still works as an alias but androidHostTest is preferred.

Device Tests (Instrumented Tests)

kotlin {
    android {
        // Enable instrumented tests (run on device/emulator)
        withDeviceTest {
            // Optional: configure the device test compilation
        }
    }
}

This creates the androidDeviceTest source set. The previous name androidInstrumentedTest still works as an alias but androidDeviceTest is preferred.

Full Test Example

kotlin {
    android {
        namespace = "com.example.shared"
        compileSdk = 35
        minSdk = 24

        withHostTest {}
        withDeviceTest {}
    }

    sourceSets {
        getByName("androidHostTest").dependencies {
            implementation(libs.junit)
            implementation(libs.robolectric)
        }
        getByName("androidDeviceTest").dependencies {
            implementation(libs.androidx.test.runner)
            implementation(libs.androidx.test.espresso.core)
        }
    }
}

Java Compilation (withJava)

If your module contains Java source files in androidMain, you must explicitly enable Java compilation:

kotlin {
    android {
        withJava()
    }
}

Without this call, .java files in src/androidMain/java/ will be ignored. Kotlin files are compiled by default.


Consumer ProGuard Rules

Before (AGP 8.x)

android {
    defaultConfig {
        consumerProguardFiles("consumer-rules.pro")
    }
}

After (AGP 9.x)

kotlin {
    android {
        consumerProguardFiles.add(file("consumer-rules.pro"))
    }
}

Warning: Consumer ProGuard rules can be silently dropped during migration if you forget this step. The old android {} block is gone, so the consumerProguardFiles call in defaultConfig has no equivalent location unless you explicitly add it in kotlin { android {} }.


JVM Target Configuration Hierarchy

There are three levels at which you can configure the JVM target. They are listed from most specific (highest priority) to least specific (lowest priority):

Level 1: Android-Specific Compiler Options (Recommended)

kotlin {
    android {
        compilerOptions {
            jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
        }
    }
}

This sets the JVM target only for the Android compilation.

Level 2: Top-Level Kotlin Compiler Options

kotlin {
    compilerOptions {
        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
    }
}

This sets the JVM target for ALL JVM-based compilations in the project (Android, JVM desktop, etc.).

Level 3: Gradle Toolchain

kotlin {
    jvmToolchain(11)
}

This sets both the JDK used for compilation and the JVM target. It is the broadest setting and affects all JVM compilations.

Priority Order

If multiple levels are set, the most specific wins:

  1. kotlin { android { compilerOptions { } } } -- highest priority
  2. kotlin { compilerOptions { } } -- medium priority
  3. kotlin { jvmToolchain() } -- lowest priority

Migration from kotlinOptions

The old kotlinOptions DSL is removed:

// REMOVED in AGP 9.0 -- do not use
androidTarget {
    compilations.all {
        kotlinOptions { jvmTarget = "11" }
    }
}

Replace with one of the three levels above.


Dependencies Configuration Changes

The top-level dependencies {} block configurations change because build variants (debug/release) are removed from the KMP library plugin.

Before

dependencies {
    debugImplementation(libs.compose.ui.tooling)
    releaseImplementation(libs.some.lib)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.test.runner)
}

After

dependencies {
    // Use string-based configuration names
    "androidRuntimeClasspath"(libs.compose.ui.tooling)

    // Or use sourceSets for most dependencies
}

kotlin {
    sourceSets {
        androidMain.dependencies {
            implementation(libs.some.lib)
        }
        getByName("androidHostTest").dependencies {
            implementation(libs.junit)
        }
        getByName("androidDeviceTest").dependencies {
            implementation(libs.androidx.test.runner)
        }
    }
}

Prefer putting dependencies inside sourceSets blocks rather than the top-level dependencies {} block. The top-level block is only needed for special configurations like androidRuntimeClasspath that have no source set equivalent.


Dependency Resolution Details

When your KMP module depends on a legacy Android library that exposes multiple variants (e.g., debug/release build types or custom flavor dimensions like free/paid), you must explicitly define how to resolve them using the localDependencySelection DSL.

Before

android {
    defaultConfig {
        // The consuming module doesn't have a 'tier' dimension,
        // so it tells Gradle to use the 'free' flavor of dependencies
        missingDimensionStrategy("tier", "free")
    }
    buildTypes {
        getByName("debug") {
            // If the dependency doesn't have a 'debug' build type, fallback to 'release'
            matchingFallbacks.add("release")
        }
    }
}

After

kotlin {
    android {
        localDependencySelection {
            // Determine which build type to consume from Android library dependencies, in order of preference
            selectBuildTypeFrom.set(listOf("debug", "release"))
            
            // Map the missing custom flavor dimensions directly
            productFlavorDimension("tier") {
                selectFrom.set(listOf("free"))
            }
        }
    }
}

Android Resources

Android resources (res/) are not processed by default with the new plugin. You must explicitly enable them:

kotlin {
    android {
        androidResources { enable = true }
    }
}

Without this, files in src/androidMain/res/ will be ignored and R class generation will not happen.


Convention Plugin Refactoring

If you use convention plugins (build-logic), update them:

Before

// build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
class KmpLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("com.android.library")
            pluginManager.apply("org.jetbrains.kotlin.multiplatform")

            extensions.configure<LibraryExtension> {
                compileSdk = 34
                defaultConfig.minSdk = 24
            }
        }
    }
}

After

// build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt
class KmpLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("org.jetbrains.kotlin.multiplatform")
            pluginManager.apply("com.android.kotlin.multiplatform.library")

            extensions.configure<KotlinMultiplatformExtension> {
                android {
                    namespace = // set per-module or pass as parameter
                    compileSdk = 35
                    minSdk = 24
                }
            }
        }
    }
}

The LibraryExtension class from AGP is no longer used. All Android configuration goes through KotlinMultiplatformExtension.android {}.


Quick Checklist

  • Update plugin IDs and versions (in libs.versions.toml if using version catalog, or directly in build files)
  • Replace plugin alias in build.gradle.kts
  • Move android {} block contents into kotlin { android {} }
  • Replace androidTarget {} with android {}
  • Replace kotlinOptions with compilerOptions
  • Enable androidResources if using Android resources
  • Enable withHostTest {} if there are any android host tests or common tests
  • Enable withDeviceTest {} if there are any android device tests
  • Add withJava() if module contains Java source files
  • Move consumer ProGuard rules to new DSL
  • Migrate top-level dependencies to source set dependencies
  • Update convention plugins if applicable
  • Rename test source dirs: androidUnitTest to androidHostTest, androidInstrumentedTest to androidDeviceTest
  • Update root build.gradle.kts plugin declarations
  • Run ./gradlew :module:assemble to verify
  • Run ./gradlew :module:testAndroidHostTest if there are any android host tests or common tests
  • Run ./gradlew :module:assembleAndroidDeviceTest if there are any android device tests

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides documentation and tools to assist in migrating Kotlin Multiplatform projects to Android Gradle Plugin (AGP) 9.0+. It includes guides for library modules, application splits, and a project analysis script. No security risks were identified.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    5/10 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 43f4771. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 6 months ago
metadata
{
  "author": "JetBrains",
  "version": "1.0.0"
}
  • kotlin
  • kmp
  • agp
  • android-gradle-plugin
  • migrations
  • multiplatform
  • gradle
  • android

README badge

README badge for kotlin/kotlin-agent-skills/kotlin-tooling-agp9-migration

Migrates Kotlin Multiplatform projects to Android Gradle Plugin 9.0+, which requires separating the Android application plugin from KMP. Handles plugin replacement, DSL migration, module splitting, and directory structure updates for both library and application modules.

Generated from the current SKILL.md.

Does this skill handle all KMP+AGP migration scenarios?
The skill covers three main paths: library modules (Path A), mandatory app+shared splits for AGP 9.0 (Path B), and recommended full restructures from monolithic modules (Path C). It assumes Kotlin Multiplatform projects; pure Android projects need only AGP version updates.
Do I need to remove the kotlin-android plugin?
Yes. AGP 9.0 has built-in Kotlin support, so org.jetbrains.kotlin.android must be removed from any module using AGP 9.0 plugins.
What happens to KAPT dependencies?
The skill identifies KAPT usage as incompatible with AGP 9.0. You must migrate to KSP or use com.android.legacy-kapt; the skill guides detection but does not automate the migration.
Do I need to rename my source directories?
Only if your module uses classic Android layout (src/main, src/test, src/androidTest). If it already uses KMP layout (src/androidMain, etc.), no directory renames are needed.
What Gradle and AGP versions does this target?
Gradle 9.1.0+ and AGP 9.0.0+. The skill includes gradle-wrapper updates and version catalog examples for both.

Generated from the current SKILL.md. These answers refresh after source changes.