Use ResolvedBom for bom checks
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:22], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
Details
Closes gh-44897
This commit is contained in:
parent
95d89fba94
commit
46a30e98bb
|
@ -65,6 +65,8 @@ public class BomPlugin implements Plugin<Project> {
|
|||
TaskProvider<CreateResolvedBom> createResolvedBom = project.getTasks()
|
||||
.register("createResolvedBom", CreateResolvedBom.class, bom);
|
||||
TaskProvider<CheckBom> checkBom = project.getTasks().register("bomrCheck", CheckBom.class, bom);
|
||||
checkBom.configure(
|
||||
(task) -> task.getResolvedBomFile().set(createResolvedBom.flatMap(CreateResolvedBom::getOutputFile)));
|
||||
project.getTasks().named("check").configure((check) -> check.dependsOn(checkBom));
|
||||
project.getTasks().register("bomrUpgrade", UpgradeBom.class, bom);
|
||||
project.getTasks().register("moveToSnapshots", MoveToSnapshots.class, bom);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,9 +16,9 @@
|
|||
|
||||
package org.springframework.boot.build.bom;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -32,15 +32,22 @@ import org.apache.maven.artifact.versioning.VersionRange;
|
|||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.ResolvedArtifact;
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||
import org.gradle.api.file.RegularFile;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import org.springframework.boot.build.bom.Library.Group;
|
||||
import org.springframework.boot.build.bom.Library.Module;
|
||||
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
|
||||
import org.springframework.boot.build.bom.Library.VersionAlignment;
|
||||
import org.springframework.boot.build.bom.ManagedDependencies.Difference;
|
||||
import org.springframework.boot.build.bom.ResolvedBom.Bom;
|
||||
import org.springframework.boot.build.bom.ResolvedBom.Id;
|
||||
import org.springframework.boot.build.bom.ResolvedBom.ResolvedLibrary;
|
||||
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
||||
|
||||
/**
|
||||
|
@ -51,19 +58,29 @@ import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
|
|||
*/
|
||||
public abstract class CheckBom extends DefaultTask {
|
||||
|
||||
private final Provider<ResolvedBom> resolvedBom;
|
||||
|
||||
private final ConfigurationContainer configurations;
|
||||
|
||||
private final DependencyHandler dependencies;
|
||||
|
||||
private final BomExtension bom;
|
||||
|
||||
private final BomResolver bomResolver;
|
||||
|
||||
@Inject
|
||||
public CheckBom(BomExtension bom) {
|
||||
this.bom = bom;
|
||||
this.configurations = getProject().getConfigurations();
|
||||
this.dependencies = getProject().getDependencies();
|
||||
this.bom = bom;
|
||||
this.resolvedBom = getResolvedBomFile().map(RegularFile::getAsFile).map(ResolvedBom::readFrom);
|
||||
this.bomResolver = new BomResolver(this.configurations, this.dependencies);
|
||||
}
|
||||
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract RegularFileProperty getResolvedBomFile();
|
||||
|
||||
@TaskAction
|
||||
void checkBom() {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
@ -191,35 +208,52 @@ public abstract class CheckBom extends DefaultTask {
|
|||
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);
|
||||
Bom mavenBom = this.bomResolver.resolveMavenBom(alignsWithBom + ":" + library.getVersion().getVersion());
|
||||
ResolvedBom resolvedBom = this.resolvedBom.get();
|
||||
Optional<ResolvedLibrary> resolvedLibrary = resolvedBom.libraries()
|
||||
.stream()
|
||||
.filter((candidate) -> candidate.name().equals(library.getName()))
|
||||
.findFirst();
|
||||
if (!resolvedLibrary.isPresent()) {
|
||||
throw new RuntimeException("Library '%s' not found in resolved bom".formatted(library.getName()));
|
||||
}
|
||||
checkDependencyManagementAlignment(resolvedLibrary.get(), mavenBom, errors);
|
||||
}
|
||||
|
||||
private File resolveBom(Library library, String alignsWithBom) {
|
||||
String coordinates = alignsWithBom + ":" + library.getVersion().getVersion() + "@pom";
|
||||
Set<ResolvedArtifact> artifacts = this.configurations
|
||||
.detachedConfiguration(this.dependencies.create(coordinates))
|
||||
.getResolvedConfiguration()
|
||||
.getResolvedArtifacts();
|
||||
if (artifacts.size() != 1) {
|
||||
throw new IllegalStateException("Expected a single file but '%s' resolved to %d artifacts"
|
||||
.formatted(coordinates, artifacts.size()));
|
||||
private void checkDependencyManagementAlignment(ResolvedLibrary library, Bom mavenBom, List<String> errors) {
|
||||
List<Id> managedByLibrary = library.managedDependencies();
|
||||
List<Id> managedByBom = managedDependenciesOf(mavenBom);
|
||||
|
||||
List<Id> missing = new ArrayList<>(managedByBom);
|
||||
missing.removeAll(managedByLibrary);
|
||||
|
||||
List<Id> unexpected = new ArrayList<>(managedByLibrary);
|
||||
unexpected.removeAll(managedByBom);
|
||||
if (missing.isEmpty() && unexpected.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
return artifacts.iterator().next().getFile();
|
||||
String error = "Dependency management does not align with " + mavenBom.id() + ":";
|
||||
if (!missing.isEmpty()) {
|
||||
error = error + "%n - Missing:%n %s".formatted(String.join("\n ",
|
||||
missing.stream().map((dependency) -> dependency.toString()).toList()));
|
||||
}
|
||||
if (!unexpected.isEmpty()) {
|
||||
error = error + "%n - Unexpected:%n %s".formatted(String.join("\n ",
|
||||
unexpected.stream().map((dependency) -> dependency.toString()).toList()));
|
||||
}
|
||||
errors.add(error);
|
||||
}
|
||||
|
||||
private List<Id> managedDependenciesOf(Bom mavenBom) {
|
||||
List<Id> managedDependencies = new ArrayList<>();
|
||||
managedDependencies.addAll(mavenBom.managedDependencies());
|
||||
if (mavenBom.parent() != null) {
|
||||
managedDependencies.addAll(managedDependenciesOf(mavenBom.parent()));
|
||||
}
|
||||
for (Bom importedBom : mavenBom.importedBoms()) {
|
||||
managedDependencies.addAll(managedDependenciesOf(importedBom));
|
||||
}
|
||||
return managedDependencies;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2025 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
|
@ -40,6 +40,7 @@ public abstract class CreateResolvedBom extends DefaultTask {
|
|||
|
||||
@Inject
|
||||
public CreateResolvedBom(BomExtension bomExtension) {
|
||||
getOutputs().upToDateWhen((spec) -> false);
|
||||
this.bomExtension = bomExtension;
|
||||
this.bomResolver = new BomResolver(getProject().getConfigurations(), getProject().getDependencies());
|
||||
getOutputFile().convention(getProject().getLayout().getBuildDirectory().file(getName() + "/resolved-bom.json"));
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue