agentleFS
KushpawanKushpawan/kmp-mobile-skills/skills/kmp-gradle-version-catalog/SKILL.md

kmp-gradle-version-catalog

Skill15 starsChanged 23 days agoApache-2.0View on GitHub

Use when adding, upgrading or auditing dependencies and Gradle plugins in a Kotlin Multiplatform project - libs.versions.toml, [versions] [libraries] [plugins] [bundles], "Could not resolve" or "No matching variant" for iosArm64, Kotlin vs Compose Multiplatform vs AGP vs KSP compatibility, one catalog shared across several repos (git submodule or published catalog), version drift, build-logic convention plugins, or includeBuild with dependencySubstitution for local library development.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 0 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every version lives in exactly one version catalog, every dependency lands in the narrowest source set that needs it, and nothing goes into …

    line 13
  2. Very specific:

    No iOS variant means the library is Android-only: put it in androidMain behind an interface (kmp-expect-actual).

    line 69
  3. Very specific:

    Use api only when the dependency's types appear in this module's public API (and therefore in an iOS export).

    line 109
  4. Very specific:

    Plugins: Add a [plugins] entry, declare it once in the root build.gradle.kts with alias(libs.plugins.x) apply false, then …

    line 111
  5. Very specific:

    When three or more modules repeat the same plugins, targets and SDK levels, move that block into a build-logic included build.

    line 159
  6. Very specific:

    Do not create build-logic in a project that has none without asking.

    line 163
  7. Very specific:

    After a Kotlin upgrade also link the iOS framework (linkDebugFrameworkIosSimulatorArm64) and build the Xcode project.

    line 221

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

KMP Gradle version catalog and dependencies

Every version lives in exactly one version catalog, every dependency lands in the narrowest source set that needs it, and nothing goes into commonMain until you have confirmed it publishes iOS variants. Upgrades of Kotlin, Compose Multiplatform, AGP and KSP move together or not at all.

When to use

  • Adding a library or Gradle plugin, or a bundle of them.
  • "Could not resolve", "No matching variant" or a Kotlin metadata/ABI version error after adding one.
  • Upgrading Kotlin, Compose Multiplatform, AGP, KSP or Gradle.
  • Several repositories should share one catalog, or their versions have drifted.
  • Extracting repeated build configuration into convention plugins.
  • Developing a library locally while an app consumes it.

When not to use

  • Creating the module that will use the dependency: kmp-feature-module.
  • Writing the platform abstraction around an Android-only library: kmp-expect-actual.
  • Migrating a project to AGP 9 as a whole: the AGP 9 migration skill in Kotlin/kotlin-agent-skills.
  • A failing build you have not yet diagnosed: kmp-build-failure-triage in this repo.

Step 0 — Read the project first

ls gradle/libs.versions.toml 2>/dev/null                       # default catalog location
grep -nE 'versionCatalogs|from\(|includeBuild|dependencySubstitution|repositoriesMode' settings.gradle.kts
cat .gitmodules 2>/dev/null                                     # catalog shared as a submodule?
ls build-logic buildSrc 2>/dev/null                             # convention plugins
grep -nE '^(kotlin|agp|composeMultiplatform|compose-multiplatform|ksp)\s*=' gradle/libs.versions.toml
grep -rnE '"[A-Za-z0-9_.-]+:[A-Za-z0-9_.-]+:[0-9]' --include='*.gradle.kts' . | grep -v /build/ | head
RecordWhy
Catalog path: gradle/libs.versions.toml, a file imported in settings.gradle.kts, or a published group:artifact:versionEdit the real source of truth, not a copy
Alias style: ktor-client-core (accessor libs.ktor.client.core) or camelCaseNew entries match existing ones
Version key style: kotlinx-coroutines vs kotlinxCoroutinesSame
Kotlin, AGP, Compose Multiplatform, KSP, Gradle versionsDecide compatibility before choosing a library version
Convention pluginsShared config belongs there, not copied into modules
includeBuild + substitution flagsLocal-library mode exists; know its property names
Existing hardcoded versions in build filesReport them; do not add more

Workflow A — Add a dependency

  1. Confirm the targets. For anything going into commonMain, confirm iOS variants exist: search klibs.io, read the library's docs, or check Maven Central for per-target artifacts (<artifact>-iosarm64, <artifact>-iossimulatorarm64):

    G=io/ktor A=ktor-client-core
    for t in iosarm64 iossimulatorarm64; do
      printf '%-18s %s\n' "$t" "$(curl -s -o /dev/null -w '%{http_code}' \
        "https://repo1.maven.org/maven2/$G/$A-$t/maven-metadata.xml")"
    done   # 200 = published; androidx artifacts live on Google Maven instead

    No iOS variant means the library is Android-only: put it in androidMain behind an interface (kmp-expect-actual). Done when you know which source set it belongs in.

  2. Confirm the Kotlin version. A library compiled by a newer Kotlin than the project's can fail with a metadata or ABI version error, especially on Kotlin/Native. Read its release notes for the Kotlin it was built with. Done when the chosen version supports the project's Kotlin.
  3. Add catalog entries. One [versions] key per version family, version.ref everywhere, aliases in the file's existing style. Pin an exact stable version taken from the library's releases page; never +, latest.release or a range unless the project already uses rich versions. Entry forms, naming rules, BOMs and rich versions are in references/catalog-syntax.md.

    [versions]
    ktor = "<exact version>"
    
    [libraries]
    ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
    ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
    ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
    ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
    ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
    ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
    
    [bundles]
    ktor-common = ["ktor-client-core", "ktor-client-content-negotiation", "ktor-serialization-kotlinx-json"]
  4. Wire it into the narrowest source set.

    kotlin {
        sourceSets {
            commonMain.dependencies { implementation(libs.bundles.ktor.common) }
            androidMain.dependencies { implementation(libs.ktor.client.okhttp) }   // engine per platform
            iosMain.dependencies { implementation(libs.ktor.client.darwin) }
            commonTest.dependencies { implementation(libs.ktor.client.mock) }
        }
    }

    Use api only when the dependency's types appear in this module's public API (and therefore in an iOS export). Done when the module compiles for every target (Verify).

  5. Plugins. Add a [plugins] entry, declare it once in the root build.gradle.kts with alias(libs.plugins.x) apply false, then alias(libs.plugins.x) in the module. Catalog plugin aliases cannot be used in settings.gradle.kts.
  6. Annotation processors (KSP). KMP needs one configuration per target, for example add("kspAndroid", libs.androidx.room.compiler), add("kspIosArm64", …), add("kspIosSimulatorArm64", …), and kspIosX64 if that target exists. A missing target either fails that target's compilation or, for processors whose output is looked up at runtime, fails only when the app runs on that platform. Done when every declared target has a line.

Workflow B — Upgrade Kotlin, Compose Multiplatform, AGP or KSP

Change one axis per commit and run the full Verify after each. These must agree:

ComponentConstraint
Kotlin Multiplatform, Compose compiler (org.jetbrains.kotlin.plugin.compose), serialization pluginSame version as Kotlin: give them all version.ref = "kotlin"
Compose Multiplatform (org.jetbrains.compose)Each release needs a minimum Kotlin; current releases need Kotlin 2.1.0+ and 2.2.20+ for iOS. See the compatibility page
material3, navigation-compose, lifecycle-* under org.jetbrains.*Own version lines; take them from the Compose Multiplatform release notes, not from the CMP version
AGPEach Kotlin Gradle plugin release supports an AGP range; see the table in Configure a Gradle project
Gradle, JDKMinimums come from AGP and KGP; AGP 9 needs JDK 17+
KSPReleases before 2.3.0 are versioned <kotlin>-<ksp> and must match Kotlin exactly; 2.3.0+ have their own line. Check the KSP releases
XcodeEach Kotlin release supports an Xcode range; see the KMP compatibility guide

Dependencies compiled with an older Kotlin are usually fine; dependencies compiled with a newer one are not. Upgrade the project's Kotlin first, then libraries that require it.

Workflow C — Share one catalog across repositories

Options, how to set each up, and how to override a version locally are in references/shared-catalogs-and-composite-builds.md. In short: a git submodule imported with from(files(...)), or a catalog published with the version-catalog plugin and imported with from("com.example:catalog:<version>").

Audit drift between catalogs, and hardcoded versions in build scripts, with the bundled script (paths relative to this skill's directory; read-only; output is a table or JSON):

python3 scripts/catalog_drift.py app=../notes-app/gradle/libs.versions.toml \
                                 sdk=../notes-sdk/gradle/libs.versions.toml --only-differs
python3 scripts/catalog_drift.py gradle/libs.versions.toml --scan-gradle . --format json
python3 scripts/catalog_drift.py --help

Exit code 1 with --fail-on-drift makes it usable in CI. Never downgrade to resolve drift without asking; align on the highest version that every consumer has been built against.

Workflow D — Convention plugins

When three or more modules repeat the same plugins, targets and SDK levels, move that block into a build-logic included build. Full setup — build-logic/settings.gradle.kts reading the same catalog, compileOnly plugin artifacts, and a KmpLibraryConventionPlugin that configures the AGP KMP library target — is in references/convention-plugins.md. Do not create build-logic in a project that has none without asking; it is a structural change.

Workflow E — Develop a library locally against an app

Gate an includeBuild behind a Gradle property so the default build always uses the published artifact:

// settings.gradle.kts of the consuming app
if (providers.gradleProperty("local.notes-sdk").orNull == "true") {
    includeBuild("../notes-sdk") {
        dependencySubstitution {
            substitute(module("com.example:notes-sdk")).using(project(":sdk"))
        }
    }
}
./gradlew :androidApp:assembleDebug -Plocal.notes-sdk=true

With the flag off, local library edits reach no consumer. Rebuild every consumer with its flag on before publishing a library change. Keep Kotlin and AGP identical across the composite. Never commit the flag as true. Details and pitfalls are in the shared-catalogs reference.

Quick reference

TaskHow
Library accessorktor-client-core → libs.ktor.client.core; [bundles] ktor-common → libs.bundles.ktor.common
Plugin accessorlibs.plugins.kotlinMultiplatform in plugins { alias(...) }
Version in Kotlin codelibs.versions.android.minSdk.get().toInt()
Reserved alias wordsextensions, class, convention; bundles, versions, plugins cannot start an alias
Common-only dependencycommonMain.dependencies { implementation(...) }
Platform-only dependencyandroidMain / iosMain
Test-onlycommonTest; JVM-only test libraries (MockK, JUnit 5) go in androidHostTest, never commonTest
BOM (Android only)androidMain.dependencies { implementation(project.dependencies.platform(libs.firebase.bom)) }
KSPadd("ksp<Target>", libs.x.compiler) for each target
Local libraryincludeBuild + dependencySubstitution, gated by -Plocal.<lib>=true

Verify

Report every check as PASS, FAIL or NOT RUN (with the reason). Never report a check that did not run as passed. Replace :shared with each module you touched.

./gradlew help                                         # catalog and settings parse
./gradlew :shared:compileCommonMainKotlinMetadata
./gradlew :shared:compileAndroidMain                   # legacy plugin: compileDebugKotlinAndroid
./gradlew :shared:compileKotlinIosSimulatorArm64       # macOS only; proves the iOS variant resolves
./gradlew :shared:allTests                             # or the project's test tasks
./gradlew :androidApp:assembleDebug                    # or :composeApp:assembleDebug
./gradlew :shared:dependencyInsight --dependency ktor-client-core --configuration <configuration>
python3 scripts/catalog_drift.py gradle/libs.versions.toml --scan-gradle . --fail-on-drift

List configuration names for dependencyInsight with ./gradlew :shared:dependencies | grep -i classpath. After a Kotlin upgrade also link the iOS framework (linkDebugFrameworkIosSimulatorArm64) and build the Xcode project.

Common mistakes

MistakeWhy it bitesFix
Android-only library in commonMainiOS compilation fails with "No matching variant" or unresolved artifactsandroidMain plus an interface in common
Version literal in build.gradle.ktsDrifts from the catalog; invisible to upgradesCatalog entry with version.ref
Compose compiler or serialization plugin on its own version keyMismatched Kotlin compiler plugins fail the build after an upgradeversion.ref = "kotlin"
Using the Compose Multiplatform version for material3, navigation or lifecycleThose artifacts have separate version lines; the version may not exist or may not matchTake each from the CMP release notes
JVM-only test library in commonTestiOS test compilation failsFakes in commonTest; JVM tools in androidHostTest
Plugin versions requested per module instead of once in the root"The Kotlin Gradle plugin was loaded multiple times in different subprojects", or "already on the classpath with an unknown version" when build-logic also provides itapply false in root, versionless alias in modules
A catalog named libs imported from a file while gradle/libs.versions.toml also existsGradle auto-imports the default file; calling from twice failsOne source: delete the stray file or rename one catalog
KSP configured for some targets onlyThat target fails to compile, or crashes at runtime where generated code is looked up dynamicallyOne ksp<Target> line per target
includeBuild always onCI and teammates build unpublished code; releases differ from what was testedGate it with a property that defaults to off
Upgrading Kotlin, AGP and CMP in one commitA failure cannot be attributedOne axis per commit
Accessing libs.plugins.* inside a precompiled script plugin's plugins {}Not supported by GradleBinary convention plugins with VersionCatalogsExtension

Related skills

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.