Fix three ways the floating versions could have gone wrong quietly

All three shared a failure mode: the build stays green while doing something
other than what the config says.

composeBom was "2026.+". The Compose BOM numbers as YYYY.MM.PP, so the year is
the major -- that float stops finding releases on 1 January 2027 and keeps
building happily against a frozen BOM, with nothing in CI or the diff to say so.
Bare "+" now, which is safe only because the prerelease guard is there.

smart-exception was floating on "0.+". Under semver a 0.x minor may break, and
this library is load-bearing precisely where breakage hides: the ffmpeg-kit
wrapper reaches for smartexception.java.Exceptions only when a call FAILS, so a
moved class shows up as an R8 missing-class error at release, or as a crash on
the error path -- the least-exercised code in the app, by its own comment.
Pinned, with that written down. It was noticed while the float was being written
and shipped anyway, which is the actual mistake here.

The prerelease guard permitted detekt's alpha by accident. The pattern wanted
digits straight after the marker word, and detekt reads "2.0.0-alpha.6" with a
dot -- so it passed on punctuation. Had it read "alpha6" the build would have
broken with no way to see why from the config. There is now an explicit
prereleasePermitted set, and the pattern tolerates both spellings, so the
exemption is a decision instead of a coincidence.

Also corrects a comment that was confidently wrong: componentSelection rejects
STATIC prerelease versions too, not only floating ones. Naming "2.12.0-alpha01"
in the catalog does not get you that alpha, it fails to resolve -- verified, not
assumed, because the obvious guess is the opposite. Prereleases are taken by
adding the group to prereleasePermitted.

Two stale references to Gradle 9.5 updated to 9.7.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:56:18 -05:00
co-authored by Claude Opus 5
parent e9542d2223
commit a7aa003020
2 changed files with 32 additions and 8 deletions
+20 -4
View File
@@ -123,15 +123,31 @@ kotlin {
// 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)
// floating was meant to buy.
//
// Note that this applies to STATIC versions too, not only floating ones: naming
// "2.12.0-alpha01" in the catalog does not get you that alpha, it fails to resolve. Verified,
// because the obvious assumption is the opposite. The way to take a prerelease is to add its
// group to prereleasePermitted below.
// Groups allowed to be prereleases anyway. Membership is a deliberate, reviewable act --
// which is the point, because the alternative is what was here first: detekt's alpha slipped
// through only because its version reads "alpha.6" and the pattern below wanted digits
// straight after the word. "alpha6" would have been rejected and the build would have broken
// for a reason nobody could see. An accident that happens to work is not an exemption.
val prereleasePermitted = setOf(
// detekt 2.0 has not left alpha, and it is the only line with Gradle 9 support.
"dev.detekt",
)
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)) {
if (candidate.group !in prereleasePermitted &&
prereleaseMarker.containsMatchIn(candidate.version)
) {
reject("prerelease; floating versions take released builds only")
}
}
+12 -4
View File
@@ -15,7 +15,10 @@ ksp = "2.3.11"
# 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.+"
# Bare `+`, not "2026.+": the year is the major in this scheme (YYYY.MM.PP), so a
# 2026-prefixed float would quietly stop finding releases on 1 January and keep
# building green against a frozen BOM.
composeBom = "+"
coreKtx = "1.+"
activityCompose = "1.+"
lifecycle = "2.+"
@@ -31,7 +34,12 @@ annotation = "1.+"
junit = "4.+"
androidxJunit = "1.+"
espressoCore = "3.+"
smartException = "0.+"
# PINNED, unlike its neighbours. Under semver a 0.x minor is allowed to break, and
# this library is load-bearing exactly where breakage is hardest to see: the wrapper
# reaches for smartexception.java.Exceptions only when an FFmpeg call FAILS, so a
# moved class surfaces as an R8 missing-class error at release time, or as a crash on
# the error path -- the least-exercised code in the app. Bump it deliberately.
smartException = "0.2.1"
# Lint/format. PINNED, deliberately, while the libraries above float.
#
@@ -44,10 +52,10 @@ smartException = "0.+"
# 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.
# Gradle 8.12, and this project is on 9.7.1.
ktlint = "14.2.0"
detekt = "2.0.0-alpha.6"
# JaCoCo coverage agent. Pinned rather than inheriting whatever Gradle 9.5 bundles,
# JaCoCo coverage agent. Pinned rather than inheriting whatever Gradle 9.7.1 bundles,
# so the agent version that reads Kotlin 2.2.10 bytecode is stated, not implied.
jacoco = "0.8.15"