All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.maven.plugin.antrun.DependencyGraph Maven / Gradle / Ivy

Go to download

This extended antrun maven plugin enables users not only to run ant scripts embedded in the POM, but also to reference maven dependencies using Ant task classes. This enables the user to delegate more complex tasks to Ant such as constructing file-based installation distros.

There is a newer version: 1.43
Show newest version
package org.jvnet.maven.plugin.antrun;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;

import java.io.File;
import java.util.AbstractList;
import java.util.ArrayList;
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.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Queue;
import java.util.LinkedList;

/**
 * Graph of dependencies among Maven artifacts.
 *
 * 

* This graph, which consists of interconnected {@link Node}s and {@link Edge}s, * represents a complete dependency graph rooted at the given Maven module. * The constructor recursively parses all POMs for the dependency and builds this information. * *

* For example, if you have 4 modules A,B,C, and D that has the dependencies among them as follows: *

 * A->B,C
 * B->D
 * C->D
 * 
*

* Then if you construct a graph from 'A', you'll get a graph of four nodes (each representing * maven module A,B,C, and D) and four edges (each representing dependencies among them.) * *

* Once constructed, a graph is accessible in several ways: * *

    *
  • * Start with {@link #getRoot() the root node} and traverse through edges like * {@link Node#getForwardEdges(DependencyGraph)}. * *
  • * Use {@link #createSubGraph(GraphVisitor)} and obtain a sub-graph that matches the given * criteria. *
* * * @author Kohsuke Kawaguchi */ public final class DependencyGraph { private final MavenComponentBag bag = MavenComponentBag.get(); private final Node root; /** * All {@link Node}s keyed by "groupId:artifactId:classifier" */ private final Map nodes = new TreeMap(); /** * If true, ignore the {@link Node} that have failed to load. */ private final boolean tolerateBrokenPOMs; /** * Forward edges. * * Edges are kept on {@link DependencyGraph} so that we can * create multiple {@link DependencyGraph}s that share the same node set. */ private final Map> forwardEdges = new HashMap>(); private final Map> backwardEdges = new HashMap>(); /** * Creates a full dependency graph with the given artifact at the top. */ public DependencyGraph(Artifact root, boolean tolerateBrokenPOMs) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { this.tolerateBrokenPOMs = tolerateBrokenPOMs; Queue q = new LinkedList(); this.root = buildNode(root,q); visitBFS(q); } /** * Creates a full dependency graph with the given project at the top. */ public DependencyGraph(MavenProject root, boolean tolerateBrokenPOMs) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { this.tolerateBrokenPOMs = tolerateBrokenPOMs; Queue q = new LinkedList(); this.root = buildNode(root,q); visitBFS(q); } /** * Completes the graph in a breadth-first fashion. */ private void visitBFS(Queue q) throws ArtifactResolutionException, ArtifactNotFoundException, ProjectBuildingException { while(!q.isEmpty()) q.poll().expand(this,q); } /** * Used to create a subgraph. *

* This method assumes that all nodes and edges are connected, * hence the 'private' access. Use {@link #createSubGraph(GraphVisitor)} * to construct a subset reliably. */ private DependencyGraph(Node root, Collection nodes, Collection edges, boolean tolerateBrokenPOMs) { this.tolerateBrokenPOMs = tolerateBrokenPOMs; if(nodes.isEmpty()) root = null; // graph is empty this.root = root; if(root!=null) { Set reachable = new HashSet(); reachable.add(root); // root is always reachable if(!nodes.contains(root)) throw new IllegalArgumentException("root "+root+" is not a part of nodes:"+nodes); for (Node n : nodes) this.nodes.put(n.getId(),n); for (Edge e : edges) { if(contains(e.src) && contains(e.dst)) { e.addEdge(forwardEdges,e.src); e.addEdge(backwardEdges,e.dst); reachable.add(e.dst); } } // some nodes were unreachable if(reachable.size()!=this.nodes.size()) throw new IllegalArgumentException(); } } /** * Gets the root node. * *

* This is non-null unless this graph is {@link #isEmpty() empty}. */ public Node getRoot() { return root; } /** * Returns true if the graph contains nothing at all. */ public boolean isEmpty() { return root==null; } /** * Returns all nodes in this graph. */ public Collection getAllNodes() { return nodes.values(); } /** * Checks if the graph contains the given node. */ public boolean contains(Node node) { return nodes.containsKey(node.getId()); } /** * Gets the associated {@link Node}, or null if none exists. */ public Node toNode(Artifact a) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { String id = a.getGroupId()+':'+a.getArtifactId()+':'+a.getClassifier(); return nodes.get(id); } /** * Gets the associated {@link Node}. If none exists, it will be created. * *

* Graph building has to be done breadth-first fashion so that the version * conflit resolution happens correctly — that is, if there exists dependency like * A -> B(1.0) -> C(2.0) and A -> C(2.2), then we want to pick up C(2.2), not C(2.0). * The criteria to do this in Maven is the length of the dependency chain, and that * can be done naturally by BFS. So we pass around a {@link Queue} to keep track of the remaining * {@link Node}s to be expanded. */ private Node buildNode(Artifact a, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { String id = a.getGroupId()+':'+a.getArtifactId()+':'+a.getClassifier(); Node n = nodes.get(id); if(n==null) { n = new Node(a,this,q); nodes.put(id, n); } return n; } private Node buildNode(MavenProject p, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { String id = p.getGroupId()+':'+p.getArtifactId()+":null"; Node n = nodes.get(id); if(n==null) { n = new Node(p, q); nodes.put(id, n); } return n; } /** * Accepts the visitor and invoke its visitor methods to create a sub-graph. * *

* This method is convenient for obtaining a sub-graph of dependencies * by filtering out nodes/edges. For example, to obtain all the transitive * dependencies that exclude provided/test dependencies, you can do: * *

     * createSubgraph(new {@link ScopeFilter}("compile","runtime"))
     * 
* * @return * A sub-graph of this graph that consists of nodes and edges for which the visitor returns true. * Can be an empty graph, but never null. */ public DependencyGraph createSubGraph(GraphVisitor visitor) { return createSubGraph(root,visitor); } /** * Accepts the visitor. Simply an alias for {@link #createSubGraph(GraphVisitor)}. */ public void accept(GraphVisitor visitor) { createSubGraph(visitor); } /** * Creates a full subgraph rooted at the given node. */ public DependencyGraph createSubGraph(Node root) { return createSubGraph(root,new DefaultGraphVisitor()); } /** * Visits the graph started at the given node, and creates a sub-graph * from visited nodes and edges. * *

* This is the slightly generalized version of {@link #createSubGraph(GraphVisitor)} */ public DependencyGraph createSubGraph(Node node, GraphVisitor visitor) { Set visited = new HashSet(); Set nodes = new HashSet(); List edges = new ArrayList(); Stack q = new Stack(); q.push(node); while(!q.isEmpty()) { DependencyGraph.Node n = q.pop(); if(visitor.visit(n)) { nodes.add(n); for (Edge e : n.getForwardEdges(this)) { if(visitor.visit(e)) { edges.add(e); if(visited.add(e.dst)) q.push(e.dst); } } } } return new DependencyGraph(node,nodes,edges, tolerateBrokenPOMs); } /** * Creates a sub-graph from the given set of nodes (which must be subset of * nodes in the current graph) with all edges { (u,v) | u \in nodes, v \in nodes } */ public DependencyGraph createSubGraph(Node root, Collection nodes) { List edges = new ArrayList(); for (List el : forwardEdges.values()) { for (Edge e : el) { if(nodes.contains(e.src) && nodes.contains(e.dst)) edges.add(e); } } return new DependencyGraph(root,nodes,edges, tolerateBrokenPOMs); } public String toString() { StringBuilder buf = new StringBuilder(); buf.append("DependencyGraph[root=").append(root).append(",\n"); buf.append(" nodes=[\n"); for (Node node : nodes.values()) buf.append(" ").append(node).append('\n'); buf.append(" ]\n"); buf.append(" edges=[\n"); for (Map.Entry> n : forwardEdges.entrySet()) { for (Edge e : n.getValue()) { buf.append(" ").append(e).append('\n'); } } buf.append(" ]\n]"); return buf.toString(); } private interface Resolver { File resolve() throws AbstractArtifactResolutionException; } private static final Resolver NULL = new Resolver() { public File resolve() { return null; } }; /** * Node, which represents an artifact. * *

* A single {@link Node} can be used in multiple {@link DependencyGraph} objects, * so the graph traversal method all takes {@link DependencyGraph} object * to determine the context in which the operation works. */ public static final class Node { /** * Basic properties of a module. * If {@link #pom} is non-null, this information is redundant, but it needs to be * kept separately for those rare cases where pom==null. */ public final String groupId,artifactId,version,type,classifier; private final MavenProject pom; private /*final*/ File artifactFile; private Resolver artifactResolver = NULL; private Node(Artifact artifact, DependencyGraph g, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { groupId = artifact.getGroupId(); artifactId = artifact.getArtifactId(); version = artifact.getVersion(); type = artifact.getType(); classifier = artifact.getClassifier(); if("system".equals(artifact.getScope())) // system scoped artifacts don't have POM, so the attempt to load it will fail. pom = null; else { pom = g.bag.mavenProjectBuilder.buildFromRepository( // this create another Artifact instance whose type is 'pom' g.bag.factory.createProjectArtifact(artifact.getGroupId(),artifact.getArtifactId(), artifact.getVersion()), g.bag.project.getRemoteArtifactRepositories(), g.bag.localRepository); q.add(this); // visit dependencies from this POM later } } private void checkArtifact(final Artifact artifact, final MavenComponentBag bag) { artifactResolver = new Resolver() { public File resolve() throws AbstractArtifactResolutionException { if(bag.project.getArtifact()==artifact) { // our own module. Trying to resolve this in the usual way is most likely to fail, // so use what we have, if any. artifactFile =artifact.getFile(); return artifactFile; } if(pom!=null) bag.resolveArtifact(artifact, pom.getRemoteArtifactRepositories()); else bag.resolveArtifact(artifact); artifactFile = artifact.getFile(); if(artifactFile==null) throw new IllegalStateException("Artifact is not resolved yet: "+artifact); return artifactFile; } }; } private Node(MavenProject pom, Queue q) { this.pom = pom; groupId = pom.getGroupId(); artifactId = pom.getArtifactId(); version = pom.getVersion(); type = pom.getPackaging(); // are these the same thing? classifier = null; q.add(this); // visit dependencies from this POM later } private void expand(DependencyGraph g, Queue q) throws ArtifactResolutionException, ArtifactNotFoundException, ProjectBuildingException { checkArtifact(pom.getArtifact(),g.bag); loadDependencies(g,q); } private void loadDependencies(DependencyGraph g, Queue q) throws ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException { for( Dependency d : (List)pom.getDependencies() ) { // the last boolean parameter is redundant, but the version that doesn't take this // has a bug. See MNG-2524 Artifact a = g.bag.factory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(), VersionRange.createFromVersion(d.getVersion()), d.getType(), d.getClassifier(), d.getScope(), false); // beware of Maven bug! make sure artifact got the value inherited from dependency assert a.getScope().equals(d.getScope()); try { new Edge(g,this,g.buildNode(a,q),d.getScope(),d.isOptional()); } catch (ProjectBuildingException e) { handleNodeResolutionException(g,e); } catch (ArtifactResolutionException e) { handleNodeResolutionException(g,e); } catch (ArtifactNotFoundException e) { handleNodeResolutionException(g,e); } } } private void handleNodeResolutionException(DependencyGraph g, Exception e) throws ProjectBuildingException { if (g.tolerateBrokenPOMs) System.err.println("Failed to parse dependencies of " + getId() + ". trail=" + getTrail(g)); else throw new ProjectBuildingException(getId(), "Failed to parse dependencies of " + getId() + ". trail=" + getTrail(g), e); } /** * Gets the parsed POM for this artifact. * * @return null * if POM is not available for this module. * That can happen for example for system-scoped artifacts. */ public MavenProject getProject() { return pom; } /** * Gets the artifact file, like a jar. * * @return * for system-scoped artifacts, this may null. If this node represents the current module * being built, this field may or may not be null, depending on whether the artifact is * already created in the current build or not. * For all the other modules, this is never null. * @throws AbstractArtifactResolutionException * Failed to resolve artifacat. */ public File getArtifactFile() throws AbstractArtifactResolutionException { if(artifactFile==null) artifactFile = artifactResolver.resolve(); return artifactFile; } /** * Gets the forward dependency edges (modules that this module depends on.) */ public List getForwardEdges(DependencyGraph g) { return getEdges(g.forwardEdges); } /** * Gets the backward dependency edges (modules that depend on this module.) */ public List getBackwardEdges(DependencyGraph g) { return getEdges(g.backwardEdges); } private List getEdges(Map> allEdges) { List edges = allEdges.get(this); if(edges==null) return Collections.emptyList(); return edges; } /** * Gets the nodes that the given node depends on. */ public List getForwardNodes(final DependencyGraph g) { return new AbstractList() { final List forward = getForwardEdges(g); public Node get(int index) { return forward.get(index).dst; } public int size() { return forward.size(); } }; } /** * Gets the nodes that depend on the given node. */ public List getBackwardNodes(final DependencyGraph g) { return new AbstractList() { final List backward = getBackwardEdges(g); public Node get(int index) { return backward.get(index).src; } public int size() { return backward.size(); } }; } public String toString() { return groupId+':'+artifactId+':'+version; } public String getId() { return groupId+':'+artifactId+':'+classifier; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Node node = (Node) o; if (!artifactId.equals(node.artifactId)) return false; if (classifier != null ? !classifier.equals(node.classifier) : node.classifier != null) return false; if (!groupId.equals(node.groupId)) return false; if (version != null ? !version.equals(node.version) : node.version != null) return false; return true; } public int hashCode() { int result; result = groupId.hashCode(); result = 31 * result + artifactId.hashCode(); result = 31 * result + (version != null ? version.hashCode() : 0); result = 31 * result + (classifier != null ? classifier.hashCode() : 0); return result; } /** * Builds the dependency trail from this node to the root node, in that order. * * This is useful as diagnostic information. */ public List getTrail(DependencyGraph graph) { List trail = new ArrayList(); Node n = this; while(n!=graph.getRoot()) { List list = n.getBackwardEdges(graph); if(list.isEmpty()) throw new AssertionError("Lost trail at "+trail+" from "+this+" with "+graph); Edge e = list.get(0); trail.add(e); n = e.src; } Collections.reverse(trail); return trail; } } public static final class Edge { /** * The module that depends on another. */ public final Node src; /** * The module that is being dependent by another. */ public final Node dst; /** * Dependency scope. Stuff like "compile", "runtime", etc. * Never null. */ public final String scope; /** * True if this dependency is optional. */ public final boolean optional; public Edge(DependencyGraph g, Node src, Node dst, String scope, boolean optional) { this.src = src; this.dst = dst; if(scope==null) scope="compile"; this.scope = scope; this.optional = optional; addEdge(g.forwardEdges,src); addEdge(g.backwardEdges,dst); } private void addEdge(Map> edgeSet, Node index) { List l = edgeSet.get(index); if(l==null) edgeSet.put(index,l=new ArrayList()); for (Edge e : l) { if(e.src.equals(this.src) && e.dst.equals(this.dst)) return; // duplicate } l.add(this); } public String toString() { StringBuilder buf = new StringBuilder(); buf.append(src).append("--(").append(scope); if(optional) buf.append("/optional"); buf.append(")-->").append(dst); return buf.toString(); } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Edge that = (Edge) o; return dst.equals(that.dst) && src.equals(that.src); } public int hashCode() { int result; result = src.hashCode(); result = 31 * result + dst.hashCode(); return result; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy