Merge branch '2.7.x' into 3.0.x

Closes gh-37223
pull/37462/head
Andy Wilkinson 1 year ago
commit cce3c9d40f

@ -44,9 +44,11 @@ dependencies {
implementation("org.springframework:spring-core")
implementation("org.springframework:spring-web")
testImplementation("org.assertj:assertj-core:3.11.1")
testImplementation("org.apache.logging.log4j:log4j-core:2.17.1")
testImplementation("org.assertj:assertj-core:3.11.1")
testImplementation("org.hamcrest:hamcrest:2.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.0")
testImplementation("org.springframework:spring-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

@ -113,8 +113,8 @@ public class BomExtension {
LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class, (version != null) ? version : "");
action.execute(libraryHandler);
LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version));
addLibrary(new Library(name, libraryVersion, libraryHandler.groups, libraryHandler.prohibitedVersions,
libraryHandler.considerSnapshots));
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots));
}
public void effectiveBomArtifact() {
@ -218,6 +218,8 @@ public class BomExtension {
private String version;
private String calendarName;
@Inject
public LibraryHandler(String version) {
this.version = version;
@ -231,6 +233,10 @@ public class BomExtension {
this.considerSnapshots = true;
}
public void setCalendarName(String calendarName) {
this.calendarName = calendarName;
}
public void group(String id, Action<GroupHandler> action) {
GroupHandler groupHandler = new GroupHandler(id);
action.execute(groupHandler);

@ -34,6 +34,8 @@ public class Library {
private final String name;
private final String calendarName;
private final LibraryVersion version;
private final List<Group> groups;
@ -48,14 +50,17 @@ public class Library {
* Create a new {@code Library} with the given {@code name}, {@code version}, and
* {@code groups}.
* @param name name of the library
* @param calendarName name of the library as it appears in the Spring Calendar. May
* be {@code null} in which case the {@code name} is used.
* @param version version of the library
* @param groups groups in the library
* @param prohibitedVersions version of the library that are prohibited
* @param considerSnapshots whether to consider snapshots
*/
public Library(String name, LibraryVersion version, List<Group> groups, List<ProhibitedVersion> prohibitedVersions,
boolean considerSnapshots) {
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots) {
this.name = name;
this.calendarName = (calendarName != null) ? calendarName : name;
this.version = version;
this.groups = groups;
this.versionProperty = "Spring Boot".equals(name) ? null
@ -68,6 +73,10 @@ public class Library {
return this.name;
}
public String getCalendarName() {
return this.calendarName;
}
public LibraryVersion getVersion() {
return this.version;
}

@ -17,13 +17,23 @@
package org.springframework.boot.build.bom.bomr;
import java.net.URI;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
import javax.inject.Inject;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.bomr.ReleaseSchedule.Release;
import org.springframework.boot.build.bom.bomr.github.Milestone;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
/**
* A {@link Task} to move to snapshot dependencies.
@ -32,6 +42,8 @@ import org.springframework.boot.build.bom.Library;
*/
public abstract class MoveToSnapshots extends UpgradeDependencies {
private static final Logger log = LoggerFactory.getLogger(MoveToSnapshots.class);
private final URI REPOSITORY_URI = URI.create("https://repo.spring.io/snapshot/");
@Inject
@ -40,6 +52,12 @@ public abstract class MoveToSnapshots extends UpgradeDependencies {
getRepositoryUris().add(this.REPOSITORY_URI);
}
@Override
@TaskAction
void upgradeDependencies() {
super.upgradeDependencies();
}
@Override
protected String issueTitle(Upgrade upgrade) {
String snapshotVersion = upgrade.getVersion().toString();
@ -63,4 +81,28 @@ public abstract class MoveToSnapshots extends UpgradeDependencies {
return library.isConsiderSnapshots() && super.eligible(library);
}
@Override
protected List<BiPredicate<Library, DependencyVersion>> determineUpdatePredicates(Milestone milestone) {
ReleaseSchedule releaseSchedule = new ReleaseSchedule();
Map<String, List<Release>> releases = releaseSchedule.releasesBetween(OffsetDateTime.now(),
milestone.getDueOn());
List<BiPredicate<Library, DependencyVersion>> predicates = super.determineUpdatePredicates(milestone);
predicates.add((library, candidate) -> {
List<Release> releasesForLibrary = releases.get(library.getCalendarName());
if (releasesForLibrary != null) {
for (Release release : releasesForLibrary) {
if (candidate.isSnapshotFor(release.getVersion())) {
return true;
}
}
}
if (log.isInfoEnabled()) {
log.info("Ignoring " + candidate + ". No release of " + library.getName() + " scheduled before "
+ milestone.getDueOn());
}
return false;
});
return predicates;
}
}

@ -0,0 +1,108 @@
/*
* Copyright 2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.bom.bomr;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
/**
* Release schedule for Spring projects, retrieved from
* <a href="https://calendar.spring.io">https://calendar.spring.io</a>.
*
* @author Andy Wilkinson
*/
class ReleaseSchedule {
private static final Pattern LIBRARY_AND_VERSION = Pattern.compile("([A-Za-z0-9 ]+) ([0-9A-Za-z.-]+)");
private final RestOperations rest;
ReleaseSchedule() {
this(new RestTemplate());
}
ReleaseSchedule(RestOperations rest) {
this.rest = rest;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Map<String, List<Release>> releasesBetween(OffsetDateTime start, OffsetDateTime end) {
ResponseEntity<List> response = this.rest
.getForEntity("https://calendar.spring.io/releases?start=" + start + "&end=" + end, List.class);
List<Map<String, String>> body = response.getBody();
Map<String, List<Release>> releasesByLibrary = new LinkedCaseInsensitiveMap<>();
body.stream()
.map(this::asRelease)
.filter(Objects::nonNull)
.forEach((release) -> releasesByLibrary.computeIfAbsent(release.getLibraryName(), (l) -> new ArrayList<>())
.add(release));
return releasesByLibrary;
}
private Release asRelease(Map<String, String> entry) {
LocalDate due = LocalDate.parse(entry.get("start"));
String title = entry.get("title");
Matcher matcher = LIBRARY_AND_VERSION.matcher(title);
if (!matcher.matches()) {
return null;
}
String library = matcher.group(1);
String version = matcher.group(2);
return new Release(library, DependencyVersion.parse(version), due);
}
static class Release {
private final String libraryName;
private final DependencyVersion version;
private final LocalDate dueOn;
Release(String libraryName, DependencyVersion version, LocalDate dueOn) {
this.libraryName = libraryName;
this.version = version;
this.dueOn = dueOn;
}
String getLibraryName() {
return this.libraryName;
}
DependencyVersion getVersion() {
return this.version;
}
LocalDate getDueOn() {
return this.dueOn;
}
}
}

@ -24,17 +24,15 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
import org.springframework.boot.build.bom.UpgradePolicy;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
/**
@ -48,15 +46,21 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
private final VersionResolver versionResolver;
private final UpgradePolicy upgradePolicy;
private final BiPredicate<Library, DependencyVersion> predicate;
private final boolean movingToSnapshots;
StandardLibraryUpdateResolver(VersionResolver versionResolver, UpgradePolicy upgradePolicy,
boolean movingToSnapshots) {
StandardLibraryUpdateResolver(VersionResolver versionResolver,
List<BiPredicate<Library, DependencyVersion>> predicates) {
this.versionResolver = versionResolver;
this.upgradePolicy = upgradePolicy;
this.movingToSnapshots = movingToSnapshots;
BiPredicate<Library, DependencyVersion> predicate = null;
for (BiPredicate<Library, DependencyVersion> p : predicates) {
if (predicate == null) {
predicate = p;
}
else {
predicate = predicate.and(p);
}
}
this.predicate = predicate;
}
@Override
@ -87,26 +91,24 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
private List<VersionOption> determineResolvedVersionOptions(Library library) {
Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>();
DependencyVersion libraryVersion = library.getVersion().getVersion();
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
moduleVersions.put(group.getId() + ":" + module.getName(),
getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
getLaterVersionsForModule(group.getId(), module.getName(), library));
}
for (String bom : group.getBoms()) {
moduleVersions.put(group.getId() + ":" + bom,
getLaterVersionsForModule(group.getId(), bom, libraryVersion));
moduleVersions.put(group.getId() + ":" + bom, getLaterVersionsForModule(group.getId(), bom, library));
}
for (String plugin : group.getPlugins()) {
moduleVersions.put(group.getId() + ":" + plugin,
getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
getLaterVersionsForModule(group.getId(), plugin, library));
}
}
List<DependencyVersion> allVersions = moduleVersions.values()
.stream()
.flatMap(SortedSet::stream)
.distinct()
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
.filter((dependencyVersion) -> this.predicate.test(library, dependencyVersion))
.toList();
if (allVersions.isEmpty()) {
return Collections.emptyList();
@ -117,32 +119,6 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
.collect(Collectors.toList());
}
private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) {
for (ProhibitedVersion prohibitedVersion : prohibitedVersions) {
String dependencyVersionToString = dependencyVersion.toString();
if (prohibitedVersion.getRange() != null && prohibitedVersion.getRange()
.containsVersion(new DefaultArtifactVersion(dependencyVersionToString))) {
return false;
}
for (String startsWith : prohibitedVersion.getStartsWith()) {
if (dependencyVersionToString.startsWith(startsWith)) {
return false;
}
}
for (String endsWith : prohibitedVersion.getEndsWith()) {
if (dependencyVersionToString.endsWith(endsWith)) {
return false;
}
}
for (String contains : prohibitedVersion.getContains()) {
if (dependencyVersionToString.contains(contains)) {
return false;
}
}
}
return true;
}
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,
DependencyVersion version) {
List<String> missingModules = new ArrayList<>();
@ -154,12 +130,8 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
return missingModules;
}
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId,
DependencyVersion currentVersion) {
SortedSet<DependencyVersion> versions = this.versionResolver.resolveVersions(groupId, artifactId);
versions.removeIf((candidate) -> !this.upgradePolicy.test(candidate, currentVersion));
versions.removeIf((candidate) -> !currentVersion.isUpgrade(candidate, this.movingToSnapshots));
return versions;
private SortedSet<DependencyVersion> getLaterVersionsForModule(String groupId, String artifactId, Library library) {
return this.versionResolver.resolveVersions(groupId, artifactId);
}
}

@ -27,11 +27,13 @@ import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.gradle.api.DefaultTask;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.internal.tasks.userinput.UserInputHandler;
@ -45,10 +47,12 @@ import org.gradle.api.tasks.options.Option;
import org.springframework.boot.build.bom.BomExtension;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
import org.springframework.boot.build.bom.bomr.github.GitHub;
import org.springframework.boot.build.bom.bomr.github.GitHubRepository;
import org.springframework.boot.build.bom.bomr.github.Issue;
import org.springframework.boot.build.bom.bomr.github.Milestone;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils;
/**
@ -70,8 +74,8 @@ public abstract class UpgradeDependencies extends DefaultTask {
protected UpgradeDependencies(BomExtension bom, boolean movingToSnapshots) {
this.bom = bom;
this.movingToSnapshots = movingToSnapshots;
getThreads().convention(2);
this.movingToSnapshots = movingToSnapshots;
}
@Input
@ -97,7 +101,7 @@ public abstract class UpgradeDependencies extends DefaultTask {
this.bom.getUpgrade().getGitHub().getRepository());
List<String> issueLabels = verifyLabels(repository);
Milestone milestone = determineMilestone(repository);
List<Upgrade> upgrades = resolveUpgrades();
List<Upgrade> upgrades = resolveUpgrades(milestone);
applyUpgrades(repository, issueLabels, milestone, upgrades);
}
@ -213,15 +217,52 @@ public abstract class UpgradeDependencies extends DefaultTask {
}
@SuppressWarnings("deprecation")
private List<Upgrade> resolveUpgrades() {
private List<Upgrade> resolveUpgrades(Milestone milestone) {
List<Upgrade> upgrades = new InteractiveUpgradeResolver(getServices().get(UserInputHandler.class),
new MultithreadedLibraryUpdateResolver(getThreads().get(),
new StandardLibraryUpdateResolver(new MavenMetadataVersionResolver(getRepositoryUris().get()),
this.bom.getUpgrade().getPolicy(), this.movingToSnapshots)))
determineUpdatePredicates(milestone))))
.resolveUpgrades(matchingLibraries(), this.bom.getLibraries());
return upgrades;
}
protected List<BiPredicate<Library, DependencyVersion>> determineUpdatePredicates(Milestone milestone) {
BiPredicate<Library, DependencyVersion> compilesWithUpgradePolicy = (library,
candidate) -> this.bom.getUpgrade().getPolicy().test(candidate, library.getVersion().getVersion());
BiPredicate<Library, DependencyVersion> isAnUpgrade = (library,
candidate) -> library.getVersion().getVersion().isUpgrade(candidate, this.movingToSnapshots);
BiPredicate<Library, DependencyVersion> isPermitted = (library, candidate) -> {
for (ProhibitedVersion prohibitedVersion : library.getProhibitedVersions()) {
String candidateString = candidate.toString();
if (prohibitedVersion.getRange() != null
&& prohibitedVersion.getRange().containsVersion(new DefaultArtifactVersion(candidateString))) {
return false;
}
for (String startsWith : prohibitedVersion.getStartsWith()) {
if (candidateString.startsWith(startsWith)) {
return false;
}
}
for (String endsWith : prohibitedVersion.getEndsWith()) {
if (candidateString.endsWith(endsWith)) {
return false;
}
}
for (String contains : prohibitedVersion.getContains()) {
if (candidateString.contains(contains)) {
return false;
}
}
}
return true;
};
List<BiPredicate<Library, DependencyVersion>> updatePredicates = new ArrayList<>();
updatePredicates.add(compilesWithUpgradePolicy);
updatePredicates.add(isAnUpgrade);
updatePredicates.add(isPermitted);
return updatePredicates;
}
private List<Library> matchingLibraries() {
List<Library> matchingLibraries = this.bom.getLibraries().stream().filter(this::eligible).toList();
if (matchingLibraries.isEmpty()) {

@ -16,6 +16,8 @@
package org.springframework.boot.build.bom.bomr.github;
import java.time.OffsetDateTime;
/**
* A milestone in a {@link GitHubRepository GitHub repository}.
*
@ -27,9 +29,12 @@ public class Milestone {
private final int number;
Milestone(String name, int number) {
private final OffsetDateTime dueOn;
Milestone(String name, int number, OffsetDateTime dueOn) {
this.name = name;
this.number = number;
this.dueOn = dueOn;
}
/**
@ -48,6 +53,10 @@ public class Milestone {
return this.number;
}
public OffsetDateTime getDueOn() {
return this.dueOn;
}
@Override
public String toString() {
return this.name + " (" + this.number + ")";

@ -17,6 +17,7 @@
package org.springframework.boot.build.bom.bomr.github;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -75,8 +76,9 @@ final class StandardGitHubRepository implements GitHubRepository {
@Override
public List<Milestone> getMilestones() {
return get("milestones?per_page=100",
(milestone) -> new Milestone((String) milestone.get("title"), (Integer) milestone.get("number")));
return get("milestones?per_page=100", (milestone) -> new Milestone((String) milestone.get("title"),
(Integer) milestone.get("number"),
(milestone.get("due_on") != null) ? OffsetDateTime.parse((String) milestone.get("due_on")) : null));
}
@Override

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 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.
@ -87,24 +87,40 @@ class ArtifactVersionDependencyVersion extends AbstractDependencyVersion {
if (this.artifactVersion.equals(other)) {
return false;
}
if (this.artifactVersion.getMajorVersion() == other.getMajorVersion()
&& this.artifactVersion.getMinorVersion() == other.getMinorVersion()
&& this.artifactVersion.getIncrementalVersion() == other.getIncrementalVersion()) {
if (sameMajorMinorIncremental(other)) {
if (!StringUtils.hasLength(this.artifactVersion.getQualifier())
|| "RELEASE".equals(this.artifactVersion.getQualifier())) {
return false;
}
if ("SNAPSHOT".equals(this.artifactVersion.getQualifier())
|| "BUILD".equals(this.artifactVersion.getQualifier())) {
if (isSnapshot()) {
return true;
}
else if ("SNAPSHOT".equals(other.getQualifier()) || "BUILD".equals(other.getQualifier())) {
else if (((ArtifactVersionDependencyVersion) candidate).isSnapshot()) {
return movingToSnapshots;
}
}
return super.isUpgrade(candidate, movingToSnapshots);
}
private boolean sameMajorMinorIncremental(ArtifactVersion other) {
return this.artifactVersion.getMajorVersion() == other.getMajorVersion()
&& this.artifactVersion.getMinorVersion() == other.getMinorVersion()
&& this.artifactVersion.getIncrementalVersion() == other.getIncrementalVersion();
}
private boolean isSnapshot() {
return "SNAPSHOT".equals(this.artifactVersion.getQualifier())
|| "BUILD".equals(this.artifactVersion.getQualifier());
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
if (!isSnapshot() || !(candidate instanceof ArtifactVersionDependencyVersion)) {
return false;
}
return sameMajorMinorIncremental(((ArtifactVersionDependencyVersion) candidate).artifactVersion);
}
@Override
public String toString() {
return this.artifactVersion.toString();

@ -46,13 +46,21 @@ public interface DependencyVersion extends Comparable<DependencyVersion> {
/**
* Returns whether the given {@code candidate} is an upgrade of this version.
* @param candidate the version the consider
* @param candidate the version to consider
* @param movingToSnapshots whether the upgrade is to be considered as part of moving
* to snaphots
* @return {@code true} if the candidate is an upgrade, otherwise false
*/
boolean isUpgrade(DependencyVersion candidate, boolean movingToSnapshots);
/**
* Returns whether this version is a snapshot for the given {@code candidate}.
* @param candidate the version to consider
* @return {@code true} if this version is a snapshot for the candidate, otherwise
* false
*/
boolean isSnapshotFor(DependencyVersion candidate);
static DependencyVersion parse(String version) {
List<Function<String, DependencyVersion>> parsers = Arrays.asList(CalendarVersionDependencyVersion::parse,
ArtifactVersionDependencyVersion::parse, ReleaseTrainDependencyVersion::parse,

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 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.
@ -72,8 +72,7 @@ final class ReleaseTrainDependencyVersion implements DependencyVersion {
if (comparison != 0) {
return comparison < 0;
}
if (movingToSnapshots && !"BUILD-SNAPSHOT".equals(this.type)
&& "BUILD-SNAPSHOT".equals(candidateReleaseTrain.type)) {
if (movingToSnapshots && !isSnapshot() && candidateReleaseTrain.isSnapshot()) {
return true;
}
comparison = this.type.compareTo(candidateReleaseTrain.type);
@ -83,6 +82,19 @@ final class ReleaseTrainDependencyVersion implements DependencyVersion {
return Integer.compare(this.version, candidateReleaseTrain.version) < 0;
}
private boolean isSnapshot() {
return "BUILD-SNAPSHOT".equals(this.type);
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
if (!isSnapshot() || !(candidate instanceof ReleaseTrainDependencyVersion)) {
return false;
}
ReleaseTrainDependencyVersion candidateReleaseTrain = (ReleaseTrainDependencyVersion) candidate;
return this.releaseTrain.equals(candidateReleaseTrain.releaseTrain);
}
@Override
public boolean isSameMajor(DependencyVersion other) {
return isSameReleaseTrain(other);

@ -48,6 +48,11 @@ final class UnstructuredDependencyVersion extends AbstractDependencyVersion impl
return this.version;
}
@Override
public boolean isSnapshotFor(DependencyVersion candidate) {
return false;
}
static UnstructuredDependencyVersion parse(String version) {
return new UnstructuredDependencyVersion(version);
}

@ -0,0 +1,62 @@
/*
* Copyright 2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.build.bom.bomr;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.build.bom.bomr.ReleaseSchedule.Release;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* Tests for {@link ReleaseSchedule}.
*
* @author Andy Wilkinson
*/
public class ReleaseScheduleTests {
private final RestTemplate rest = new RestTemplate();
private final ReleaseSchedule releaseSchedule = new ReleaseSchedule(this.rest);
private final MockRestServiceServer server = MockRestServiceServer.bindTo(this.rest).build();
@Test
void releasesBetween() {
this.server
.expect(requestTo("https://calendar.spring.io/releases?start=2023-09-01T00:00Z&end=2023-09-21T23:59Z"))
.andRespond(withSuccess(new ClassPathResource("releases.json"), MediaType.APPLICATION_JSON));
Map<String, List<Release>> releases = this.releaseSchedule
.releasesBetween(OffsetDateTime.parse("2023-09-01T00:00Z"), OffsetDateTime.parse("2023-09-21T23:59Z"));
assertThat(releases).hasSize(23);
assertThat(releases.get("Spring Framework")).hasSize(3);
assertThat(releases.get("Spring Boot")).hasSize(4);
assertThat(releases.get("Spring Modulith")).hasSize(1);
assertThat(releases.get("spring graphql")).hasSize(3);
}
}

@ -51,9 +51,9 @@ class UpgradeApplicatorTests {
String originalContents = Files.readString(bom.toPath());
File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade(
new Library("ActiveMQ", new LibraryVersion(DependencyVersion.parse("5.15.11")), null, null, false),
DependencyVersion.parse("5.16")));
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
.apply(new Upgrade(new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")),
null, null, false), DependencyVersion.parse("5.16")));
String bomContents = Files.readString(bom.toPath());
assertThat(bomContents).hasSize(originalContents.length() - 3);
}
@ -65,7 +65,7 @@ class UpgradeApplicatorTests {
File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade(
new Library("Kotlin", new LibraryVersion(DependencyVersion.parse("1.3.70")), null, null, false),
new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null, null, false),
DependencyVersion.parse("1.4")));
Properties properties = new Properties();
try (InputStream in = new FileInputStream(gradleProperties)) {

@ -62,6 +62,71 @@ class ArtifactVersionDependencyVersionTests {
assertThat(version("1.10.2").isSameMinor(version("1.9.1"))).isFalse();
}
@Test
void isSnapshotForWhenSnapshotForReleaseShouldReturnTrue() {
assertThat(version("1.10.2-SNAPSHOT").isSnapshotFor(version("1.10.2"))).isTrue();
}
@Test
void isSnapshotForWhenBuildSnapshotForReleaseShouldReturnTrue() {
assertThat(version("1.10.2.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RELEASE"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForReleaseCandidateShouldReturnTrue() {
assertThat(version("1.10.2-SNAPSHOT").isSnapshotFor(version("1.10.2-RC2"))).isTrue();
}
@Test
void isSnapshotForWhenBuildSnapshotForReleaseCandidateShouldReturnTrue() {
assertThat(version("1.10.2.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RC2"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForMilestoneShouldReturnTrue() {
assertThat(version("1.10.2-SNAPSHOT").isSnapshotFor(version("1.10.2-M1"))).isTrue();
}
@Test
void isSnapshotForWhenBuildSnapshotForMilestoneShouldReturnTrue() {
assertThat(version("1.10.2.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.M1"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForDifferentReleaseShouldReturnFalse() {
assertThat(version("1.10.1-SNAPSHOT").isSnapshotFor(version("1.10.2"))).isFalse();
}
@Test
void isSnapshotForWhenBuildSnapshotForDifferentReleaseShouldReturnTrue() {
assertThat(version("1.10.1.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RELEASE"))).isFalse();
}
@Test
void isSnapshotForWhenSnapshotForDifferentReleaseCandidateShouldReturnTrue() {
assertThat(version("1.10.1-SNAPSHOT").isSnapshotFor(version("1.10.2-RC2"))).isFalse();
}
@Test
void isSnapshotForWhenBuildSnapshotForDifferentReleaseCandidateShouldReturnTrue() {
assertThat(version("1.10.1.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.RC2"))).isFalse();
}
@Test
void isSnapshotForWhenSnapshotForDifferentMilestoneShouldReturnTrue() {
assertThat(version("1.10.1-SNAPSHOT").isSnapshotFor(version("1.10.2-M1"))).isFalse();
}
@Test
void isSnapshotForWhenBuildSnapshotForDifferentMilestoneShouldReturnTrue() {
assertThat(version("1.10.1.BUILD-SNAPSHOT").isSnapshotFor(version("1.10.2.M1"))).isFalse();
}
@Test
void isSnapshotForWhenNotSnapshotShouldReturnFalse() {
assertThat(version("1.10.1-M1").isSnapshotFor(version("1.10.1"))).isFalse();
}
private ArtifactVersionDependencyVersion version(String version) {
return ArtifactVersionDependencyVersion.parse(version);
}

@ -67,6 +67,46 @@ class ReleaseTrainDependencyVersionTests {
assertThat(version("Kay-SR6").isSameMinor(calendarVersion("2020.0.0"))).isFalse();
}
@Test
void isSnapshotForWhenSnapshotForServiceReleaseShouldReturnTrue() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-SR2"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForReleaseShouldReturnTrue() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-RELEASE"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForReleaseCandidateShouldReturnTrue() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-RC1"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForMilestoneShouldReturnTrue() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Kay-M2"))).isTrue();
}
@Test
void isSnapshotForWhenSnapshotForDifferentReleaseShouldReturnFalse() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Lovelace-RELEASE"))).isFalse();
}
@Test
void isSnapshotForWhenSnapshotForDifferentReleaseCandidateShouldReturnTrue() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Lovelace-RC2"))).isFalse();
}
@Test
void isSnapshotForWhenSnapshotForDifferentMilestoneShouldReturnTrue() {
assertThat(version("Kay-BUILD-SNAPSHOT").isSnapshotFor(version("Lovelace-M1"))).isFalse();
}
@Test
void isSnapshotForWhenNotSnapshotShouldReturnFalse() {
assertThat(version("Kay-M1").isSnapshotFor(version("Kay-RELEASE"))).isFalse();
}
private static ReleaseTrainDependencyVersion version(String input) {
return ReleaseTrainDependencyVersion.parse(input);
}

@ -0,0 +1,272 @@
[
{
"allDay": true,
"start": "2023-09-22",
"title": "Spring Modulith 1.0.1",
"url": "https://github.com/spring-projects/spring-modulith/milestone/15"
},
{
"allDay": true,
"start": "2023-09-22",
"title": "Spring Modulith 1.1 M1",
"url": "https://github.com/spring-projects/spring-modulith/milestone/16"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor 2020.0.36",
"url": "https://github.com/reactor/reactor/milestone/51"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor 2022.0.11",
"url": "https://github.com/reactor/reactor/milestone/52"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor 2023.0.0-M3",
"url": "https://github.com/reactor/reactor/milestone/53"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor Core 3.4.33",
"url": "https://github.com/reactor/reactor-core/milestone/158"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor Core 3.5.10",
"url": "https://github.com/reactor/reactor-core/milestone/159"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor Core 3.6.0-M3",
"url": "https://github.com/reactor/reactor-core/milestone/160"
},
{
"allDay": true,
"start": "2023-09-13",
"title": "Sts4 4.20.0.RELEASE",
"url": "https://github.com/spring-projects/sts4/milestone/66"
},
{
"allDay": true,
"start": "2023-09-20",
"title": "Spring Batch 5.1.0-M3",
"url": "https://github.com/spring-projects/spring-batch/milestone/150"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Integration 6.2.0-M3",
"url": "https://github.com/spring-projects/spring-integration/milestone/306"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Integration 5.5.19",
"url": "https://github.com/spring-projects/spring-integration/milestone/309"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Integration 6.1.3",
"url": "https://github.com/spring-projects/spring-integration/milestone/310"
},
{
"allDay": true,
"start": "2023-09-15",
"title": "Spring Data Release 2023.1.0-M3",
"url": "https://github.com/spring-projects/spring-data-release/milestone/30"
},
{
"allDay": true,
"start": "2023-09-15",
"title": "Spring Data Release 2021.2.16",
"url": "https://github.com/spring-projects/spring-data-release/milestone/39"
},
{
"allDay": true,
"start": "2023-09-15",
"title": "Spring Data Release 2022.0.10",
"url": "https://github.com/spring-projects/spring-data-release/milestone/40"
},
{
"allDay": true,
"start": "2023-09-15",
"title": "Spring Data Release 2023.0.4",
"url": "https://github.com/spring-projects/spring-data-release/milestone/41"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Graphql 1.0.5",
"url": "https://github.com/spring-projects/spring-graphql/milestone/27"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Graphql 1.1.6",
"url": "https://github.com/spring-projects/spring-graphql/milestone/33"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Graphql 1.2.3",
"url": "https://github.com/spring-projects/spring-graphql/milestone/34"
},
{
"allDay": true,
"start": "2023-09-19",
"title": "Spring Authorization Server 1.2.0-M1",
"url": "https://github.com/spring-projects/spring-authorization-server/milestone/34"
},
{
"allDay": true,
"start": "2023-09-18",
"title": "Spring Kafka 3.1.0-M1",
"url": "https://github.com/spring-projects/spring-kafka/milestone/225"
},
{
"allDay": true,
"start": "2023-09-14",
"title": "Spring Cloud Dataflow 2.11.0",
"url": "https://github.com/spring-cloud/spring-cloud-dataflow/milestone/159"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Micrometer 1.9.15",
"url": "https://github.com/micrometer-metrics/micrometer/milestone/217"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Micrometer 1.10.11",
"url": "https://github.com/micrometer-metrics/micrometer/milestone/218"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Micrometer 1.11.4",
"url": "https://github.com/micrometer-metrics/micrometer/milestone/219"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Micrometer 1.12.0-M3",
"url": "https://github.com/micrometer-metrics/micrometer/milestone/220"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Tracing 1.0.10",
"url": "https://github.com/micrometer-metrics/tracing/milestone/33"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Tracing 1.1.5",
"url": "https://github.com/micrometer-metrics/tracing/milestone/34"
},
{
"allDay": true,
"start": "2023-09-26",
"title": "Spring Cloud Release 2023.0.0-M2",
"url": "https://github.com/spring-cloud/spring-cloud-release/milestone/134"
},
{
"allDay": true,
"start": "2023-09-11",
"title": "Context Propagation 1.0.6",
"url": "https://github.com/micrometer-metrics/context-propagation/milestone/19"
},
{
"allDay": true,
"start": "2023-09-14",
"title": "Spring Ldap 3.2.0-M3",
"url": "https://github.com/spring-projects/spring-ldap/milestone/63"
},
{
"allDay": true,
"start": "2023-09-21",
"title": "Spring Boot 3.2.0-M3",
"url": "https://github.com/spring-projects/spring-boot/milestone/306"
},
{
"allDay": true,
"start": "2023-09-21",
"title": "Spring Boot 2.7.16",
"url": "https://github.com/spring-projects/spring-boot/milestone/315"
},
{
"allDay": true,
"start": "2023-09-21",
"title": "Spring Boot 3.0.11",
"url": "https://github.com/spring-projects/spring-boot/milestone/316"
},
{
"allDay": true,
"start": "2023-09-21",
"title": "Spring Boot 3.1.4",
"url": "https://github.com/spring-projects/spring-boot/milestone/317"
},
{
"allDay": true,
"start": "2023-09-14",
"title": "Spring Cloud Deployer 2.9.0",
"url": "https://github.com/spring-cloud/spring-cloud-deployer/milestone/116"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor Kafka 1.3.21",
"url": "https://github.com/reactor/reactor-kafka/milestone/38"
},
{
"allDay": true,
"start": "2023-09-18",
"title": "Spring Security 6.2.0-M3",
"url": "https://github.com/spring-projects/spring-security/milestone/308"
},
{
"allDay": true,
"start": "2023-09-22",
"title": "Stream Applications 4.0.0",
"url": "https://github.com/spring-cloud/stream-applications/milestone/7"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor Netty 1.1.11",
"url": "https://github.com/reactor/reactor-netty/milestone/153"
},
{
"allDay": true,
"start": "2023-09-12",
"title": "Reactor Netty 1.0.36",
"url": "https://github.com/reactor/reactor-netty/milestone/154"
},
{
"allDay": true,
"start": "2023-09-14",
"title": "Spring Framework 6.0.12",
"url": "https://github.com/spring-projects/spring-framework/milestone/331"
},
{
"allDay": true,
"start": "2023-09-14",
"title": "Spring Framework 5.3.30",
"url": "https://github.com/spring-projects/spring-framework/milestone/332"
},
{
"allDay": true,
"start": "2023-09-14",
"title": "Spring Framework 6.1.0-RC1",
"url": "https://github.com/spring-projects/spring-framework/milestone/333"
}
]

@ -1168,6 +1168,7 @@ bom {
}
library("Reactor Bom", "2022.0.10") {
considerSnapshots()
calendarName = "Reactor"
group("io.projectreactor") {
imports = [
"reactor-bom"
@ -1364,6 +1365,11 @@ bom {
}
library("Spring Data Bom", "2022.0.9") {
considerSnapshots()
calendarName = "Spring Data Release"
prohibit {
versionRange "[2022.0.0-M1,)"
because "it uses Spring Framework 6"
}
group("org.springframework.data") {
imports = [
"spring-data-bom"

Loading…
Cancel
Save