Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/it/projects/tree-excluded/expected-included-survives.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
org.apache.maven.its.dependency:test:jar:1.0-SNAPSHOT
\- org.apache.maven:maven-project:jar:2.0.6:compile
\- org.apache.maven:maven-artifact-manager:jar:2.0.6:compile
\- org.apache.maven.wagon:wagon-provider-api:jar:1.0-beta-2:compile
5 changes: 1 addition & 4 deletions src/it/projects/tree-excluded/expected-v4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@ org.apache.maven.its.dependency:test:jar:1.0-SNAPSHOT
| +- org.apache.maven:maven-repository-metadata:jar:2.0.6:compile
| \- org.apache.maven.wagon:wagon-provider-api:jar:1.0-beta-2:compile
+- org.apache.maven:maven-plugin-registry:jar:2.0.6:compile
+- org.apache.maven:maven-artifact:jar:2.0.6:compile
\- org.codehaus.plexus:plexus-container-default:jar:1.0-alpha-9-stable-1:compile
+- junit:junit:jar:3.8.1:compile
\- classworlds:classworlds:jar:1.1:compile
\- org.apache.maven:maven-artifact:jar:2.0.6:compile
5 changes: 1 addition & 4 deletions src/it/projects/tree-excluded/expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@ org.apache.maven.its.dependency:test:jar:1.0-SNAPSHOT
| +- org.apache.maven:maven-repository-metadata:jar:2.0.6:compile
| \- org.apache.maven.wagon:wagon-provider-api:jar:1.0-beta-2:compile
+- org.apache.maven:maven-plugin-registry:jar:2.0.6:compile
+- org.apache.maven:maven-artifact:jar:2.0.6:compile
\- org.codehaus.plexus:plexus-container-default:jar:1.0-alpha-9-stable-1:compile
+- junit:junit:jar:3.8.1:compile
\- classworlds:classworlds:jar:1.1-alpha-2:compile
\- org.apache.maven:maven-artifact:jar:2.0.6:compile
9 changes: 8 additions & 1 deletion src/it/projects/tree-excluded/invoker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@
# specific language governing permissions and limitations
# under the License.

invoker.goals = ${project.groupId}:${project.artifactId}:${project.version}:tree
invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:tree
invoker.userPropertiesFile.1 = test.properties

invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:tree
invoker.userPropertiesFile.2 = test-included-excluded.properties

invoker.goals.3 = ${project.groupId}:${project.artifactId}:${project.version}:tree
invoker.userPropertiesFile.3 = test-included-survives.properties
20 changes: 20 additions & 0 deletions src/it/projects/tree-excluded/test-included-excluded.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

outputFile = target/included-excluded.txt
includes = junit:junit
excludes = org.codehaus.plexus:*
20 changes: 20 additions & 0 deletions src/it/projects/tree-excluded/test-included-survives.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

outputFile = target/included-survives.txt
includes = :wagon*
excludes = org.codehaus.plexus:*
5 changes: 5 additions & 0 deletions src/it/projects/tree-excluded/verify.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ def expected = mavenVersion.startsWith('4.') ? "expected-v4.txt" : "expected.txt
assertThat(new File(basedir, "target/tree.txt"))
.hasSameTextualContentAs(new File(basedir, expected))

assertThat(new File(basedir, "target/included-excluded.txt").text).isEmpty()

assertThat(new File(basedir, "target/included-survives.txt"))
.hasSameTextualContentAs(new File(basedir, "expected-included-survives.txt"))

return true
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.plugins.dependency.tree;

import java.util.ArrayDeque;
import java.util.Deque;

import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;

/**
* A dependency node visitor that delegates accepted nodes and prunes rejected nodes and their descendants.
*/
final class PruningDependencyNodeVisitor implements DependencyNodeVisitor {
private final DependencyNodeVisitor visitor;

private final DependencyNodeFilter filter;

private final Deque<Boolean> acceptedNodes = new ArrayDeque<>();

PruningDependencyNodeVisitor(DependencyNodeVisitor visitor, DependencyNodeFilter filter) {
this.visitor = visitor;
this.filter = filter;
}

@Override
public boolean visit(DependencyNode node) {
boolean accepted = filter.accept(node);
acceptedNodes.push(accepted);
return accepted && visitor.visit(node);
}

@Override
public boolean endVisit(DependencyNode node) {
return !acceptedNodes.pop() || visitor.endVisit(node);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

Expand All @@ -49,7 +48,6 @@
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.filter.AndDependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.filter.ArtifactDependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
Expand Down Expand Up @@ -153,6 +151,8 @@ public class TreeMojo extends AbstractMojo {
* For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
* <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.
* </p>
* Paths leading to included artifacts are retained. If an artifact is also beneath a subtree matched by
* {@link #excludes}, the exclusion takes precedence and the artifact is not included.
*
* @see StrictPatternIncludesArtifactFilter
* @since 2.0-alpha-6
Expand All @@ -174,6 +174,8 @@ public class TreeMojo extends AbstractMojo {
* For example, <code>org.apache.*</code> will match all artifacts whose group id starts with
* <code>org.apache.</code>, and <code>:::*-SNAPSHOT</code> will match all snapshot artifacts.
* </p>
* A matching artifact and its entire dependency subtree are removed from the serialized dependency tree.
* Exclusions are applied before {@link #includes} and take precedence.
*
* @see StrictPatternExcludesArtifactFilter
* @since 2.0-alpha-6
Expand Down Expand Up @@ -333,18 +335,27 @@ private String serializeDependencyTree(DependencyNode theRootNode) {
// TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
visitor = new BuildingDependencyNodeVisitor(visitor);

DependencyNodeFilter filter = createDependencyNodeFilter();
DependencyNodeFilter includesFilter = createIncludesDependencyNodeFilter();
DependencyNodeFilter excludesFilter = createExcludesDependencyNodeFilter();

if (filter != null) {
if (includesFilter != null) {
CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter);
DependencyNodeVisitor firstPassVisitor =
new FilteringDependencyNodeVisitor(collectingVisitor, includesFilter);
if (excludesFilter != null) {
firstPassVisitor = new PruningDependencyNodeVisitor(firstPassVisitor, excludesFilter);
}
theRootNode.accept(firstPassVisitor);

DependencyNodeFilter secondPassFilter =
new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes());
visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter);
}

if (excludesFilter != null) {
visitor = new PruningDependencyNodeVisitor(visitor, excludesFilter);
}

theRootNode.accept(visitor);

return writer.toString();
Expand Down Expand Up @@ -397,27 +408,25 @@ private GraphTokens toGraphTokens(String theTokens) {
*
* @return the dependency node filter, or <code>null</code> if none required
*/
private DependencyNodeFilter createDependencyNodeFilter() {
List<DependencyNodeFilter> filters = new ArrayList<>();

// filter includes
private DependencyNodeFilter createIncludesDependencyNodeFilter() {
if (includes != null && !includes.isEmpty()) {

getLog().debug("+ Filtering dependency tree by artifact include patterns: " + includes);

ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter(includes);
filters.add(new ArtifactDependencyNodeFilter(artifactFilter));
return new ArtifactDependencyNodeFilter(artifactFilter);
}

// filter excludes
if (excludes != null && !excludes.isEmpty()) {
return null;
}

private DependencyNodeFilter createExcludesDependencyNodeFilter() {
if (excludes != null && !excludes.isEmpty()) {
getLog().debug("+ Filtering dependency tree by artifact exclude patterns: " + excludes);

ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter(excludes);
filters.add(new ArtifactDependencyNodeFilter(artifactFilter));
return new ArtifactDependencyNodeFilter(artifactFilter);
}

return filters.isEmpty() ? null : new AndDependencyNodeFilter(filters);
return null;
}
}
9 changes: 8 additions & 1 deletion src/site/apt/examples/filtering-the-dependency-tree.apt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ mvn dependency:tree -Dincludes=velocity:velocity
mvn dependency:tree -Dexcludes=org.codehaus.plexus
+---+

A dependency matching an exclude pattern and its entire dependency subtree are removed from the serialized tree.
This affects only the displayed tree; it does not change the project's dependency resolution.

* Specifying multiple patterns

Multiple patterns can be specified when filtering the dependency tree by separating the patterns with commas. For
Expand All @@ -79,9 +82,13 @@ mvn dependency:tree -Dexcludes=org.apache.maven*,org.codehaus.plexus

* Including and excluding dependencies from the tree

Both include and exclude patterns and be specified together to filter the dependency tree. For example, to locate
Both include and exclude patterns can be specified together to filter the dependency tree. For example, to locate
all non-snapshot Plexus dependencies in the tree, we can execute the following:

+---+
mvn dependency:tree -Dincludes=org.codehaus.plexus -Dexcludes=:::*-SNAPSHOT
+---+

Excludes are applied first and take precedence over includes. Includes then select matching dependencies from the
remaining tree and retain the paths leading to those dependencies. An include therefore cannot restore a dependency
beneath an excluded subtree.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.plugins.dependency.tree;

import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;

class PruningDependencyNodeVisitorTest {
@Test
void evaluatesFilterOnlyWhenStartingNodeVisit() {
DefaultDependencyNode root = newNode(null);
root.setChildren(Collections.emptyList());
AtomicInteger filterInvocations = new AtomicInteger();
AtomicInteger endVisits = new AtomicInteger();
DependencyNodeVisitor visitor = new DependencyNodeVisitor() {
@Override
public boolean visit(DependencyNode node) {
return true;
}

@Override
public boolean endVisit(DependencyNode node) {
endVisits.incrementAndGet();
return true;
}
};

root.accept(new PruningDependencyNodeVisitor(visitor, node -> filterInvocations.incrementAndGet() == 1));

assertEquals(1, filterInvocations.get());
assertEquals(1, endVisits.get());
}

@Test
void prunesRejectedSubtreeAndContinuesWithSiblings() {
DefaultDependencyNode root = newNode(null);
DefaultDependencyNode rejected = newNode(root);
DefaultDependencyNode rejectedChild = newNode(rejected);
DefaultDependencyNode sibling = newNode(root);

root.setChildren(Arrays.asList(rejected, sibling));
rejected.setChildren(Collections.singletonList(rejectedChild));
rejectedChild.setChildren(Collections.emptyList());
sibling.setChildren(Collections.emptyList());

CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
DependencyNodeFilter filter = node -> node != rejected;

root.accept(new PruningDependencyNodeVisitor(collectingVisitor, filter));

assertEquals(Arrays.asList(root, sibling), collectingVisitor.getNodes());
}

private DefaultDependencyNode newNode(DependencyNode parent) {
return new DefaultDependencyNode(parent, mock(Artifact.class), null, null, null);
}
}