The GitHub Actions job "Code Style" on 
grails-core.git/fix/sbom-groovydoc-reproducibility has succeeded.
Run started by GitHub user jamesfredley (triggered by jamesfredley).

Head commit for run:
a9f581749509b83e6c5079ba77b21163f1b09dac / James Fredley 
<[email protected]>
fix: make SBOM and groovydoc output reproducible across CI and local builds

The container-based release-vote verification described in RELEASE.md
fails on v8.0.0-M1 because nine jars (grails-async*, grails-bootstrap,
grails-bootstrap-javadoc, grails-cache) are not byte-identical between
the CI-published copies and a fresh local rebuild from the source
distribution. Three independent root causes are addressed.

1. SbomPlugin direct task: strip the build-system externalReference

   The upstream cyclonedx-gradle-plugin auto-injects an externalReference
   of type build-system pointing at the GitHub Actions run URL when CI
   environment variables are present. That URL is unknowable from
   source, so its presence breaks the deterministic serialNumber hash
   that SbomPlugin already computes from the BOM content. Filtering it
   out before the hash recomputation makes META-INF/sbom.json match
   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, a non-reproducible Instant.now()
   timestamp, and CI auto-detected externalReferences. 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.

3. FieldDefinition / PropertyDefinition: qualify Builder return types

   Both classes declare a static inner Builder class with the same
   simple name, and both extend AbstractMemberDefinition. The static
   builder() method previously used the unqualified return type
   "static Builder builder()". When groovydoc walks the class graph,
   it resolves that simple name based on file system iteration order;
   one filesystem ordering causes it to render every Builder reference
   as PropertyDefinition.Builder, another causes FieldDefinition.Builder.
   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.

Verified by building grails-async-core, grails-bootstrap, and grails-cache
twice with the same SOURCE_DATE_EPOCH and confirming both
META-INF/sbom.json and META-INF/sbom/application.cdx.json are
byte-identical across runs, with build-system and vcs externalReferences
stripped and a deterministic urn:uuid serialNumber.

Existing FieldDefinitionSpec and PropertyDefinitionSpec test suites
continue to pass because the bytecode-level builder() return type
is unchanged.

Assisted-by: claude-code:claude-opus-4

Report URL: https://github.com/apache/grails-core/actions/runs/25258840096

With regards,
GitHub Actions via GitBox

Reply via email to