From 9cd6af9ecf3895fcfb78ca64715024b7c768c3a4 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 26 Mar 2025 09:15:01 +0000 Subject: [PATCH] Polish CheckBom task --- .../boot/build/bom/CheckBom.java | 318 +++++++++++------- 1 file changed, 189 insertions(+), 129 deletions(-) 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 7f2d73ea8d7..ee5ad1b3862 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 @@ -58,34 +58,30 @@ import org.springframework.boot.build.bom.bomr.version.DependencyVersion; */ public abstract class CheckBom extends DefaultTask { - private final Provider resolvedBom; - - private final ConfigurationContainer configurations; - - private final DependencyHandler dependencies; - private final BomExtension bom; - private final BomResolver bomResolver; + private final List checks; @Inject public CheckBom(BomExtension bom) { - this.configurations = getProject().getConfigurations(); - this.dependencies = getProject().getDependencies(); + ConfigurationContainer configurations = getProject().getConfigurations(); + DependencyHandler dependencies = getProject().getDependencies(); + Provider resolvedBom = getResolvedBomFile().map(RegularFile::getAsFile).map(ResolvedBom::readFrom); + this.checks = List.of(new CheckExclusions(configurations, dependencies), new CheckProhibitedVersions(), + new CheckVersionAlignment(), + new CheckDependencyManagementAlignment(resolvedBom, configurations, dependencies)); 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(); + public abstract RegularFileProperty getResolvedBomFile(); @TaskAction void checkBom() { List errors = new ArrayList<>(); for (Library library : this.bom.getLibraries()) { - checkLibrary(library, errors); + errors.addAll(checkLibrary(library)); } if (!errors.isEmpty()) { System.out.println(); @@ -95,165 +91,229 @@ public abstract class CheckBom extends DefaultTask { } } - private void checkLibrary(Library library, List errors) { + private List checkLibrary(Library library) { List libraryErrors = new ArrayList<>(); - checkExclusions(library, libraryErrors); - checkProhibitedVersions(library, libraryErrors); - checkVersionAlignment(library, libraryErrors); - checkDependencyManagementAlignment(library, libraryErrors); + this.checks.stream().flatMap((check) -> check.check(library).stream()).forEach(libraryErrors::add); + List errors = new ArrayList<>(); if (!libraryErrors.isEmpty()) { errors.add(library.getName()); for (String libraryError : libraryErrors) { errors.add(" - " + libraryError); } } + return errors; + } + + private interface LibraryCheck { + + List check(Library library); + } - private void checkExclusions(Library library, List errors) { - for (Group group : library.getGroups()) { - for (Module module : group.getModules()) { - if (!module.getExclusions().isEmpty()) { - checkExclusions(group.getId(), module, library.getVersion().getVersion(), errors); + private static final class CheckExclusions implements LibraryCheck { + + private final ConfigurationContainer configurations; + + private final DependencyHandler dependencies; + + private CheckExclusions(ConfigurationContainer configurations, DependencyHandler dependencies) { + this.configurations = configurations; + this.dependencies = dependencies; + } + + @Override + public List check(Library library) { + List errors = new ArrayList<>(); + for (Group group : library.getGroups()) { + for (Module module : group.getModules()) { + if (!module.getExclusions().isEmpty()) { + checkExclusions(group.getId(), module, library.getVersion().getVersion(), errors); + } } } + return errors; } - } - private void checkExclusions(String groupId, Module module, DependencyVersion version, List errors) { - Set resolved = this.configurations - .detachedConfiguration(this.dependencies.create(groupId + ":" + module.getName() + ":" + version)) - .getResolvedConfiguration() - .getResolvedArtifacts() - .stream() - .map((artifact) -> artifact.getModuleVersion().getId()) - .map((id) -> id.getGroup() + ":" + id.getModule().getName()) - .collect(Collectors.toSet()); - Set exclusions = module.getExclusions() - .stream() - .map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId()) - .collect(Collectors.toSet()); - Set unused = new TreeSet<>(); - for (String exclusion : exclusions) { - if (!resolved.contains(exclusion)) { - if (exclusion.endsWith(":*")) { - String group = exclusion.substring(0, exclusion.indexOf(':') + 1); - if (resolved.stream().noneMatch((candidate) -> candidate.startsWith(group))) { + private void checkExclusions(String groupId, Module module, DependencyVersion version, List errors) { + Set resolved = this.configurations + .detachedConfiguration(this.dependencies.create(groupId + ":" + module.getName() + ":" + version)) + .getResolvedConfiguration() + .getResolvedArtifacts() + .stream() + .map((artifact) -> artifact.getModuleVersion().getId()) + .map((id) -> id.getGroup() + ":" + id.getModule().getName()) + .collect(Collectors.toSet()); + Set exclusions = module.getExclusions() + .stream() + .map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId()) + .collect(Collectors.toSet()); + Set unused = new TreeSet<>(); + for (String exclusion : exclusions) { + if (!resolved.contains(exclusion)) { + if (exclusion.endsWith(":*")) { + String group = exclusion.substring(0, exclusion.indexOf(':') + 1); + if (resolved.stream().noneMatch((candidate) -> candidate.startsWith(group))) { + unused.add(exclusion); + } + } + else { unused.add(exclusion); } } - else { - unused.add(exclusion); - } + } + exclusions.removeAll(resolved); + if (!unused.isEmpty()) { + errors.add("Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions); } } - exclusions.removeAll(resolved); - if (!unused.isEmpty()) { - errors.add("Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions); - } + } - private void checkProhibitedVersions(Library library, List errors) { - ArtifactVersion currentVersion = new DefaultArtifactVersion(library.getVersion().getVersion().toString()); - for (ProhibitedVersion prohibited : library.getProhibitedVersions()) { - if (prohibited.isProhibited(library.getVersion().getVersion().toString())) { - errors.add("Current version " + currentVersion + " is prohibited"); - } - else { - VersionRange versionRange = prohibited.getRange(); - if (versionRange != null) { - for (Restriction restriction : versionRange.getRestrictions()) { - ArtifactVersion upperBound = restriction.getUpperBound(); - if (upperBound == null) { - return; - } - int comparison = currentVersion.compareTo(upperBound); - if ((restriction.isUpperBoundInclusive() && comparison <= 0) - || ((!restriction.isUpperBoundInclusive()) && comparison < 0)) { - return; - } + private static final class CheckProhibitedVersions implements LibraryCheck { + + @Override + public List check(Library library) { + List errors = new ArrayList<>(); + ArtifactVersion currentVersion = new DefaultArtifactVersion(library.getVersion().getVersion().toString()); + for (ProhibitedVersion prohibited : library.getProhibitedVersions()) { + if (prohibited.isProhibited(library.getVersion().getVersion().toString())) { + errors.add("Current version " + currentVersion + " is prohibited"); + } + else { + VersionRange versionRange = prohibited.getRange(); + if (versionRange != null) { + check(currentVersion, versionRange, errors); } - errors.add("Version range " + versionRange + " is ineffective as the current version, " - + currentVersion + ", is greater than its upper bound"); } } + return errors; } - } - private void checkVersionAlignment(Library library, List errors) { - VersionAlignment versionAlignment = library.getVersionAlignment(); - if (versionAlignment == null) { - return; + private void check(ArtifactVersion currentVersion, VersionRange versionRange, List errors) { + for (Restriction restriction : versionRange.getRestrictions()) { + ArtifactVersion upperBound = restriction.getUpperBound(); + if (upperBound == null) { + return; + } + int comparison = currentVersion.compareTo(upperBound); + if ((restriction.isUpperBoundInclusive() && comparison <= 0) + || ((!restriction.isUpperBoundInclusive()) && comparison < 0)) { + return; + } + } + errors.add("Version range " + versionRange + " is ineffective as the current version, " + currentVersion + + ", is greater than its upper bound"); } - Set alignedVersions = versionAlignment.resolve(); - if (alignedVersions.size() == 1) { - String alignedVersion = alignedVersions.iterator().next(); - if (!alignedVersion.equals(library.getVersion().getVersion().toString())) { - errors.add("Version " + library.getVersion().getVersion() + " is misaligned. It should be " - + alignedVersion + "."); + + } + + private static final class CheckVersionAlignment implements LibraryCheck { + + @Override + public List check(Library library) { + List errors = new ArrayList<>(); + VersionAlignment versionAlignment = library.getVersionAlignment(); + if (versionAlignment != null) { + check(versionAlignment, library, errors); } + return errors; } - else { - if (alignedVersions.isEmpty()) { - errors.add("Version alignment requires a single version but none were found."); + + private void check(VersionAlignment versionAlignment, Library library, List errors) { + Set alignedVersions = versionAlignment.resolve(); + if (alignedVersions.size() == 1) { + String alignedVersion = alignedVersions.iterator().next(); + if (!alignedVersion.equals(library.getVersion().getVersion().toString())) { + errors.add("Version " + library.getVersion().getVersion() + " is misaligned. It should be " + + alignedVersion + "."); + } } else { - errors.add("Version alignment requires a single version but " + alignedVersions.size() + " were found: " - + alignedVersions + "."); + if (alignedVersions.isEmpty()) { + errors.add("Version alignment requires a single version but none were found."); + } + else { + errors.add("Version alignment requires a single version but " + alignedVersions.size() + + " were found: " + alignedVersions + "."); + } } } - } - private void checkDependencyManagementAlignment(Library library, List errors) { - String alignsWithBom = library.getAlignsWithBom(); - if (alignsWithBom == null) { - return; - } - Bom mavenBom = this.bomResolver.resolveMavenBom(alignsWithBom + ":" + library.getVersion().getVersion()); - ResolvedBom resolvedBom = this.resolvedBom.get(); - Optional 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 void checkDependencyManagementAlignment(ResolvedLibrary library, Bom mavenBom, List errors) { - List managedByLibrary = library.managedDependencies(); - List managedByBom = managedDependenciesOf(mavenBom); + private static final class CheckDependencyManagementAlignment implements LibraryCheck { - List missing = new ArrayList<>(managedByBom); - missing.removeAll(managedByLibrary); + private final Provider resolvedBom; - List unexpected = new ArrayList<>(managedByLibrary); - unexpected.removeAll(managedByBom); - if (missing.isEmpty() && unexpected.isEmpty()) { - return; + private final BomResolver bomResolver; + + private CheckDependencyManagementAlignment(Provider resolvedBom, + ConfigurationContainer configurations, DependencyHandler dependencies) { + this.resolvedBom = resolvedBom; + this.bomResolver = new BomResolver(configurations, dependencies); } - 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())); + + @Override + public List check(Library library) { + List errors = new ArrayList<>(); + String alignsWithBom = library.getAlignsWithBom(); + if (alignsWithBom != null) { + Bom mavenBom = this.bomResolver + .resolveMavenBom(alignsWithBom + ":" + library.getVersion().getVersion()); + ResolvedLibrary resolvedLibrary = getResolvedLibrary(library); + checkDependencyManagementAlignment(resolvedLibrary, mavenBom, errors); + } + return errors; } - if (!unexpected.isEmpty()) { - error = error + "%n - Unexpected:%n %s".formatted(String.join("\n ", - unexpected.stream().map((dependency) -> dependency.toString()).toList())); + + private ResolvedLibrary getResolvedLibrary(Library library) { + ResolvedBom resolvedBom = this.resolvedBom.get(); + Optional 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())); + } + return resolvedLibrary.get(); } - errors.add(error); - } - private List managedDependenciesOf(Bom mavenBom) { - List managedDependencies = new ArrayList<>(); - managedDependencies.addAll(mavenBom.managedDependencies()); - if (mavenBom.parent() != null) { - managedDependencies.addAll(managedDependenciesOf(mavenBom.parent())); + private void checkDependencyManagementAlignment(ResolvedLibrary library, Bom mavenBom, List errors) { + List managedByLibrary = library.managedDependencies(); + List managedByBom = managedDependenciesOf(mavenBom); + + List missing = new ArrayList<>(managedByBom); + missing.removeAll(managedByLibrary); + + List unexpected = new ArrayList<>(managedByLibrary); + unexpected.removeAll(managedByBom); + if (missing.isEmpty() && unexpected.isEmpty()) { + return; + } + 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); } - for (Bom importedBom : mavenBom.importedBoms()) { - managedDependencies.addAll(managedDependenciesOf(importedBom)); + + private List managedDependenciesOf(Bom mavenBom) { + List 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; } - return managedDependencies; + } }