Let the libraries float on minor and patch
Library versions now read "1.+" instead of "1.19.0". Three groups stay pinned, and the reasons differ: agp/kotlin/ksp are version-locked to each other -- AGP 9.3.1's POM declares kotlin-gradle-plugin 2.2.10, so a float that picked up Kotlin 2.4.x would put the Compose compiler ahead of the Kotlin AGP actually compiles with. ktlint/detekt/jacoco because a linter is not a library. A library bump that misbehaves usually still compiles; a new lint rule makes files nobody touched stop passing, turning a PR red for something absent from its diff. Upgrading those is worth a commit that reads the new findings. The FFmpeg AAR is a committed file, not a coordinate. The componentSelection block is the part that makes this safe rather than the part that makes it work. Gradle resolves "+" to the highest version it can find and does not skip prereleases, and androidx routinely publishes alphas numbered above the current stable: lifecycle 2.12.0-alpha01, work 2.12.0-rc01, navigation 2.10.0-rc01, datastore 1.3.0-alpha10, annotation 1.11.0-alpha01 all outrank the releases this app uses. Without the guard, five dependencies would have moved onto unreleased code on the next build with nothing in the diff to say so. With it, every float resolves to exactly the version that was pinned before -- checked against :app:dependencies, not assumed. So this changes nothing today. Every library was already at its newest stable when the catalog was audited; floating is about what happens next month, not this commit. Trying a prerelease is still possible: name the exact version, which pins it rather than floating it. That is the right way round -- an alpha should be a deliberate act with a version number attached to it. Verified: ktlint, detekt, lint, unit tests, androidTest compile, assembleDebug all green, and the configuration cache still reuses across runs of the same task set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,12 +9,28 @@ this file covers only what is not obvious from the code.
|
||||
|
||||
## Build, test, lint
|
||||
|
||||
**Do not pick a JDK for the daemon — the repo does.** `gradle/gradle-daemon-jvm.properties` pins
|
||||
`toolchainVersion=25` and carries foojay download URLs per platform, so Gradle provisions and runs
|
||||
the daemon on **Java 25** regardless of what `JAVA_HOME` points at (that only sets the launcher).
|
||||
`./gradlew --version` prints both if you need to confirm which is which. This is why CI's
|
||||
`java-version: '17'` is not the version that compiles anything, and why the daemon JVM is identical
|
||||
on a laptop and on a runner. Change it with `./gradlew updateDaemonJvm`, not by hand.
|
||||
**Everything is on Java 24** — 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 |
|
||||
| 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
|
||||
`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.
|
||||
|
||||
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
|
||||
--gradle-distribution-sha256-sum <sha>` so the two stay consistent.
|
||||
|
||||
```bash
|
||||
./gradlew :app:assembleDebug # build debug APK
|
||||
@@ -74,6 +90,29 @@ install for code that can never run — and on API 37 the full APK does not fit
|
||||
settled first.
|
||||
- `kotlin.code.style=official`. Gradle stays Kotlin DSL.
|
||||
|
||||
## Dependency versions
|
||||
|
||||
Libraries **float on minor + patch** (`coreKtx = "1.+"`). Three groups deliberately do not:
|
||||
|
||||
- **`agp`, `kotlin`, `ksp` are version-locked to each other.** AGP 9.3.1's POM declares
|
||||
`kotlin-gradle-plugin` 2.2.10, and that is what AGP's built-in Kotlin compiles with. Android lint
|
||||
will suggest Kotlin 2.4.10; taking it breaks the Compose compiler unless KGP is *also* forced onto
|
||||
the root buildscript classpath. Move all three together, by hand, or none.
|
||||
- **ktlint, detekt and JaCoCo are pinned.** A new rule in a linter makes files nobody touched stop
|
||||
passing, so CI goes red on a PR whose diff cannot explain it. Upgrading them is its own commit:
|
||||
run the tool, read the new findings, fix or relax them.
|
||||
- **The FFmpeg AAR** is a committed file, not a coordinate.
|
||||
|
||||
**`+` does not mean "newest stable" on its own** — Gradle will happily resolve it to an alpha, and
|
||||
androidx routinely publishes alphas numbered above the current stable (at last check: lifecycle,
|
||||
navigation, work, datastore and annotation all did). The `componentSelection` block in
|
||||
`app/build.gradle.kts` rejects prereleases, which is the only reason `2.+` means 2.11.0 rather than
|
||||
2.12.0-alpha01. **Do not remove it.** To try a prerelease, name the exact version in the catalog —
|
||||
that pins it, which is the right way round.
|
||||
|
||||
Because versions float, a build can change without a commit. `./gradlew :app:dependencies
|
||||
--configuration debugRuntimeClasspath` shows what actually resolved.
|
||||
|
||||
## Traps
|
||||
|
||||
- **Do not apply `org.jetbrains.kotlin.android`.** AGP 9 has built-in Kotlin; applying the legacy
|
||||
|
||||
@@ -114,6 +114,31 @@ kotlin {
|
||||
compilerOptions {}
|
||||
}
|
||||
|
||||
// --- Prerelease guard for the floating dependency versions ------------------------------
|
||||
// The library versions in libs.versions.toml float on minor + patch ("1.+"). Gradle resolves
|
||||
// `+` to the highest version it finds, and it does NOT skip prereleases -- so without this,
|
||||
// androidx would quietly hand the app an alpha. That is not hypothetical here: at the time
|
||||
// of writing lifecycle, navigation, work, datastore and annotation ALL publish an alpha or
|
||||
// rc numbered above their newest stable, so five of the floats would have moved onto
|
||||
// unreleased code on the next build, with nothing in the diff to say so.
|
||||
//
|
||||
// Rejecting them here means "+" reads as "the newest RELEASED version", which is what
|
||||
// floating was meant to buy. Trying an alpha stays possible -- name the exact version in
|
||||
// the catalog, and it is pinned rather than floating, which is the right way round.
|
||||
val prereleaseMarker = Regex("""[-.](alpha|beta|rc|eap|dev|snapshot|pre|m)\d*$""", RegexOption.IGNORE_CASE)
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
componentSelection {
|
||||
all {
|
||||
if (prereleaseMarker.containsMatchIn(candidate.version)) {
|
||||
reject("prerelease; floating versions take released builds only")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detekt {
|
||||
// Merge the project overrides in config/detekt onto detekt's bundled defaults, so this
|
||||
// repo's file only has to carry the rules it actually changes.
|
||||
|
||||
+30
-19
@@ -11,27 +11,38 @@ agp = "9.3.1"
|
||||
kotlin = "2.2.10"
|
||||
ksp = "2.3.11"
|
||||
|
||||
# AndroidX / Compose
|
||||
composeBom = "2026.08.00"
|
||||
coreKtx = "1.19.0"
|
||||
activityCompose = "1.13.0"
|
||||
lifecycle = "2.11.0"
|
||||
navigation = "2.9.8"
|
||||
work = "2.11.2"
|
||||
datastore = "1.2.1"
|
||||
media3 = "1.11.0"
|
||||
room = "2.8.4"
|
||||
documentfile = "1.1.0"
|
||||
annotation = "1.10.0"
|
||||
# AndroidX / Compose -- floating on minor + patch. The prerelease guard in
|
||||
# app/build.gradle.kts is what keeps `+` from selecting an alpha: several of these
|
||||
# (lifecycle, navigation, work, datastore, annotation) publish alphas and RCs with
|
||||
# version numbers ABOVE their newest stable, and Gradle's `+` would take them.
|
||||
composeBom = "2026.+"
|
||||
coreKtx = "1.+"
|
||||
activityCompose = "1.+"
|
||||
lifecycle = "2.+"
|
||||
navigation = "2.+"
|
||||
work = "2.+"
|
||||
datastore = "1.+"
|
||||
media3 = "1.+"
|
||||
room = "2.+"
|
||||
documentfile = "1.+"
|
||||
annotation = "1.+"
|
||||
|
||||
# Test
|
||||
junit = "4.13.2"
|
||||
androidxJunit = "1.3.0"
|
||||
espressoCore = "3.7.0"
|
||||
smartException = "0.2.1"
|
||||
# Test -- floating, same rules as above.
|
||||
junit = "4.+"
|
||||
androidxJunit = "1.+"
|
||||
espressoCore = "3.+"
|
||||
smartException = "0.+"
|
||||
|
||||
# Lint/format. ktlint owns formatting; detekt owns static analysis (its formatting
|
||||
# ruleset stays off, so the two can never disagree about the same line).
|
||||
# Lint/format. PINNED, deliberately, while the libraries above float.
|
||||
#
|
||||
# A library bump that misbehaves usually still compiles. An analysis-tool bump does
|
||||
# something worse: a new rule in ktlint or detekt makes files nobody touched stop
|
||||
# passing, so CI goes red on a PR whose diff cannot explain it. Upgrading these is
|
||||
# therefore an act with its own commit -- run the tool, read the new findings, fix or
|
||||
# relax them -- which is exactly the reviewable step floating is meant to skip.
|
||||
#
|
||||
# ktlint owns formatting; detekt owns static analysis (its formatting ruleset stays
|
||||
# off, so the two can never disagree about the same line).
|
||||
# detekt 2.0 is the only line with Gradle 9 support -- stable 1.23.x tops out at
|
||||
# Gradle 8.12, and this project is on 9.5.
|
||||
ktlint = "14.2.0"
|
||||
|
||||
Reference in New Issue
Block a user