From 24fac495713be4896034d951ddcf7e8e422a95c2 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 20 Feb 2025 22:51:04 +0100 Subject: [PATCH] Fix recommended release selection in an archived platform stream --- ...usPlatformArchivedStreamSelectionTest.java | 131 ++++++++++++++++++ .../registry/ExtensionCatalogResolver.java | 86 +++++++----- 2 files changed, 180 insertions(+), 37 deletions(-) create mode 100644 independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformArchivedStreamSelectionTest.java diff --git a/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformArchivedStreamSelectionTest.java b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformArchivedStreamSelectionTest.java new file mode 100644 index 0000000000000..1562e9a0fd6dd --- /dev/null +++ b/independent-projects/tools/devtools-testing/src/test/java/io/quarkus/devtools/project/create/QuarkusPlatformArchivedStreamSelectionTest.java @@ -0,0 +1,131 @@ +package io.quarkus.devtools.project.create; + +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.quarkus.devtools.testing.registry.client.TestRegistryClientBuilder; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.registry.catalog.PlatformStreamCoords; + +public class QuarkusPlatformArchivedStreamSelectionTest extends MultiplePlatformBomsTestBase { + + private static final String ACME_PLATFORM_KEY = "org.acme.platform"; + + @BeforeAll + public static void setup() throws Exception { + TestRegistryClientBuilder.newInstance() + //.debug() + .baseDir(configDir()) + // registry + .newRegistry("registry.acme.org") + // platform key + .newPlatform(ACME_PLATFORM_KEY) + // 3.0 STREAM + .newStream("3.0") + // 3.0.5 release + .newRelease("3.0.5") + .quarkusVersion("3.0.5") + // default bom including quarkus-core + essential metadata + .addCoreMember().release() + .newMember("acme-zoo-bom").addExtension("org.acme", "acme-rabbit", "3.0.5") + .release().stream().platform() + // 2.0 STREAM + .newArchivedStream("2.0") + .newRelease("2.0.5") + .quarkusVersion("2.0.5") + // default bom including quarkus-core + essential metadata + .addCoreMember().release() + .newMember("acme-zoo-bom").addExtension("org.acme", "acme-rabbit", "2.0.5") + .release().stream() + .newRelease("2.0.4") + .quarkusVersion("2.0.4") + // default bom including quarkus-core + essential metadata + .addCoreMember().release() + .newMember("acme-zoo-bom").addExtension("org.acme", "acme-rabbit", "2.0.4") + .release().stream() + .newRelease("2.0.3") + .quarkusVersion("2.0.3") + // default bom including quarkus-core + essential metadata + .addCoreMember().release() + .newMember("acme-zoo-bom") + .addExtension("org.acme", "acme-rabbit", "2.0.3") + .addExtension("org.acme", "acme-giraffe", "2.0.3") + .release().stream().platform() + // 1.0 STREAM + .newStream("1.0") + .newRelease("1.0.5") + .quarkusVersion("1.0.5") + // default bom including quarkus-core + essential metadata + .addCoreMember().release() + .newMember("acme-zoo-bom") + .addExtension("org.acme", "acme-rabbit", "1.0.5") + .addExtension("org.acme", "acme-giraffe", "1.0.5") + .registry() + .clientBuilder() + .build(); + + enableRegistryClient(); + } + + protected String getMainPlatformKey() { + return ACME_PLATFORM_KEY; + } + + @Test + public void testLatestRecommendedStream() throws Exception { + final Path projectDir = newProjectDir("latest-recommended-stream-selection"); + createProject(projectDir, List.of("acme-rabbit")); + + assertModel(projectDir, + toPlatformBomCoords("acme-zoo-bom"), + List.of(ArtifactCoords.jar("org.acme", "acme-rabbit", null)), + "3.0.5"); + } + + @Test + public void testLatestRecommendedMatchingStreamRelease() throws Exception { + final Path projectDir = newProjectDir("latest-recommended-matching-stream"); + createProject(projectDir, List.of("acme-rabbit", "acme-giraffe")); + + assertModel(projectDir, + toPlatformBomCoords("acme-zoo-bom"), + List.of(ArtifactCoords.jar("org.acme", "acme-rabbit", null), + ArtifactCoords.jar("org.acme", "acme-giraffe", null)), + "1.0.5"); + } + + @Test + public void testArchivedStreamSelection() throws Exception { + final Path projectDir = newProjectDir("archived-stream-selection"); + createProject(projectDir, new PlatformStreamCoords(ACME_PLATFORM_KEY, "2.0"), + List.of("acme-rabbit")); + + assertModel(projectDir, + toPlatformBomCoords("acme-zoo-bom"), + List.of(ArtifactCoords.jar("org.acme", "acme-rabbit", null)), + "2.0.5"); + } + + /** + * This one may seem like an edge case. This test makes sure a release that includes an extension + * that was removed in later releases in the same stream still gets selected when that extension + * is requested by a user. + * + * @throws Exception in case of an error + */ + @Test + public void testArchivedMatchingStreamRelease() throws Exception { + final Path projectDir = newProjectDir("archived-stream-selection"); + createProject(projectDir, new PlatformStreamCoords(ACME_PLATFORM_KEY, "2.0"), + List.of("acme-rabbit", "acme-giraffe")); + + assertModel(projectDir, + toPlatformBomCoords("acme-zoo-bom"), + List.of(ArtifactCoords.jar("org.acme", "acme-rabbit", null), + ArtifactCoords.jar("org.acme", "acme-giraffe", null)), + "2.0.3"); + } +} diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java index ce568498fc92d..894002c858264 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java @@ -5,7 +5,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -326,7 +325,8 @@ void addUpstreamQuarkusVersion(String quarkusVersion) { } List getRegistriesForQuarkusCore(String quarkusVersion) { - return registriesByQuarkusCore.computeIfAbsent(quarkusVersion, v -> getRegistriesForQuarkusVersion(v)); + return registriesByQuarkusCore.computeIfAbsent(quarkusVersion, + ExtensionCatalogResolver.this::getRegistriesForQuarkusVersion); } public int getCompatibilityCode(String quarkusVersion) { @@ -337,11 +337,7 @@ public int getCompatibilityCode(String quarkusVersion, String upstreamQuarkusVer Integer i = compatibilityCodes.get(quarkusVersion); if (i == null) { if (upstreamQuarkusVersion != null) { - i = compatibilityCodes.get(upstreamQuarkusVersion); - if (i == null) { - i = compatibilityCodes.size(); - compatibilityCodes.put(upstreamQuarkusVersion, i); - } + i = compatibilityCodes.computeIfAbsent(upstreamQuarkusVersion, cc -> compatibilityCodes.size()); } else { i = compatibilityCodes.size(); } @@ -356,6 +352,16 @@ void appendAllNonPlatformExtensions() throws RegistryResolutionException { } } + void addUpstreamExtensionCatalogs(String quarkusCoreVersion, Set processedPlatformKeys) + throws RegistryResolutionException { + collectPlatformExtensions(quarkusCoreVersion, this, processedPlatformKeys); + int i = 0; + while (i < upstreamQuarkusVersions.size()) { + collectPlatformExtensions(upstreamQuarkusVersions.get(i++), this, processedPlatformKeys); + } + upstreamQuarkusVersions.clear(); + } + ExtensionCatalog build() throws RegistryResolutionException { appendAllNonPlatformExtensions(); if (catalogs.isEmpty()) { @@ -474,12 +480,12 @@ public ExtensionCatalog resolveExtensionCatalog(String quarkusCoreVersion) throw } private ExtensionCatalog resolveExtensionCatalog(String quarkusCoreVersion, - final ExtensionCatalogBuilder catalogBuilder, Set preferredPlatforms) + final ExtensionCatalogBuilder catalogBuilder, Set preferredPlatformKeys) throws RegistryResolutionException { - collectPlatformExtensions(quarkusCoreVersion, catalogBuilder, preferredPlatforms); + collectPlatformExtensions(quarkusCoreVersion, catalogBuilder, preferredPlatformKeys); int i = 0; while (i < catalogBuilder.upstreamQuarkusVersions.size()) { - collectPlatformExtensions(catalogBuilder.upstreamQuarkusVersions.get(i++), catalogBuilder, preferredPlatforms); + collectPlatformExtensions(catalogBuilder.upstreamQuarkusVersions.get(i++), catalogBuilder, preferredPlatformKeys); } return catalogBuilder.build(); } @@ -489,11 +495,11 @@ public ExtensionCatalog resolveExtensionCatalog(PlatformStreamCoords streamCoord ensureRegistriesConfigured(); final PlatformStream stream = findPlatformStreamOrFail(streamCoords); - final List catalogs = new ArrayList<>(); + ExtensionCatalogBuilder catalogBuilder = new ExtensionCatalogBuilder(); for (PlatformRelease release : stream.getReleases()) { - catalogs.add(resolveExtensionCatalog(release.getMemberBoms())); + collectExtensionCatalogs(release.getMemberBoms(), catalogBuilder); } - return CatalogMergeUtility.merge(catalogs); + return catalogBuilder.build(); } protected PlatformStream findPlatformStreamOrFail(PlatformStreamCoords streamCoords) @@ -556,15 +562,13 @@ protected RegistryResolutionException unknownStreamException(PlatformStreamCoord break; } } - for (Platform platform : platforms.getPlatforms()) { - knownPlatforms.add(platform); - } + knownPlatforms.addAll(platforms.getPlatforms()); } final StringBuilder buf = new StringBuilder(); if (requestedPlatform != null) { buf.append("Failed to locate stream ").append(stream.getStreamId()) - .append(" in platform " + requestedPlatform.getPlatformKey()); + .append(" in platform ").append(requestedPlatform.getPlatformKey()); } else if (knownPlatforms.isEmpty()) { buf.append("None of the registries provided any platform"); } else { @@ -582,18 +586,27 @@ protected RegistryResolutionException unknownStreamException(PlatformStreamCoord return new RegistryResolutionException(buf.toString()); } - @SuppressWarnings("unchecked") public ExtensionCatalog resolveExtensionCatalog(Collection preferredPlatforms) throws RegistryResolutionException { if (preferredPlatforms.isEmpty()) { return resolveExtensionCatalog(); } - final ExtensionCatalogBuilder catalogBuilder = new ExtensionCatalogBuilder(); - final Set preferredPlatformKeys = new HashSet<>(); + collectExtensionCatalogs(preferredPlatforms, catalogBuilder); + return catalogBuilder.build(); + } + + @SuppressWarnings("unchecked") + private void collectExtensionCatalogs(Collection preferredPlatforms, ExtensionCatalogBuilder catalogBuilder) + throws RegistryResolutionException { + final Set preferredPlatformKeys = new HashSet<>(4); + final Set addedPlatformBoms = new HashSet<>(); String quarkusVersion = null; int platformIndex = 0; for (ArtifactCoords bom : preferredPlatforms) { + if (!addedPlatformBoms.add(bom)) { + continue; + } final List registries; try { registries = filterRegistries(r -> r.checkPlatform(bom)); @@ -657,12 +670,11 @@ public ExtensionCatalog resolveExtensionCatalog(Collection prefe if (!preferredPlatformKeys.add(platformKey)) { continue; } - final Platform.Mutable p = Platform.builder() - .setPlatformKey(platformKey); + final Platform.Mutable platform = Platform.builder().setPlatformKey(platformKey); final PlatformStream.Mutable stream = PlatformStream.builder() .setId(String.valueOf(md.getOrDefault("stream", "default"))); - p.addStream(stream); + platform.addStream(stream); final PlatformRelease.Mutable release = PlatformRelease.builder() .setVersion(PlatformReleaseVersion @@ -674,14 +686,16 @@ public ExtensionCatalog resolveExtensionCatalog(Collection prefe o = md.get("members"); if (o != null) { final Collection col = (Collection) o; - final List coords = new ArrayList<>(col.size()); + final List memberCatalogs = new ArrayList<>(col.size()); for (String s : col) { - coords.add(ArtifactCoords.fromString(s)); + var memberCatalogCoords = ArtifactCoords.fromString(s); + memberCatalogs.add(memberCatalogCoords); + addedPlatformBoms.add(PlatformArtifacts.getBomArtifactForCatalog(memberCatalogCoords)); } - release.setMemberBoms(coords); + release.setMemberBoms(memberCatalogs); } - collectPlatformExtensions(quarkusVersion, catalogBuilder, registry, platformIndex, p); + collectPlatformExtensions(catalogBuilder, registry, platformIndex, platform); continue; } } @@ -709,10 +723,8 @@ public ExtensionCatalog resolveExtensionCatalog(Collection prefe } throw new RegistryResolutionException(buf.toString()); } - return catalogBuilder.catalogs.isEmpty() ? null : catalogBuilder.build(); } - return preferredPlatforms.isEmpty() ? catalogBuilder.build() - : resolveExtensionCatalog(quarkusVersion, catalogBuilder, preferredPlatformKeys); + catalogBuilder.addUpstreamExtensionCatalogs(quarkusVersion, preferredPlatformKeys); } public void clearRegistryCache() throws RegistryResolutionException { @@ -736,7 +748,7 @@ private void appendNonPlatformExtensions( } } - public void appendNonPlatformExtensions(RegistryExtensionResolver registry, ExtensionCatalogBuilder catalogBuilder, + private void appendNonPlatformExtensions(RegistryExtensionResolver registry, ExtensionCatalogBuilder catalogBuilder, String quarkusVersion) throws RegistryResolutionException { final ExtensionCatalog.Mutable nonPlatformCatalog = registry.resolveNonPlatformExtensions(quarkusVersion); if (nonPlatformCatalog == null) { @@ -766,7 +778,7 @@ private int getRegistryIndex(String registryId) { } private void collectPlatformExtensions(String quarkusCoreVersion, ExtensionCatalogBuilder catalogBuilder, - Set processedPlatforms) + Set processedPlatformKeys) throws RegistryResolutionException { final List quarkusVersionRegistries = catalogBuilder .getRegistriesForQuarkusCore(quarkusCoreVersion); @@ -780,18 +792,18 @@ private void collectPlatformExtensions(String quarkusCoreVersion, ExtensionCatal if (platforms.isEmpty()) { continue; } - int platformIndex = processedPlatforms.size(); + int platformIndex = processedPlatformKeys.size(); for (Platform p : platforms) { - if (processedPlatforms.contains(p.getPlatformKey())) { + if (processedPlatformKeys.contains(p.getPlatformKey())) { continue; } ++platformIndex; - collectPlatformExtensions(quarkusCoreVersion, catalogBuilder, registry, platformIndex, p); + collectPlatformExtensions(catalogBuilder, registry, platformIndex, p); } } } - private void collectPlatformExtensions(String quarkusCoreVersion, ExtensionCatalogBuilder catalogBuilder, + private void collectPlatformExtensions(ExtensionCatalogBuilder catalogBuilder, RegistryExtensionResolver registry, int platformIndex, Platform p) throws RegistryResolutionException { @@ -880,6 +892,6 @@ private List filterRegistries(Function