Skip to content

Make generatePom lock-safe and deterministic - #747

Merged
alexander-yevsyukov merged 5 commits into
masterfrom
fix-pom-report-resolution
Aug 13, 2026
Merged

Make generatePom lock-safe and deterministic#747
alexander-yevsyukov merged 5 commits into
masterfrom
fix-pom-report-resolution

Conversation

@alexander-yevsyukov

Copy link
Copy Markdown
Contributor

What & why

generatePom resolved the configurations of subprojects from the root project's doLast. Gradle 9.6 forbids cross-project resolution at execution time (IllegalResolutionException); the failure was swallowed at info level, and the report silently fell back to the declared dependency versions. As a result:

  • gradle build and standalone gradle generatePom wrote different docs/dependencies/pom.xml files, producing spurious diffs in consumer-repo PRs;
  • false "The project uses several versions of X" warnings were emitted for artifacts already settled by force(...) (observed in compiler: spine-validation-jvm-runtime, kotlin-build-tools-impl).

Now every project collects the versions selected by its own dependency resolution in a collectResolvedVersions task — lock-safe by construction — and generatePom depends on these tasks and merges their output files, mirroring the LicenseReporter per-project + merge structure.

Notable findings (full log in .agents/tasks/pom-report-cross-project-resolution.md)

  • The originally preferred alternative — capturing rootComponent Providers at configuration time — was disproved empirically: the provider resolves lazily, so its first .get() from the root task fails with the same lock error.
  • Narrowing resolution to the four source-set classpaths was rejected: declared dependencies come from all configurations, so the narrowing would re-introduce the false positives (e.g. kotlin-build-tools-impl on kotlinBuildToolsApiClasspath).
  • The old defensive catch is removed entirely: probing showed Gradle's resolution-graph API is lenient (failOnVersionConflict() casualties and even crashing eachDependency rules become UnresolvedDependencyResult edges — nothing rethrows from allComponents), so the report cannot break the build by design, and a genuine error now fails loudly instead of degrading silently.

Verification

  • New PomGeneratorIgTest drives 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; standalone generatePom and clean build write identical files.
  • Verified on compiler itself: standalone generatePom previously dropped a dozen <version> entries and a whole artifact; with the fix it writes a file byte-identical to the committed pom.xml produced by a full build.
  • ./gradlew :buildSrc:build detekt green (92 tests); pre-PR gate ran spine-code-review, kotlin-engineer, review-docs, and dependency-audit — all findings applied.

Also in this PR

  • Local Spine SDK bumps: Base → .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).
  • Jackson dependency-object doc links and artifact-declaration cleanup.

🤖 Generated with Claude Code

alexander-yevsyukov and others added 5 commits August 13, 2026 16:50
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>
@alexander-yevsyukov
alexander-yevsyukov requested a lite review from Copilot August 13, 2026 17:54
@alexander-yevsyukov alexander-yevsyukov self-assigned this Aug 13, 2026
@alexander-yevsyukov alexander-yevsyukov moved this to 🏗 In progress in v2.0 Aug 13, 2026
@alexander-yevsyukov alexander-yevsyukov moved this from 🏗 In progress to In Review in v2.0 Aug 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 collectResolvedVersions tasks and updates generatePom to depend on them and read their outputs.
  • Refactors DependencyWriter/PomXmlWriter to 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.

@alexander-yevsyukov
alexander-yevsyukov merged commit 9c37ceb into master Aug 13, 2026
3 checks passed
@alexander-yevsyukov
alexander-yevsyukov deleted the fix-pom-report-resolution branch August 13, 2026 18:08
@github-project-automation github-project-automation Bot moved this from In Review to ✅ Done in v2.0 Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

3 participants