jamesfredley opened a new pull request, #15624:
URL: https://github.com/apache/grails-core/pull/15624

   ## Summary
   
   The container-based release-vote verification described in `RELEASE.md` 
fails on **v8.0.0-M1** because nine jars are not byte-identical between the 
CI-published copies and a fresh local rebuild from the source distribution. The 
SbomPlugin's existing deterministic `serialNumber` logic and the project's 
existing reproducibility infrastructure are correct, but three independent root 
causes upstream of them break reproducibility for these specific files.
   
   This PR addresses all three.
   
   ### Affected jars (verified via `verify.sh v8.0.0-M1`)
   
   | Jar | What differs | Fix in this PR |
   |---|---|---|
   | `grails-async`, `grails-async-core`, `grails-async-gpars`, 
`grails-async-rxjava{,2,3}`, `grails-bootstrap` | `META-INF/sbom.json` | Fix #1 
|
   | `grails-cache` | `META-INF/sbom.json` and 
`META-INF/sbom/application.cdx.json` | Fix #1 + Fix #2 |
   | `grails-bootstrap-javadoc` | 4 HTML files for 
`FieldDefinition`/`PropertyDefinition` | Fix #3 |
   
   ## Root causes and fixes
   
   ### 1. `SbomPlugin` direct task: strip the auto-injected `build-system` 
externalReference
   
   `cyclonedx-gradle-plugin` v3.0.0 auto-injects a `build-system` 
externalReference pointing at 
`https://github.com/apache/grails-core/actions/runs/<run_id>` when 
`GITHUB_ACTIONS` env vars are present. This URL is unknowable from source, so 
its presence breaks the deterministic `serialNumber` hash that `SbomPlugin` 
already computes from the BOM content. Stripping this reference before the hash 
recompute makes `META-INF/sbom.json` byte-identical between CI and local 
rebuilds.
   
   ### 2. `SbomPlugin` aggregate task: apply the same reproducibility transforms
   
   Spring Boot 4 (`CycloneDxPluginAction`) wires the cyclonedx-gradle-plugin 
aggregate task (`CyclonedxAggregateTask`, default name `cyclonedxBom`) into 
Grails-plugin jars, packaging its output at 
`META-INF/sbom/application.cdx.json`. `SbomPlugin` previously only configured 
the direct task (`CyclonedxDirectTask`), so the aggregate SBOM had:
   - a random `serialNumber` UUID
   - an `Instant.now()` timestamp that ignored `SOURCE_DATE_EPOCH`
   - CI auto-detected externalReferences (`build-system` and `vcs`)
   
   The rewrite logic is extracted into a shared `rewriteSbomFile` helper 
applied to both task types via `configureSbomTask` and a new 
`configureAggregateSbomReproducibility` method. The aggregate strip set covers 
both `build-system` AND `vcs` because the aggregate task has no explicit 
`externalReferences` configuration to fall back on (unlike the direct task 
which sets four explicit refs).
   
   ### 3. `FieldDefinition` / `PropertyDefinition`: qualify `Builder` return 
types for groovydoc
   
   Both classes declare a static inner `Builder` class with the same simple 
name, and both extend `AbstractMemberDefinition`. The `static Builder 
builder()` factory method previously used the unqualified return type 
`Builder`. When **groovydoc** walks the class graph, it resolves that simple 
name based on file system iteration order. Different filesystems produce 
different (and incorrect) HTML:
   - One ordering: every `Builder` reference renders as 
`PropertyDefinition.Builder` (so `FieldDefinition.html` is wrong)
   - Other ordering: every `Builder` reference renders as 
`FieldDefinition.Builder` (so `PropertyDefinition.html` is wrong)
   
   This is **not a Gradle 9 issue** (Gradle 9's reproducibility fixes target 
`AbstractArchiveTask`, not the groovydoc tool's name resolution), and 
`noTimestamp = true` is already configured in `GroovydocEnhancerPlugin` so the 
difference is content, not metadata. Qualifying the return types as `static 
FieldDefinition.Builder builder()` and `static PropertyDefinition.Builder 
builder()` forces groovydoc to render the correct (and consistent) types 
regardless of iteration order.
   
   **Compiled bytecode is unchanged** because the nested `Builder` classes 
already resolved correctly via lexical scope at the `groovyc` stage.
   
   ## Verification
   
   Built `grails-async-core`, `grails-bootstrap`, and `grails-cache` twice with 
the same `SOURCE_DATE_EPOCH` and confirmed:
   
   ```
   ✅ grails-async-core: byte-identical between runs
   ✅ grails-bootstrap : byte-identical between runs
   ✅ grails-cache     : byte-identical between runs (sbom.json AND 
application.cdx.json)
   ✅ no build-system externalReference in any sbom.json
   ✅ no build-system or vcs externalReference in application.cdx.json
   ✅ deterministic urn:uuid serialNumber format
   ```
   
   Existing `FieldDefinitionSpec` and `PropertyDefinitionSpec` test suites 
continue to pass because the bytecode-level `builder()` return type is 
unchanged.
   
   End-to-end CI-vs-local validation will happen on this PR's CI build: once 
merged and a release is cut, `verify.sh v<next_release>` should pass on the 
previously-failing 9 jars.
   
   ## Test plan
   
   - [x] `./gradlew :grails-bootstrap:test --tests 
"grails.codegen.model.FieldDefinitionSpec" --tests 
"grails.codegen.model.PropertyDefinitionSpec"` passes
   - [x] `./gradlew :grails-bootstrap:codeStyle` passes
   - [x] `./gradlew :build-logic:compileGroovy` (in `build-logic/`) passes
   - [x] `:grails-async-core:cyclonedxDirectBom`, 
`:grails-bootstrap:cyclonedxDirectBom`, `:grails-cache:cyclonedxDirectBom` 
produce byte-identical output across two consecutive runs with the same 
`SOURCE_DATE_EPOCH`
   - [x] `:grails-cache:cyclonedxBom` (aggregate) produces byte-identical 
output across two runs
   
   ## Notes for reviewers
   
   - The aggregate task strip set (`['build-system', 'vcs']`) is intentionally 
broader than the direct task's (`['build-system']`). The direct task explicitly 
sets a known-good `vcs` reference at `SbomPlugin.groovy:191-193`, so we only 
need to strip the auto-injected `build-system`. The aggregate task has no such 
explicit config, so any externalReferences it has come from CI/`.git` 
auto-detection that won't reproduce locally.
   
   - Build-logic does not currently have a `codeStyle` task or unit-test 
infrastructure for plugins; the verification was done via behavioral 
integration build. If you'd like to add `SbomPluginSpec` coverage in this PR, 
happy to extend - just say the word.
   
   - For the groovydoc fix, an alternative approach would be renaming one of 
the inner `Builder` classes (`FieldDefinitionBuilder` / 
`PropertyDefinitionBuilder`) to remove the simple-name collision entirely. 
That's an API-breaking change so I went with the non-breaking qualification 
approach. If you'd prefer the rename, happy to switch.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to