Skip to content

Android Gradle Plugin 9 Restructure

Marko Koschak edited this page Jul 12, 2026 · 1 revision

Android Gradle Plugin 9 Restructure

This document summarizes the structural changes made to the Korge project to adopt Android Gradle Plugin (AGP) 9.x, why each change was needed, and how the result is verified.

Why this work was needed

AGP 9 changes two fundamental rules that the old Korge project layout violated:

  1. An Android module is now strictly either an application or a library — never both. com.android.application (which produces an APK) can no longer coexist with Kotlin-Multiplatform library configuration in the same module. A game therefore can no longer be a single module that simultaneously holds the shared game code and acts as the Android application.

  2. The legacy com.android.library plugin is gone. KMP library modules must use the new com.android.kotlin.multiplatform.library plugin instead.

In addition, AGP 9 ships built-in Kotlin support: application modules must not apply org.jetbrains.kotlin.android anymore (doing so now fails the build).

To satisfy these rules, the engine's "core" must be pure shared library code, and each platform's entry point (the thing that actually launches the game: an Android Activity, a JVM main, a browser bootstrap, an iOS framework) must live in its own application module that depends on the shared library. This is the "shared library + one application module per entry point" pattern that this release establishes.

A secondary goal was to remove the kproject Gradle plugin from the active build. It was a source-/git-based dependency mechanism that is no longer wanted and is unrelated to the AGP 9 migration; it has been archived for possible later revival rather than deleted.

Starting point

Much of the engine was already AGP-9-ready before this work:

  • AGP was already 9.2.0 in gradle/libs.versions.toml.
  • The legacy com.android.library plugin had already been fully removed; only com.android.kotlin.multiplatform.library was in use.
  • korge and korge-core were already clean KMP libraries.

So the remaining work was concentrated in the example/app layer (korge-sandbox, e2e/*) and in the Gradle plugin build (kproject removal, Java-version packaging), not in the engine libraries themselves.

Changes

1. Archived the kproject Gradle plugin

kproject is deeply self-contained: nothing in the main korge-gradle-plugin imported it, and the entire korge-gradle-plugin-settings module existed only to host it.

  • Moved (with history preserved) into a new top-level _kproject-archive/ directory, which is not part of any Gradle build:
    • korge-gradle-plugin-common/.../org/korge/kproject/** and SettingsKProjectExt.kt (plus the matching tests and test resource).
    • korge-gradle-plugin/.../org/korge/kproject/** (KProjectPlugin, KProjectRootPlugin, AndroidConfig).
    • The whole korge-gradle-plugin-settings/ module.
  • Removed the kproject / kprojectRoot plugin registrations from korge-gradle-plugin/build.gradle.kts.
  • Removed :korge-gradle-plugin-settings from korge-gradle-plugins/settings.gradle.kts.
  • Removed the now-unused jgit dependency from korge-gradle-plugin-common (it existed only for kproject's git resolution).

Result: the gradle-plugins composite build no longer references kproject and still publishes korge-gradle-plugin and korge-gradle-plugin-common.

2. Restructured korge-sandbox into the entry-point pattern

Previously korge-sandbox was shared (which mixed game code, the JVM launcher and the Korge "executable" plugin) plus a stub androidApp. It is now a clean reference for the new layout:

  • shared — a pure KMP library (kotlin.multiplatform + com.android.kotlin.multiplatform.library) holding all the game/sample code, with jvm, android, js, wasmJs and ios* targets, depending on projects.korge. It no longer applies the Korge executable plugin and contains no launcher.
  • jvmApp — desktop entry point (application + kotlin.jvm), runs JvmMain (moved here from shared), depends on :shared.
  • androidApp — Android entry point: com.android.application only (no kotlin-android, per AGP 9), with MainActivity : KorgwActivity and an AndroidManifest.xml, depending on :shared. minSdk is raised to ≥21 and multidex is enabled because the engine exceeds the 64K method limit.
  • webApp — JS + WASM browser entry point (kotlin.multiplatform, binaries.executable()), delegating to the shared entry, depending on :shared.
  • iosApp — iOS framework that exports :shared. It is gated to macOS in settings.gradle.kts because linking an Apple framework only works on macOS; this keeps the build green on Windows/Linux.

To let every app module call the game uniformly, the shared application entry point (Main.kt, helpers.kt, MainRenderImagesJvmNative.kt, and JvmMain) was moved out of the default (root) Kotlin package into package org.korge.application. Root-package declarations cannot be imported from a packaged file, which made multi-module entry points impossible before.

Root settings.gradle.kts now includes :korge-sandbox:{shared, jvmApp, androidApp, webApp} (and :iosApp only on macOS).

3. Made the Gradle plugin consumable on Java 21

The project targets Java 21 (javaTargetCompatibility = 21), and consumers such as the e2e builds run on Java 21. When the plugins were published from a Java 22 JVM, the resulting korge-gradle-plugin artifact was tagged "requires JVM 22" and compiled to Java 22 bytecode, so Java-21 consumers failed with UnsupportedClassVersionError / "requires at least JVM runtime version 22".

Fixes so the published plugin is always Java-21-compatible regardless of the building JDK:

  • Added a Java source/target compatibility block to korge-gradle-plugin/build.gradle.kts (matching korge-gradle-plugin-common), pinning the published metadata.
  • Added korge-gradle-plugins/gradle/gradle-daemon-jvm.properties (copied from the root build) so the plugins build runs on a Java 21 daemon → Java-21 bytecode.
  • Added a proper Gradle wrapper to korge-gradle-plugins/ (it previously had none), so the plugins can be published directly "from their own folder" with cd korge-gradle-plugins && ./gradlew publishToMavenLocal.

4. Fixed the e2e test projects

  • e2e/e2e-test — already follows the split (a Korge shared module that also drives the JVM screenshot test harness, plus a separate androidApp). It now assembles cleanly against the freshly published artifacts; no structural change was required.
  • e2e/e2e-test-multi — had a pre-existing breakage unrelated to AGP 9: its settings.gradle sourced a deleted file (gradle/repositories.settings.gradle) via Eval.xy, so it could not even configure. The repositories are now declared inline in settings.gradle, and the project assembles.

Verification

All commands below were run and succeeded:

# 1. Engine libraries + the restructured sandbox (root build)
./gradlew publishToMavenLocal
#    -> publishes korge, korge-core, korge-ipc, korge-reload-agent (+ all platform variants)

# 2. Gradle plugins, published from their own folder
cd korge-gradle-plugins && ./gradlew publishToMavenLocal
#    -> publishes korge-gradle-plugin, korge-gradle-plugin-common as Java-21 artifacts

# 3. Sandbox entry-point modules
./gradlew :korge-sandbox:jvmApp:compileKotlin \
          :korge-sandbox:webApp:compileKotlinJs :korge-sandbox:webApp:compileKotlinWasmJs \
          :korge-sandbox:androidApp:assembleDebug   # produces androidApp-debug.apk

# 4. e2e projects (consume the published artifacts)
cd e2e/e2e-test       && ./gradlew assemble          # jvm + js + android
cd e2e/e2e-test-multi && ./gradlew assemble          # via root wrapper: ./gradlew -p e2e/e2e-test-multi assemble

Known limitations / follow-ups

  • e2e/e2e-test-integration (SwiftUI sample) depends on the org.korge.engine.settings (kproject) plugin, which has been archived. It is intentionally left untouched and will not build until kproject is revisited. This matches the decision to defer kproject.
  • iOS modules (korge-sandbox:iosApp, iOS framework linking) can only be built on macOS; they are gated out of the Windows/Linux build and were not exercised here.
  • The _kproject-archive/ directory is retained outside the build purely so kproject can be resurrected later; it can be deleted entirely if that is no longer desired.

Clone this wiki locally