Merge branch '2.5.x'

Closes gh-27047
pull/27085/head
Andy Wilkinson 3 years ago
commit 5d6a612b4b

@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource; import javax.xml.transform.dom.DOMSource;
@ -56,10 +57,15 @@ import org.w3c.dom.Document;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.DeployedPlugin;
import org.springframework.boot.build.bom.Library.DependencyConstraintsDependencyVersions;
import org.springframework.boot.build.bom.Library.DependencyLockDependencyVersions;
import org.springframework.boot.build.bom.Library.DependencyVersions;
import org.springframework.boot.build.bom.Library.Exclusion; import org.springframework.boot.build.bom.Library.Exclusion;
import org.springframework.boot.build.bom.Library.Group; 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.Module;
import org.springframework.boot.build.bom.Library.ProhibitedVersion; 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.bom.bomr.version.DependencyVersion;
import org.springframework.boot.build.mavenplugin.MavenExec; import org.springframework.boot.build.mavenplugin.MavenExec;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
@ -102,12 +108,19 @@ public class BomExtension {
this.upgradeHandler.gitHub.repository, this.upgradeHandler.gitHub.issueLabels)); this.upgradeHandler.gitHub.repository, this.upgradeHandler.gitHub.issueLabels));
} }
public void library(String name, Action<LibraryHandler> action) {
this.library(name, null, action);
}
public void library(String name, String version, Action<LibraryHandler> action) { public void library(String name, String version, Action<LibraryHandler> action) {
ObjectFactory objects = this.project.getObjects(); ObjectFactory objects = this.project.getObjects();
LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class); LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class, (version != null) ? version : "",
objects);
action.execute(libraryHandler); action.execute(libraryHandler);
addLibrary(new Library(name, DependencyVersion.parse(version), libraryHandler.groups, LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version),
libraryHandler.prohibitedVersions)); libraryHandler.versionAlignment);
addLibrary(new Library(name, libraryVersion, libraryHandler.groups, libraryHandler.prohibitedVersions,
libraryHandler.dependencyVersions));
} }
public void effectiveBomArtifact() { public void effectiveBomArtifact() {
@ -173,17 +186,18 @@ public class BomExtension {
this.libraries.add(library); this.libraries.add(library);
String versionProperty = library.getVersionProperty(); String versionProperty = library.getVersionProperty();
if (versionProperty != null) { if (versionProperty != null) {
this.properties.put(versionProperty, library.getVersion()); this.properties.put(versionProperty, library.getVersion().getVersion());
} }
for (Group group : library.getGroups()) { for (Group group : library.getGroups()) {
for (Module module : group.getModules()) { for (Module module : group.getModules()) {
putArtifactVersionProperty(group.getId(), module.getName(), versionProperty); putArtifactVersionProperty(group.getId(), module.getName(), versionProperty);
this.dependencyHandler.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME, this.dependencyHandler.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
createDependencyNotation(group.getId(), module.getName(), library.getVersion())); createDependencyNotation(group.getId(), module.getName(), library.getVersion().getVersion()));
} }
for (String bomImport : group.getBoms()) { for (String bomImport : group.getBoms()) {
putArtifactVersionProperty(group.getId(), bomImport, versionProperty); putArtifactVersionProperty(group.getId(), bomImport, versionProperty);
String bomDependency = createDependencyNotation(group.getId(), bomImport, library.getVersion()); String bomDependency = createDependencyNotation(group.getId(), bomImport,
library.getVersion().getVersion());
this.dependencyHandler.add(JavaPlatformPlugin.API_CONFIGURATION_NAME, this.dependencyHandler.add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
this.dependencyHandler.platform(bomDependency)); this.dependencyHandler.platform(bomDependency));
this.dependencyHandler.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME, this.dependencyHandler.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME,
@ -198,6 +212,27 @@ public class BomExtension {
private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>(); private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();
private final ObjectFactory objectFactory;
private String version;
private VersionAlignment versionAlignment;
private DependencyVersions dependencyVersions;
@Inject
public LibraryHandler(String version, ObjectFactory objectFactory) {
this.version = version;
this.objectFactory = objectFactory;
}
public void version(String version, Action<VersionHandler> action) {
this.version = version;
VersionHandler versionHandler = new VersionHandler();
action.execute(versionHandler);
this.versionAlignment = new VersionAlignment(versionHandler.libraryName);
}
public void group(String id, Action<GroupHandler> action) { public void group(String id, Action<GroupHandler> action) {
GroupHandler groupHandler = new GroupHandler(id); GroupHandler groupHandler = new GroupHandler(id);
action.execute(groupHandler); action.execute(groupHandler);
@ -217,6 +252,23 @@ public class BomExtension {
} }
} }
public void dependencyVersions(Action<DependencyVersionsHandler> action) {
DependencyVersionsHandler dependencyVersionsHandler = this.objectFactory
.newInstance(DependencyVersionsHandler.class, this.version);
action.execute(dependencyVersionsHandler);
this.dependencyVersions = dependencyVersionsHandler.dependencyVersions;
}
public static class VersionHandler {
private String libraryName;
public void shouldAlignWithVersionFrom(String libraryName) {
this.libraryName = libraryName;
}
}
public static class ProhibitedVersionHandler { public static class ProhibitedVersionHandler {
private String reason; private String reason;
@ -282,6 +334,37 @@ public class BomExtension {
} }
public static class DependencyVersionsHandler {
private final String libraryVersion;
private DependencyVersions dependencyVersions;
@Inject
public DependencyVersionsHandler(String libraryVersion) {
this.libraryVersion = libraryVersion;
}
public void extractFrom(Action<ExtractFromHandler> action) {
action.execute(new ExtractFromHandler());
}
public class ExtractFromHandler {
public void dependencyLock(String location) {
DependencyVersionsHandler.this.dependencyVersions = new DependencyLockDependencyVersions(location,
DependencyVersionsHandler.this.libraryVersion);
}
public void dependencyConstraints(String location) {
DependencyVersionsHandler.this.dependencyVersions = new DependencyConstraintsDependencyVersions(
location, DependencyVersionsHandler.this.libraryVersion);
}
}
}
} }
public static class UpgradeHandler { public static class UpgradeHandler {

@ -50,7 +50,7 @@ public class CheckBom extends DefaultTask {
for (Group group : library.getGroups()) { for (Group group : library.getGroups()) {
for (Module module : group.getModules()) { for (Module module : group.getModules()) {
if (!module.getExclusions().isEmpty()) { if (!module.getExclusions().isEmpty()) {
checkExclusions(group.getId(), module, library.getVersion()); checkExclusions(group.getId(), module, library.getVersion().getVersion());
} }
} }
} }

@ -16,11 +16,20 @@
package org.springframework.boot.build.bom; package org.springframework.boot.build.bom;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.artifact.versioning.VersionRange;
import org.gradle.api.GradleException;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
@ -34,7 +43,7 @@ public class Library {
private final String name; private final String name;
private final DependencyVersion version; private final LibraryVersion version;
private final List<Group> groups; private final List<Group> groups;
@ -42,6 +51,8 @@ public class Library {
private final List<ProhibitedVersion> prohibitedVersions; private final List<ProhibitedVersion> prohibitedVersions;
private final DependencyVersions dependencyVersions;
/** /**
* Create a new {@code Library} with the given {@code name}, {@code version}, and * Create a new {@code Library} with the given {@code name}, {@code version}, and
* {@code groups}. * {@code groups}.
@ -49,22 +60,24 @@ public class Library {
* @param version version of the library * @param version version of the library
* @param groups groups in the library * @param groups groups in the library
* @param prohibitedVersions version of the library that are prohibited * @param prohibitedVersions version of the library that are prohibited
* @param dependencyVersions the library's dependency versions
*/ */
public Library(String name, DependencyVersion version, List<Group> groups, public Library(String name, LibraryVersion version, List<Group> groups, List<ProhibitedVersion> prohibitedVersions,
List<ProhibitedVersion> prohibitedVersions) { DependencyVersions dependencyVersions) {
this.name = name; this.name = name;
this.version = version; this.version = version;
this.groups = groups; this.groups = groups;
this.versionProperty = "Spring Boot".equals(name) ? null this.versionProperty = "Spring Boot".equals(name) ? null
: name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version"; : name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version";
this.prohibitedVersions = prohibitedVersions; this.prohibitedVersions = prohibitedVersions;
this.dependencyVersions = dependencyVersions;
} }
public String getName() { public String getName() {
return this.name; return this.name;
} }
public DependencyVersion getVersion() { public LibraryVersion getVersion() {
return this.version; return this.version;
} }
@ -80,6 +93,10 @@ public class Library {
return this.prohibitedVersions; return this.prohibitedVersions;
} }
public DependencyVersions getDependencyVersions() {
return this.dependencyVersions;
}
/** /**
* A version or range of versions that are prohibited from being used in a bom. * A version or range of versions that are prohibited from being used in a bom.
*/ */
@ -104,6 +121,27 @@ public class Library {
} }
public static class LibraryVersion {
private final DependencyVersion version;
private final VersionAlignment versionAlignment;
public LibraryVersion(DependencyVersion version, VersionAlignment versionAlignment) {
this.version = version;
this.versionAlignment = versionAlignment;
}
public DependencyVersion getVersion() {
return this.version;
}
public VersionAlignment getVersionAlignment() {
return this.versionAlignment;
}
}
/** /**
* A collection of modules, Maven plugins, and Maven boms with the same group ID. * A collection of modules, Maven plugins, and Maven boms with the same group ID.
*/ */
@ -194,4 +232,128 @@ public class Library {
} }
public interface DependencyVersions {
String getVersion(String groupId, String artifactId);
default boolean available() {
return true;
}
}
public static class DependencyLockDependencyVersions implements DependencyVersions {
private final Map<String, Map<String, String>> dependencyVersions = new HashMap<>();
private final String sourceTemplate;
private final String libraryVersion;
public DependencyLockDependencyVersions(String sourceTemplate, String libraryVersion) {
this.sourceTemplate = sourceTemplate;
this.libraryVersion = libraryVersion;
}
@Override
public boolean available() {
return !this.libraryVersion.contains("-SNAPSHOT");
}
@Override
public String getVersion(String groupId, String artifactId) {
if (this.dependencyVersions.isEmpty()) {
loadVersions();
}
return this.dependencyVersions.computeIfAbsent(groupId, (key) -> Collections.emptyMap()).get(artifactId);
}
private void loadVersions() {
String source = this.sourceTemplate.replace("<libraryVersion>", this.libraryVersion);
try {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(URI.create(source).toURL().openStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.startsWith("#")) {
String[] components = line.split(":");
Map<String, String> groupDependencies = this.dependencyVersions
.computeIfAbsent(components[0], (key) -> new HashMap<>());
groupDependencies.put(components[1], components[2]);
}
}
}
}
catch (IOException ex) {
throw new GradleException("Failed to load versions from dependency lock file '" + source + "'", ex);
}
}
}
public static class DependencyConstraintsDependencyVersions implements DependencyVersions {
private static final Pattern CONSTRAINT_PATTERN = Pattern.compile("api \"(.+):(.+):(.+)\"");
private final Map<String, Map<String, String>> dependencyVersions = new HashMap<>();
private final String sourceTemplate;
private final String libraryVersion;
public DependencyConstraintsDependencyVersions(String sourceTemplate, String libraryVersion) {
this.sourceTemplate = sourceTemplate;
this.libraryVersion = libraryVersion;
}
@Override
public String getVersion(String groupId, String artifactId) {
if (this.dependencyVersions.isEmpty()) {
loadVersions();
}
return this.dependencyVersions.computeIfAbsent(groupId, (key) -> Collections.emptyMap()).get(artifactId);
}
private void loadVersions() {
String version = this.libraryVersion;
if (version.endsWith("-SNAPSHOT")) {
version = version.substring(0, version.lastIndexOf('.')) + ".x";
}
String source = this.sourceTemplate.replace("<libraryVersion>", version);
try {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(URI.create(source).toURL().openStream()))) {
String line;
while ((line = reader.readLine()) != null) {
Matcher matcher = CONSTRAINT_PATTERN.matcher(line.trim());
if (matcher.matches()) {
Map<String, String> groupDependencies = this.dependencyVersions
.computeIfAbsent(matcher.group(1), (key) -> new HashMap<>());
groupDependencies.put(matcher.group(2), matcher.group(3));
}
}
}
}
catch (IOException ex) {
throw new GradleException(
"Failed to load versions from dependency constraints declared in '" + source + "'", ex);
}
}
}
public static class VersionAlignment {
private final String libraryName;
public VersionAlignment(String libraryName) {
this.libraryName = libraryName;
}
public String getLibraryName() {
return this.libraryName;
}
}
} }

@ -19,19 +19,25 @@ package org.springframework.boot.build.bom.bomr;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.internal.tasks.userinput.UserInputHandler; import org.gradle.api.internal.tasks.userinput.UserInputHandler;
import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.DependencyVersions;
import org.springframework.boot.build.bom.Library.Group; import org.springframework.boot.build.bom.Library.Group;
import org.springframework.boot.build.bom.Library.Module; import org.springframework.boot.build.bom.Library.Module;
import org.springframework.boot.build.bom.Library.ProhibitedVersion; import org.springframework.boot.build.bom.Library.ProhibitedVersion;
import org.springframework.boot.build.bom.Library.VersionAlignment;
import org.springframework.boot.build.bom.UpgradePolicy; import org.springframework.boot.build.bom.UpgradePolicy;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -59,39 +65,91 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver {
@Override @Override
public List<Upgrade> resolveUpgrades(Collection<Library> libraries) { public List<Upgrade> resolveUpgrades(Collection<Library> libraries) {
return libraries.stream().map(this::resolveUpgrade).filter((upgrade) -> upgrade != null) Map<String, Library> librariesByName = new HashMap<>();
.collect(Collectors.toList()); for (Library library : libraries) {
librariesByName.put(library.getName(), library);
}
return libraries.stream().map((library) -> resolveUpgrade(library, librariesByName))
.filter((upgrade) -> upgrade != null).collect(Collectors.toList());
}
private Upgrade resolveUpgrade(Library library, Map<String, Library> libraries) {
List<VersionOption> versionOptions = getVersionOptions(library, libraries);
if (versionOptions.isEmpty()) {
return null;
}
VersionOption current = new VersionOption(library.getVersion().getVersion());
VersionOption selected = this.userInputHandler
.selectOption(library.getName() + " " + library.getVersion().getVersion(), versionOptions, current);
return (selected.equals(current)) ? null : new Upgrade(library, selected.version);
} }
private Upgrade resolveUpgrade(Library library) { private List<VersionOption> getVersionOptions(Library library, Map<String, Library> libraries) {
if (library.getVersion().getVersionAlignment() != null) {
VersionOption alignedVersionOption = alignedVersionOption(library, libraries);
if (!isPermitted(alignedVersionOption.version, library.getProhibitedVersions())) {
throw new InvalidUserDataException("Version alignment failed. Version " + alignedVersionOption.version
+ " from " + library.getName() + " is prohibited");
}
return Collections.singletonList(alignedVersionOption);
}
Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>(); Map<String, SortedSet<DependencyVersion>> moduleVersions = new LinkedHashMap<>();
DependencyVersion libraryVersion = library.getVersion().getVersion();
for (Group group : library.getGroups()) { for (Group group : library.getGroups()) {
for (Module module : group.getModules()) { for (Module module : group.getModules()) {
moduleVersions.put(group.getId() + ":" + module.getName(), moduleVersions.put(group.getId() + ":" + module.getName(),
getLaterVersionsForModule(group.getId(), module.getName(), library.getVersion())); getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion));
} }
for (String bom : group.getBoms()) { for (String bom : group.getBoms()) {
moduleVersions.put(group.getId() + ":" + bom, moduleVersions.put(group.getId() + ":" + bom,
getLaterVersionsForModule(group.getId(), bom, library.getVersion())); getLaterVersionsForModule(group.getId(), bom, libraryVersion));
} }
for (String plugin : group.getPlugins()) { for (String plugin : group.getPlugins()) {
moduleVersions.put(group.getId() + ":" + plugin, moduleVersions.put(group.getId() + ":" + plugin,
getLaterVersionsForModule(group.getId(), plugin, library.getVersion())); getLaterVersionsForModule(group.getId(), plugin, libraryVersion));
} }
} }
List<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct() List<DependencyVersion> allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct()
.filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions())) .filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions()))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (allVersions.isEmpty()) { if (allVersions.isEmpty()) {
return null; return Collections.emptyList();
} }
List<VersionOption> versionOptions = allVersions.stream() return allVersions.stream()
.map((version) -> new VersionOption(version, getMissingModules(moduleVersions, version))) .map((version) -> new ResolvedVersionOption(version, getMissingModules(moduleVersions, version)))
.collect(Collectors.toList()); .collect(Collectors.toList());
VersionOption current = new VersionOption(library.getVersion(), Collections.emptyList()); }
VersionOption selected = this.userInputHandler.selectOption(library.getName() + " " + library.getVersion(),
versionOptions, current); private VersionOption alignedVersionOption(Library library, Map<String, Library> libraries) {
return (selected.equals(current)) ? null : new Upgrade(library, selected.version); VersionAlignment versionAlignment = library.getVersion().getVersionAlignment();
Library alignmentLibrary = libraries.get(versionAlignment.getLibraryName());
DependencyVersions dependencyVersions = alignmentLibrary.getDependencyVersions();
if (dependencyVersions == null) {
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ "' as it does not define any dependency versions");
}
if (!dependencyVersions.available()) {
return null;
}
Set<String> versions = new HashSet<>();
for (Group group : library.getGroups()) {
for (Module module : group.getModules()) {
String version = dependencyVersions.getVersion(group.getId(), module.getName());
if (version != null) {
versions.add(version);
}
}
}
if (versions.isEmpty()) {
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ "' as its dependency versions do not include any of this library's modules");
}
if (versions.size() > 1) {
throw new InvalidUserDataException("Cannot align with library '" + versionAlignment.getLibraryName()
+ "' as it uses multiple different versions of this library's modules");
}
String requiredVersion = versions.iterator().next();
return new AlignedVersionOption(DependencyVersion.parse(requiredVersion), alignmentLibrary);
} }
private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) { private boolean isPermitted(DependencyVersion dependencyVersion, List<ProhibitedVersion> prohibitedVersions) {
@ -125,23 +183,53 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver {
return versions; return versions;
} }
private static final class VersionOption { private static class VersionOption {
private final DependencyVersion version; private final DependencyVersion version;
protected VersionOption(DependencyVersion version) {
this.version = version;
}
@Override
public String toString() {
return this.version.toString();
}
}
private static final class AlignedVersionOption extends VersionOption {
private final Library alignedWith;
private AlignedVersionOption(DependencyVersion version, Library alignedWith) {
super(version);
this.alignedWith = alignedWith;
}
@Override
public String toString() {
return super.toString() + " (aligned with " + this.alignedWith.getName() + " "
+ this.alignedWith.getVersion().getVersion() + ")";
}
}
private static final class ResolvedVersionOption extends VersionOption {
private final List<String> missingModules; private final List<String> missingModules;
private VersionOption(DependencyVersion version, List<String> missingModules) { private ResolvedVersionOption(DependencyVersion version, List<String> missingModules) {
this.version = version; super(version);
this.missingModules = missingModules; this.missingModules = missingModules;
} }
@Override @Override
public String toString() { public String toString() {
if (this.missingModules.isEmpty()) { if (this.missingModules.isEmpty()) {
return this.version.toString(); return super.toString();
} }
return this.version + " (some modules are missing: " return super.toString() + " (some modules are missing: "
+ StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")"; + StringUtils.collectionToDelimitedString(this.missingModules, ", ") + ")";
} }

@ -46,8 +46,14 @@ class UpgradeApplicator {
Matcher matcher = Pattern.compile("library\\(\"" + upgrade.getLibrary().getName() + "\", \"(.+)\"\\)") Matcher matcher = Pattern.compile("library\\(\"" + upgrade.getLibrary().getName() + "\", \"(.+)\"\\)")
.matcher(buildFileContents); .matcher(buildFileContents);
if (!matcher.find()) { if (!matcher.find()) {
throw new IllegalStateException("Failed to find definition for library '" + upgrade.getLibrary().getName() matcher = Pattern
+ "' in bom '" + this.buildFile + "'"); .compile("library\\(\"" + upgrade.getLibrary().getName() + "\"\\) \\{\\s+version\\(\"(.+)\"\\)",
Pattern.MULTILINE)
.matcher(buildFileContents);
if (!matcher.find()) {
throw new IllegalStateException("Failed to find definition for library '"
+ upgrade.getLibrary().getName() + "' in bom '" + this.buildFile + "'");
}
} }
String version = matcher.group(1); String version = matcher.group(1);
if (version.startsWith("${") && version.endsWith("}")) { if (version.startsWith("${") && version.endsWith("}")) {
@ -55,7 +61,7 @@ class UpgradeApplicator {
return this.gradleProperties; return this.gradleProperties;
} }
else { else {
updateBuildFile(upgrade, buildFileContents); updateBuildFile(upgrade, buildFileContents, matcher.start(1), matcher.end(1));
return this.buildFile; return this.buildFile;
} }
} }
@ -63,15 +69,15 @@ class UpgradeApplicator {
private void updateGradleProperties(Upgrade upgrade, String version) throws IOException { private void updateGradleProperties(Upgrade upgrade, String version) throws IOException {
String property = version.substring(2, version.length() - 1); String property = version.substring(2, version.length() - 1);
String gradlePropertiesContents = new String(Files.readAllBytes(this.gradleProperties), StandardCharsets.UTF_8); String gradlePropertiesContents = new String(Files.readAllBytes(this.gradleProperties), StandardCharsets.UTF_8);
String modified = gradlePropertiesContents.replace(property + "=" + upgrade.getLibrary().getVersion(), String modified = gradlePropertiesContents.replace(
property + "=" + upgrade.getVersion()); property + "=" + upgrade.getLibrary().getVersion().getVersion(), property + "=" + upgrade.getVersion());
overwrite(this.gradleProperties, modified); overwrite(this.gradleProperties, modified);
} }
private void updateBuildFile(Upgrade upgrade, String buildFileContents) throws IOException { private void updateBuildFile(Upgrade upgrade, String buildFileContents, int versionStart, int versionEnd)
String modified = buildFileContents.replace( throws IOException {
"library(\"" + upgrade.getLibrary().getName() + "\", \"" + upgrade.getLibrary().getVersion() + "\")", String modified = buildFileContents.substring(0, versionStart) + upgrade.getVersion()
"library(\"" + upgrade.getLibrary().getName() + "\", \"" + upgrade.getVersion() + "\")"); + buildFileContents.substring(versionEnd);
overwrite(this.buildFile, modified); overwrite(this.buildFile, modified);
} }

@ -197,6 +197,34 @@ public class BomPluginIntegrationTests {
}); });
} }
// @Test
// void versionAlignmentIsVerified() throws IOException {
// try (PrintWriter out = new PrintWriter(new FileWriter(this.buildFile))) {
// out.println("plugins {");
// out.println(" id 'org.springframework.boot.bom'");
// out.println("}");
// out.println("bom {");
// out.println(" library('OAuth2 OIDC SDK', '8.36.1') {");
// out.println(" alignedWith('Spring Security') {");
// out.println(
// "
// source('https://github.com/spring-projects/spring-security/blob/${libraryVersion}/config/gradle/dependency-locks/optional.lockfile')");
// out.println(" pattern('com.nimbusds:oauth2-oidc-sdk:(.+)')");
// out.println(" }");
// out.println(" group('com.nimbusds') {");
// out.println(" modules = [");
// out.println(" 'oauth2-oidc-sdk'");
// out.println(" ]");
// out.println(" }");
// out.println(" }");
// out.println(" library('Spring Security', '5.4.7') {");
// out.println(" }");
// out.println("}");
// }
// System.out.println(runGradle(DeployedPlugin.GENERATE_POM_TASK_NAME,
// "-s").getOutput());
// }
private BuildResult runGradle(String... args) { private BuildResult runGradle(String... args) {
return GradleRunner.create().withDebug(true).withProjectDir(this.projectDir).withArguments(args) return GradleRunner.create().withDebug(true).withProjectDir(this.projectDir).withArguments(args)
.withPluginClasspath().build(); .withPluginClasspath().build();

@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.build.bom.Library; import org.springframework.boot.build.bom.Library;
import org.springframework.boot.build.bom.Library.LibraryVersion;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion; import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
@ -51,13 +52,28 @@ class UpgradeApplicatorTests {
String originalContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8); String originalContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
File gradleProperties = new File(this.temp, "gradle.properties"); File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()) new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade(
.apply(new Upgrade(new Library("ActiveMQ", DependencyVersion.parse("5.15.11"), null, null), new Library("ActiveMQ", new LibraryVersion(DependencyVersion.parse("5.15.11"), null), null, null, null),
DependencyVersion.parse("5.16"))); DependencyVersion.parse("5.16")));
String bomContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8); String bomContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
assertThat(bomContents.length()).isEqualTo(originalContents.length() - 3); assertThat(bomContents.length()).isEqualTo(originalContents.length() - 3);
} }
@Test
void whenUpgradeIsAppliedToLibraryWithAlignedVersionThenBomIsUpdated() throws IOException {
File bom = new File(this.temp, "bom.gradle");
FileCopyUtils.copy(new File("src/test/resources/bom.gradle"), bom);
String originalContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
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("OAuth2 OIDC SDK", new LibraryVersion(DependencyVersion.parse("8.36.1"), null),
null, null, null), DependencyVersion.parse("8.36.2")));
String bomContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8);
assertThat(bomContents.length()).isEqualTo(originalContents.length());
assertThat(bomContents).contains("version(\"8.36.2\")");
}
@Test @Test
void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdated() throws IOException { void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdated() throws IOException {
File bom = new File(this.temp, "bom.gradle"); File bom = new File(this.temp, "bom.gradle");
@ -65,7 +81,8 @@ class UpgradeApplicatorTests {
File gradleProperties = new File(this.temp, "gradle.properties"); File gradleProperties = new File(this.temp, "gradle.properties");
FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties); FileCopyUtils.copy(new File("src/test/resources/gradle.properties"), gradleProperties);
new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade( new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade(
new Library("Kotlin", DependencyVersion.parse("1.3.70"), null, null), DependencyVersion.parse("1.4"))); new Library("Kotlin", new LibraryVersion(DependencyVersion.parse("1.3.70"), null), null, null, null),
DependencyVersion.parse("1.4")));
Properties properties = new Properties(); Properties properties = new Properties();
try (InputStream in = new FileInputStream(gradleProperties)) { try (InputStream in = new FileInputStream(gradleProperties)) {
properties.load(in); properties.load(in);

@ -48,4 +48,14 @@ bom {
] ]
} }
} }
library("OAuth2 OIDC SDK") {
version("8.36.1") {
shouldAlignWithVersionFrom("Spring Security")
}
group("com.nimbusds") {
modules = [
"oauth2-oidc-sdk"
]
}
}
} }

Loading…
Cancel
Save