Problem
Split from #47, which showed the Java lanes miss ~93% of their dependency graph because Syft reads pom.xml (a declaration) rather than a resolved graph. #47 now covers Maven only; this issue covers Gradle, which is the harder half.
Why Gradle is harder
Gradle is worse off than Maven, for two compounding reasons.
Syft only catalogs gradle.lockfile — not build.gradle, not settings.gradle, not gradle/libs.versions.toml. Gradle dependency locking is opt-in and most projects never enable it, so for a typical Gradle project Syft finds nothing at all. Not a partial graph — nothing.
The obvious fix requires mutating the consumer's project.cyclonedx-gradle-plugin is normally applied with apply plugin: 'org.cyclonedx.bom' in the build script. Our reusable workflows must not edit consumer projects, so the documented path is unavailable.
Maven had a clean escape hatch — mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom is a direct goal invocation needing no pom.xml entry, verified working. Gradle has no documented equivalent.
Options to investigate
A. Init-script injection.gradle --init-script <file> can apply a plugin and register tasks without touching the project's build files. This is a standard, supported Gradle mechanism.
⚠️Unverified. This is not documented by the CycloneDX project for this purpose. It should be prototyped against real consumer projects before being committed to a reusable workflow. Known unknowns: plugin resolution from an init script, compatibility across the Gradle versions in use, behaviour with Kotlin DSL (build.gradle.kts) as well as Groovy, and multi-project builds.
B. Resolve the graph with Gradle's own tooling, then synthesise.gradle dependencies or gradle :app:dependencies --configuration runtimeClasspath emits the resolved tree as text. Parsing that is unappealing (format is presentation-oriented and not a stable contract), but it needs no plugin at all.
C. mvn dependency:copy-dependencies equivalent — resolve to a directory, then Syft it. Gradle can be asked to copy the runtime classpath into a directory via an init-script-registered Copy task; Syft's java-archive cataloger then reads real JARs, which it does well. Trades one init-script dependency for another but reuses tooling we already run.
D. Encourage gradle.lockfile adoption. Would make the existing Syft path work correctly with no new machinery, but depends on consumer projects opting in — not something a reusable workflow can assume, though it may be worth documenting as the recommended configuration.
Suggested sequencing
Land Maven first (#47) and let it prove the java-sbom-action contract in production. This issue then becomes "add a Gradle backend to an action that already exists and works", rather than two unproven changes at once.
Option A is the most promising and should be prototyped first; C is the credible fallback if plugin resolution from an init script proves awkward.
Definition of done
- A Gradle project with no
gradle.lockfile produces an SBOM containing its resolved transitive dependencies - Works for Groovy and Kotlin DSL build scripts
- Works for multi-project builds
- No modification to any file in the consumer's repository
- Same output contract as the Maven backend (
sbom-cyclonedx.json, component_count, dependency_manager)
Related
Problem
Split from #47, which showed the Java lanes miss ~93% of their dependency graph because Syft reads
pom.xml(a declaration) rather than a resolved graph. #47 now covers Maven only; this issue covers Gradle, which is the harder half.Why Gradle is harder
Gradle is worse off than Maven, for two compounding reasons.
Syft only catalogs
gradle.lockfile— notbuild.gradle, notsettings.gradle, notgradle/libs.versions.toml. Gradle dependency locking is opt-in and most projects never enable it, so for a typical Gradle project Syft finds nothing at all. Not a partial graph — nothing.The obvious fix requires mutating the consumer's project.
cyclonedx-gradle-pluginis normally applied withapply plugin: 'org.cyclonedx.bom'in the build script. Our reusable workflows must not edit consumer projects, so the documented path is unavailable.Maven had a clean escape hatch —
mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBomis a direct goal invocation needing nopom.xmlentry, verified working. Gradle has no documented equivalent.Options to investigate
A. Init-script injection.
gradle --init-script <file>can apply a plugin and register tasks without touching the project's build files. This is a standard, supported Gradle mechanism.B. Resolve the graph with Gradle's own tooling, then synthesise.
gradle dependenciesorgradle :app:dependencies --configuration runtimeClasspathemits the resolved tree as text. Parsing that is unappealing (format is presentation-oriented and not a stable contract), but it needs no plugin at all.C.
mvn dependency:copy-dependenciesequivalent — resolve to a directory, then Syft it. Gradle can be asked to copy the runtime classpath into a directory via an init-script-registeredCopytask; Syft'sjava-archivecataloger then reads real JARs, which it does well. Trades one init-script dependency for another but reuses tooling we already run.D. Encourage
gradle.lockfileadoption. Would make the existing Syft path work correctly with no new machinery, but depends on consumer projects opting in — not something a reusable workflow can assume, though it may be worth documenting as the recommended configuration.Suggested sequencing
Land Maven first (#47) and let it prove the
java-sbom-actioncontract in production. This issue then becomes "add a Gradle backend to an action that already exists and works", rather than two unproven changes at once.Option A is the most promising and should be prototyped first; C is the credible fallback if plugin resolution from an init script proves awkward.
Definition of done
gradle.lockfileproduces an SBOM containing its resolved transitive dependenciessbom-cyclonedx.json,component_count,dependency_manager)Related