Skip to content

Commit

Permalink
Merge branch '3.2.x'
Browse files Browse the repository at this point in the history
Closes gh-40533
  • Loading branch information
wilkinsona committed Apr 26, 2024
2 parents a12e3d4 + eb7e7b6 commit 8221de2
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 29 deletions.
Expand Up @@ -115,16 +115,18 @@ public void library(String name, Action<LibraryHandler> action) {

public void library(String name, String version, Action<LibraryHandler> action) {
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);
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)
VersionAlignment versionAlignment = (libraryHandler.alignWith.version != null)
? new VersionAlignment(libraryHandler.alignWith.version.from,
libraryHandler.alignWith.version.managedBy, this.project, this.libraries, libraryHandler.groups)
: null;
addLibrary(new Library(name, libraryHandler.calendarName, libraryVersion, libraryHandler.groups,
libraryHandler.prohibitedVersions, libraryHandler.considerSnapshots, versionAlignment,
libraryHandler.linkRootName, libraryHandler.links));
libraryHandler.alignWith.dependencyManagementDeclaredIn, libraryHandler.linkRootName,
libraryHandler.links));
}

public void effectiveBomArtifact() {
Expand Down Expand Up @@ -224,21 +226,22 @@ public static class LibraryHandler {

private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();

private final AlignWithHandler alignWith;

private boolean considerSnapshots = false;

private String version;

private String calendarName;

private AlignWithVersionHandler alignWithVersion;

private String linkRootName;

private final Map<String, Function<LibraryVersion, String>> links = new HashMap<>();

@Inject
public LibraryHandler(String version) {
public LibraryHandler(Project project, String version) {
this.version = version;
this.alignWith = project.getObjects().newInstance(AlignWithHandler.class);
}

public void version(String version) {
Expand Down Expand Up @@ -267,9 +270,8 @@ public void prohibit(Action<ProhibitedHandler> action) {
handler.endsWith, handler.contains, handler.reason));
}

public void alignWithVersion(Action<AlignWithVersionHandler> action) {
this.alignWithVersion = new AlignWithVersionHandler();
action.execute(this.alignWithVersion);
public void alignWith(Action<AlignWithHandler> action) {
action.execute(this.alignWith);
}

public void links(Action<LinksHandler> action) {
Expand Down Expand Up @@ -400,18 +402,35 @@ public void setClassifier(String classifier) {

}

public static class AlignWithVersionHandler {
public static class AlignWithHandler {

private VersionHandler version;

private String from;
private String dependencyManagementDeclaredIn;

private String managedBy;
public void version(Action<VersionHandler> action) {
this.version = new VersionHandler();
action.execute(this.version);
}

public void from(String from) {
this.from = from;
public void dependencyManagementDeclaredIn(String bomCoordinates) {
this.dependencyManagementDeclaredIn = bomCoordinates;
}

public void managedBy(String managedBy) {
this.managedBy = managedBy;
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;
}

}

}
Expand Down
Expand Up @@ -16,6 +16,7 @@

package org.springframework.boot.build.bom;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
Expand All @@ -36,6 +37,7 @@
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.ManagedDependencies.Difference;
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;

/**
Expand Down Expand Up @@ -71,6 +73,7 @@ private void checkLibrary(Library library, List<String> errors) {
checkExclusions(library, libraryErrors);
checkProhibitedVersions(library, libraryErrors);
checkVersionAlignment(library, libraryErrors);
checkDependencyManagementAlignment(library, libraryErrors);
if (!libraryErrors.isEmpty()) {
errors.add(library.getName());
for (String libraryError : libraryErrors) {
Expand Down Expand Up @@ -174,4 +177,40 @@ private void checkVersionAlignment(Library library, List<String> errors) {
}
}

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();
}

}
Expand Up @@ -62,6 +62,8 @@ public class Library {

private final VersionAlignment versionAlignment;

private final String alignsWithBom;

private final String linkRootName;

private final Map<String, Function<LibraryVersion, String>> links;
Expand All @@ -77,13 +79,15 @@ public class 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
* @param alignsWithBom the coordinates of the bom, if any, that this library should
* align with
* @param linkRootName the root name to use when generating link variable or
* {@code null} to generate one based on the library {@code name}
* @param links a list of HTTP links relevant to the library
*/
public Library(String name, String calendarName, LibraryVersion version, List<Group> groups,
List<ProhibitedVersion> prohibitedVersions, boolean considerSnapshots, VersionAlignment versionAlignment,
String linkRootName, Map<String, Function<LibraryVersion, String>> links) {
String alignsWithBom, String linkRootName, Map<String, Function<LibraryVersion, String>> links) {
this.name = name;
this.calendarName = (calendarName != null) ? calendarName : name;
this.version = version;
Expand All @@ -93,6 +97,7 @@ public Library(String name, String calendarName, LibraryVersion version, List<Gr
this.prohibitedVersions = prohibitedVersions;
this.considerSnapshots = considerSnapshots;
this.versionAlignment = versionAlignment;
this.alignsWithBom = alignsWithBom;
this.linkRootName = (linkRootName != null) ? linkRootName : generateLinkRootName(name);
this.links = Collections.unmodifiableMap(links);
}
Expand Down Expand Up @@ -137,6 +142,10 @@ public String getLinkRootName() {
return this.linkRootName;
}

public String getAlignsWithBom() {
return this.alignsWithBom;
}

public Map<String, String> getLinks() {
Map<String, String> links = new TreeMap<>();
this.links.forEach((name, linkFactory) -> links.put(name, linkFactory.apply(this.version)));
Expand Down
@@ -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();
}

}

}
Expand Up @@ -144,9 +144,10 @@ private Library mockLibrary(Map<String, Function<LibraryVersion, String>> links)
List<ProhibitedVersion> prohibitedVersion = Collections.emptyList();
boolean considerSnapshots = false;
VersionAlignment versionAlignment = null;
String alignsWithBom = null;
String linkRootName = null;
Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots,
versionAlignment, linkRootName, links);
versionAlignment, alignsWithBom, linkRootName, links);
return library;
}

Expand Down
Expand Up @@ -47,10 +47,11 @@ void getLinkRootNameWhenNoneSpecified() {
List<ProhibitedVersion> prohibitedVersion = Collections.emptyList();
boolean considerSnapshots = false;
VersionAlignment versionAlignment = null;
String alignsWithBom = null;
String linkRootName = null;
Map<String, Function<LibraryVersion, String>> links = Collections.emptyMap();
Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots,
versionAlignment, linkRootName, links);
versionAlignment, alignsWithBom, linkRootName, links);
assertThat(library.getLinkRootName()).isEqualTo("spring-framework");
}

Expand All @@ -63,10 +64,11 @@ void getLinkRootNameWhenSpecified() {
List<ProhibitedVersion> prohibitedVersion = Collections.emptyList();
boolean considerSnapshots = false;
VersionAlignment versionAlignment = null;
String alignsWithBom = null;
String linkRootName = "spring-data";
Map<String, Function<LibraryVersion, String>> links = Collections.emptyMap();
Library library = new Library(name, calendarName, version, groups, prohibitedVersion, considerSnapshots,
versionAlignment, linkRootName, links);
versionAlignment, alignsWithBom, linkRootName, links);
assertThat(library.getLinkRootName()).isEqualTo("spring-data");
}

Expand Down

0 comments on commit 8221de2

Please sign in to comment.