Both the dependency declaration and its catalog entry named EscapedCoroutineErrors.kt as the sole reason kotlinx-coroutines-test is on the test classpath. That file is gone, and nothing in the gate -- not ktlint, not detekt, not lint -- fails on prose naming a deleted file, so this would have survived as a reference a reader could only resolve through git history. The dependency itself stays, and for a reason worth restating where it is declared: `runTest` is what registers the collector callback, so the one test that deliberately lets an error escape is the scope that receives it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
181 lines
11 KiB
TOML
181 lines
11 KiB
TOML
[versions]
|
|
# 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.4.10"
|
|
ksp = "2.3.11"
|
|
|
|
# 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.
|
|
# 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.+"
|
|
navigation = "2.+"
|
|
work = "2.+"
|
|
datastore = "1.+"
|
|
media3 = "1.+"
|
|
room = "2.+"
|
|
documentfile = "1.+"
|
|
annotation = "1.+"
|
|
|
|
# Test -- floating, same rules as above.
|
|
junit = "4.+"
|
|
androidxJunit = "1.+"
|
|
espressoCore = "3.+"
|
|
# UiAutomator. FLOATING, and the argument for it is the one the guard already makes:
|
|
# androidx.test.uiautomator is inside `floatedGroupPrefixes` ("androidx."), so `2.+` reads
|
|
# as "the newest RELEASED 2.x" exactly the way `work = "2.+"` does -- and this library does
|
|
# publish alphas above its stable, so without the guard it would be a pin.
|
|
#
|
|
# Not pinned like ktlint/detekt/robolectric, because it is not that kind of dependency. Those
|
|
# are pinned because a new *rule* or a new *runtime* makes untouched files fail -- the tool
|
|
# changes its verdict on code nobody edited. UiAutomator has no verdict: it clicks what a
|
|
# selector names, and a selector that stops matching is this repo's test to fix, in a diff
|
|
# that says so. `2.` and not bare `+` because 3.x does not exist yet and a major is where the
|
|
# selector API would be free to change under exactly that assumption.
|
|
uiautomator = "2.+"
|
|
# 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.
|
|
#
|
|
# 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.7.1.
|
|
ktlint = "14.2.0"
|
|
detekt = "2.0.0-alpha.6"
|
|
# 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"
|
|
|
|
# Robolectric. PINNED, and it belongs with ktlint/detekt/jacoco above rather than with
|
|
# the floating libraries, for two reasons that both point the same way.
|
|
#
|
|
# First, the prerelease guard in app/build.gradle.kts only covers the groups this project
|
|
# floats -- "androidx.", "junit", "com.arthenica" -- so org.robolectric is unguarded, and
|
|
# a "4.+" here would resolve straight to 4.17-beta-3, which is the newest thing published.
|
|
# Second, Robolectric is not a library the app ships: it is the JVM's Android runtime, and
|
|
# a version bump changes which android-all jar the tests execute against. That is the same
|
|
# "a tool moved under a diff that cannot explain it" failure the linters are pinned for.
|
|
#
|
|
# 4.16.1 is the newest RELEASED version; the 4.17 line is beta-only at the time of writing.
|
|
robolectric = "4.16.1"
|
|
|
|
# kotlinx-coroutines-test. Already on the unit-test classpath transitively, through
|
|
# compose-ui-test-junit4 -- declared here because a source file now imports it, and a direct
|
|
# import of a transitive is a dependency nobody chose.
|
|
#
|
|
# PINNED, for the same reason as robolectric above: org.jetbrains.kotlinx is not one of the
|
|
# groups in the prerelease guard's `floatedGroupPrefixes`, so a "1.+" here would be free to
|
|
# resolve to a milestone build. This value is what the Compose BOM already resolves it to, so
|
|
# stating it changes nothing in the graph today; if the BOM moves ahead, Gradle takes the
|
|
# higher version and this stays a floor rather than a conflict.
|
|
coroutinesTest = "1.9.0"
|
|
|
|
[libraries]
|
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
|
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
|
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
|
|
|
# Compose — versions come from the BOM, so no version.ref here.
|
|
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
|
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
compose-material3-windowsize = { group = "androidx.compose.material3", name = "material3-window-size-class" }
|
|
compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
|
compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
|
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
|
compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
|
|
|
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
|
|
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
|
|
androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "work" }
|
|
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
|
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
|
|
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
|
|
|
|
# Media3 — the hardware fast path (Apache-2.0).
|
|
media3-transformer = { group = "androidx.media3", name = "media3-transformer", version.ref = "media3" }
|
|
media3-effect = { group = "androidx.media3", name = "media3-effect", version.ref = "media3" }
|
|
media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" }
|
|
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
|
|
media3-muxer = { group = "androidx.media3", name = "media3-muxer", version.ref = "media3" }
|
|
|
|
# Room — job history. Added in a later phase; KSP plugin stays unapplied until then.
|
|
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
|
|
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
|
|
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
|
|
|
# Required at runtime by the ffmpeg-kit-next wrapper: AbstractSession.fail()
|
|
# references smartexception.java.Exceptions. Debug builds tolerate its absence through
|
|
# lazy class loading, so this only surfaces as a crash on the first FFmpeg error --
|
|
# or, as it did here, as an R8 missing-class error.
|
|
smart-exception-java = { group = "com.arthenica", name = "smart-exception-java", version.ref = "smartException" }
|
|
|
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
|
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
|
|
|
# The only way to touch UI this app does not own. Compose's own matchers stop at this
|
|
# process's composition, and the system file picker is a DocumentsUI activity in another
|
|
# process -- so a SAF round trip is unreachable without it.
|
|
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
|
|
|
|
# Robolectric — an Android runtime for the JVM test source set, so file-lifecycle behaviour
|
|
# that needs a real Context can be verified without a device. The instrumented suite cannot
|
|
# run on the development host at all (see CLAUDE.md), so an androidTest-only red test is not
|
|
# a TDD loop anyone here can execute.
|
|
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
|
|
|
|
# Only for its `runTest`, and only so the one test that deliberately lets a coroutine error
|
|
# escape owns the collector callback while it does -- otherwise the error is kept process-wide
|
|
# and rethrown at whichever `runTest` starts next. See ConversionViewModelProbeFailureTest.
|
|
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesTest" }
|
|
|
|
[plugins]
|
|
# 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" }
|
|
# DO NOT add org.jetbrains.kotlin.android — AGP 9 built-in Kotlin makes it a build failure.
|