jamesfredley commented on code in PR #15614:
URL: https://github.com/apache/grails-core/pull/15614#discussion_r3171778301
##########
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy:
##########
@@ -384,4 +396,53 @@ class SbomPlugin implements Plugin<Project> {
}
}
}
+
+ /**
+ * Wires this project's own SBOM into the shadow jar produced by the
+ * com.gradleup.shadow plugin.
+ *
+ * Without this, shadow's first-wins merge picks up a META-INF/sbom.json
+ * from one of the bundled transitive jars (typically grails-shell-cli's),
+ * giving the fat jar the wrong serialNumber and metadata.component
+ * (it ends up describing grails-shell-cli rather than the fat jar's own
+ * project). Two fat jars that both bundle grails-shell-cli (e.g.
+ * :grails-cli and :grails-cli-shadow) then end up with byte-identical
+ * META-INF/sbom.json entries and identical urn:uuid serialNumbers,
+ * which violates the CycloneDX 1.6 specification.
+ *
+ * The fix is symmetrical with publishSbomForJarProjects: we exclude any
+ * META-INF/sbom.json that arrives from transitive dependencies during
+ * the shadow merge, then re-introduce this project's own SBOM (whose
+ * serialNumber is project-path-seeded and unique per fix(sbom): mix
+ * projectPath into deterministic UUID seed).
+ *
+ * Uses the broad Task type to avoid a compile-time dependency on
+ * com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar from
+ * build-logic; the cast is safe because ShadowJar extends Jar.
+ */
+ private static void publishSbomForShadowJarProjects(Project project,
Provider<RegularFile> sbomOutputLocation) {
Review Comment:
Good call - moved the entire shadow jar SBOM wiring out of SbomPlugin and
into `grails-forge/grails-cli/build.gradle` in 23f586d4f6. The generic plugin
no longer knows about `com.gradleup.shadow` at all; its only responsibility is
wiring `cyclonedxDirectBom` into the regular `jar` via
`publishSbomForJarProjects`.
Where the logic lives now (inside the existing `shadowJarTask.configure {
ShadowJar it -> ... }` block):
```groovy
TaskProvider<CyclonedxDirectTask> cyclonedxDirectBomTask =
tasks.named('cyclonedxDirectBom', CyclonedxDirectTask)
shadowJarTask.configure { ShadowJar it ->
// ...existing transforms / mergeServiceFiles / excludes...
it.exclude(
// ...
'META-INF/sbom.json',
// ...
)
if (!project.findProperty('skipJavaComponent')) {
it.from(cyclonedxDirectBomTask.flatMap { CyclonedxDirectTask t ->
t.jsonOutput }) { CopySpec spec ->
spec.into('META-INF')
spec.rename { 'sbom.json' }
}
it.manifest { Manifest manifest ->
manifest.attributes('Sbom-Location': 'META-INF/sbom.json')
manifest.attributes('Sbom-Format': 'CycloneDX')
}
}
}
```
Why this fits better than a hook in `SbomPlugin`:
* No more raw `Task -> Jar` cast - `grails-cli/build.gradle` already imports
`com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar` and now also
imports `org.cyclonedx.gradle.CyclonedxDirectTask`, so the wiring is fully
type-safe at the consumer.
* `cyclonedxDirectBomTask.flatMap { it.jsonOutput }` ties the shadow jar to
exactly the post-processed JSON output (rather than `from(taskProvider)`, which
would copy any future task outputs verbatim).
* The `skipJavaComponent` guard is preserved to match the convention used by
`publishSbomForJarProjects`.
* `:grails-cli-shadow/build.gradle` is left as-is - it already excludes
`META-INF/sbom.json` directly because it does not apply
`org.apache.grails.buildsrc.sbom`.
Verified on Gradle 9.4.1 with `--rerun-tasks`:
| Jar | `serialNumber` | `metadata.component.name` |
|---|---|---|
| `:grails-cli` (regular) | `urn:uuid:beabd2c0-...` | `grails-cli` |
| `:grails-cli` (`-all` FAT) | `urn:uuid:beabd2c0-...` | `grails-cli` |
| `:grails-cli-shadow` (`-all` FAT) | _(no SBOM - excluded)_ | _(no SBOM)_ |
| `:grails-forge-cli` (regular) | `urn:uuid:401e573c-...` |
`grails-forge-cli` |
Distinct `serialNumber` values across distinct projects, the fat jar's
`metadata.component.name` is `grails-cli` (not the leaked `grails-shell-cli`
from before), and the `:grails-cli` regular and `-all` jar deliberately share a
`serialNumber` because they describe the same project.
##########
build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy:
##########
@@ -384,4 +396,53 @@ class SbomPlugin implements Plugin<Project> {
}
}
}
+
+ /**
+ * Wires this project's own SBOM into the shadow jar produced by the
+ * com.gradleup.shadow plugin.
+ *
+ * Without this, shadow's first-wins merge picks up a META-INF/sbom.json
+ * from one of the bundled transitive jars (typically grails-shell-cli's),
+ * giving the fat jar the wrong serialNumber and metadata.component
+ * (it ends up describing grails-shell-cli rather than the fat jar's own
+ * project). Two fat jars that both bundle grails-shell-cli (e.g.
+ * :grails-cli and :grails-cli-shadow) then end up with byte-identical
+ * META-INF/sbom.json entries and identical urn:uuid serialNumbers,
+ * which violates the CycloneDX 1.6 specification.
+ *
+ * The fix is symmetrical with publishSbomForJarProjects: we exclude any
+ * META-INF/sbom.json that arrives from transitive dependencies during
+ * the shadow merge, then re-introduce this project's own SBOM (whose
+ * serialNumber is project-path-seeded and unique per fix(sbom): mix
+ * projectPath into deterministic UUID seed).
+ *
+ * Uses the broad Task type to avoid a compile-time dependency on
Review Comment:
On the missing-compile-dependency angle: I went back through
`build-logic/plugins/build.gradle` and the git history (`git log -S 'shadow' --
build-logic/`) and could not find a prior commit where build-logic had a
compile dependency on `com.gradleup.shadow`. The only build-logic
implementation deps that have ever been there are `grails-publish-plugin`,
`org.gradle.crypto.checksum`, and `org.cyclonedx.bom`.
Either way, the new approach side-steps that question entirely:
`grails-cli/build.gradle` already pulls in `com.gradleup.shadow` via its own
plugins block, so the shadow types are on the buildscript classpath at the
point where they are actually used, with no need to add anything to build-logic.
If a second module ever ends up needing both `com.gradleup.shadow` and
`org.apache.grails.buildsrc.sbom`, the natural next step would be a small
dedicated convention plugin (e.g. `org.apache.grails.buildsrc.shadow-sbom`)
that explicitly depends on shadow, rather than re-adding shadow knowledge to
the generic SBOM plugin. Today there is exactly one such project, so a
per-project configuration is the simplest fit.
--
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]