diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bd91b93..2d98f98 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c3ab12a..b9c4b85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"