The GitHub Actions job "CI" on grails-core.git/fix/sbom-groovydoc-reproducibility has failed. Run started by GitHub user jamesfredley (triggered by jamesfredley).
Head commit for run: 7f753f8be59701bd4b4b51d6b300ef36958e22cb / James Fredley <[email protected]> fix(sbom): make per-module SBOM and groovydoc output byte-reproducible Container-based release-vote verification (per RELEASE.md) on v8.0.0-M1 found nine jars not byte-identical between CI-published copies and a fresh local rebuild from source: grails-async{,-core,-gpars,-rxjava, -rxjava2,-rxjava3}, grails-bootstrap, grails-cache, and the grails-bootstrap-javadoc jar. Two distinct root causes; both fixed at source rather than via JSON post-processing or jar excludes. 1. SbomPlugin: register CyclonedxDirectTask without applying CyclonedxPlugin, and set includeBuildSystem = false The cyclonedx-gradle-plugin's apply() method unconditionally registers BOTH the per-module direct task (cyclonedxDirectBom) AND the aggregate task (cyclonedxBom, a CyclonedxAggregateTask). The aggregate is wired by Spring Boot 4's CycloneDxPluginAction to package its output at META-INF/sbom/application.cdx.json - misleading for library jars (Grails plugins are libraries, not applications) and an extra non-reproducible artifact to manage. Grails ships only per-module SBOMs at META-INF/sbom.json. Registering CyclonedxDirectTask ourselves means the aggregate task is never registered, so there is nothing to disable, exclude, or filter; Spring Boot's reaction never fires either since it triggers on plugin-application of CyclonedxPlugin. Separately, cyclonedx-gradle-plugin 3.0.0 began auto-injecting a build-system externalReference populated from CI env vars (e.g. the GitHub Actions run URL), which is unknowable from a local rebuild and broke byte-identical reproducibility. The plugin exposes a first-class includeBuildSystem property; setting it to false suppresses the reference at the plugin level instead of stripping it from the JSON afterwards. We intentionally leave build info out of the SBOM until that toolchain is ready. PR #15614's projectPath-into-UUID-seed fix is preserved unchanged. 2. FieldDefinition / PropertyDefinition: qualify Builder return types Both classes declare a static inner Builder of the same simple name. Groovydoc's SimpleGroovyClassDoc resolves unqualified Builder references via a shared short-name cache populated in filesystem iteration order, producing different (and incorrect) HTML between filesystems. Qualifying the return types as FieldDefinition.Builder / PropertyDefinition.Builder makes groovydoc output reproducible. Bytecode is unchanged (groovyc resolves nested Builder via lexical scope at compile time). This is the root cause filed upstream as GROOVY-11954 and fixed in Groovy 4.0.32, 5.0.6, and 6.0.0-alpha-1 (https://github.com/apache/groovy/pull/2484). Grails currently pins groovy.version=4.0.31 and 4.0.32 has not yet been published to Maven Central, so the qualification stays in place for v8.0.0-M1. Each builder() factory carries a TODO(GROOVY-11954) note so a future Groovy bump can drop the qualification (the bytecode is unaffected). Verified -------- - :grails-bootstrap:tasks --all reports cyclonedxDirectBom and no cyclonedxBom on grails-plugin modules. - META-INF/sbom.json contains no build-system externalReference. - Two consecutive :grails-bootstrap:jar runs with the same SOURCE_DATE_EPOCH produce byte-identical jars (same SHA-256). - Per-module META-INF/sbom.json is the only SBOM in produced jars (no META-INF/sbom/application.cdx.json). - :grails-bootstrap:test for FieldDefinitionSpec and PropertyDefinitionSpec passes. Assisted-by: claude-code:claude-4.6-opus Report URL: https://github.com/apache/grails-core/actions/runs/25278557421 With regards, GitHub Actions via GitBox
