diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index daaa422..fbc65f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin - java-version: '24' # Matches the daemon JVM pinned in gradle/gradle-daemon-jvm.properties + java-version: '25' # Matches the daemon JVM pinned in gradle/gradle-daemon-jvm.properties # Gradle runs through the committed wrapper rather than a setup action. The # wrapper verifies its own distribution against distributionSha256Sum, and @@ -66,7 +66,7 @@ jobs: - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin - java-version: '24' + java-version: '25' - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: diff --git a/.github/workflows/status_check.yml b/.github/workflows/status_check.yml index c20e8f0..6cb261f 100644 --- a/.github/workflows/status_check.yml +++ b/.github/workflows/status_check.yml @@ -80,7 +80,7 @@ jobs: - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin - java-version: '24' # Matches the daemon JVM pinned in gradle/gradle-daemon-jvm.properties + java-version: '25' # Matches the daemon JVM pinned in gradle/gradle-daemon-jvm.properties # Gradle is invoked through the committed wrapper rather than a setup action. # The wrapper verifies its own distribution against distributionSha256Sum, and @@ -140,7 +140,7 @@ jobs: - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin - java-version: '24' + java-version: '25' - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -209,7 +209,7 @@ jobs: - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin - java-version: '24' + java-version: '25' - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: diff --git a/.idea/misc.xml b/.idea/misc.xml index 2de12f6..6c84484 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/CLAUDE.md b/CLAUDE.md index 0c61649..80c0667 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,24 +9,34 @@ this file covers only what is not obvious from the code. ## Build, test, lint -**Everything is on Java 24** — daemon, CI, IDE, and the app's own bytecode. Four places say so and +**Everything is on Java 25** — daemon, CI, IDE, and the app's own bytecode. Four places say so and they must not drift apart: | Where | What sets it | |---|---| -| Gradle daemon | `gradle/gradle-daemon-jvm.properties` → `toolchainVersion=24` | -| CI | `java-version: '24'` in both workflows | +| Gradle daemon | `gradle/gradle-daemon-jvm.properties` → `toolchainVersion=25` | +| CI | `java-version: '25'` in both workflows | | IDE | `.idea/misc.xml` | | App bytecode | `compileOptions` in `app/build.gradle.kts` | **Do not pick a JDK for the daemon — the repo does.** `gradle-daemon-jvm.properties` carries foojay -download URLs per platform, so Gradle provisions and runs the daemon on Java 24 regardless of what +download URLs per platform, so Gradle provisions and runs the daemon on Java 25 regardless of what `JAVA_HOME` says (that only sets the *launcher* — `./gradlew --version` prints both). Change it with `./gradlew updateDaemonJvm --jvm-version=NN`, never by hand. -**24, not 25, is deliberate.** Kotlin 2.2.10 refuses `jvmTarget` 25 outright — its available targets -stop at 24 — so the app's bytecode cannot join a 25 toolchain, and 24 is the highest number all four -rows can actually hold. D8 dexes Java 24 class files and R8 minifies them, both verified. +**Reaching 25 in the bytecode row took a deliberate build change.** AGP 9's built-in Kotlin compiles +with the KGP it bundles — 2.2.10 for AGP 9.3.1 — and that caps `jvmTarget` at 24. The root +`build.gradle.kts` puts KGP (and the lockstep Compose compiler plugin) on the buildscript classpath +so AGP picks up 2.4.10 instead, which supports up to 26. That is why the module applies +`com.android.application` and the Compose plugin by `id()` rather than from the catalog. Verified end +to end, not assumed: compiled classes report major version 69, D8 dexes them, and R8 minifies them. + +Consequences worth knowing before touching any of it: + +- Raising `kotlin` requires a matching `compose-compiler-gradle-plugin`; they are one version. +- Still **do not** apply `org.jetbrains.kotlin.android` — incompatible with AGP 9's DSL. +- Java 24 is *not* an option even though Kotlin allows it: Adoptium dropped the EOL non-LTS, so + there is no installable temurin-24. 25 is LTS and in the repo. The Gradle wrapper does **not** float and cannot: `distributionUrl` names one archive and `distributionSha256Sum` is that file's checksum. Bump it with `./gradlew wrapper --gradle-version X diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d98f98..1a7c223 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,9 +1,11 @@ import org.gradle.testing.jacoco.tasks.JacocoReport plugins { - alias(libs.plugins.android.application) + // Applied by id: these two come from the root buildscript classpath, which is what + // overrides AGP's bundled Kotlin. See the comment in the root build file. + id("com.android.application") // Required even under AGP 9: the Compose compiler plugin is NOT built in. - alias(libs.plugins.kotlin.compose) + id("org.jetbrains.kotlin.plugin.compose") // Lint/format. Resolved from the Gradle Plugin Portal, not AGP's buildscript // classpath -- neither is an Android plugin. @@ -69,8 +71,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_24 - targetCompatibility = JavaVersion.VERSION_24 + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 } lint { diff --git a/build.gradle.kts b/build.gradle.kts index 3756278..31a6e3e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,24 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - alias(libs.plugins.android.application) apply false -} \ No newline at end of file +// +// AGP 9 uses "built-in Kotlin": it compiles Kotlin itself, with the KGP it bundles (2.2.10 for +// AGP 9.3.1, per its POM). That version caps jvmTarget at 24. Putting a newer KGP on this single +// buildscript classpath is what raises the ceiling -- AGP's built-in Kotlin then compiles with +// 2.4.10 instead. The Compose compiler plugin must match KGP exactly, so it moves in lockstep. +// +// This is why the module below applies these two by id() rather than alias(): the plugins come +// from here, not from the version catalog's plugin resolution. The catalog still carries the +// version numbers, so there is exactly one place to edit. +// +// We still do NOT apply org.jetbrains.kotlin.android -- that plugin is incompatible with AGP 9's +// built-in-Kotlin DSL and fails the build. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:${libs.versions.agp.get()}") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.get()}") + classpath("org.jetbrains.kotlin:compose-compiler-gradle-plugin:${libs.versions.kotlin.get()}") + } +} diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties index d4871e0..ebdb796 100644 --- a/gradle/gradle-daemon-jvm.properties +++ b/gradle/gradle-daemon-jvm.properties @@ -1,12 +1,12 @@ #This file is generated by updateDaemonJvm -toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6d0adbce30460017fe61d2993dfa663e/redirect -toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/50f16d2dc2bb80a421afc1af38fc92e3/redirect -toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6d0adbce30460017fe61d2993dfa663e/redirect -toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/50f16d2dc2bb80a421afc1af38fc92e3/redirect -toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/4f4ebe4f162f6deb29540c4ebe629d79/redirect -toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/79d5995ef1c3e4df39a3b2f545cada5e/redirect -toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6d0adbce30460017fe61d2993dfa663e/redirect -toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/50f16d2dc2bb80a421afc1af38fc92e3/redirect -toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/826893cbcf0f86d8eb0975e2fb0788f7/redirect -toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/61fdd87038cf8b094dadad8566609a29/redirect -toolchainVersion=24 +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1df82b76031285ef2e6a4f59ffe9299d/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/4d0277c49991306ac78048580fe34bcf/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a9fa3b809e484a1e1ac4308a5dda661a/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/aa99913fa6d767d899ffc96082da01ca/redirect +toolchainVersion=25 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b9c4b85..ffde3ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,22 @@ [versions] -# Build tooling. -# NOTE: AGP 9 has BUILT-IN Kotlin support. Applying org.jetbrains.kotlin.android -# FAILS the build. AGP 9.3.1 brings kotlin-gradle-plugin 2.2.10 transitively, so the -# Compose compiler plugin below must match that version, not the newest Kotlin release. -# Verified against the AGP 9.3.1 POM, which declares kotlin-gradle-plugin 2.2.10 at -# runtime scope. Kotlin 2.4.10 exists and Android lint will suggest it -- taking that -# suggestion breaks the build unless KGP is ALSO forced onto the root buildscript -# classpath. These three move together or not at all; none of them may float. +# Build tooling. Pinned, never floating. agp and kotlin are coupled (below); ksp is +# staged for a later phase and unapplied today -- KSP2 versions independently of Kotlin. +# +# AGP 9 has BUILT-IN Kotlin: it compiles Kotlin itself rather than delegating to the +# org.jetbrains.kotlin.android plugin, which is incompatible with its DSL and fails the +# build if applied. DO NOT add it. +# +# By default AGP compiles with the KGP it bundles -- 2.2.10 for AGP 9.3.1, per its POM -- +# and that version caps jvmTarget at 24, which would cap the app's bytecode below the JDK +# everything else runs on. The root build.gradle.kts therefore puts KGP on the buildscript +# classpath explicitly, which AGP's built-in Kotlin picks up instead. That is the only +# reason `kotlin` here can be ahead of what AGP ships. +# +# The Compose compiler plugin is versioned in lockstep with Kotlin and is read from this +# same `kotlin` entry, so the two cannot drift. Raising `kotlin` means checking that a +# matching compose-compiler-gradle-plugin exists. agp = "9.3.1" -kotlin = "2.2.10" +kotlin = "2.4.10" ksp = "2.3.11" # AndroidX / Compose -- floating on minor + patch. The prerelease guard in @@ -108,8 +116,12 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "a androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } [plugins] -android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +# com.android.application and org.jetbrains.kotlin.plugin.compose are deliberately absent. +# They come from the root buildscript classpath (see build.gradle.kts) so that a newer KGP +# can override AGP's bundled one, and the module applies them by id(). An alias here would +# be dead weight that reads like the source of truth. +# +# ktlint and detekt are ordinary plugin-portal resolutions and stay aliases. ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } detekt = { id = "dev.detekt", version.ref = "detekt" }