From 99c9c06af39438b48faf76b1bcae6f0c1e4f7f79 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 17 Apr 2024 20:51:40 +0200 Subject: [PATCH] [MRESOLVER-535][MRESOLVER-538] Add decorator ability to graph dumper; show ranges (#464) Ability to pass in any function that is able to "decorate" the print out of the graph. This should have a follow up Pr that enable/disable (or simply move all into decorators). Also, verbose tree should show use of version ranges, for cases when someone wants to detect them in whole transitive hull. --- https://issues.apache.org/jira/browse/MRESOLVER-535 https://issues.apache.org/jira/browse/MRESOLVER-538 --- .../examples/GetDependencyHierarchy.java | 12 +- .../graph/visitor/DependencyGraphDumper.java | 271 +++++++++++++----- 2 files changed, 213 insertions(+), 70 deletions(-) diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java index 6b39fc3ce..28f6c6700 100644 --- a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java +++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/GetDependencyHierarchy.java @@ -18,7 +18,7 @@ */ package org.apache.maven.resolver.examples; -import java.util.Arrays; +import java.util.Collections; import org.apache.maven.resolver.examples.util.Booter; import org.eclipse.aether.RepositorySystem; @@ -34,6 +34,9 @@ import org.eclipse.aether.util.graph.transformer.ConflictResolver; import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper; +import static org.eclipse.aether.util.graph.visitor.DependencyGraphDumper.artifactProperties; +import static org.eclipse.aether.util.graph.visitor.DependencyGraphDumper.defaultsWith; + /** * Visualizes the transitive dependencies of an artifact similar to m2e's dependency hierarchy view. */ @@ -68,7 +71,12 @@ public static void main(String[] args) throws Exception { CollectResult collectResult = system.collectDependencies(session, collectRequest); - collectResult.getRoot().accept(new DependencyGraphDumper(System.out::println, Arrays.asList("color"))); + collectResult + .getRoot() + .accept(new DependencyGraphDumper( + System.out::println, + defaultsWith( + Collections.singleton(artifactProperties(Collections.singleton("color")))))); } } } diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java index 3d3d15b55..2f379570c 100644 --- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/visitor/DependencyGraphDumper.java @@ -20,13 +20,16 @@ import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.aether.artifact.Artifact; @@ -37,6 +40,7 @@ import org.eclipse.aether.util.artifact.ArtifactIdUtils; import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; import org.eclipse.aether.util.graph.transformer.ConflictResolver; +import org.eclipse.aether.version.VersionConstraint; import static java.util.Objects.requireNonNull; @@ -47,28 +51,216 @@ * @since 1.9.8 */ public class DependencyGraphDumper implements DependencyVisitor { + /** + * Decorator of "effective dependency": shows effective scope and optionality. + */ + public static Function effectiveDependency() { + return dependencyNode -> { + Dependency d = dependencyNode.getDependency(); + if (d != null) { + if (!d.getScope().isEmpty()) { + String result = d.getScope(); + if (d.isOptional()) { + result += ", optional"; + } + return "[" + result + "]"; + } + } + return null; + }; + } + /** + * Decorator of "managed version": explains on nodes what was managed. + */ + public static Function premanagedVersion() { + return dependencyNode -> { + if (dependencyNode.getArtifact() != null) { + String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(dependencyNode); + if (premanagedVersion != null + && !premanagedVersion.equals( + dependencyNode.getArtifact().getBaseVersion())) { + return "(version managed from " + premanagedVersion + ")"; + } + } + return null; + }; + } + /** + * Decorator of "managed scope": explains on nodes what was managed. + */ + public static Function premanagedScope() { + return dependencyNode -> { + Dependency d = dependencyNode.getDependency(); + if (d != null) { + String premanagedScope = DependencyManagerUtils.getPremanagedScope(dependencyNode); + if (premanagedScope != null && !premanagedScope.equals(d.getScope())) { + return "(scope managed from " + premanagedScope + ")"; + } + } + return null; + }; + } + /** + * Decorator of "managed optionality": explains on nodes what was managed. + */ + public static Function premanagedOptional() { + return dependencyNode -> { + Dependency d = dependencyNode.getDependency(); + if (d != null) { + Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(dependencyNode); + if (premanagedOptional != null && !premanagedOptional.equals(d.getOptional())) { + return "(optionality managed from " + premanagedOptional + ")"; + } + } + return null; + }; + } + /** + * Decorator of "managed exclusions": explains on nodes what was managed. + */ + public static Function premanagedExclusions() { + return dependencyNode -> { + Dependency d = dependencyNode.getDependency(); + if (d != null) { + Collection premanagedExclusions = + DependencyManagerUtils.getPremanagedExclusions(dependencyNode); + if (premanagedExclusions != null && !equals(premanagedExclusions, d.getExclusions())) { + return "(exclusions managed from " + premanagedExclusions + ")"; + } + } + return null; + }; + } + /** + * Decorator of "managed properties": explains on nodes what was managed. + */ + public static Function premanagedProperties() { + return dependencyNode -> { + if (dependencyNode.getArtifact() != null) { + Map premanagedProperties = + DependencyManagerUtils.getPremanagedProperties(dependencyNode); + if (premanagedProperties != null + && !equals( + premanagedProperties, + dependencyNode.getArtifact().getProperties())) { + return "(properties managed from " + premanagedProperties + ")"; + } + } + return null; + }; + } + /** + * Decorator of "range member": explains on nodes what range it participates in. + */ + public static Function rangeMember() { + return dependencyNode -> { + VersionConstraint constraint = dependencyNode.getVersionConstraint(); + if (constraint != null && constraint.getRange() != null) { + return "(range '" + constraint.getRange() + "')"; + } + return null; + }; + } + /** + * Decorator of "winner node": explains on losers why lost. + */ + public static Function winnerNode() { + return dependencyNode -> { + if (dependencyNode.getArtifact() != null) { + DependencyNode winner = + (DependencyNode) dependencyNode.getData().get(ConflictResolver.NODE_DATA_WINNER); + if (winner != null) { + if (ArtifactIdUtils.equalsId(dependencyNode.getArtifact(), winner.getArtifact())) { + return "(nearer exists)"; + } else { + Artifact w = winner.getArtifact(); + String result = "conflicts with "; + if (ArtifactIdUtils.toVersionlessId(dependencyNode.getArtifact()) + .equals(ArtifactIdUtils.toVersionlessId(w))) { + result += w.getVersion(); + } else { + result += w; + } + return "(" + result + ")"; + } + } + } + return null; + }; + } + /** + * Decorator of "artifact properties": prints out asked properties, if present. + */ + public static Function artifactProperties(Collection properties) { + requireNonNull(properties, "properties"); + return dependencyNode -> { + if (!properties.isEmpty() && dependencyNode.getDependency() != null) { + String props = properties.stream() + .map(p -> p + "=" + + dependencyNode.getDependency().getArtifact().getProperty(p, "n/a")) + .collect(Collectors.joining(",")); + if (!props.isEmpty()) { + return "(" + props + ")"; + } + } + return null; + }; + } + + /** + * The standard "default" decorators. + * + * @since 2.0.0 + */ + private static final List> DEFAULT_DECORATORS = + Collections.unmodifiableList(Arrays.asList( + effectiveDependency(), + premanagedVersion(), + premanagedScope(), + premanagedOptional(), + premanagedExclusions(), + premanagedProperties(), + rangeMember(), + winnerNode())); + + /** + * Extends {@link #DEFAULT_DECORATORS} decorators with passed in ones. + * + * @since 2.0.0 + */ + public static List> defaultsWith( + Collection> extras) { + requireNonNull(extras, "extras"); + ArrayList> result = new ArrayList<>(DEFAULT_DECORATORS); + result.addAll(extras); + return result; + } private final Consumer consumer; - private final Collection properties; + private final List> decorators; private final Deque nodes = new ArrayDeque<>(); /** * Creates instance with given consumer. + * + * @param consumer The string consumer, must not be {@code null}. */ public DependencyGraphDumper(Consumer consumer) { - this(consumer, Collections.emptyList()); + this(consumer, DEFAULT_DECORATORS); } /** - * Creates instance with given consumer and properties (to print out). + * Creates instance with given consumer and decorators. * + * @param consumer The string consumer, must not be {@code null}. + * @param decorators The decorators to apply, must not be {@code null}. * @since 2.0.0 */ - public DependencyGraphDumper(Consumer consumer, Collection properties) { + public DependencyGraphDumper(Consumer consumer, Collection> decorators) { this.consumer = requireNonNull(consumer); - this.properties = new ArrayList<>(properties); + this.decorators = new ArrayList<>(decorators); } @Override @@ -116,77 +308,20 @@ protected String formatNode(Deque nodes) { StringBuilder buffer = new StringBuilder(128); Artifact a = node.getArtifact(); buffer.append(a); - Dependency d = node.getDependency(); - if (d != null && !d.getScope().isEmpty()) { - buffer.append(" [").append(d.getScope()); - if (d.isOptional()) { - buffer.append(", optional"); - } - buffer.append("]"); - } - String premanaged = DependencyManagerUtils.getPremanagedVersion(node); - if (premanaged != null && !premanaged.equals(a.getBaseVersion())) { - buffer.append(" (version managed from ").append(premanaged).append(")"); - } - - premanaged = DependencyManagerUtils.getPremanagedScope(node); - if (premanaged != null && d != null && !premanaged.equals(d.getScope())) { - buffer.append(" (scope managed from ").append(premanaged).append(")"); - } - - Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(node); - if (premanagedOptional != null && d != null && !premanagedOptional.equals(d.getOptional())) { - buffer.append(" (optionality managed from ") - .append(premanagedOptional) - .append(")"); - } - - Collection premanagedExclusions = DependencyManagerUtils.getPremanagedExclusions(node); - if (premanagedExclusions != null && d != null && !equals(premanagedExclusions, d.getExclusions())) { - buffer.append(" (exclusions managed from ") - .append(premanagedExclusions) - .append(")"); - } - - Map premanagedProperties = DependencyManagerUtils.getPremanagedProperties(node); - if (premanagedProperties != null && !equals(premanagedProperties, a.getProperties())) { - buffer.append(" (properties managed from ") - .append(premanagedProperties) - .append(")"); - } - - DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER); - if (winner != null) { - if (ArtifactIdUtils.equalsId(a, winner.getArtifact())) { - buffer.append(" (nearer exists)"); - } else { - Artifact w = winner.getArtifact(); - buffer.append(" (conflicts with "); - if (ArtifactIdUtils.toVersionlessId(a).equals(ArtifactIdUtils.toVersionlessId(w))) { - buffer.append(w.getVersion()); - } else { - buffer.append(w); - } - buffer.append(")"); - } - } - - if (!properties.isEmpty() && node.getDependency() != null) { - String props = properties.stream() - .map(p -> p + "=" + node.getDependency().getArtifact().getProperty(p, "n/a")) - .collect(Collectors.joining(",")); - if (!props.isEmpty()) { - buffer.append(" (").append(props).append(")"); + for (Function decorator : decorators) { + String decoration = decorator.apply(node); + if (decoration != null) { + buffer.append(" ").append(decoration); } } return buffer.toString(); } - private boolean equals(Collection c1, Collection c2) { + private static boolean equals(Collection c1, Collection c2) { return c1 != null && c2 != null && c1.size() == c2.size() && c1.containsAll(c2); } - private boolean equals(Map m1, Map m2) { + private static boolean equals(Map m1, Map m2) { return m1 != null && m2 != null && m1.size() == m2.size()