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

   ## 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 (PR #15614) is 
correct, but three independent root causes upstream of it break reproducibility 
for these specific files.
   
   This PR addresses all three.
   
   > Replaces #15624, which was opened against `grails8-groovy6-canary`. Same 
commit, retargeted to `8.0.x` where the fix is most directly useful and where 
CI is stable.
   
   ### 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.
   
   > **Note on relationship to #15614**: PR #15614's 
`projectPath`-into-UUID-seed fix was already merged and active when v8.0.0-M1 
was tagged (verified at commit `6b7c015`), yet these 9 jars still failed 
reproducibility. #15614 prevents serialNumber *collisions* between distinct 
modules with identical BOM content. This PR fixes the *same module* having 
different BOM content between CI and local because of CI-only auto-injection. 
The two fixes are orthogonal and complementary - this PR's refactored helper 
preserves #15614's `projectPath` mixing.
   
   ### 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 sbom.json:           byte-identical between runs
   ✅ grails-bootstrap sbom.json:            byte-identical between runs
   ✅ grails-cache sbom.json:                byte-identical between runs
   ✅ grails-cache application.cdx.json:     byte-identical between runs
   ✅ no build-system externalReference in any sbom.json
   ✅ no build-system or vcs externalReference in application.cdx.json
   ✅ deterministic urn:uuid serialNumber format (with #15614's projectPath 
mixing preserved)
   ```
   
   Existing `FieldDefinitionSpec` and `PropertyDefinitionSpec` test suites pass.
   
   ## Test plan
   
   - [x] `:grails-bootstrap:test --tests 
"grails.codegen.model.FieldDefinitionSpec" --tests 
"grails.codegen.model.PropertyDefinitionSpec"` passes
   - [x] `:grails-bootstrap:codeStyle` passes
   - [x] `:build-logic:compileGroovy` 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.
   
   - 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