Add dependency management alignment infrastructure
Closes gh-40531
This commit is contained in:
parent
6c3a21f85c
commit
d2a5bb3b26
|
@ -111,15 +111,17 @@ public class BomExtension {
|
||||||
|
|
||||||
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, (version != null) ? version : "");
|
LibraryHandler libraryHandler = objects.newInstance(LibraryHandler.class, this.project,
|
||||||
|
(version != null) ? version : "");
|
||||||
action.execute(libraryHandler);
|
action.execute(libraryHandler);
|
||||||
LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version));
|
LibraryVersion libraryVersion = new LibraryVersion(DependencyVersion.parse(libraryHandler.version));
|
||||||
VersionAlignment versionAlignment = (libraryHandler.alignWithVersion != null)
|
VersionAlignment versionAlignment = (libraryHandler.alignWith.version != null)
|
||||||
? new VersionAlignment(libraryHandler.alignWithVersion.from, libraryHandler.alignWithVersion.managedBy,
|
? new VersionAlignment(libraryHandler.alignWith.version.from,
|
||||||
this.project, this.libraries, libraryHandler.groups)
|
libraryHandler.alignWith.version.managedBy, this.project, this.libraries, libraryHandler.groups)
|
||||||
: null;
|
: null;
|
||||||
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
|
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
|
||||||
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment));
|
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment,
|
||||||
|
libraryHandler.alignWith.dependencyManagementDeclaredIn));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void effectiveBomArtifact() {
|
public void effectiveBomArtifact() {
|
||||||
|
@ -219,17 +221,18 @@ public class BomExtension {
|
||||||
|
|
||||||
private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();
|
private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();
|
||||||
|
|
||||||
|
private final AlignWithHandler alignWith;
|
||||||
|
|
||||||
private boolean considerSnapshots = false;
|
private boolean considerSnapshots = false;
|
||||||
|
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
private String calendarName;
|
private String calendarName;
|
||||||
|
|
||||||
private AlignWithVersionHandler alignWithVersion;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LibraryHandler(String version) {
|
public LibraryHandler(Project project, String version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
this.alignWith = project.getObjects().newInstance(AlignWithHandler.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void version(String version) {
|
public void version(String version) {
|
||||||
|
@ -258,9 +261,8 @@ public class BomExtension {
|
||||||
handler.endsWith, handler.contains, handler.reason));
|
handler.endsWith, handler.contains, handler.reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void alignWithVersion(Action<AlignWithVersionHandler> action) {
|
public void alignWith(Action<AlignWithHandler> action) {
|
||||||
this.alignWithVersion = new AlignWithVersionHandler();
|
action.execute(this.alignWith);
|
||||||
action.execute(this.alignWithVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ProhibitedHandler {
|
public static class ProhibitedHandler {
|
||||||
|
@ -380,18 +382,35 @@ public class BomExtension {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AlignWithVersionHandler {
|
public static class AlignWithHandler {
|
||||||
|
|
||||||
private String from;
|
private VersionHandler version;
|
||||||
|
|
||||||
private String managedBy;
|
private String dependencyManagementDeclaredIn;
|
||||||
|
|
||||||
public void from(String from) {
|
public void version(Action<VersionHandler> action) {
|
||||||
this.from = from;
|
this.version = new VersionHandler();
|
||||||
|
action.execute(this.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void managedBy(String managedBy) {
|
public void dependencyManagementDeclaredIn(String bomCoordinates) {
|
||||||
this.managedBy = managedBy;
|
this.dependencyManagementDeclaredIn = bomCoordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VersionHandler {
|
||||||
|
|
||||||
|
private String from;
|
||||||
|
|
||||||
|
private String managedBy;
|
||||||
|
|
||||||
|
public void from(String from) {
|
||||||
|
this.from = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void managedBy(String managedBy) {
|
||||||
|
this.managedBy = managedBy;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.build.bom;
|
package org.springframework.boot.build.bom;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -36,6 +37,7 @@ 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.Library.VersionAlignment;
|
||||||
|
import org.springframework.boot.build.bom.ManagedDependencies.Difference;
|
||||||
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +73,7 @@ public class CheckBom extends DefaultTask {
|
||||||
checkExclusions(library, libraryErrors);
|
checkExclusions(library, libraryErrors);
|
||||||
checkProhibitedVersions(library, libraryErrors);
|
checkProhibitedVersions(library, libraryErrors);
|
||||||
checkVersionAlignment(library, libraryErrors);
|
checkVersionAlignment(library, libraryErrors);
|
||||||
|
checkDependencyManagementAlignment(library, libraryErrors);
|
||||||
if (!libraryErrors.isEmpty()) {
|
if (!libraryErrors.isEmpty()) {
|
||||||
errors.add(library.getName());
|
errors.add(library.getName());
|
||||||
for (String libraryError : libraryErrors) {
|
for (String libraryError : libraryErrors) {
|
||||||
|
@ -174,4 +177,40 @@ public class CheckBom extends DefaultTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkDependencyManagementAlignment(Library library, List<String> errors) {
|
||||||
|
String alignsWithBom = library.getAlignsWithBom();
|
||||||
|
if (alignsWithBom == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File bom = resolveBom(library, alignsWithBom);
|
||||||
|
ManagedDependencies managedByBom = ManagedDependencies.ofBom(bom);
|
||||||
|
ManagedDependencies managedByLibrary = ManagedDependencies.ofLibrary(library);
|
||||||
|
Difference diff = managedByBom.diff(managedByLibrary);
|
||||||
|
if (!diff.isEmpty()) {
|
||||||
|
String error = "Dependency management does not align with " + library.getAlignsWithBom() + ":";
|
||||||
|
if (!diff.missing().isEmpty()) {
|
||||||
|
error = error + "%n - Missing:%n %s"
|
||||||
|
.formatted(String.join("\n ", diff.missing()));
|
||||||
|
}
|
||||||
|
if (!diff.unexpected().isEmpty()) {
|
||||||
|
error = error + "%n - Unexpected:%n %s"
|
||||||
|
.formatted(String.join("\n ", diff.unexpected()));
|
||||||
|
}
|
||||||
|
errors.add(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File resolveBom(Library library, String alignsWithBom) {
|
||||||
|
String coordinates = alignsWithBom + ":" + library.getVersion().getVersion() + "@pom";
|
||||||
|
Set<File> files = getProject().getConfigurations()
|
||||||
|
.detachedConfiguration(getProject().getDependencies().create(coordinates))
|
||||||
|
.getResolvedConfiguration()
|
||||||
|
.getFiles();
|
||||||
|
if (files.size() != 1) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Expected a single file but '" + coordinates + "' resolved to " + files.size());
|
||||||
|
}
|
||||||
|
return files.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ public class Library {
|
||||||
|
|
||||||
private final VersionAlignment versionAlignment;
|
private final VersionAlignment versionAlignment;
|
||||||
|
|
||||||
|
private final String alignsWithBom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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}.
|
||||||
|
@ -70,9 +72,12 @@ public class Library {
|
||||||
* @param prohibitedVersions version of the library that are prohibited
|
* @param prohibitedVersions version of the library that are prohibited
|
||||||
* @param considerSnapshots whether to consider snapshots
|
* @param considerSnapshots whether to consider snapshots
|
||||||
* @param versionAlignment version alignment, if any, for the library
|
* @param versionAlignment version alignment, if any, for the library
|
||||||
|
* @param alignsWithBom the coordinates of the bom, if any, that this library should
|
||||||
|
* inline
|
||||||
*/
|
*/
|
||||||
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
|
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
|
||||||
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment) {
|
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment,
|
||||||
|
String alignsWithBom) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.calendarName = (calendarName != null) ? calendarName : name;
|
this.calendarName = (calendarName != null) ? calendarName : name;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
@ -82,6 +87,7 @@ public class Library {
|
||||||
this.prohibitedVersions = prohibitedVersions;
|
this.prohibitedVersions = prohibitedVersions;
|
||||||
this.considerSnapshots = considerSnapshots;
|
this.considerSnapshots = considerSnapshots;
|
||||||
this.versionAlignment = versionAlignment;
|
this.versionAlignment = versionAlignment;
|
||||||
|
this.alignsWithBom = alignsWithBom;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -116,6 +122,10 @@ public class Library {
|
||||||
return this.versionAlignment;
|
return this.versionAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAlignsWithBom() {
|
||||||
|
return this.alignsWithBom;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.xpath.XPath;
|
||||||
|
import javax.xml.xpath.XPathConstants;
|
||||||
|
import javax.xml.xpath.XPathFactory;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import org.springframework.boot.build.bom.Library.Group;
|
||||||
|
import org.springframework.boot.build.bom.Library.Module;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Managed dependencies from a bom or library.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class ManagedDependencies {
|
||||||
|
|
||||||
|
private final Set<String> ids;
|
||||||
|
|
||||||
|
ManagedDependencies(Set<String> ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> getIds() {
|
||||||
|
return this.ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
Difference diff(ManagedDependencies other) {
|
||||||
|
Set<String> missing = new HashSet<>(this.ids);
|
||||||
|
missing.removeAll(other.ids);
|
||||||
|
Set<String> unexpected = new HashSet<>(other.ids);
|
||||||
|
unexpected.removeAll(this.ids);
|
||||||
|
return new Difference(missing, unexpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ManagedDependencies ofBom(File bom) {
|
||||||
|
try {
|
||||||
|
Document bomDocument = DocumentBuilderFactory.newInstance()
|
||||||
|
.newDocumentBuilder()
|
||||||
|
.parse(new InputSource(new FileReader(bom)));
|
||||||
|
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||||
|
NodeList dependencyNodes = (NodeList) xpath
|
||||||
|
.evaluate("/project/dependencyManagement/dependencies/dependency", bomDocument, XPathConstants.NODESET);
|
||||||
|
NodeList propertyNodes = (NodeList) xpath.evaluate("/project/properties/*", bomDocument,
|
||||||
|
XPathConstants.NODESET);
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
for (int i = 0; i < propertyNodes.getLength(); i++) {
|
||||||
|
Node property = propertyNodes.item(i);
|
||||||
|
String name = property.getNodeName();
|
||||||
|
String value = property.getTextContent();
|
||||||
|
properties.put("${%s}".formatted(name), value);
|
||||||
|
}
|
||||||
|
Set<String> managedDependencies = new HashSet<>();
|
||||||
|
for (int i = 0; i < dependencyNodes.getLength(); i++) {
|
||||||
|
Node dependency = dependencyNodes.item(i);
|
||||||
|
String groupId = (String) xpath.evaluate("groupId/text()", dependency, XPathConstants.STRING);
|
||||||
|
String artifactId = (String) xpath.evaluate("artifactId/text()", dependency, XPathConstants.STRING);
|
||||||
|
String version = (String) xpath.evaluate("version/text()", dependency, XPathConstants.STRING);
|
||||||
|
String classifier = (String) xpath.evaluate("classifier/text()", dependency, XPathConstants.STRING);
|
||||||
|
if (version.startsWith("${") && version.endsWith("}")) {
|
||||||
|
version = properties.get(version);
|
||||||
|
}
|
||||||
|
managedDependencies.add(asId(groupId, artifactId, version, classifier));
|
||||||
|
}
|
||||||
|
return new ManagedDependencies(managedDependencies);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String asId(String groupId, String artifactId, String version, String classifier) {
|
||||||
|
String id = groupId + ":" + artifactId + ":" + version;
|
||||||
|
if (classifier != null && classifier.length() > 0) {
|
||||||
|
id = id + ":" + classifier;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ManagedDependencies ofLibrary(Library library) {
|
||||||
|
Set<String> managedByLibrary = new HashSet<>();
|
||||||
|
for (Group group : library.getGroups()) {
|
||||||
|
for (Module module : group.getModules()) {
|
||||||
|
managedByLibrary.add(asId(group.getId(), module.getName(), library.getVersion().getVersion().toString(),
|
||||||
|
module.getClassifier()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ManagedDependencies(managedByLibrary);
|
||||||
|
}
|
||||||
|
|
||||||
|
record Difference(Set<String> missing, Set<String> unexpected) {
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
return this.missing.isEmpty() && this.unexpected.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ class UpgradeApplicatorTests {
|
||||||
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(new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")),
|
.apply(new Upgrade(new Library("ActiveMQ", null, new LibraryVersion(DependencyVersion.parse("5.15.11")),
|
||||||
null, null, false, null), DependencyVersion.parse("5.16")));
|
null, null, false, null, null), DependencyVersion.parse("5.16")));
|
||||||
String bomContents = Files.readString(bom.toPath());
|
String bomContents = Files.readString(bom.toPath());
|
||||||
assertThat(bomContents).hasSize(originalContents.length() - 3);
|
assertThat(bomContents).hasSize(originalContents.length() - 3);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ class UpgradeApplicatorTests {
|
||||||
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(new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null,
|
.apply(new Upgrade(new Library("Kotlin", null, new LibraryVersion(DependencyVersion.parse("1.3.70")), null,
|
||||||
null, false, null), DependencyVersion.parse("1.4")));
|
null, false, 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);
|
||||||
|
|
|
@ -1051,9 +1051,11 @@ bom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
library("Neo4j Java Driver", "5.19.0") {
|
library("Neo4j Java Driver", "5.19.0") {
|
||||||
alignWithVersion {
|
alignWith {
|
||||||
from "org.springframework.data:spring-data-neo4j"
|
version {
|
||||||
managedBy "Spring Data Bom"
|
from "org.springframework.data:spring-data-neo4j"
|
||||||
|
managedBy "Spring Data Bom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
group("org.neo4j.driver") {
|
group("org.neo4j.driver") {
|
||||||
modules = [
|
modules = [
|
||||||
|
|
Loading…
Reference in New Issue