From b7589a14713c4d7a9f451f85b20d158f423e2335 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 6 Oct 2025 15:31:52 +0100 Subject: [PATCH] Raise the minimum supported version of the CycloneDX plugin to 3.0.0 Closes gh-47250 --- .../modules/gradle-plugin/pages/reacting.adoc | 6 +- ...Action.java => CyclonedxPluginAction.java} | 62 +++++++++---------- .../boot/gradle/plugin/SpringBootPlugin.java | 2 +- ...yclonedxPluginActionIntegrationTests.java} | 9 ++- .../testkit/PluginClasspathGradleBuild.java | 22 +++---- .../antora/modules/how-to/pages/build.adoc | 2 +- .../build.gradle | 2 +- 7 files changed, 55 insertions(+), 50 deletions(-) rename build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/{CycloneDxPluginAction.java => CyclonedxPluginAction.java} (67%) rename build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/{CycloneDxPluginActionIntegrationTests.java => CyclonedxPluginActionIntegrationTests.java} (89%) diff --git a/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc b/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc index d4dc98cccde..d1ec0b5817b 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc +++ b/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/reacting.adoc @@ -97,6 +97,10 @@ When the {url-native-build-tools-docs-gradle-plugin}[GraalVM Native Image plugin When the {url-cyclonedx-docs-gradle-plugin}[CycloneDX plugin] is applied to a project, the Spring Boot plugin: -. Configures the `cyclonedxBom` task to use the `application` project type and output the SBOM to the `application.cdx` file in JSON format without full license texts. +. Configures the `cyclonedxBom` task to: +.. Use the `application` project type. +.. Output a JSON-format SBOM to the `application.cdx.json` file. +.. Disable the XML-format SBOM. +.. Disable full license texts. . Adds the SBOM under `META-INF/sbom` in the generated jar or war file. . Adds the `Sbom-Format` and `Sbom-Location` to the manifest of the jar or war file. diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CycloneDxPluginAction.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CyclonedxPluginAction.java similarity index 67% rename from build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CycloneDxPluginAction.java rename to build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CyclonedxPluginAction.java index 83f241220ea..330e6c272b8 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CycloneDxPluginAction.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/CyclonedxPluginAction.java @@ -16,8 +16,9 @@ package org.springframework.boot.gradle.plugin; -import org.cyclonedx.gradle.CycloneDxPlugin; -import org.cyclonedx.gradle.CycloneDxTask; +import org.cyclonedx.gradle.CyclonedxAggregateTask; +import org.cyclonedx.gradle.CyclonedxPlugin; +import org.cyclonedx.model.Component; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -34,78 +35,83 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.bundling.BootWar; /** - * {@link Action} that is executed in response to the {@link CycloneDxPlugin} being + * {@link Action} that is executed in response to the {@link CyclonedxPlugin} being * applied. * * @author Moritz Halbritter + * @author Andy Wilkinson */ -final class CycloneDxPluginAction implements PluginApplicationAction { +final class CyclonedxPluginAction implements PluginApplicationAction { @Override public Class> getPluginClass() { - return CycloneDxPlugin.class; + return CyclonedxPlugin.class; } @Override public void execute(Project project) { - TaskProvider cycloneDxTaskProvider = project.getTasks() - .named("cyclonedxBom", CycloneDxTask.class); - configureCycloneDxTask(cycloneDxTaskProvider); + TaskProvider cycloneDxTaskProvider = project.getTasks() + .named("cyclonedxBom", CyclonedxAggregateTask.class); + configureCycloneDxTask(cycloneDxTaskProvider, project); configureJavaPlugin(project, cycloneDxTaskProvider); configureSpringBootPlugin(project, cycloneDxTaskProvider); } - private void configureCycloneDxTask(TaskProvider taskProvider) { + private void configureCycloneDxTask(TaskProvider taskProvider, Project project) { taskProvider.configure((task) -> { - task.getProjectType().convention("application"); - task.getOutputFormat().convention("json"); - task.getOutputName().convention("application.cdx"); + task.getProjectType().convention(Component.Type.APPLICATION); + task.getXmlOutput().unsetConvention(); + task.getJsonOutput() + .convention(project.getLayout().getBuildDirectory().file("reports/cyclonedx/application.cdx.json")); task.getIncludeLicenseText().convention(false); }); } - private void configureJavaPlugin(Project project, TaskProvider cycloneDxTaskProvider) { + private void configureJavaPlugin(Project project, TaskProvider cycloneDxTaskProvider) { configurePlugin(project, JavaPlugin.class, (javaPlugin) -> { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); SourceSet main = javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); configureTask(project, main.getProcessResourcesTaskName(), Copy.class, (copy) -> { copy.dependsOn(cycloneDxTaskProvider); - Provider sbomFileName = cycloneDxTaskProvider - .map((cycloneDxTask) -> cycloneDxTask.getOutputName().get() + getSbomExtension(cycloneDxTask)); - copy.from(cycloneDxTaskProvider, (spec) -> spec.include(sbomFileName.get()).into("META-INF/sbom")); + Provider sbomFileName = cycloneDxTaskProvider.flatMap( + (cycloneDxTask) -> cycloneDxTask.getJsonOutput().map((file) -> file.getAsFile().getName())); + copy.from(cycloneDxTaskProvider, + (spec) -> spec.include((element) -> element.getName().equals(sbomFileName.get())) + .into("META-INF/sbom")); }); }); } - private void configureSpringBootPlugin(Project project, TaskProvider cycloneDxTaskProvider) { + private void configureSpringBootPlugin(Project project, + TaskProvider cycloneDxTaskProvider) { configurePlugin(project, SpringBootPlugin.class, (springBootPlugin) -> { configureBootJarTask(project, cycloneDxTaskProvider); configureBootWarTask(project, cycloneDxTaskProvider); }); } - private void configureBootJarTask(Project project, TaskProvider cycloneDxTaskProvider) { + private void configureBootJarTask(Project project, TaskProvider cycloneDxTaskProvider) { configureTask(project, SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> configureBootJarTask(bootJar, cycloneDxTaskProvider)); } - private void configureBootWarTask(Project project, TaskProvider cycloneDxTaskProvider) { + private void configureBootWarTask(Project project, TaskProvider cycloneDxTaskProvider) { configureTask(project, SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> configureBootWarTask(bootWar, cycloneDxTaskProvider)); } - private void configureBootJarTask(BootJar task, TaskProvider cycloneDxTaskProvider) { + private void configureBootJarTask(BootJar task, TaskProvider cycloneDxTaskProvider) { configureJarTask(task, cycloneDxTaskProvider, ""); } - private void configureBootWarTask(BootWar task, TaskProvider cycloneDxTaskProvider) { + private void configureBootWarTask(BootWar task, TaskProvider cycloneDxTaskProvider) { configureJarTask(task, cycloneDxTaskProvider, "WEB-INF/classes/"); } - private void configureJarTask(Jar task, TaskProvider cycloneDxTaskProvider, + private void configureJarTask(Jar task, TaskProvider cycloneDxTaskProvider, String sbomLocationPrefix) { - Provider sbomFileName = cycloneDxTaskProvider.map((cycloneDxTask) -> "META-INF/sbom/" - + cycloneDxTask.getOutputName().get() + getSbomExtension(cycloneDxTask)); + Provider sbomFileName = cycloneDxTaskProvider + .map((cycloneDxTask) -> "META-INF/sbom/" + cycloneDxTask.getJsonOutput().get().getAsFile().getName()); task.manifest((manifest) -> { manifest.getAttributes().put("Sbom-Format", "CycloneDX"); manifest.getAttributes() @@ -113,14 +119,6 @@ final class CycloneDxPluginAction implements PluginApplicationAction { }); } - private String getSbomExtension(CycloneDxTask task) { - String format = task.getOutputFormat().get(); - if ("all".equals(format)) { - return ".json"; - } - return "." + format; - } - private void configureTask(Project project, String name, Class type, Action action) { project.getTasks().withType(type).configureEach((task) -> { if (task.getName().equals(name)) { diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java index 28caefb44f9..9c9d9e026c3 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java @@ -148,7 +148,7 @@ public class SpringBootPlugin implements Plugin { List actions = Arrays.asList(new JavaPluginAction(singlePublishedArtifact), new WarPluginAction(singlePublishedArtifact), new DependencyManagementPluginAction(), new ApplicationPluginAction(), new KotlinPluginAction(), new NativeImagePluginAction(), - new CycloneDxPluginAction()); + new CyclonedxPluginAction()); for (PluginApplicationAction action : actions) { withPluginClassOfAction(action, (pluginClass) -> project.getPlugins().withType(pluginClass, (plugin) -> action.execute(project))); diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CycloneDxPluginActionIntegrationTests.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CyclonedxPluginActionIntegrationTests.java similarity index 89% rename from build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CycloneDxPluginActionIntegrationTests.java rename to build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CyclonedxPluginActionIntegrationTests.java index e5bbb26180a..a85ed5b588b 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CycloneDxPluginActionIntegrationTests.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/CyclonedxPluginActionIntegrationTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.gradle.plugin; import java.io.File; import java.io.IOException; +import java.util.List; +import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildResult; @@ -31,12 +33,12 @@ import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link CycloneDxPluginAction}. + * Integration tests for {@link CyclonedxPluginAction}. * * @author Andy Wilkinson */ @GradleCompatibility -class CycloneDxPluginActionIntegrationTests { +class CyclonedxPluginActionIntegrationTests { @SuppressWarnings("NullAway.Init") GradleBuild gradleBuild; @@ -61,7 +63,8 @@ class CycloneDxPluginActionIntegrationTests { assertThat(jar.getManifest().getMainAttributes().getValue("Sbom-Format")).isEqualTo("CycloneDX"); String sbomLocation = jar.getManifest().getMainAttributes().getValue("Sbom-Location"); assertThat(sbomLocation).isEqualTo(sbomLocationPrefix + "META-INF/sbom/application.cdx.json"); - assertThat(jar.getEntry(sbomLocation)).isNotNull(); + List entryNames = jar.stream().map(JarEntry::getName).toList(); + assertThat(entryNames).contains(sbomLocation); } } diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java index d276a326978..0dd30256b8c 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java @@ -29,7 +29,7 @@ import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http2.HttpVersionPolicy; -import org.cyclonedx.gradle.CycloneDxPlugin; +import org.cyclonedx.gradle.CyclonedxPlugin; import org.gradle.testkit.runner.GradleRunner; import org.jetbrains.kotlin.gradle.fus.BuildUidService; import org.jetbrains.kotlin.gradle.model.KotlinProject; @@ -113,20 +113,20 @@ public class PluginClasspathGradleBuild extends GradleBuild { classpath.add(new File(pathOfJarContaining("org.graalvm.buildtools.gradle.NativeImagePlugin"))); classpath.add(new File(pathOfJarContaining("org.graalvm.reachability.GraalVMReachabilityMetadataRepository"))); classpath.add(new File(pathOfJarContaining("org.graalvm.buildtools.utils.SharedConstants"))); - // CycloneDx dependencies - classpath.add(new File(pathOfJarContaining(CycloneDxPlugin.class))); - classpath.add(new File(pathOfJarContaining("com.github.packageurl.MalformedPackageURLException"))); - classpath.add(new File(pathOfJarContaining("org.cyclonedx.parsers.Parser"))); - classpath.add(new File(pathOfJarContaining("org.apache.maven.project.MavenProject"))); - classpath.add(new File(pathOfJarContaining("org.apache.maven.model.building.ModelBuildingException"))); - classpath.add(new File(pathOfJarContaining("org.codehaus.plexus.util.xml.pull.XmlPullParserException"))); - classpath.add(new File(pathOfJarContaining("org.apache.commons.lang3.StringUtils"))); - classpath.add(new File(pathOfJarContaining("com.networknt.schema.resource.SchemaMapper"))); + // Cyclonedx dependencies + classpath.add(new File(pathOfJarContaining(CyclonedxPlugin.class))); + classpath.add(new File(pathOfJarContaining("com.ctc.wstx.api.WriterConfig"))); classpath.add(new File(pathOfJarContaining("com.fasterxml.jackson.core.Versioned"))); classpath.add(new File(pathOfJarContaining("com.fasterxml.jackson.databind.JsonSerializer"))); classpath.add(new File(pathOfJarContaining("com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator"))); + classpath.add(new File(pathOfJarContaining("com.github.packageurl.MalformedPackageURLException"))); + classpath.add(new File(pathOfJarContaining("com.google.common.collect.ImmutableMap"))); + classpath.add(new File(pathOfJarContaining("com.networknt.schema.resource.SchemaMappers"))); classpath.add(new File(pathOfJarContaining("org.apache.commons.collections4.CollectionUtils"))); - classpath.add(new File(pathOfJarContaining("org.apache.commons.io.FileUtils"))); + classpath.add(new File(pathOfJarContaining("org.apache.maven.model.building.ModelBuildingException"))); + classpath.add(new File(pathOfJarContaining("org.codehaus.plexus.util.xml.pull.XmlPullParserException"))); + classpath.add(new File(pathOfJarContaining("org.codehaus.stax2.ri.Stax2WriterAdapter"))); + classpath.add(new File(pathOfJarContaining("org.cyclonedx.model.ExternalReference"))); return classpath; } diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc index e06c9646d10..1dfb1b307b9 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/build.adoc @@ -112,7 +112,7 @@ Gradle users can achieve the same result by using the {url-cyclonedx-docs-gradle [source,gradle] ---- plugins { - id 'org.cyclonedx.bom' version '2.3.0' + id 'org.cyclonedx.bom' version '3.0.0' } ---- diff --git a/platform/spring-boot-internal-dependencies/build.gradle b/platform/spring-boot-internal-dependencies/build.gradle index bd01fc9d9f9..fd35d8fbb6b 100644 --- a/platform/spring-boot-internal-dependencies/build.gradle +++ b/platform/spring-boot-internal-dependencies/build.gradle @@ -77,7 +77,7 @@ bom { ] } } - library("CycloneDX Gradle Plugin", "2.3.0") { + library("CycloneDX Gradle Plugin", "3.0.1") { group("org.cyclonedx") { modules = [ "cyclonedx-gradle-plugin"