From 363de4cfa5b58776f980f1feec2eff790b7d80fc Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 23 Jun 2021 17:07:55 +0100 Subject: [PATCH] Add dependency lock and constraint version alignment to Bomr Closes gh-27044 --- buildSrc/build.gradle | 1 + .../boot/build/bom/BomExtension.java | 79 +++++++- .../boot/build/bom/CheckBom.java | 2 +- .../boot/build/bom/Library.java | 170 +++++++++++++++++- .../bom/bomr/InteractiveUpgradeResolver.java | 124 +++++++++++-- .../build/bom/bomr/UpgradeApplicator.java | 24 ++- .../build/bom/BomPluginIntegrationTests.java | 28 +++ .../bom/bomr/UpgradeApplicatorTests.java | 25 ++- buildSrc/src/test/resources/bom.gradle | 10 ++ 9 files changed, 421 insertions(+), 42 deletions(-) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index b1fd89786a..624ef0a16c 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -29,6 +29,7 @@ dependencies { testImplementation("org.assertj:assertj-core:3.11.1") testImplementation("org.apache.logging.log4j:log4j-core:2.12.1") testImplementation("org.junit.jupiter:junit-jupiter:5.6.0") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.6.3") } checkstyle { 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 95f87bd498..b2370b7109 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 @@ -56,10 +56,15 @@ import org.w3c.dom.Document; import org.w3c.dom.NodeList; 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.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; @@ -101,11 +106,17 @@ public class BomExtension { this.upgradeHandler.gitHub.repository, this.upgradeHandler.gitHub.issueLabels)); } + public void library(String name, Closure closure) { + this.library(name, null, closure); + } + public void library(String name, String version, Closure closure) { - LibraryHandler libraryHandler = new LibraryHandler(); + LibraryHandler libraryHandler = new LibraryHandler(version); ConfigureUtil.configure(closure, libraryHandler); - addLibrary(new Library(name, DependencyVersion.parse(version), libraryHandler.groups, - libraryHandler.prohibitedVersions)); + LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version), + libraryHandler.versionAlignment); + addLibrary(new Library(name, libraryVersion, libraryHandler.groups, libraryHandler.prohibitedVersions, + libraryHandler.dependencyVersions)); } public void effectiveBomArtifact() { @@ -171,17 +182,18 @@ public class BomExtension { this.libraries.add(library); String versionProperty = library.getVersionProperty(); if (versionProperty != null) { - this.properties.put(versionProperty, library.getVersion()); + this.properties.put(versionProperty, library.getVersion().getVersion()); } for (Group group : library.getGroups()) { for (Module module : group.getModules()) { putArtifactVersionProperty(group.getId(), module.getName(), versionProperty); 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()) { 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.platform(bomDependency)); this.dependencyHandler.add(BomPlugin.API_ENFORCED_CONFIGURATION_NAME, @@ -196,6 +208,23 @@ public class BomExtension { private final List prohibitedVersions = new ArrayList<>(); + private String version; + + private VersionAlignment versionAlignment; + + private DependencyVersions dependencyVersions; + + public LibraryHandler(String version) { + this.version = version; + } + + public void version(String version, Closure closure) { + this.version = version; + VersionHandler versionHandler = new VersionHandler(); + ConfigureUtil.configure(closure, versionHandler); + this.versionAlignment = new VersionAlignment(versionHandler.libraryName); + } + public void group(String id, Closure closure) { GroupHandler groupHandler = new GroupHandler(id); ConfigureUtil.configure(closure, groupHandler); @@ -215,6 +244,21 @@ public class BomExtension { } } + public void dependencyVersions(Closure closure) { + DependencyVersionsHandler dependencyVersionsHandler = new DependencyVersionsHandler(); + ConfigureUtil.configure(closure, dependencyVersionsHandler); + } + + public static class VersionHandler { + + private String libraryName; + + public void shouldAlignWithVersionFrom(String libraryName) { + this.libraryName = libraryName; + } + + } + public static class ProhibitedVersionHandler { private String reason; @@ -277,6 +321,29 @@ public class BomExtension { } + public class DependencyVersionsHandler { + + public void extractFrom(Closure closure) { + ExtractFromHandler extractFromHandler = new ExtractFromHandler(); + ConfigureUtil.configure(closure, extractFromHandler); + } + + public class ExtractFromHandler { + + public void dependencyLock(String location) { + LibraryHandler.this.dependencyVersions = new DependencyLockDependencyVersions(location, + LibraryHandler.this.version); + } + + public void dependencyConstraints(String location) { + LibraryHandler.this.dependencyVersions = new DependencyConstraintsDependencyVersions(location, + LibraryHandler.this.version); + } + + } + + } + } public static class UpgradeHandler { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java index 876fe8eada..2c1cc19362 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java @@ -50,7 +50,7 @@ public class CheckBom extends DefaultTask { for (Group group : library.getGroups()) { for (Module module : group.getModules()) { if (!module.getExclusions().isEmpty()) { - checkExclusions(group.getId(), module, library.getVersion()); + checkExclusions(group.getId(), module, library.getVersion().getVersion()); } } } 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 80b510e2ad..b14afda0e8 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,11 +16,20 @@ 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.HashMap; import java.util.List; 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.gradle.api.GradleException; import org.springframework.boot.build.bom.bomr.version.DependencyVersion; @@ -34,7 +43,7 @@ public class Library { private final String name; - private final DependencyVersion version; + private final LibraryVersion version; private final List groups; @@ -42,6 +51,8 @@ public class Library { private final List prohibitedVersions; + private final DependencyVersions dependencyVersions; + /** * Create a new {@code Library} with the given {@code name}, {@code version}, and * {@code groups}. @@ -49,22 +60,24 @@ public class Library { * @param version version of the library * @param groups groups in the library * @param prohibitedVersions version of the library that are prohibited + * @param dependencyVersions the library's dependency versions */ - public Library(String name, DependencyVersion version, List groups, - List prohibitedVersions) { + public Library(String name, LibraryVersion version, List groups, List prohibitedVersions, + DependencyVersions dependencyVersions) { this.name = name; this.version = version; this.groups = groups; this.versionProperty = "Spring Boot".equals(name) ? null : name.toLowerCase(Locale.ENGLISH).replace(' ', '-') + ".version"; this.prohibitedVersions = prohibitedVersions; + this.dependencyVersions = dependencyVersions; } public String getName() { return this.name; } - public DependencyVersion getVersion() { + public LibraryVersion getVersion() { return this.version; } @@ -80,6 +93,10 @@ public class Library { return this.prohibitedVersions; } + public DependencyVersions getDependencyVersions() { + return this.dependencyVersions; + } + /** * 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. */ @@ -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> 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("", 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 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> 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("", 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 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; + } + + } + } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java index ff71a617cd..6c943272e6 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/InteractiveUpgradeResolver.java @@ -19,19 +19,25 @@ package org.springframework.boot.build.bom.bomr; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedSet; import java.util.stream.Collectors; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.gradle.api.InvalidUserDataException; import org.gradle.api.internal.tasks.userinput.UserInputHandler; 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.Module; 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.bomr.version.DependencyVersion; import org.springframework.util.StringUtils; @@ -59,39 +65,91 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver { @Override public List resolveUpgrades(Collection libraries) { - return libraries.stream().map(this::resolveUpgrade).filter((upgrade) -> upgrade != null) - .collect(Collectors.toList()); + Map librariesByName = new HashMap<>(); + 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) { + private Upgrade resolveUpgrade(Library library, Map libraries) { + List 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 List getVersionOptions(Library library, Map 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> 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(), library.getVersion())); + getLaterVersionsForModule(group.getId(), module.getName(), libraryVersion)); } for (String bom : group.getBoms()) { moduleVersions.put(group.getId() + ":" + bom, - getLaterVersionsForModule(group.getId(), bom, library.getVersion())); + getLaterVersionsForModule(group.getId(), bom, libraryVersion)); } for (String plugin : group.getPlugins()) { moduleVersions.put(group.getId() + ":" + plugin, - getLaterVersionsForModule(group.getId(), plugin, library.getVersion())); + getLaterVersionsForModule(group.getId(), plugin, libraryVersion)); } } List allVersions = moduleVersions.values().stream().flatMap(SortedSet::stream).distinct() .filter((dependencyVersion) -> isPermitted(dependencyVersion, library.getProhibitedVersions())) .collect(Collectors.toList()); if (allVersions.isEmpty()) { - return null; + return Collections.emptyList(); } - List versionOptions = allVersions.stream() - .map((version) -> new VersionOption(version, getMissingModules(moduleVersions, version))) + return allVersions.stream() + .map((version) -> new ResolvedVersionOption(version, getMissingModules(moduleVersions, version))) .collect(Collectors.toList()); - VersionOption current = new VersionOption(library.getVersion(), Collections.emptyList()); - VersionOption selected = this.userInputHandler.selectOption(library.getName() + " " + library.getVersion(), - versionOptions, current); - return (selected.equals(current)) ? null : new Upgrade(library, selected.version); + } + + private VersionOption alignedVersionOption(Library library, Map libraries) { + 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 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 prohibitedVersions) { @@ -125,23 +183,53 @@ public final class InteractiveUpgradeResolver implements UpgradeResolver { return versions; } - private static final class VersionOption { + private static class VersionOption { 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 missingModules; - private VersionOption(DependencyVersion version, List missingModules) { - this.version = version; + private ResolvedVersionOption(DependencyVersion version, List missingModules) { + super(version); this.missingModules = missingModules; } @Override public String toString() { 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, ", ") + ")"; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java index 2f47151f18..1588ed7b5b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/UpgradeApplicator.java @@ -46,8 +46,14 @@ class UpgradeApplicator { Matcher matcher = Pattern.compile("library\\(\"" + upgrade.getLibrary().getName() + "\", \"(.+)\"\\)") .matcher(buildFileContents); if (!matcher.find()) { - throw new IllegalStateException("Failed to find definition for library '" + upgrade.getLibrary().getName() - + "' in bom '" + this.buildFile + "'"); + matcher = Pattern + .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); if (version.startsWith("${") && version.endsWith("}")) { @@ -55,7 +61,7 @@ class UpgradeApplicator { return this.gradleProperties; } else { - updateBuildFile(upgrade, buildFileContents); + updateBuildFile(upgrade, buildFileContents, matcher.start(1), matcher.end(1)); return this.buildFile; } } @@ -63,15 +69,15 @@ class UpgradeApplicator { private void updateGradleProperties(Upgrade upgrade, String version) throws IOException { String property = version.substring(2, version.length() - 1); String gradlePropertiesContents = new String(Files.readAllBytes(this.gradleProperties), StandardCharsets.UTF_8); - String modified = gradlePropertiesContents.replace(property + "=" + upgrade.getLibrary().getVersion(), - property + "=" + upgrade.getVersion()); + String modified = gradlePropertiesContents.replace( + property + "=" + upgrade.getLibrary().getVersion().getVersion(), property + "=" + upgrade.getVersion()); overwrite(this.gradleProperties, modified); } - private void updateBuildFile(Upgrade upgrade, String buildFileContents) throws IOException { - String modified = buildFileContents.replace( - "library(\"" + upgrade.getLibrary().getName() + "\", \"" + upgrade.getLibrary().getVersion() + "\")", - "library(\"" + upgrade.getLibrary().getName() + "\", \"" + upgrade.getVersion() + "\")"); + private void updateBuildFile(Upgrade upgrade, String buildFileContents, int versionStart, int versionEnd) + throws IOException { + String modified = buildFileContents.substring(0, versionStart) + upgrade.getVersion() + + buildFileContents.substring(versionEnd); overwrite(this.buildFile, modified); } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java index 42f96bb074..66ad572783 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/BomPluginIntegrationTests.java @@ -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) { return GradleRunner.create().withDebug(true).withProjectDir(this.projectDir).withArguments(args) .withPluginClasspath().build(); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java index aef6b32ff5..c1c2c9c85d 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/bom/bomr/UpgradeApplicatorTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; 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.util.FileCopyUtils; @@ -51,13 +52,28 @@ class UpgradeApplicatorTests { 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("ActiveMQ", DependencyVersion.parse("5.15.11"), null, null), - DependencyVersion.parse("5.16"))); + new UpgradeApplicator(bom.toPath(), gradleProperties.toPath()).apply(new Upgrade( + new Library("ActiveMQ", new LibraryVersion(DependencyVersion.parse("5.15.11"), null), null, null, null), + DependencyVersion.parse("5.16"))); String bomContents = new String(Files.readAllBytes(bom.toPath()), StandardCharsets.UTF_8); 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 void whenUpgradeIsAppliedToLibraryWithVersionPropertyThenGradlePropertiesIsUpdated() throws IOException { File bom = new File(this.temp, "bom.gradle"); @@ -65,7 +81,8 @@ 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", 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(); try (InputStream in = new FileInputStream(gradleProperties)) { properties.load(in); diff --git a/buildSrc/src/test/resources/bom.gradle b/buildSrc/src/test/resources/bom.gradle index 1d36dc42dc..03d7288b15 100644 --- a/buildSrc/src/test/resources/bom.gradle +++ b/buildSrc/src/test/resources/bom.gradle @@ -48,4 +48,14 @@ bom { ] } } + library("OAuth2 OIDC SDK") { + version("8.36.1") { + shouldAlignWithVersionFrom("Spring Security") + } + group("com.nimbusds") { + modules = [ + "oauth2-oidc-sdk" + ] + } + } }