Skip to content

Commit

Permalink
Fix duplicate entries in generated BOM or catalog (#725)
Browse files Browse the repository at this point in the history
* Fix potential duplicate entries in BOM/catalog

If a dependency was managed and that it also was part of the
list of modules for which version inference should be triggered,
then we would end up with duplicate entries in the BOM.

This commit fixes that by checking, when version inference occurs,
if a library with the same GAV coordinates was already registered.

* Fix "Duplicate entry for alias" error

In case of nested BOM inlining, it was possible that we missed a case
where an alias was already included, which lead to the _Gradle_ (not ours)
warning "Duplicate entry for alias". However, we should avoid these,
because we already capture duplicate entries (which may be fine in case
we are actually overriding nested catalog versions).
  • Loading branch information
melix authored Oct 18, 2024
1 parent 9844ec6 commit 2743091
Showing 1 changed file with 85 additions and 76 deletions.
161 changes: 85 additions & 76 deletions src/main/groovy/io/micronaut/build/MicronautBomPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
Expand Down Expand Up @@ -285,7 +284,7 @@ private void configureLate(Project project, MicronautBomExtension bomExtension,
String group = String.valueOf(project.getGroup());
Optional<VersionCatalog> versionCatalog = findVersionCatalog(project, bomExtension);
final VersionCatalogConverter modelConverter = new VersionCatalogConverter(
project.getRootProject().file("gradle/" + bomExtension.getCatalogName().get() + ".versions.toml"),
project.getRootProject().file("gradle/" + bomExtension.getCatalogName().get() + ".versions.toml"),
project.getExtensions().findByType(CatalogPluginExtension.class)
);
var libraryDefinitions = new ArrayList<InterceptedVersionCatalogBuilder.LibraryDefinition>();
Expand Down Expand Up @@ -445,12 +444,14 @@ public void execute(Task task) {
return result;
});
var includedAliases = bomExtension.getInlinedAliases();
var knownLibraries = new ArrayList<String>();
modelConverter.onLibrary(l -> knownLibraries.add(l.groupId() + ":" + l.artifactId()));
modelConverter.afterBuildingModel(builderState -> {
api.getAllDependencyConstraints().forEach(MicronautBomPlugin::checkVersionConstraint);
runtime.getAllDependencyConstraints().forEach(MicronautBomPlugin::checkVersionConstraint);
maybeInlineNestedCatalogs(logFile, catalogArtifacts, bomArtifacts, builderState, inlineNestedCatalogs, inlineNestedBOMs, excludedInlinedAliases, includedAliases, inlinedPomProperties, inlinedMavenDependencies, project);
performVersionInference(project, bomExtension, builderState, libraryDefinitions, inlinedPomProperties, inlinedMavenDependencies, project.getConfigurations().findByName(BOM_VERSION_INFERENCE_CONFIGURATION_NAME));
try (var writer = new PrintWriter(new FileWriter(logFile.toFile(), true))) {
try (var log = new PrintWriter(Files.newBufferedWriter(logFile))) {
maybeInlineNestedCatalogs(log, catalogArtifacts, bomArtifacts, builderState, inlineNestedCatalogs, inlineNestedBOMs, excludedInlinedAliases, includedAliases, inlinedPomProperties, inlinedMavenDependencies, project);
performVersionInference(log, project, bomExtension, builderState, libraryDefinitions, inlinedPomProperties, inlinedMavenDependencies, project.getConfigurations().findByName(BOM_VERSION_INFERENCE_CONFIGURATION_NAME), knownLibraries);
var unresolvedDependencies = new LinkedHashSet<ComponentSelector>();
catalogs.getIncoming()
.getResolutionResult()
Expand All @@ -467,9 +468,9 @@ public void execute(Task task) {
}
});
if (!unresolvedDependencies.isEmpty()) {
writer.println("[WARNING] There were unresolved dependencies during inlining! This may cause incomplete catalogs!");
log.println("[WARNING] There were unresolved dependencies during inlining! This may cause incomplete catalogs!");
for (ComponentSelector unresolvedDependency : unresolvedDependencies) {
writer.println(" " + unresolvedDependency);
log.println(" " + unresolvedDependency);
}
}
} catch (IOException e) {
Expand All @@ -493,13 +494,19 @@ public void execute(Task task) {
});
}

private static void performVersionInference(Project project,
private static void performVersionInference(PrintWriter log,
Project project,
MicronautBomExtension bomExtension,
VersionCatalogConverter.BuilderState builderState,
ArrayList<InterceptedVersionCatalogBuilder.LibraryDefinition> libraryDefinitions,
List<InterceptedVersionCatalogBuilder.LibraryDefinition> libraryDefinitions,
Map<String, String> inlinedPomProperties,
List<InlinedDependency> inlinedMavenDependencies,
Configuration versionInferenceConfiguration) {
Configuration versionInferenceConfiguration,
List<String> knownLibrariesList) {
// This copy serves both optimization (search in set instead of list) but also because the
// list which is passed as an input will be augmented by this very method, so we have to
// create a defensive copy.
var knownLibraries = knownLibrariesList.stream().collect(Collectors.toUnmodifiableSet());
var inferredLibraries = bomExtension.getInferredManagedDependencies().getOrElse(Map.of());
if (!inferredLibraries.isEmpty()) {
Set<String> found = new HashSet<>();
Expand All @@ -508,13 +515,19 @@ private static void performVersionInference(Project project,
versionInferenceConfiguration.getDependencies().add(project.getDependencies().create(lib.toString()));
}
});
var seen = new HashSet<String>();
versionInferenceConfiguration.getIncoming()
.getResolutionResult()
.allDependencies(dep -> {
if (dep instanceof ResolvedDependencyResult resolved) {
if (resolved.getSelected().getId() instanceof ModuleComponentIdentifier mid) {
var module = mid.getModuleIdentifier().toString();
if (inferredLibraries.containsKey(module)) {
if (seen.add(module) && inferredLibraries.containsKey(module)) {
if (knownLibraries.contains(module)) {
found.add(module);
log.println("Library " + module + " is already present in the catalog, remove it from the inferred versions list");
return;
}
found.add(module);
var alias = inferredLibraries.get(module);
if (!builderState.getKnownVersionAliases().containsKey(alias)) {
Expand Down Expand Up @@ -574,7 +587,7 @@ private void makeImportedBOMsLast(Node dependencyManagementDependenciesNode) {
});
}

private void maybeInlineNestedCatalogs(Path logFile,
private void maybeInlineNestedCatalogs(PrintWriter log,
Set<ResolvedArtifactResult> catalogs,
Set<ResolvedArtifactResult> bomArtifacts,
VersionCatalogConverter.BuilderState builderState,
Expand All @@ -586,71 +599,67 @@ private void maybeInlineNestedCatalogs(Path logFile,
List<InlinedDependency> inlinedMavenDependencies,
// This last argument breaks configuration cache but for now we have no choice :(
Project p) {
try (var log = new PrintWriter(Files.newBufferedWriter(logFile))) {
if (Boolean.TRUE.equals(inlineNestedCatalogs.get())) {
VersionCatalogBuilder builder = builderState.getBuilder();
Map<String, VersionCatalogConverter.AliasRecord> knownAliases = builderState.getKnownAliases();
Map<String, VersionCatalogConverter.AliasRecord> knownPluginAliases = builderState.getKnownPluginAliases();
Map<String, VersionCatalogConverter.AliasRecord> knownVersionAliases = builderState.getKnownVersionAliases();

// We're looking for catalogs in the first place, because they define aliases and properties
// that we can inline. Then, there are remaining dependencies which are non Micronaut modules
// which do not publish catalogs. The ignored bom files list is the list of files that we can
// safely ignore because there are catalogs.
Set<String> ignoredBomFiles = new HashSet<>();
var knownCatalogModules = catalogs.stream()
.map(ResolvedArtifactResult::getVariant)
.map(ResolvedVariantResult::getOwner)
.filter(ModuleComponentIdentifier.class::isInstance)
.map(ModuleComponentIdentifier.class::cast)
.map(mci -> mci.getModuleIdentifier().toString())
.collect(Collectors.toSet());
List<String> extraBomsToResolve = new ArrayList<>();
catalogs.forEach(catalogArtifact -> {
var catalogFile = catalogArtifact.getFile();
var excludes = determineExcludes(excludedInlinedAliases, catalogFile);
var includes = inlinedAliases.get().getOrDefault(baseNameOf(catalogFile), Set.of());
var excludedAliases = findRegularEntries(excludes);
var excludedAliasesPrefixes = findWildcardEntries(excludes);
var includeAliases = findRegularEntries(includes);
var includedAliasesPrefixes = findWildcardEntries(includes);

performSingleCatalogFileInclusion(
log,
inlinedPomProperties,
catalogFile,
ignoredBomFiles,
includeAliases,
includedAliasesPrefixes,
excludedAliases,
excludedAliasesPrefixes,
knownAliases,
knownVersionAliases,
builder,
knownPluginAliases,
inlinedMavenDependencies,
knownCatalogModules,
extraBomsToResolve);
}
);
if (Boolean.TRUE.equals(inlineNestedBOMs.get())) {
log.println("Regular BOMs (without version catalog) inlining is enabled");
inlineRegularBoms(log, bomArtifacts, excludedInlinedAliases, inlinedAliases, inlinedPomProperties, inlinedMavenDependencies, ignoredBomFiles, knownAliases, knownVersionAliases, builder);
if (!extraBomsToResolve.isEmpty()) {
log.println("Found the following BOMs to be recursively included: ");
extraBomsToResolve.forEach(bom -> log.println(" - " + bom));
var extraBoms = p.getConfigurations().getByName(EXTRA_BOMS_INLINING_CONFIGURATION_NAME);
extraBoms.getDependencies().addAll(extraBomsToResolve.stream()
.map(bom -> p.getDependencies().create(bom + "@pom"))
.toList());
inlineRegularBoms(log, extraBoms.getIncoming().getArtifacts().getArtifacts(), excludedInlinedAliases, inlinedAliases, inlinedPomProperties, inlinedMavenDependencies, ignoredBomFiles, knownAliases, knownVersionAliases,
builder);
}
if (Boolean.TRUE.equals(inlineNestedCatalogs.get())) {
VersionCatalogBuilder builder = builderState.getBuilder();
Map<String, VersionCatalogConverter.AliasRecord> knownAliases = builderState.getKnownAliases();
Map<String, VersionCatalogConverter.AliasRecord> knownPluginAliases = builderState.getKnownPluginAliases();
Map<String, VersionCatalogConverter.AliasRecord> knownVersionAliases = builderState.getKnownVersionAliases();

// We're looking for catalogs in the first place, because they define aliases and properties
// that we can inline. Then, there are remaining dependencies which are non Micronaut modules
// which do not publish catalogs. The ignored bom files list is the list of files that we can
// safely ignore because there are catalogs.
Set<String> ignoredBomFiles = new HashSet<>();
var knownCatalogModules = catalogs.stream()
.map(ResolvedArtifactResult::getVariant)
.map(ResolvedVariantResult::getOwner)
.filter(ModuleComponentIdentifier.class::isInstance)
.map(ModuleComponentIdentifier.class::cast)
.map(mci -> mci.getModuleIdentifier().toString())
.collect(Collectors.toSet());
List<String> extraBomsToResolve = new ArrayList<>();
catalogs.forEach(catalogArtifact -> {
var catalogFile = catalogArtifact.getFile();
var excludes = determineExcludes(excludedInlinedAliases, catalogFile);
var includes = inlinedAliases.get().getOrDefault(baseNameOf(catalogFile), Set.of());
var excludedAliases = findRegularEntries(excludes);
var excludedAliasesPrefixes = findWildcardEntries(excludes);
var includeAliases = findRegularEntries(includes);
var includedAliasesPrefixes = findWildcardEntries(includes);

performSingleCatalogFileInclusion(
log,
inlinedPomProperties,
catalogFile,
ignoredBomFiles,
includeAliases,
includedAliasesPrefixes,
excludedAliases,
excludedAliasesPrefixes,
knownAliases,
knownVersionAliases,
builder,
knownPluginAliases,
inlinedMavenDependencies,
knownCatalogModules,
extraBomsToResolve);
}
);
if (Boolean.TRUE.equals(inlineNestedBOMs.get())) {
log.println("Regular BOMs (without version catalog) inlining is enabled");
inlineRegularBoms(log, bomArtifacts, excludedInlinedAliases, inlinedAliases, inlinedPomProperties, inlinedMavenDependencies, ignoredBomFiles, knownAliases, knownVersionAliases, builder);
if (!extraBomsToResolve.isEmpty()) {
log.println("Found the following BOMs to be recursively included: ");
extraBomsToResolve.forEach(bom -> log.println(" - " + bom));
var extraBoms = p.getConfigurations().getByName(EXTRA_BOMS_INLINING_CONFIGURATION_NAME);
extraBoms.getDependencies().addAll(extraBomsToResolve.stream()
.map(bom -> p.getDependencies().create(bom + "@pom"))
.toList());
inlineRegularBoms(log, extraBoms.getIncoming().getArtifacts().getArtifacts(), excludedInlinedAliases, inlinedAliases, inlinedPomProperties, inlinedMavenDependencies, ignoredBomFiles, knownAliases, knownVersionAliases,
builder);
}

}
} catch (IOException e) {
LOGGER.error("Unable to write log file for inlining", e);

}
}

Expand Down Expand Up @@ -925,7 +934,7 @@ private void performNestedBomsInclusion(PrintWriter log,
var alias = convertToAlias(dep.getArtifactId());
var includeExcludeReason = shouldInclude(alias, includeAliases, includeAliasesPrefixes, excludeFromInlining, excludeFromInliningPrefixes);
if (includeExcludeReason.included()) {
if (knownAliasesSnakeCase.contains(alias)) {
if (knownAliasesSnakeCase.contains(alias) || knownAliases.containsKey(alias)) {
maybeWarn(knownAliases, alias, bomFileName);
} else {
if (knownVersionAliasesSnakeCase.contains(alias)) {
Expand Down

0 comments on commit 2743091

Please sign in to comment.