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 5020bbbdb76a93..dd985579bed4e3 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 @@ -44,6 +44,7 @@ import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.maven.dependency.DependencyFlags; import io.quarkus.maven.dependency.ResolvableDependency; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.maven.dependency.ResolvedDependencyBuilder; @@ -327,7 +328,7 @@ private ApplicationModel buildAppModel(ResolvedDependency appArtifact, } directDeps = filtered; } - var parallel = true; //Boolean.getBoolean("parallel"); + var parallel = Boolean.getBoolean("parallel"); var collectRtDepsRequest = MavenArtifactResolver.newCollectRequest(artifact, directDeps, managedDeps, List.of(), repos); try { if (parallel) { @@ -352,7 +353,11 @@ private ApplicationModel buildAppModel(ResolvedDependency appArtifact, "Failed to inject extension deployment dependencies for " + appArtifact.toCompactCoords(), e); } - return appBuilder.build(); + var model = appBuilder.build(); + for (var d : model.getDependenciesWithAnyFlag(DependencyFlags.DIRECT)) { + System.out.println("- " + d.toCompactCoords()); + } + return model; } private io.quarkus.maven.dependency.ResolvedDependency resolve(ArtifactCoords appArtifact, Artifact mvnArtifact, diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java index 5bf0e8315f0cb3..999c94f7410fc9 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java @@ -132,9 +132,9 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver this.managedDeps = collectRtDepsRequest.getManagedDependencies(); DependencyNode root = resolveRuntimeDeps(collectRtDepsRequest); boolean doLog = false; - if (doLog) + if (doLog) { System.out.println("Resolve runtime " + (System.currentTimeMillis() - start)); - + } this.managedDeps = managedDeps.isEmpty() ? new ArrayList<>() : managedDeps; var current = System.currentTimeMillis(); @@ -181,9 +181,10 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver processDeploymentDeps(root); - new BuildDependencyGraphVisitor(resolver, appBuilder, buildTreeConsumer).visit(root); - if (doLog) + //new BuildDependencyGraphVisitor(resolver, appBuilder, buildTreeConsumer).visit(root); + if (doLog) { System.out.println("Added deployment " + (System.currentTimeMillis() - current)); + } for (var d : appBuilder.getDependencies()) { if (!d.isFlagSet(DependencyFlags.RELOADABLE) && !d.isFlagSet(DependencyFlags.VISITED)) { @@ -202,8 +203,9 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver collectPlatformProperties(); collectCompileOnly(collectRtDepsRequest, root); - if (doLog) + if (doLog) { System.out.println("TOTAL: " + (System.currentTimeMillis() - start)); + } } private void processDeploymentDeps(DependencyNode root) { @@ -224,6 +226,10 @@ private void processDeploymentDeps(DependencyNode root) { for (var d : app.children) { d.addToModel(); } + + if (buildTreeConsumer != null) { + new AppDepLogger(buildTreeConsumer).process(app); + } } private void injectDeployment(List activatedConditionalDeps) { @@ -231,19 +237,33 @@ private void injectDeployment(List activatedConditionalDe int i = 0; for (ExtensionDependency extDep : topExtensionDeps) { futures[i++] = CompletableFuture.runAsync(() -> { - try { - injectDeploymentDependencies(extDep); - } catch (BootstrapDependencyProcessingException e) { - throw new RuntimeException(e); + var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact)); + if (resolvedDep == null) { + try { + injectDeploymentDependencies(extDep); + } catch (BootstrapDependencyProcessingException e) { + throw new RuntimeException(e); + } + } else { + // if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath + // in which case we also clear the reloadable flag on it, in case it's coming from the workspace + resolvedDep.clearFlag(DependencyFlags.RELOADABLE); } }); } for (ConditionalDependency cd : activatedConditionalDeps) { futures[i++] = CompletableFuture.runAsync(() -> { - try { - injectDeploymentDependencies(cd.getExtensionDependency()); - } catch (BootstrapDependencyProcessingException e) { - throw new RuntimeException(e); + var resolvedDep = appBuilder.getDependency(getKey(cd.getExtensionDependency().info.deploymentArtifact)); + if (resolvedDep == null) { + try { + injectDeploymentDependencies(cd.getExtensionDependency()); + } catch (BootstrapDependencyProcessingException e) { + throw new RuntimeException(e); + } + } else { + // if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath + // in which case we also clear the reloadable flag on it, in case it's coming from the workspace + resolvedDep.clearFlag(DependencyFlags.RELOADABLE); } }); } @@ -366,6 +386,15 @@ private DependencyNode resolveRuntimeDeps(CollectRequest request) mutableSession.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true); mutableSession.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); session = mutableSession; + + var ctx = new BootstrapMavenContext(BootstrapMavenContext.config() + .setRepositorySystem(resolver.getSystem()) + .setRepositorySystemSession(session) + .setRemoteRepositories(resolver.getRepositories()) + .setRemoteRepositoryManager(resolver.getRemoteRepositoryManager()) + .setCurrentProject(resolver.getMavenContext().getCurrentProject())); + resolver = new MavenArtifactResolver(ctx); + try { return resolver.getSystem().collectDependencies(session, request).getRoot(); } catch (DependencyCollectionException e) { @@ -1032,4 +1061,66 @@ private static String toCompactCoords(Artifact a) { b.append(a.getVersion()); return b.toString(); } + + private static class AppDepLogger { + + private final Consumer consumer; + final List depth = new ArrayList<>(); + + private AppDepLogger(Consumer consumer) { + this.consumer = consumer; + } + + public void process(AppDep dep) { + consume(dep); + + final int childrenTotal = dep.children.size(); + if (childrenTotal > 0) { + if (childrenTotal == 1) { + depth.add(false); + process(dep.children.get(0)); + } else { + depth.add(true); + int i = 0; + while (i < childrenTotal) { + process(dep.children.get(i++)); + if (i == childrenTotal - 1) { + depth.set(depth.size() - 1, false); + } + } + } + depth.remove(depth.size() - 1); + } + } + + private void consume(AppDep dep) { + var buf = new StringBuilder(); + if (!depth.isEmpty()) { + for (int i = 0; i < depth.size() - 1; ++i) { + if (depth.get(i)) { + //buf.append("| "); + buf.append('\u2502').append(" "); + } else { + buf.append(" "); + } + } + if (depth.get(depth.size() - 1)) { + //buf.append("|- "); + buf.append('\u251c').append('\u2500').append(' '); + } else { + //buf.append("\\- "); + buf.append('\u2514').append('\u2500').append(' '); + } + } + buf.append(dep.node.getArtifact()); + if (!depth.isEmpty()) { + buf.append(" (").append(dep.node.getDependency().getScope()); + if (dep.node.getDependency().isOptional()) { + buf.append(" optional"); + } + buf.append(')'); + } + consumer.accept(buf.toString()); + } + } } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java index e802e4eb61b397..81a4bce6cede73 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java @@ -248,6 +248,7 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver * @throws BootstrapMavenException in case of a failure */ private void collectCompileOnly(CollectRequest collectRtDepsRequest, DependencyNode root) throws BootstrapMavenException { + System.out.println("ApplicationDependencyTreeResolver.collectCompileOnly " + collectCompileOnly); if (collectCompileOnly.isEmpty()) { return; } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/GraphToTree.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/GraphToTree.java new file mode 100644 index 00000000000000..02864f4555c1a9 --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/GraphToTree.java @@ -0,0 +1,211 @@ +package io.quarkus.bootstrap.resolver.maven; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositoryException; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.DependencyGraphTransformationContext; +import org.eclipse.aether.collection.DependencyGraphTransformer; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; + +import io.quarkus.maven.dependency.ArtifactKey; + +public class GraphToTree { + + private static final Map nodes = new HashMap<>(); + + public static void main(String[] args) throws Exception { + + var resolver = getResolver(); + + var root = resolver + .collectDependencies(new DefaultArtifact("io.quarkus", "quarkus-vertx-http", "jar", "3.7.1"), List.of()) + .getRoot(); + + //toNode(root, 0); + /* + * for(var node : nodes.values()) { + * log(node.winner.node.getArtifact() + " / " + node.winningDepth); + * for(var v : node.variations) { + * log("- " + v.node.getArtifact() + " / " + v.depth); + * } + * } + */ + + //logDep(root); + } + + private static Node toNode(NodeWithParent node, IdentityHashMap visited) { + var n = nodes.computeIfAbsent(getKey(node.node), Node::new); + n.add(node); + for (var c : node.node.getChildren()) { + if (c.getData().containsKey(ConflictResolver.NODE_DATA_WINNER)) { + continue; + } + var np = new NodeWithParent(c, node); + toNode(np, visited); + } + return n; + } + + private static void logDep(DependencyNode node) { + logDep(node, 0); + } + + private static void logDep(DependencyNode node, int depth) { + var sb = new StringBuilder(); + for (int i = 0; i < depth; ++i) { + sb.append(" "); + } + var a = node.getArtifact(); + sb.append(a.getGroupId()).append(':').append(a.getArtifactId()).append(':'); + if (!a.getClassifier().isEmpty()) { + sb.append(a.getClassifier()).append(':'); + } + if (!a.getExtension().equals("jar")) { + sb.append(a.getExtension()).append(':'); + } + sb.append(a.getVersion()).append(" ").append(node.hashCode()); + log(sb); + for (var c : node.getChildren()) { + if (c.getData().containsKey(ConflictResolver.NODE_DATA_WINNER)) { + continue; + } + logDep(c, depth + 1); + } + } + + private static MavenArtifactResolver getResolver() throws BootstrapMavenException { + var resolver = MavenArtifactResolver.builder() + .setWorkspaceDiscovery(false) + .build(); + var session = new DefaultRepositorySystemSession(resolver.getSession()); + session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true); + session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); + var originalGraphTransformer = session.getDependencyGraphTransformer(); + session.setDependencyGraphTransformer(new DependencyGraphTransformer() { + @Override + public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext ctx) + throws RepositoryException { + toNode(new NodeWithParent(node, null), new IdentityHashMap<>()); + for (var n : nodes.values()) { + n.rewire(); + } + logDep(node); + return originalGraphTransformer.transformGraph(node, ctx); + } + }); + resolver = MavenArtifactResolver.builder() + .setRepositorySystem(resolver.getSystem()) + .setRepositorySystemSession(session) + .setRemoteRepositoryManager(resolver.getRemoteRepositoryManager()) + .setRemoteRepositories(resolver.getRepositories()) + .build(); + return resolver; + } + + private static void log(Object o) { + System.out.println(o); + } + + private static ArtifactKey getKey(DependencyNode node) { + var a = node.getArtifact(); + return ArtifactKey.of(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension()); + } + + private static boolean isSameKey(DependencyNode n1, DependencyNode n2) { + var a1 = n1.getArtifact(); + var a2 = n2.getArtifact(); + return a1.getArtifactId().equals(a2.getArtifactId()) + && a1.getGroupId().equals(a2.getGroupId()) + && a1.getClassifier().equals(a2.getClassifier()) + && a1.getExtension().equals(a2.getExtension()); + } + + private static class NodeWithParent { + final NodeWithParent parent; + final DependencyNode node; + final int depth; + + private NodeWithParent(DependencyNode node, NodeWithParent parent) { + this.parent = parent; + this.node = node; + this.depth = parent == null ? 0 : parent.depth + 1; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + NodeWithParent that = (NodeWithParent) o; + return (parent == null ? that.parent == null : parent == that.parent) + && node == that.node; + } + + @Override + public int hashCode() { + return Objects.hash(parent, node); + } + } + + private static class Node { + final ArtifactKey key; + final List variations = new ArrayList<>(); + int winningDepth; + NodeWithParent winner; + + private Node(ArtifactKey key) { + this.key = key; + } + + private void add(NodeWithParent node) { + if (winner == null) { + winner = node; + winningDepth = node.depth; + } else if (winningDepth > node.depth) { + variations.add(winner); + winner = node; + winningDepth = node.depth; + } else { + variations.add(node); + } + } + + private void rewire() { + if (winner.parent == null) { + return; + } + var winningParent = nodes.get(getKey(winner.parent.node)); + if (winningParent.winner == winner.parent) { + return; + } + var parentNode = winningParent.winner.node; + var children = parentNode.getChildren(); + for (int i = 0; i < children.size(); ++i) { + var child = children.get(i); + if (isSameKey(child, winner.node)) { + children.set(i, winner.node); + break; + } + } + for (var eliminated : variations) { + if (winningParent.winner.node == eliminated.parent.node) { + continue; + } + if (!eliminated.parent.node.getChildren().isEmpty()) { + eliminated.parent.node.setChildren(List.of()); + } + } + } + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java index 42cf4b4290f538..c56d326a8743b0 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java @@ -155,6 +155,6 @@ public static ResolvedDependencyBuilder toAppArtifact(Artifact artifact, Workspa } public static boolean hasWinner(DependencyNode node) { - return node.getData().containsKey(ConflictResolver.NODE_DATA_WINNER); + return node.getData().containsKey(ConflictResolver.NODE_DATA_WINNER) && node.getChildren().isEmpty(); } }