Repository navigation
build: order the Room schema snapshot after every KSP round, not just the test ones - #1712
Merged
Merged
Conversation
… the test ones Reported as #1711. Running release lint in the same Gradle invocation as the debug test tasks failed the build: Task ':app:syncRoomSchemaSnapshot' uses this output of task ':app:kspFullReleaseKotlin' without declaring an explicit or implicit dependency. room.schemaLocation is set once, outside any variant block, so EVERY variant's KSP round writes that directory. syncRoomSchemaSnapshot reads it while declaring a relationship with only some of its producers: dependsOn one variant, and mustRunAfter the UnitTest rounds. The dependsOn narrowness is deliberate and stays — the schema derives from the entities alone, so every round writes identical JSON and depending on all of them would cost four KSP rounds per test run. The ordering line was the gap. It covered the hazard that had been observed, unit-test KSP clearing the snapshot after Sync copied it, but kspFullReleaseKotlin writes the same directory and was not covered. It is normally absent from the graph, which is why this never surfaced; pull it in and Gradle fails on the undeclared pair, with an error that reads like a Room problem. Broadened to every ksp*Kotlin task. That is a strict superset of the old matcher — checked name by name, nothing the old one matched fails the new one — so the original guard is intact. Verified rather than reasoned, since mustRunAfter silently forcing extra work is the regression worth fearing: - the previously-failing three-task command now succeeds - it runs kspFullDebugKotlin, kspFullDebugUnitTestKotlin and kspFullReleaseKotlin exactly once each - a PLAIN testFullDebugUnitTest still runs only the debug and unit-test rounds, no release round, so the one-variant optimisation the neighbouring comment protects is untouched - 4,725 tests, 0 failures No parity work: Room/KSP schema generation is Android-only. The schema_oracle.json fixture this pipeline feeds does have a byte-identical Swift twin, but that is committed data and build wiring cannot affect it.
ryanbr
added a commit
that referenced
this pull request
Aug 28, 2026
Follow-up to #1712, which is where the need showed up. That change added a matcher spelled startsWith("ksp") && endsWith("Kotlin") a few lines below one that already existed and read almost identically — and the existing one deliberately INVERTS on the UnitTest/AndroidTest rounds. I did not notice while writing it, which is the argument for this rather than against it: two near-identical predicates meaning opposite things is a trap regardless of who reads them next. The shared half is now named, and its comment states the opposition instead of leaving it to be diffed by eye. isMainSourceSetKspTask excludes those rounds so Gradle is not told they output a directory they never write and then clears it out from under the round that did; syncRoomSchemaSnapshot's mustRunAfter includes them because they still race the Sync. Pure refactor. Both predicates were checked name by name against every KSP and compile task name this project generates, and neither changes for any of them. Verified anyway, because the failure mode here is a missing schema export rather than a compile error, so it does not announce itself: the three-task command from #1712 still succeeds, the debug, unit-test and release rounds still run exactly once each, 4,725 tests pass, and SchemaOracleTest — the test that fails when the export goes missing — is 4 for 4. Run before and after the comment relocation below, with identical results. One note on how it got here: the first version inserted the new declaration BETWEEN the pre-existing comment block and the function it documents, orphaning the load-bearing exclusion rationale onto the wrong function. Caught in review and moved above the block. Worth recording because it is the exact hazard this change exists to reduce, and because a comment attached to the wrong function is invisible to every check in CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses #1711. One line, plus the comment explaining why the old scope was too narrow.
The cause
room.schemaLocationis set once, outside any variant block (app/build.gradle.kts:160-162), so every variant's KSP round writes that directory.syncRoomSchemaSnapshotreads it while declaring a relationship with only some of its producers:dependsOn(tasks.matching { it.name == "kspFullDebugKotlin" }) // one variant mustRunAfter(tasks.matching { it.name.startsWith("ksp") && it.name.contains("UnitTest") }) // some roundsThe
dependsOnnarrowness is deliberate and stays — the schema derives from the entities alone, so every round writes byte-identical JSON, and depending on all of them would cost four KSP rounds per test run.The ordering line was the gap. It covered the hazard that had actually been observed (unit-test KSP clearing the snapshot after Sync copied it), but
kspFullReleaseKotlinwrites the same directory and wasn't covered. It's normally absent from the graph — which is why this never surfaced — butlintVitalFullReleasein the same invocation pulls it in, and Gradle then fails on the undeclared producer/consumer pair with an error that reads like a Room problem.The change
mustRunAfter(tasks.matching { it.name.startsWith("ksp") && it.name.endsWith("Kotlin") })A strict superset of the old matcher — I checked name by name across the variant/test task names this project generates, and nothing the old pattern matched fails the new one. The original guard is intact.
Verified, not reasoned
mustRunAftersilently forcing extra work is the regression worth fearing here, so it is measured rather than argued:kspFullDebugKotlin,kspFullDebugUnitTestKotlin,kspFullReleaseKotlin— once eachtestFullDebugUnitTestThat third row is the one that matters: it is the exact property the neighbouring comment protects, and it holds.
Parity
None required. Room/KSP schema generation is Android-only — there is no
room.schemaLocationor generated-schema step on the Swift side. Theschema_oracle.jsonfixture this pipeline feeds does have a byte-identical Swift twin read bySchemaOracleTests, but that is committed data and build wiring cannot affect it.