Make generatePom lock-safe and deterministic - #747
Merged
Conversation
The `generatePom` task resolved the configurations of subprojects from the root project's `doLast` action. Gradle 9.6 forbids cross-project resolution at execution time (`IllegalResolutionException`); the failure was swallowed at the `info` level, and the report silently fell back to the declared dependency versions. The generated `pom.xml` thus depended on which tasks had already resolved their classpaths in the same invocation — `gradle build` and `gradle generatePom` wrote different files — and false "several versions" warnings were emitted for artifacts already settled by `force(...)`. Now every project collects the versions selected by its own dependency resolution in a `collectResolvedVersions` task, which is always lock-safe, and `generatePom` depends on these tasks and merges their output files. The defensive catch is gone: reading a resolution graph is lenient (failures become `UnresolvedDependencyResult` edges), so nothing needs to be swallowed, and a genuine error now fails the build loudly. The new `PomGeneratorIgTest` drives a real multi-project build via Gradle TestKit, locking the forced-version, genuine-conflict, failing-configuration, and determinism behaviors. See `.agents/tasks/pom-report-cross-project-resolution.md` for the full investigation log, including the empirical disproof of the captured-`Provider` alternative and the decision to keep the full configuration scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base -> 2.0.0-SNAPSHOT.440 Compiler -> 2.0.0-SNAPSHOT.066 CoreJvm -> 2.0.0-SNAPSHOT.523 CoreJvmCompiler -> 2.0.0-SNAPSHOT.082 ToolBase -> 2.0.0-SNAPSHOT.410 Validation -> 2.0.0-SNAPSHOT.462 `CoreJvmCompiler` points to the version produced by SpineEventEngine/core-jvm-compiler#108, which is about to be merged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lock the "several versions" warning for a module that fails to resolve, fix Markdown widows, mirror the memory frontmatter in the index, and link the task file to its memory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Makes generatePom Gradle-9.6+ compatible and deterministic by removing cross-project configuration resolution at task execution time. Each project now records its own resolved dependency versions in a lock-safe way, and the root generatePom task merges these results so pom.xml output is stable across different invocations.
Changes:
- Introduces per-project
collectResolvedVersionstasks and updatesgeneratePomto depend on them and read their outputs. - Refactors
DependencyWriter/PomXmlWriterto accept a per-project “resolved versions” lookup function (instead of resolving from the root task). - Adds a Gradle TestKit integration test to assert determinism and correct conflict/force handling; updates buildSrc test setup accordingly.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/ResolvedVersions.kt | Adds per-project resolved-version collection + serialization and a Project.resolvedVersions() helper. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt | Registers collectors for all projects and makes generatePom depend on them; wires reading via ResolvedVersions::readFrom. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomXmlWriter.kt | Injects a resolvedVersionsOf(Project) function into XML generation. |
| buildSrc/src/main/kotlin/io/spine/gradle/report/pom/DependencyWriter.kt | Refactors dependency collection to consume injected resolved-version maps; removes cross-project resolution logic from this layer. |
| buildSrc/src/test/kotlin/io/spine/gradle/report/pom/PomGeneratorIgTest.kt | New TestKit-based functional test covering determinism and correct warning/version selection. |
| buildSrc/src/test/kotlin/io/spine/gradle/report/pom/DependencyWriterSpec.kt | Updates unit specs to use the new injection point; adds a regression case for “resolution graph is lenient”. |
| buildSrc/build.gradle.kts | Adds gradleTestKit() and passes buildSrc.classpath to tests for TestKit fixture builds. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt | Bumps local Base version constants. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt | Bumps local Compiler fallback version constants. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt | Bumps local CoreJvm version constant. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt | Bumps local CoreJvmCompiler version constants. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt | Bumps local ToolBase version constants. |
| buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt | Bumps local Validation version constant. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/Jackson.kt | Fixes/normalizes artifact declaration for moneta and updates JSTEP-1 reference formatting. |
| buildSrc/src/main/kotlin/io/spine/dependency/lib/JacksonV2.kt | Adds explicit JSTEP-1 link and clarifies 2.x vs 3.x group/package note. |
| .agents/tasks/pom-report-cross-project-resolution.md | Adds investigation log / rationale for the approach taken. |
| .agents/memory/project/pom-report-per-project-collectors.md | Adds project memory capturing the “per-project collectors” design constraint. |
| .agents/memory/MEMORY.md | Indexes the new project memory entry. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
armiol
approved these changes
Aug 13, 2026
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.
What & why
generatePomresolved the configurations of subprojects from the root project'sdoLast. Gradle 9.6 forbids cross-project resolution at execution time (IllegalResolutionException); the failure was swallowed atinfolevel, and the report silently fell back to the declared dependency versions. As a result:gradle buildand standalonegradle generatePomwrote differentdocs/dependencies/pom.xmlfiles, producing spurious diffs in consumer-repo PRs;X" warnings were emitted for artifacts already settled byforce(...)(observed incompiler:spine-validation-jvm-runtime,kotlin-build-tools-impl).Now every project collects the versions selected by its own dependency resolution in a
collectResolvedVersionstask — lock-safe by construction — andgeneratePomdepends on these tasks and merges their output files, mirroring theLicenseReporterper-project + merge structure.Notable findings (full log in
.agents/tasks/pom-report-cross-project-resolution.md)rootComponentProviders at configuration time — was disproved empirically: the provider resolves lazily, so its first.get()from the root task fails with the same lock error.kotlin-build-tools-implonkotlinBuildToolsApiClasspath).catchis removed entirely: probing showed Gradle's resolution-graph API is lenient (failOnVersionConflict()casualties and even crashingeachDependencyrules becomeUnresolvedDependencyResultedges — nothing rethrows fromallComponents), so the report cannot break the build by design, and a genuine error now fails loudly instead of degrading silently.Verification
PomGeneratorIgTestdrives a real multi-project build via Gradle TestKit (parallel on): forced version reported without a warning; genuine cross-module conflict still warned with the newest version kept; a wholly-failing configuration falls back to declared versions with an honest warning; standalonegeneratePomandclean buildwrite identical files.compileritself: standalonegeneratePompreviously dropped a dozen<version>entries and a whole artifact; with the fix it writes a file byte-identical to the committedpom.xmlproduced by a full build../gradlew :buildSrc:build detektgreen (92 tests); pre-PR gate ranspine-code-review,kotlin-engineer,review-docs, anddependency-audit— all findings applied.Also in this PR
.440, Compiler →.066, CoreJvm →.523, ToolBase →.410, Validation →.462, and CoreJvmCompiler →.082(the version produced by Bump Compiler and ToolBase core-jvm-compiler#108, about to merge).🤖 Generated with Claude Code