Polish CheckBom task
This commit is contained in:
parent
46a30e98bb
commit
9cd6af9ecf
|
@ -58,34 +58,30 @@ 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;
|
||||
private final List<LibraryCheck> checks;
|
||||
|
||||
@Inject
|
||||
public CheckBom(BomExtension bom) {
|
||||
this.configurations = getProject().getConfigurations();
|
||||
this.dependencies = getProject().getDependencies();
|
||||
ConfigurationContainer configurations = getProject().getConfigurations();
|
||||
DependencyHandler dependencies = getProject().getDependencies();
|
||||
Provider<ResolvedBom> 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<String> 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<String> errors) {
|
||||
private List<String> checkLibrary(Library library) {
|
||||
List<String> 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<String> errors = new ArrayList<>();
|
||||
if (!libraryErrors.isEmpty()) {
|
||||
errors.add(library.getName());
|
||||
for (String libraryError : libraryErrors) {
|
||||
errors.add(" - " + libraryError);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private void checkExclusions(Library library, List<String> errors) {
|
||||
for (Group group : library.getGroups()) {
|
||||
for (Module module : group.getModules()) {
|
||||
if (!module.getExclusions().isEmpty()) {
|
||||
checkExclusions(group.getId(), module, library.getVersion().getVersion(), errors);
|
||||
private interface LibraryCheck {
|
||||
|
||||
List<String> check(Library library);
|
||||
|
||||
}
|
||||
|
||||
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<String> check(Library library) {
|
||||
List<String> 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<String> errors) {
|
||||
Set<String> 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<String> exclusions = module.getExclusions()
|
||||
.stream()
|
||||
.map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId())
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> 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<String> errors) {
|
||||
Set<String> 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<String> exclusions = module.getExclusions()
|
||||
.stream()
|
||||
.map((exclusion) -> exclusion.getGroupId() + ":" + exclusion.getArtifactId())
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
exclusions.removeAll(resolved);
|
||||
if (!unused.isEmpty()) {
|
||||
errors.add("Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class CheckProhibitedVersions implements LibraryCheck {
|
||||
|
||||
@Override
|
||||
public List<String> check(Library library) {
|
||||
List<String> 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 {
|
||||
unused.add(exclusion);
|
||||
}
|
||||
}
|
||||
}
|
||||
exclusions.removeAll(resolved);
|
||||
if (!unused.isEmpty()) {
|
||||
errors.add("Unnecessary exclusions on " + groupId + ":" + module.getName() + ": " + exclusions);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkProhibitedVersions(Library library, List<String> 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;
|
||||
}
|
||||
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 check(ArtifactVersion currentVersion, VersionRange versionRange, List<String> 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class CheckVersionAlignment implements LibraryCheck {
|
||||
|
||||
@Override
|
||||
public List<String> check(Library library) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
VersionAlignment versionAlignment = library.getVersionAlignment();
|
||||
if (versionAlignment != null) {
|
||||
check(versionAlignment, library, errors);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private void check(VersionAlignment versionAlignment, Library library, List<String> errors) {
|
||||
Set<String> 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 {
|
||||
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 checkVersionAlignment(Library library, List<String> errors) {
|
||||
VersionAlignment versionAlignment = library.getVersionAlignment();
|
||||
if (versionAlignment == null) {
|
||||
return;
|
||||
private static final class CheckDependencyManagementAlignment implements LibraryCheck {
|
||||
|
||||
private final Provider<ResolvedBom> resolvedBom;
|
||||
|
||||
private final BomResolver bomResolver;
|
||||
|
||||
private CheckDependencyManagementAlignment(Provider<ResolvedBom> resolvedBom,
|
||||
ConfigurationContainer configurations, DependencyHandler dependencies) {
|
||||
this.resolvedBom = resolvedBom;
|
||||
this.bomResolver = new BomResolver(configurations, dependencies);
|
||||
}
|
||||
Set<String> 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 + ".");
|
||||
|
||||
@Override
|
||||
public List<String> check(Library library) {
|
||||
List<String> 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;
|
||||
}
|
||||
else {
|
||||
if (alignedVersions.isEmpty()) {
|
||||
errors.add("Version alignment requires a single version but none were found.");
|
||||
|
||||
private ResolvedLibrary getResolvedLibrary(Library library) {
|
||||
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()));
|
||||
}
|
||||
else {
|
||||
errors.add("Version alignment requires a single version but " + alignedVersions.size() + " were found: "
|
||||
+ alignedVersions + ".");
|
||||
return resolvedLibrary.get();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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 void checkDependencyManagementAlignment(Library library, List<String> errors) {
|
||||
String alignsWithBom = library.getAlignsWithBom();
|
||||
if (alignsWithBom == null) {
|
||||
return;
|
||||
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;
|
||||
}
|
||||
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 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue