Merge branch '3.1.x' into 3.2.x

Closes gh-39133
This commit is contained in:
Andy Wilkinson 2024-01-15 15:21:31 +00:00
commit c0fedc8f74
9 changed files with 220 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -62,6 +62,7 @@ import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.Library.Module;
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;
import org.springframework.boot.build.mavenplugin.MavenExec;
import org.springframework.util.FileCopyUtils;
@ -113,8 +114,12 @@ public class BomExtension {
LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class, (version != null) ? version : "");
action.execute(libraryHandler);
LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version));
VersionAlignment versionAlignment = (libraryHandler.alignWithVersion != null)
? new VersionAlignment(libraryHandler.alignWithVersion.from, libraryHandler.alignWithVersion.managedBy,
this.project, this.libraries, libraryHandler.groups)
: null;
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots));
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment));
}
public void effectiveBomArtifact() {
@ -220,6 +225,8 @@ public class BomExtension {
private String calendarName;
private AlignWithVersionHandler alignWithVersion;
@Inject
public LibraryHandler(String version) {
this.version = version;
@ -251,6 +258,11 @@ public class BomExtension {
handler.endsWith, handler.contains, handler.reason));
}
public void alignWithVersion(Action<AlignWithVersionHandler> action) {
this.alignWithVersion = new AlignWithVersionHandler();
action.execute(this.alignWithVersion);
}
public static class ProhibitedHandler {
private String reason;
@ -368,6 +380,22 @@ public class BomExtension {
}
public static class AlignWithVersionHandler {
private String from;
private String managedBy;
public void from(String from) {
this.from = from;
}
public void managedBy(String managedBy) {
this.managedBy = managedBy;
}
}
}
public static class UpgradeHandler {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -35,6 +35,7 @@ import org.gradle.api.tasks.TaskAction;
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.Library.VersionAlignment;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
/**
@ -69,6 +70,7 @@ public class CheckBom extends DefaultTask {
List<String> libraryErrors = new ArrayList<>();
checkExclusions(library, libraryErrors);
checkProhibitedVersions(library, libraryErrors);
checkVersionAlignment(library, libraryErrors);
if (!libraryErrors.isEmpty()) {
errors.add(library.getName());
for (String libraryError : libraryErrors) {
@ -148,4 +150,28 @@ public class CheckBom extends DefaultTask {
}
}
private void checkVersionAlignment(Library library, List<String> errors) {
VersionAlignment versionAlignment = library.getVersionAlignment();
if (versionAlignment == null) {
return;
}
Set<String> alignedVersions = versionAlignment.resolve();
if (alignedVersions.size() == 1) {
String alignedVersion = alignedVersions.iterator().next();
if (!alignedVersion.equals(library.getVersion().getVersion().toString())) {
errors.add("Version " + library.getVersion().getVersion() + " is misaligned. It should be "
+ alignedVersion + ".");
}
}
else {
if (alignedVersions.isEmpty()) {
errors.add("Version alignment requires a single version but none were found.");
}
else {
errors.add("Version alignment requires a single version but " + alignedVersions.size() + " were found: "
+ alignedVersions + ".");
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -16,12 +16,22 @@
package org.springframework.boot.build.bom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.artifacts.result.DependencyResult;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
@ -47,6 +57,8 @@ public class Library {
private final boolean considerSnapshots;
private final VersionAlignment versionAlignment;
/**
* Create a new {@code Library} with the given {@code name}, {@code version}, and
* {@code groups}.
@ -57,9 +69,10 @@ public class Library {
* @param groups groups in the library
* @param prohibitedVersions version of the library that are prohibited
* @param considerSnapshots whether to consider snapshots
* @param versionAlignment version alignment, if any, for the library
*/
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots) {
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment) {
this.name = name;
this.calendarName = (calendarName != null) ? calendarName : name;
this.version = version;
@ -68,6 +81,7 @@ public class Library {
: name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version";
this.prohibitedVersions = prohibitedVersions;
this.considerSnapshots = considerSnapshots;
this.versionAlignment = versionAlignment;
}
public String getName() {
@ -98,6 +112,10 @@ public class Library {
return this.considerSnapshots;
}
public VersionAlignment getVersionAlignment() {
return this.versionAlignment;
}
/**
* A version or range of versions that are prohibited from being used in a bom.
*/
@ -280,4 +298,99 @@ public class Library {
}
/**
* Version alignment for a library.
*/
public static class VersionAlignment {
private final String from;
private final String managedBy;
private final Project project;
private final List<Library> libraries;
private final List<Group> groups;
private Set<String> alignedVersions;
VersionAlignment(String from, String managedBy, Project project, List<Library> libraries, List<Group> groups) {
this.from = from;
this.managedBy = managedBy;
this.project = project;
this.libraries = libraries;
this.groups = groups;
}
public Set<String> resolve() {
if (this.managedBy == null) {
throw new IllegalStateException("Version alignment without managedBy is not supported");
}
if (this.alignedVersions != null) {
return this.alignedVersions;
}
Library managingLibrary = this.libraries.stream()
.filter((candidate) -> this.managedBy.equals(candidate.getName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Managing library '" + this.managedBy + "' not found."));
Map<String, String> versions = resolveAligningDependencies(managingLibrary);
Set<String> versionsInLibrary = new HashSet<>();
for (Group group : this.groups) {
for (Module module : group.getModules()) {
String version = versions.get(group.getId() + ":" + module.getName());
if (version != null) {
versionsInLibrary.add(version);
}
}
for (String plugin : group.getPlugins()) {
String version = versions.get(group.getId() + ":" + plugin);
if (version != null) {
versionsInLibrary.add(version);
}
}
}
this.alignedVersions = versionsInLibrary;
return this.alignedVersions;
}
private Map<String, String> resolveAligningDependencies(Library manager) {
DependencyHandler dependencyHandler = this.project.getDependencies();
List<Dependency> boms = manager.getGroups()
.stream()
.flatMap((group) -> group.getBoms()
.stream()
.map((bom) -> dependencyHandler
.platform(group.getId() + ":" + bom + ":" + manager.getVersion().getVersion())))
.toList();
List<Dependency> dependencies = new ArrayList<>();
dependencies.addAll(boms);
dependencies.add(dependencyHandler.create(this.from));
Configuration alignmentConfiguration = this.project.getConfigurations()
.detachedConfiguration(dependencies.toArray(new Dependency[0]));
Map<String, String> versions = new HashMap<>();
for (DependencyResult dependency : alignmentConfiguration.getIncoming()
.getResolutionResult()
.getAllDependencies()) {
versions.put(dependency.getFrom().getModuleVersion().getModule().toString(),
dependency.getFrom().getModuleVersion().getVersion());
}
return versions;
}
String getFrom() {
return this.from;
}
String getManagedBy() {
return this.managedBy;
}
@Override
public String toString() {
return "version from dependencies of " + this.from + " that is managed by " + this.managedBy;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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,6 +20,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -57,11 +58,16 @@ class MultithreadedLibraryUpdateResolver implements LibraryUpdateResolver {
LOGGER.info("Looking for updates using {} threads", this.threads);
ExecutorService executorService = Executors.newFixedThreadPool(this.threads);
try {
return librariesToUpgrade.stream()
.map((library) -> executorService.submit(
() -> this.delegate.findLibraryUpdates(Collections.singletonList(library), librariesByName)))
.flatMap(this::getResult)
.toList();
return librariesToUpgrade.stream().map((library) -> {
if (library.getVersionAlignment() == null) {
return executorService.submit(() -> this.delegate
.findLibraryUpdates(Collections.singletonList(library), librariesByName));
}
else {
return CompletableFuture.completedFuture(
this.delegate.findLibraryUpdates(Collections.singletonList(library), librariesByName));
}
}).flatMap(this::getResult).toList();
}
finally {
executorService.shutdownNow();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.BiPredicate;
@ -31,6 +32,7 @@ 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.VersionAlignment;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
/**
@ -63,7 +65,7 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
}
LOGGER.info("Looking for updates for {}", library.getName());
long start = System.nanoTime();
List<VersionOption> versionOptions = getVersionOptions(library, librariesByName);
List<VersionOption> versionOptions = getVersionOptions(library);
result.add(new LibraryWithVersionOptions(library, versionOptions));
LOGGER.info("Found {} updates for {}, took {}", versionOptions.size(), library.getName(),
Duration.ofNanos(System.nanoTime() - start));
@ -75,8 +77,21 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
return library.getName().equals("Spring Boot");
}
protected List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
return determineResolvedVersionOptions(library);
protected List<VersionOption> getVersionOptions(Library library) {
VersionOption option = determineAlignedVersionOption(library);
return (option != null) ? List.of(option) : determineResolvedVersionOptions(library);
}
private VersionOption determineAlignedVersionOption(Library library) {
VersionAlignment versionAlignment = library.getVersionAlignment();
if (versionAlignment != null) {
Set<String> alignedVersions = versionAlignment.resolve();
if (alignedVersions != null && alignedVersions.size() == 1) {
return new VersionOption.AlignedVersionOption(
DependencyVersion.parse(alignedVersions.iterator().next()), versionAlignment);
}
}
return null;
}
private List<VersionOption> determineResolvedVersionOptions(Library library) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -226,13 +226,13 @@ public abstract class UpgradeDependencies extends DefaultTask {
protected List<BiPredicate<Library, DependencyVersion>> determineUpdatePredicates(Milestone milestone) {
List<BiPredicate<Library, DependencyVersion>> updatePredicates = new ArrayList<>();
updatePredicates.add(this::compilesWithUpgradePolicy);
updatePredicates.add(this::compliesWithUpgradePolicy);
updatePredicates.add(this::isAnUpgrade);
updatePredicates.add(this::isNotProhibited);
return updatePredicates;
}
private boolean compilesWithUpgradePolicy(Library library, DependencyVersion candidate) {
private boolean compliesWithUpgradePolicy(Library library, DependencyVersion candidate) {
return this.bom.getUpgrade().getPolicy().test(candidate, library.getVersion().getVersion());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -18,7 +18,7 @@ package org.springframework.boot.build.bom.bomr;
import java.util.List;
import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.VersionAlignment;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils;
@ -46,17 +46,16 @@ class VersionOption {
static final class AlignedVersionOption extends VersionOption {
private final Library alignedWith;
private final VersionAlignment alignedWith;
AlignedVersionOption(DependencyVersion version, Library alignedWith) {
AlignedVersionOption(DependencyVersion version, VersionAlignment alignedWith) {
super(version);
this.alignedWith = alignedWith;
}
@Override
public String toString() {
return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
+ this.alignedWith.getVersion().getVersion() + ")";
return super.toString() + " (aligned with " + this.alignedWith + ")";
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -53,7 +53,7 @@ class UpgradeApplicatorTests {
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
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")));
null, null, false, null), DependencyVersion.parse("5.16")));
String bomContents = Files.readString(bom.toPath());
assertThat(bomContents).hasSize(originalContents.length() - 3);
}
@ -64,9 +64,9 @@ class UpgradeApplicatorTests {
FileCopyUtils.copy(new File("src/test/resources/bom.gradle"), bom);
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", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null, null, false),
DependencyVersion.parse("1.4")));
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath())
.apply(new Upgrade(new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null,
null, false, null), DependencyVersion.parse("1.4")));
Properties properties = new Properties();
try (InputStream in = new FileInputStream(gradleProperties)) {
properties.load(in);

View File

@ -1075,6 +1075,10 @@ bom {
}
}
library("Neo4j Java Driver", "5.13.0") {
alignWithVersion {
from "org.springframework.data:spring-data-neo4j"
managedBy "Spring Data Bom"
}
group("org.neo4j.driver") {
modules = [
"neo4j-java-driver"