From 08b61d72182de747f44e038729bec2fa7204d4ac Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Wed, 11 Dec 2024 13:04:39 +0100 Subject: [PATCH] wip --- .../runnerjar/OptionalDepsTest.java | 70 ++++++++- .../resolver/BootstrapAppModelResolver.java | 2 +- .../bootstrap/resolver/maven/DepNode.java | 65 ++++++++ .../IncubatingApplicationModelResolver.java | 57 ++++++- .../maven/OrderedDependencyVisitor.java | 11 ++ .../maven/OrderedDependencyVisitorTest.java | 141 ++++++++++++++++++ 6 files changed, 333 insertions(+), 13 deletions(-) create mode 100644 independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DepNode.java diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java index 949b145da1b7b..2d64701a4593c 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java @@ -5,14 +5,16 @@ import java.util.HashSet; import java.util.Set; +import org.eclipse.aether.util.artifact.JavaScopes; + import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsDependency; import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactDependency; import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.DependencyFlags; -import io.quarkus.maven.dependency.GACTV; public class OptionalDepsTest extends BootstrapFromOriginalJarTestBase { @@ -70,17 +72,73 @@ protected TsArtifact composeApplication() { @Override protected void assertAppModel(ApplicationModel model) throws Exception { final Set expected = new HashSet<>(); - expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), "compile", + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.DIRECT, + DependencyFlags.OPTIONAL, + DependencyFlags.RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.RUNTIME_CP, + DependencyFlags.DEPLOYMENT_CP)); + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-dep", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.OPTIONAL, + DependencyFlags.RUNTIME_CP, + DependencyFlags.DEPLOYMENT_CP)); + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.OPTIONAL, + DependencyFlags.DEPLOYMENT_CP)); + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "app-optional-dep", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.OPTIONAL, + DependencyFlags.DIRECT, + DependencyFlags.RUNTIME_CP, DependencyFlags.DEPLOYMENT_CP)); - expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-b-deployment-dep", "1"), "compile", + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.OPTIONAL, + DependencyFlags.RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.RUNTIME_CP, + DependencyFlags.DEPLOYMENT_CP)); + + expected.add(new ArtifactDependency(ArtifactCoords.jar( + TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.OPTIONAL, DependencyFlags.DEPLOYMENT_CP)); - expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-b-deployment", "1"), "compile", + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment-dep", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.OPTIONAL, DependencyFlags.DEPLOYMENT_CP)); - expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-d-deployment", "1"), "compile", + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-d", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.DIRECT, + DependencyFlags.RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.RUNTIME_CP, DependencyFlags.DEPLOYMENT_CP)); - assertEquals(expected, getDeploymentOnlyDeps(model)); + + expected.add(new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP)); + + assertEquals(expected, getDependenciesWithFlag(model, DependencyFlags.DEPLOYMENT_CP)); } } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java index 2bc13f5a18ccd..a9d478fba3c73 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java @@ -370,7 +370,7 @@ private ApplicationModel buildAppModel(ResolvedDependencyBuilder appArtifact, if (logTime) { start = System.currentTimeMillis(); } - if (incubatingModelResolver) { + if (true) { IncubatingApplicationModelResolver.newInstance() .setArtifactResolver(mvn) .setApplicationModelBuilder(appBuilder) diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DepNode.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DepNode.java new file mode 100644 index 0000000000000..5f4e8afd780eb --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DepNode.java @@ -0,0 +1,65 @@ +package io.quarkus.bootstrap.resolver.maven; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.aether.graph.DependencyNode; + +import io.quarkus.maven.dependency.ArtifactCoords; + +class DepNode { + + static DepNode of(DependencyNode node) { + return new DepNode(node); + } + + final DepNode parent; + final ArtifactCoords coords; + final List deps; + + DepNode(DependencyNode node) { + this(null, node); + } + + DepNode(DepNode parent, DependencyNode node) { + this.parent = parent; + var a = node.getArtifact(); + this.coords = ArtifactCoords.of(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension(), a.getVersion()); + if (node.getChildren().isEmpty()) { + deps = List.of(); + } else { + deps = new ArrayList<>(node.getChildren().size()); + for (var d : node.getChildren()) { + deps.add(new DepNode(this, d)); + } + } + } + + void assertEquals(DepNode other) { + if (!other.coords.equals(coords)) { + throw new RuntimeException(); + } + int i = 0; + while (i < deps.size() && i < other.deps.size()) { + deps.get(i).assertEquals(other.deps.get(i)); + ++i; + } + if (i < deps.size()) { + throw new RuntimeException(); + } + if (i < other.deps.size()) { + throw new RuntimeException(); + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof DepNode other) { + if (!other.coords.equals(coords)) { + return false; + } + return deps.equals(other.deps); + } + return false; + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java index 23fac1e5f4725..85bd9e5d5964d 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java @@ -16,10 +16,12 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.function.BiConsumer; @@ -251,7 +253,8 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver if (!runtimeModelOnly) { injectDeploymentDeps(); } - root = normalize(resolver.getSession(), root); + root = newNormalize(root); + //root = normalize(resolver.getSession(), root); populateModelBuilder(root); // clear the reloadable flags @@ -476,6 +479,24 @@ private static DependencyNode normalize(RepositorySystemSession session, Depende } } + private static DependencyNode newNormalize(DependencyNode root) { + //var start = System.currentTimeMillis(); + var visitor = new OrderedDependencyVisitor(root); + final Set visited = new HashSet<>(); + while (visitor.hasNext()) { + var node = visitor.next(); + if (hasWinner(node)) { + continue; + } + var key = getKey(node.getArtifact()); + if (!visited.add(key)) { + visitor.setNearerExists(); + } + } + //System.out.println("newNormalize " + (System.currentTimeMillis() - start)); + return root; + } + /** * Resolves a project's runtime dependencies. This is the first step in the Quarkus application model resolution. * These dependencies do not include Quarkus conditional dependencies. @@ -977,6 +998,8 @@ private void collectDeploymentDeps() { + "or the artifact does not have any dependencies while at least a dependency on the runtime artifact " + info.runtimeArtifact + " is expected"); } + ensureScopeAndOptionality(deploymentNode, runtimeNode.getDependency().getScope(), + runtimeNode.getDependency().isOptional()); replaceRuntimeExtensionNodes(deploymentNode); if (!presentInTargetGraph) { @@ -998,6 +1021,7 @@ private void injectDependencyDependency(DependencyNode parentDeploymentNode) { } void replaceRuntimeExtensionNodes(DependencyNode deploymentNode) { + final boolean setOptional = this.runtimeNode.getDependency().isOptional(); var deploymentVisitor = new OrderedDependencyVisitor(deploymentNode); // skip the root node deploymentVisitor.next(); @@ -1058,9 +1082,13 @@ void activate() { return; } activated = true; + final AppDep parent = conditionalDep.parent; final DependencyNode originalNode = collectDependencies(conditionalDep.node.getArtifact(), - conditionalDep.parent.ext.exclusions, - conditionalDep.parent.node.getRepositories()); + parent.ext.exclusions, + parent.node.getRepositories()); + ensureScopeAndOptionality(originalNode, parent.ext.runtimeNode.getDependency().getScope(), + parent.ext.runtimeNode.getDependency().isOptional()); + final DefaultDependencyNode rtNode = (DefaultDependencyNode) conditionalDep.node; rtNode.setRepositories(originalNode.getRepositories()); // if this node has conditional dependencies on its own, they may have been activated by this time @@ -1077,10 +1105,10 @@ void activate() { visitRuntimeDeps(); conditionalDep.setFlags( (byte) (COLLECT_DEPLOYMENT_INJECTION_POINTS | (collectReloadableModules ? COLLECT_RELOADABLE_MODULES : 0))); - if (conditionalDep.parent.resolvedDep != null) { - conditionalDep.parent.resolvedDep.addDependency(conditionalDep.resolvedDep.getArtifactCoords()); + if (parent.resolvedDep != null) { + parent.resolvedDep.addDependency(conditionalDep.resolvedDep.getArtifactCoords()); } - conditionalDep.parent.ext.runtimeNode.getChildren().add(rtNode); + parent.ext.runtimeNode.getChildren().add(rtNode); } private void visitRuntimeDeps() { @@ -1103,6 +1131,23 @@ boolean isSatisfied() { } } + private static void ensureScopeAndOptionality(DependencyNode node, String scope, boolean optional) { + var dep = node.getDependency(); + if (optional == dep.isOptional() && scope.equals(dep.getScope())) { + return; + } + var visitor = new OrderedDependencyVisitor(node); + while (visitor.hasNext()) { + dep = visitor.next().getDependency(); + if (optional != dep.isOptional()) { + visitor.getCurrent().setOptional(optional); + } + if (!scope.equals(dep.getScope())) { + visitor.getCurrent().setScope(scope); + } + } + } + private static boolean isSameKey(Artifact a1, Artifact a2) { return a2.getArtifactId().equals(a1.getArtifactId()) && a2.getGroupId().equals(a1.getGroupId()) diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java index ae23a0f8c98c6..16a8b646463d4 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java @@ -5,7 +5,9 @@ import java.util.List; import java.util.NoSuchElementException; +import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; /** * Walks a dependency tree by visiting dependencies in the order of their priorities @@ -102,4 +104,13 @@ DependencyNode next() { void replaceCurrent(DependencyNode newNode) { currentList.set(currentIndex, newNode); } + + /** + * Adds a link to a "winning" node, indicating that a nearer dependency on the artifact exists in the graph + */ + void setNearerExists() { + final DependencyNode currentNode = currentList.get(currentIndex); + currentNode.setChildren(List.of()); + currentNode.setData(ConflictResolver.NODE_DATA_WINNER, new DefaultDependencyNode(currentNode.getDependency())); + } } diff --git a/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java b/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java index b77d7ef1d1932..83de964f218a5 100644 --- a/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java +++ b/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java @@ -8,6 +8,7 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; import org.junit.jupiter.api.Test; public class OrderedDependencyVisitorTest { @@ -133,6 +134,146 @@ public void main() { assertThat(visitor.hasNext()).isFalse(); } + @Test + public void testRemove() { + + var root = newNode("root"); + + // direct dependencies + var colors = newNode("colors"); + var pets = newNode("pets"); + var trees = newNode("trees"); + root.setChildren(List.of(colors, pets, trees)); + + // colors + var red = newNode("red"); + var green = newNode("green"); + var blue = newNode("blue"); + colors.setChildren(List.of(red, green, blue)); + + // pets + var dog = newNode("dog"); + var cat = newNode("cat"); + pets.setChildren(List.of(dog, cat)); + // pets, puppy + var puppy = newNode("puppy"); + dog.setChildren(List.of(puppy)); + + // trees + var pine = newNode("pine"); + trees.setChildren(Arrays.asList(pine)); // List.of() can't be used for replace + var oak = newNode("oak"); + // oak, acorn + var acorn = newNode("acorn"); + oak.setChildren(List.of(acorn)); + + // create a visitor + var visitor = new OrderedDependencyVisitor(root); + + // assertions + assertThat(visitor.hasNext()).isTrue(); + + // distance 0 + assertThat(visitor.next()).isSameAs(root); + assertThat(visitor.getCurrent()).isSameAs(root); + assertThat(visitor.getCurrentDistance()).isEqualTo(0); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, colors + assertThat(visitor.next()).isSameAs(colors); + assertThat(visitor.getCurrent()).isSameAs(colors); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + visitor.setNearerExists(); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, pets + assertThat(visitor.next()).isSameAs(pets); + assertThat(visitor.getCurrent()).isSameAs(pets); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, trees + var node = visitor.next(); + assertThat(node).isSameAs(trees); + assertThat(visitor.getCurrent()).isSameAs(trees); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + assertThat(node.getData().get(ConflictResolver.NODE_DATA_WINNER)).isNull(); + visitor.setNearerExists(); + assertThat(node.getData().get(ConflictResolver.NODE_DATA_WINNER)).isNotNull(); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, pets, dog + assertThat(visitor.next()).isSameAs(dog); + assertThat(visitor.getCurrent()).isSameAs(dog); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, pets, cat + assertThat(visitor.next()).isSameAs(cat); + assertThat(visitor.getCurrent()).isSameAs(cat); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 3, pets, dog, puppy + assertThat(visitor.next()).isSameAs(puppy); + assertThat(visitor.getCurrent()).isSameAs(puppy); + assertThat(visitor.getCurrentDistance()).isEqualTo(3); + assertThat(visitor.hasNext()).isFalse(); + + // re-iterate to make sure the changes applied on the first visit were applied + + visitor = new OrderedDependencyVisitor(root); + assertThat(visitor.hasNext()).isTrue(); + + // distance 0 + assertThat(visitor.next()).isSameAs(root); + assertThat(visitor.getCurrent()).isSameAs(root); + assertThat(visitor.getCurrentDistance()).isEqualTo(0); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, colors + node = visitor.next(); + assertThat(node.getData().get(ConflictResolver.NODE_DATA_WINNER)).isNotNull(); + assertThat(node).isSameAs(colors); + assertThat(visitor.getCurrent()).isSameAs(colors); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, pets + assertThat(visitor.next()).isSameAs(pets); + assertThat(visitor.getCurrent()).isSameAs(pets); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, trees + node = visitor.next(); + assertThat(node.getData().get(ConflictResolver.NODE_DATA_WINNER)).isNotNull(); + assertThat(node).isSameAs(trees); + assertThat(visitor.getCurrent()).isSameAs(trees); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, pets, dog + assertThat(visitor.next()).isSameAs(dog); + assertThat(visitor.getCurrent()).isSameAs(dog); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, pets, cat + assertThat(visitor.next()).isSameAs(cat); + assertThat(visitor.getCurrent()).isSameAs(cat); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 3, pets, dog, puppy + assertThat(visitor.next()).isSameAs(puppy); + assertThat(visitor.getCurrent()).isSameAs(puppy); + assertThat(visitor.getCurrentDistance()).isEqualTo(3); + assertThat(visitor.hasNext()).isFalse(); + } + private static DependencyNode newNode(String artifactId) { return new DefaultDependencyNode(new DefaultArtifact(ORG_ACME, artifactId, JAR, VERSION)); }