From 25b72d66fce1e6e7768f5c23fae91c83e0ab156f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 15 Apr 2025 10:58:38 +0100 Subject: [PATCH 1/2] Test Gradle plugin against 9.0.0-milestone-9 --- .../plugin/WarPluginActionIntegrationTests.java | 14 ++++++++++---- .../AbstractBootArchiveIntegrationTests.java | 2 ++ .../tasks/bundling/BootJarIntegrationTests.java | 2 ++ .../testsupport/gradle/testkit/GradleBuild.java | 4 ++++ .../testsupport/gradle/testkit/GradleVersions.java | 12 ++++++------ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests.java index 09e23e284a..38d4d10bca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/WarPluginActionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,9 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.gradle.testkit.runner.BuildResult; @@ -61,9 +63,13 @@ class WarPluginActionIntegrationTests { assertThat(result.task(":bootWar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":war").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); - assertThat(buildLibs.listFiles()).containsExactlyInAnyOrder( - new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war"), - new File(buildLibs, this.gradleBuild.getProjectDir().getName() + "-plain.war")); + List expected = new ArrayList<>(); + expected.add(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".war")); + expected.add(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + "-plain.war")); + if (this.gradleBuild.gradleVersionIsAtLeast("9.0-milestone-2")) { + expected.add(new File(buildLibs, this.gradleBuild.getProjectDir().getName() + "-plain.jar")); + } + assertThat(buildLibs.listFiles()).containsExactlyInAnyOrderElementsOf(expected); } @TestTemplate diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index 4fb7f0ebf4..202172abf4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -50,6 +50,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.loader.tools.FileUtils; @@ -597,6 +598,7 @@ abstract class AbstractBootArchiveIntegrationTests { @TestTemplate void dirModeAndFileModeAreApplied() throws IOException { + Assumptions.assumeTrue(this.gradleBuild.gradleVersionIsLessThan("9.0-milestone-1")); BuildResult result = this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.8-rc-1") .expectDeprecationMessages("The CopyProcessingSpec.setDirMode(Integer) method has been deprecated", "The CopyProcessingSpec.setFileMode(Integer) method has been deprecated", diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java index cf14bba28c..f03f733b8f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java @@ -25,6 +25,7 @@ import java.util.jar.JarFile; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.junit.GradleCompatibility; @@ -56,6 +57,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { @TestTemplate void whenAResolvableCopyOfAnUnresolvableConfigurationIsResolvedThenResolutionSucceeds() { + Assumptions.assumeTrue(this.gradleBuild.gradleVersionIsLessThan("9.0-milestone-1")); this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.0").build("build"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java index 5fb7f6ccf8..901ee096e0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleBuild.java @@ -134,6 +134,10 @@ public class GradleBuild { return GradleVersion.version(this.gradleVersion).compareTo(GradleVersion.version(version)) >= 0; } + public boolean gradleVersionIsLessThan(String version) { + return GradleVersion.version(this.gradleVersion).compareTo(GradleVersion.version(version)) < 0; + } + public BuildResult build(String... arguments) { try { BuildResult result = prepareRunner(arguments).build(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java index 6d894460e7..c846364414 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-test-support/src/main/java/org/springframework/boot/testsupport/gradle/testkit/GradleVersions.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,18 +34,18 @@ public final class GradleVersions { public static List allCompatible() { if (isJavaVersion(JavaVersion.VERSION_24)) { - return Arrays.asList(GradleVersion.current().getVersion()); + return Arrays.asList(GradleVersion.current().getVersion(), "9.0.0-milestone-9"); } if (isJavaVersion(JavaVersion.VERSION_23)) { - return Arrays.asList("8.10", GradleVersion.current().getVersion()); + return Arrays.asList(GradleVersion.current().getVersion(), "9.0.0-milestone-9"); } if (isJavaVersion(JavaVersion.VERSION_22)) { - return Arrays.asList("8.8", GradleVersion.current().getVersion()); + return Arrays.asList("8.8", GradleVersion.current().getVersion(), "9.0.0-milestone-9"); } if (isJavaVersion(JavaVersion.VERSION_21)) { - return Arrays.asList("8.5", GradleVersion.current().getVersion()); + return Arrays.asList("8.5", GradleVersion.current().getVersion(), "9.0.0-milestone-9"); } - return Arrays.asList("7.6.4", "8.4", GradleVersion.current().getVersion()); + return Arrays.asList("7.6.4", "8.4", GradleVersion.current().getVersion(), "9.0.0-milestone-9"); } public static String minimumCompatible() { From 5b2cb79bb167b3d4603d9713f782a3423c3a1084 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 27 May 2025 13:56:00 +0100 Subject: [PATCH 2/2] Configure more version alignment for Spring Data dependencies Closes gh-45698 --- .../boot/build/bom/BomExtension.java | 53 +++++++- .../boot/build/bom/Library.java | 115 +++++++++++++++++- .../spring-boot-dependencies/build.gradle | 20 +++ 3 files changed, 180 insertions(+), 8 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java index 9c4eedda69..d7754fdcd1 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java @@ -38,6 +38,9 @@ import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.JavaPlatformPlugin; +import org.springframework.boot.build.bom.BomExtension.LibraryHandler.AlignWithHandler.PropertyHandler; +import org.springframework.boot.build.bom.BomExtension.LibraryHandler.AlignWithHandler.VersionHandler; +import org.springframework.boot.build.bom.Library.DependencyVersionAlignment; import org.springframework.boot.build.bom.Library.Exclusion; import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.ImportedBom; @@ -45,6 +48,7 @@ import org.springframework.boot.build.bom.Library.LibraryVersion; import org.springframework.boot.build.bom.Library.Link; import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.Library.PermittedDependency; +import org.springframework.boot.build.bom.Library.PomPropertyVersionAlignment; import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.VersionAlignment; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; @@ -106,16 +110,26 @@ public class BomExtension { (version != null) ? version : ""); action.execute(libraryHandler); LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version)); - VersionAlignment versionAlignment = (libraryHandler.alignWith.version != null) - ? new VersionAlignment(libraryHandler.alignWith.version.from, - libraryHandler.alignWith.version.managedBy, this.project, this.libraries, libraryHandler.groups) - : null; addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups, - libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment, + libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment(libraryHandler), libraryHandler.alignWith.dependencyManagementDeclaredIn, libraryHandler.linkRootName, libraryHandler.links)); } + private VersionAlignment versionAlignment(LibraryHandler libraryHandler) { + VersionHandler version = libraryHandler.alignWith.version; + if (version != null) { + return new DependencyVersionAlignment(version.from, version.managedBy, this.project, this.libraries, + libraryHandler.groups); + } + PropertyHandler property = libraryHandler.alignWith.property; + if (property != null) { + return new PomPropertyVersionAlignment(property.name, property.of, property.managedBy, this.project, + this.libraries); + } + return null; + } + private String createDependencyNotation(String groupId, String artifactId, DependencyVersion version) { return groupId + ":" + artifactId + ":" + version; } @@ -383,6 +397,8 @@ public class BomExtension { private VersionHandler version; + private PropertyHandler property; + private String dependencyManagementDeclaredIn; public void version(Action action) { @@ -390,6 +406,11 @@ public class BomExtension { action.execute(this.version); } + public void property(Action action) { + this.property = new PropertyHandler(); + action.execute(this.property); + } + public void dependencyManagementDeclaredIn(String bomCoordinates) { this.dependencyManagementDeclaredIn = bomCoordinates; } @@ -410,6 +431,28 @@ public class BomExtension { } + public static class PropertyHandler { + + private String name; + + private String of; + + private String managedBy; + + public void name(String name) { + this.name = name; + } + + public void of(String dependency) { + this.of = dependency; + } + + public void managedBy(String managedBy) { + this.managedBy = managedBy; + } + + } + } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java index cde765e1ed..723537732f 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/Library.java @@ -16,6 +16,7 @@ package org.springframework.boot.build.bom; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -31,6 +32,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; + import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; import org.gradle.api.Project; @@ -38,6 +44,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolutionResult; +import org.w3c.dom.Document; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; @@ -406,10 +413,16 @@ public class Library { } + public interface VersionAlignment { + + Set resolve(); + + } + /** - * Version alignment for a library. + * Version alignment for a library based on a dependency of another module. */ - public static class VersionAlignment { + public static class DependencyVersionAlignment implements VersionAlignment { private final String from; @@ -423,7 +436,8 @@ public class Library { private Set alignedVersions; - VersionAlignment(String from, String managedBy, Project project, List libraries, List groups) { + DependencyVersionAlignment(String from, String managedBy, Project project, List libraries, + List groups) { this.from = from; this.managedBy = managedBy; this.project = project; @@ -431,6 +445,7 @@ public class Library { this.groups = groups; } + @Override public Set resolve() { if (this.alignedVersions != null) { return this.alignedVersions; @@ -539,6 +554,100 @@ public class Library { } + /** + * Version alignment for a library based on a property in the pom of another module. + */ + public static class PomPropertyVersionAlignment implements VersionAlignment { + + private final String name; + + private final String from; + + private final String managedBy; + + private final Project project; + + private final List libraries; + + private Set alignedVersions; + + PomPropertyVersionAlignment(String name, String from, String managedBy, Project project, + List libraries) { + this.name = name; + this.from = from; + this.managedBy = managedBy; + this.project = project; + this.libraries = libraries; + } + + @Override + public Set resolve() { + if (this.alignedVersions != null) { + return this.alignedVersions; + } + Configuration alignmentConfiguration = this.project.getConfigurations() + .detachedConfiguration(getAligningDependencies().toArray(new Dependency[0])); + Set files = alignmentConfiguration.resolve(); + if (files.size() != 1) { + throw new IllegalStateException( + "Expected a single file when resolving the pom of " + this.from + " but found " + files.size()); + } + File pomFile = files.iterator().next(); + return Set.of(propertyFrom(pomFile)); + } + + private List getAligningDependencies() { + Library managingLibrary = findManagingLibrary(); + List boms = getBomDependencies(managingLibrary); + List dependencies = new ArrayList<>(); + dependencies.addAll(boms); + dependencies.add(this.project.getDependencies().create(this.from + "@pom")); + return dependencies; + } + + private Library findManagingLibrary() { + if (this.managedBy == null) { + return null; + } + return this.libraries.stream() + .filter((candidate) -> this.managedBy.equals(candidate.getName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Managing library '" + this.managedBy + "' not found.")); + } + + private List getBomDependencies(Library manager) { + return manager.getGroups() + .stream() + .flatMap((group) -> group.getBoms() + .stream() + .map((bom) -> this.project.getDependencies() + .platform(group.getId() + ":" + bom.name() + ":" + manager.getVersion().getVersion()))) + .toList(); + } + + private String propertyFrom(File pomFile) { + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = documentBuilder.parse(pomFile); + XPath xpath = XPathFactory.newInstance().newXPath(); + return xpath.evaluate("/project/properties/" + this.name + "/text()", document); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public String toString() { + String result = "version from properties of " + this.from; + if (this.managedBy != null) { + result += " that is managed by " + this.managedBy; + } + return result; + } + + } + public record Link(String rootName, Function factory, List packages) { private static final Pattern PACKAGE_EXPAND = Pattern.compile("^(.*)\\[(.*)\\]$"); diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 183871a5bb..ae1121153f 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -939,6 +939,13 @@ bom { } } library("Jedis", "5.0.2") { + alignWith { + property { + name "jedis" + of "org.springframework.data:spring-data-redis" + managedBy "Spring Data Bom" + } + } group("redis.clients") { modules = [ "jedis" @@ -1150,6 +1157,13 @@ bom { } } library("Lettuce", "6.3.2.RELEASE") { + alignWith { + property { + name "lettuce" + of "org.springframework.data:spring-data-redis" + managedBy "Spring Data Bom" + } + } group("io.lettuce") { modules = [ "lettuce-core" @@ -1452,6 +1466,12 @@ bom { } } library("MongoDB", "5.0.1") { + alignWith { + version { + from "org.springframework.data:spring-data-mongodb" + managedBy "Spring Data Bom" + } + } group("org.mongodb") { modules = [ "bson",