Skip to content

Commit 2f7d598

Browse files
committed
Check imported boms for unwanted dependency management
Closes gh-42523
1 parent 9cd6af9 commit 2f7d598

File tree

8 files changed

+279
-202
lines changed

8 files changed

+279
-202
lines changed

buildSrc/src/main/java/org/springframework/boot/build/bom/BomExtension.java

+30-7
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@
4040

4141
import org.springframework.boot.build.bom.Library.Exclusion;
4242
import org.springframework.boot.build.bom.Library.Group;
43+
import org.springframework.boot.build.bom.Library.ImportedBom;
4344
import org.springframework.boot.build.bom.Library.LibraryVersion;
4445
import org.springframework.boot.build.bom.Library.Link;
4546
import org.springframework.boot.build.bom.Library.Module;
47+
import org.springframework.boot.build.bom.Library.PermittedDependency;
4648
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
4749
import org.springframework.boot.build.bom.Library.VersionAlignment;
4850
import org.springframework.boot.build.bom.bomr.version.DependencyVersion;
@@ -152,8 +154,8 @@ private void addLibrary(Library library) {
152154
for (Module module : group.getModules()) {
153155
addModule(library, dependencies, versionProperty, group, module);
154156
}
155-
for (String bomImport : group.getBoms()) {
156-
addBomImport(library, dependencies, versionProperty, group, bomImport);
157+
for (ImportedBom bomImport : group.getBoms()) {
158+
addBomImport(library, dependencies, versionProperty, group, bomImport.name());
157159
}
158160
}
159161
}
@@ -176,6 +178,8 @@ private void addBomImport(Library library, DependencyHandler dependencies, Strin
176178

177179
public static class LibraryHandler {
178180

181+
private final Project project;
182+
179183
private final List<Group> groups = new ArrayList<>();
180184

181185
private final List<ProhibitedVersion> prohibitedVersions = new ArrayList<>();
@@ -194,6 +198,7 @@ public static class LibraryHandler {
194198

195199
@Inject
196200
public LibraryHandler(Project project, String version) {
201+
this.project = project;
197202
this.version = version;
198203
this.alignWith = project.getObjects().newInstance(AlignWithHandler.class);
199204
}
@@ -211,7 +216,7 @@ public void setCalendarName(String calendarName) {
211216
}
212217

213218
public void group(String id, Action<GroupHandler> action) {
214-
GroupHandler groupHandler = new GroupHandler(id);
219+
GroupHandler groupHandler = this.project.getObjects().newInstance(GroupHandler.class, id);
215220
action.execute(groupHandler);
216221
this.groups
217222
.add(new Group(groupHandler.id, groupHandler.modules, groupHandler.plugins, groupHandler.imports));
@@ -290,16 +295,17 @@ public void because(String because) {
290295

291296
}
292297

293-
public class GroupHandler extends GroovyObjectSupport {
298+
public static class GroupHandler extends GroovyObjectSupport {
294299

295300
private final String id;
296301

297302
private List<Module> modules = new ArrayList<>();
298303

299-
private List<String> imports = new ArrayList<>();
304+
private List<ImportedBom> imports = new ArrayList<>();
300305

301306
private List<String> plugins = new ArrayList<>();
302307

308+
@Inject
303309
public GroupHandler(String id) {
304310
this.id = id;
305311
}
@@ -310,8 +316,14 @@ public void setModules(List<Object> modules) {
310316
.toList();
311317
}
312318

313-
public void setImports(List<String> imports) {
314-
this.imports = imports;
319+
public void bom(String bom) {
320+
this.imports.add(new ImportedBom(bom));
321+
}
322+
323+
public void bom(String bom, Action<ImportBomHandler> action) {
324+
ImportBomHandler handler = new ImportBomHandler();
325+
action.execute(handler);
326+
this.imports.add(new ImportedBom(bom, handler.permittedDependencies));
315327
}
316328

317329
public void setPlugins(List<String> plugins) {
@@ -354,6 +366,17 @@ public void setClassifier(String classifier) {
354366

355367
}
356368

369+
public class ImportBomHandler {
370+
371+
private final List<PermittedDependency> permittedDependencies = new ArrayList<>();
372+
373+
public void permit(String allowed) {
374+
String[] components = allowed.split(":");
375+
this.permittedDependencies.add(new PermittedDependency(components[0], components[1]));
376+
}
377+
378+
}
379+
357380
}
358381

359382
public static class AlignWithHandler {

buildSrc/src/main/java/org/springframework/boot/build/bom/BomResolver.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.w3c.dom.NodeList;
4343

4444
import org.springframework.boot.build.bom.Library.Group;
45+
import org.springframework.boot.build.bom.Library.ImportedBom;
4546
import org.springframework.boot.build.bom.Library.Link;
4647
import org.springframework.boot.build.bom.Library.Module;
4748
import org.springframework.boot.build.bom.ResolvedBom.Bom;
@@ -84,9 +85,9 @@ ResolvedBom resolve(BomExtension bomExtension) {
8485
Id id = new Id(group.getId(), module.getName(), library.getVersion().getVersion().toString());
8586
managedDependencies.add(id);
8687
}
87-
for (String imported : group.getBoms()) {
88+
for (ImportedBom imported : group.getBoms()) {
8889
Bom bom = bomFrom(resolveBom(
89-
"%s:%s:%s".formatted(group.getId(), imported, library.getVersion().getVersion())));
90+
"%s:%s:%s".formatted(group.getId(), imported.name(), library.getVersion().getVersion())));
9091
imports.add(bom);
9192
}
9293
}

buildSrc/src/main/java/org/springframework/boot/build/bom/CheckBom.java

+142-16
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
package org.springframework.boot.build.bom;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.LinkedHashMap;
2023
import java.util.List;
24+
import java.util.Map;
2125
import java.util.Optional;
2226
import java.util.Set;
2327
import java.util.TreeSet;
@@ -42,7 +46,9 @@
4246
import org.gradle.api.tasks.TaskAction;
4347

4448
import org.springframework.boot.build.bom.Library.Group;
49+
import org.springframework.boot.build.bom.Library.ImportedBom;
4550
import org.springframework.boot.build.bom.Library.Module;
51+
import org.springframework.boot.build.bom.Library.PermittedDependency;
4652
import org.springframework.boot.build.bom.Library.ProhibitedVersion;
4753
import org.springframework.boot.build.bom.Library.VersionAlignment;
4854
import org.springframework.boot.build.bom.ResolvedBom.Bom;
@@ -69,7 +75,8 @@ public CheckBom(BomExtension bom) {
6975
Provider<ResolvedBom> resolvedBom = getResolvedBomFile().map(RegularFile::getAsFile).map(ResolvedBom::readFrom);
7076
this.checks = List.of(new CheckExclusions(configurations, dependencies), new CheckProhibitedVersions(),
7177
new CheckVersionAlignment(),
72-
new CheckDependencyManagementAlignment(resolvedBom, configurations, dependencies));
78+
new CheckDependencyManagementAlignment(resolvedBom, configurations, dependencies),
79+
new CheckForUnwantedDependencyManagement(resolvedBom));
7380
this.bom = bom;
7481
}
7582

@@ -241,31 +248,22 @@ private void check(VersionAlignment versionAlignment, Library library, List<Stri
241248

242249
}
243250

244-
private static final class CheckDependencyManagementAlignment implements LibraryCheck {
251+
private abstract static class ResolvedLibraryCheck implements LibraryCheck {
245252

246253
private final Provider<ResolvedBom> resolvedBom;
247254

248-
private final BomResolver bomResolver;
249-
250-
private CheckDependencyManagementAlignment(Provider<ResolvedBom> resolvedBom,
251-
ConfigurationContainer configurations, DependencyHandler dependencies) {
255+
private ResolvedLibraryCheck(Provider<ResolvedBom> resolvedBom) {
252256
this.resolvedBom = resolvedBom;
253-
this.bomResolver = new BomResolver(configurations, dependencies);
254257
}
255258

256259
@Override
257260
public List<String> check(Library library) {
258-
List<String> errors = new ArrayList<>();
259-
String alignsWithBom = library.getAlignsWithBom();
260-
if (alignsWithBom != null) {
261-
Bom mavenBom = this.bomResolver
262-
.resolveMavenBom(alignsWithBom + ":" + library.getVersion().getVersion());
263-
ResolvedLibrary resolvedLibrary = getResolvedLibrary(library);
264-
checkDependencyManagementAlignment(resolvedLibrary, mavenBom, errors);
265-
}
266-
return errors;
261+
ResolvedLibrary resolvedLibrary = getResolvedLibrary(library);
262+
return check(library, resolvedLibrary);
267263
}
268264

265+
protected abstract List<String> check(Library library, ResolvedLibrary resolvedLibrary);
266+
269267
private ResolvedLibrary getResolvedLibrary(Library library) {
270268
ResolvedBom resolvedBom = this.resolvedBom.get();
271269
Optional<ResolvedLibrary> resolvedLibrary = resolvedBom.libraries()
@@ -278,6 +276,30 @@ private ResolvedLibrary getResolvedLibrary(Library library) {
278276
return resolvedLibrary.get();
279277
}
280278

279+
}
280+
281+
private static final class CheckDependencyManagementAlignment extends ResolvedLibraryCheck {
282+
283+
private final BomResolver bomResolver;
284+
285+
private CheckDependencyManagementAlignment(Provider<ResolvedBom> resolvedBom,
286+
ConfigurationContainer configurations, DependencyHandler dependencies) {
287+
super(resolvedBom);
288+
this.bomResolver = new BomResolver(configurations, dependencies);
289+
}
290+
291+
@Override
292+
public List<String> check(Library library, ResolvedLibrary resolvedLibrary) {
293+
List<String> errors = new ArrayList<>();
294+
String alignsWithBom = library.getAlignsWithBom();
295+
if (alignsWithBom != null) {
296+
Bom mavenBom = this.bomResolver
297+
.resolveMavenBom(alignsWithBom + ":" + library.getVersion().getVersion());
298+
checkDependencyManagementAlignment(resolvedLibrary, mavenBom, errors);
299+
}
300+
return errors;
301+
}
302+
281303
private void checkDependencyManagementAlignment(ResolvedLibrary library, Bom mavenBom, List<String> errors) {
282304
List<Id> managedByLibrary = library.managedDependencies();
283305
List<Id> managedByBom = managedDependenciesOf(mavenBom);
@@ -316,4 +338,108 @@ private List<Id> managedDependenciesOf(Bom mavenBom) {
316338

317339
}
318340

341+
private static final class CheckForUnwantedDependencyManagement extends ResolvedLibraryCheck {
342+
343+
private CheckForUnwantedDependencyManagement(Provider<ResolvedBom> resolvedBom) {
344+
super(resolvedBom);
345+
}
346+
347+
@Override
348+
public List<String> check(Library library, ResolvedLibrary resolvedLibrary) {
349+
Map<String, Set<String>> unwanted = findUnwantedDependencyManagement(library, resolvedLibrary);
350+
List<String> errors = new ArrayList<>();
351+
if (!unwanted.isEmpty()) {
352+
StringBuilder error = new StringBuilder("Unwanted dependency management:");
353+
unwanted.forEach((bom, dependencies) -> {
354+
error.append("%n - %s:".formatted(bom));
355+
error.append("%n - %s".formatted(String.join("\n - ", dependencies)));
356+
});
357+
errors.add(error.toString());
358+
}
359+
Map<String, Set<String>> unnecessary = findUnnecessaryPermittedDependencies(library, resolvedLibrary);
360+
if (!unnecessary.isEmpty()) {
361+
StringBuilder error = new StringBuilder("Dependencies permitted unnecessarily:");
362+
unnecessary.forEach((bom, dependencies) -> {
363+
error.append("%n - %s:".formatted(bom));
364+
error.append("%n - %s".formatted(String.join("\n - ", dependencies)));
365+
});
366+
errors.add(error.toString());
367+
}
368+
return errors;
369+
}
370+
371+
private Map<String, Set<String>> findUnwantedDependencyManagement(Library library,
372+
ResolvedLibrary resolvedLibrary) {
373+
Map<String, Set<String>> unwanted = new LinkedHashMap<>();
374+
for (Bom bom : resolvedLibrary.importedBoms()) {
375+
Set<String> notPermitted = new TreeSet<>();
376+
Set<Id> managedDependencies = managedDependenciesOf(bom);
377+
managedDependencies.stream()
378+
.filter((dependency) -> unwanted(bom, dependency, findPermittedDependencies(library, bom)))
379+
.map(Id::toString)
380+
.forEach(notPermitted::add);
381+
if (!notPermitted.isEmpty()) {
382+
unwanted.put(bom.id().artifactId(), notPermitted);
383+
}
384+
}
385+
return unwanted;
386+
}
387+
388+
private List<PermittedDependency> findPermittedDependencies(Library library, Bom bom) {
389+
for (Group group : library.getGroups()) {
390+
for (ImportedBom importedBom : group.getBoms()) {
391+
if (importedBom.name().equals(bom.id().artifactId()) && group.getId().equals(bom.id().groupId())) {
392+
return importedBom.permittedDependencies();
393+
}
394+
}
395+
}
396+
return Collections.emptyList();
397+
}
398+
399+
private Set<Id> managedDependenciesOf(Bom bom) {
400+
Set<Id> managedDependencies = new TreeSet<>();
401+
if (bom != null) {
402+
managedDependencies.addAll(bom.managedDependencies());
403+
managedDependencies.addAll(managedDependenciesOf(bom.parent()));
404+
for (Bom importedBom : bom.importedBoms()) {
405+
managedDependencies.addAll(managedDependenciesOf(importedBom));
406+
}
407+
}
408+
return managedDependencies;
409+
}
410+
411+
private boolean unwanted(Bom bom, Id managedDependency, List<PermittedDependency> permittedDependencies) {
412+
if (bom.id().groupId().equals(managedDependency.groupId())
413+
|| managedDependency.groupId().startsWith(bom.id().groupId() + ".")) {
414+
return false;
415+
}
416+
for (PermittedDependency permittedDependency : permittedDependencies) {
417+
if (permittedDependency.artifactId().equals(managedDependency.artifactId())
418+
&& permittedDependency.groupId().equals(managedDependency.groupId())) {
419+
return false;
420+
}
421+
}
422+
return true;
423+
}
424+
425+
private Map<String, Set<String>> findUnnecessaryPermittedDependencies(Library library,
426+
ResolvedLibrary resolvedLibrary) {
427+
Map<String, Set<String>> unnecessary = new HashMap<>();
428+
for (Bom bom : resolvedLibrary.importedBoms()) {
429+
Set<String> permittedDependencies = findPermittedDependencies(library, bom).stream()
430+
.map((dependency) -> dependency.groupId() + ":" + dependency.artifactId())
431+
.collect(Collectors.toCollection(TreeSet::new));
432+
Set<String> dependencies = managedDependenciesOf(bom).stream()
433+
.map((dependency) -> dependency.groupId() + ":" + dependency.artifactId())
434+
.collect(Collectors.toCollection(TreeSet::new));
435+
permittedDependencies.removeAll(dependencies);
436+
if (!permittedDependencies.isEmpty()) {
437+
unnecessary.put(bom.id().artifactId(), permittedDependencies);
438+
}
439+
}
440+
return unnecessary;
441+
}
442+
443+
}
444+
319445
}

0 commit comments

Comments
 (0)