Skip to content

Commit 5c7d0bc

Browse files
[MENFORCER-494] Allow banning dynamic versions in whole tree (#294)
* [MENFORCER-494] Allow banning dynamic versions in whole tree This commit introduces the possibility of banning dynamic versions in the entire dependency tree before Maven computes the final dependency tree. * Fix format --------- Co-authored-by: Slawomir Jaranowski <s.jaranowski@gmail.com>
1 parent e687c46 commit 5c7d0bc

File tree

9 files changed

+268
-35
lines changed

9 files changed

+268
-35
lines changed

enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/BanDynamicVersions.java

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
import javax.inject.Named;
2323

2424
import java.text.ChoiceFormat;
25-
import java.util.ArrayDeque;
2625
import java.util.ArrayList;
2726
import java.util.Collection;
2827
import java.util.Collections;
29-
import java.util.Deque;
3028
import java.util.List;
3129
import java.util.Objects;
3230
import java.util.function.Predicate;
@@ -40,9 +38,12 @@
4038
import org.apache.maven.project.MavenProject;
4139
import org.eclipse.aether.RepositorySystem;
4240
import org.eclipse.aether.collection.DependencyCollectionException;
41+
import org.eclipse.aether.graph.DependencyFilter;
4342
import org.eclipse.aether.graph.DependencyNode;
44-
import org.eclipse.aether.graph.DependencyVisitor;
45-
import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
43+
import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
44+
import org.eclipse.aether.util.graph.visitor.PathRecordingDependencyVisitor;
45+
import org.eclipse.aether.util.version.GenericVersionScheme;
46+
import org.eclipse.aether.version.InvalidVersionSpecificationException;
4647
import org.eclipse.aether.version.VersionConstraint;
4748

4849
/**
@@ -110,6 +111,14 @@ public final class BanDynamicVersions extends AbstractStandardEnforcerRule {
110111
*/
111112
private List<String> ignores = null;
112113

114+
/**
115+
* {@code true} if dependencies should be checked before Maven computes the final
116+
* dependency tree. Setting this property will make the rule check dependencies
117+
* before any conflicts are resolved. This is similar to the {@code verbose}
118+
* parameter for the {@code tree} goal for {@code maven-dependency-plugin}.
119+
*/
120+
private boolean verbose;
121+
113122
private final ResolverUtil resolverUtil;
114123

115124
@Inject
@@ -118,25 +127,24 @@ public BanDynamicVersions(
118127
this.resolverUtil = Objects.requireNonNull(resolverUtil);
119128
}
120129

121-
private final class BannedDynamicVersionCollector implements DependencyVisitor {
122-
123-
private final Deque<DependencyNode> nodeStack; // all intermediate nodes (without the root node)
130+
private final class BannedDynamicVersionCollector implements DependencyFilter {
124131

125132
private boolean isRoot = true;
126133

127134
private List<String> violations;
128135

129136
private final Predicate<DependencyNode> predicate;
130137

138+
private GenericVersionScheme versionScheme;
139+
131140
public List<String> getViolations() {
132141
return violations;
133142
}
134143

135144
BannedDynamicVersionCollector(Predicate<DependencyNode> predicate) {
136-
this.nodeStack = new ArrayDeque<>();
137145
this.predicate = predicate;
138-
this.isRoot = true;
139146
this.violations = new ArrayList<>();
147+
this.versionScheme = new GenericVersionScheme();
140148
}
141149

142150
private boolean isBannedDynamicVersion(VersionConstraint versionConstraint) {
@@ -163,30 +171,51 @@ private boolean isBannedDynamicVersion(VersionConstraint versionConstraint) {
163171
}
164172

165173
@Override
166-
public boolean visitEnter(DependencyNode node) {
174+
public boolean accept(DependencyNode node, List<DependencyNode> parents) {
167175
if (isRoot) {
168176
isRoot = false;
169-
} else {
170-
getLog().debug("Found node " + node + " with version constraint " + node.getVersionConstraint());
171-
if (predicate.test(node) && isBannedDynamicVersion(node.getVersionConstraint())) {
172-
violations.add("Dependency "
173-
+ node.getDependency()
174-
+ dumpIntermediatePath(nodeStack)
175-
+ " is referenced with a banned dynamic version "
176-
+ node.getVersionConstraint());
177-
return false;
177+
return false;
178+
}
179+
getLog().debug("Found node " + node + " with version constraint " + node.getVersionConstraint());
180+
if (!predicate.test(node)) {
181+
return false;
182+
}
183+
VersionConstraint versionConstraint = node.getVersionConstraint();
184+
if (isBannedDynamicVersion(versionConstraint)) {
185+
addViolation(versionConstraint, node, parents);
186+
return true;
187+
}
188+
try {
189+
if (verbose) {
190+
String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node);
191+
if (premanagedVersion != null) {
192+
VersionConstraint premanagedContraint = versionScheme.parseVersionConstraint(premanagedVersion);
193+
if (isBannedDynamicVersion(premanagedContraint)) {
194+
addViolation(premanagedContraint, node, parents);
195+
return true;
196+
}
197+
}
178198
}
179-
nodeStack.addLast(node);
199+
} catch (InvalidVersionSpecificationException ex) {
200+
// This should never happen.
201+
throw new RuntimeException("Failed to parse version for " + node, ex);
180202
}
181-
return true;
203+
return false;
182204
}
183205

184-
@Override
185-
public boolean visitLeave(DependencyNode node) {
186-
if (!nodeStack.isEmpty()) {
187-
nodeStack.removeLast();
206+
private void addViolation(
207+
VersionConstraint versionContraint, DependencyNode node, List<DependencyNode> parents) {
208+
List<DependencyNode> intermediatePath = new ArrayList<>(parents);
209+
if (!intermediatePath.isEmpty()) {
210+
// This project is also included in the path, but we do
211+
// not want that in the report.
212+
intermediatePath.remove(intermediatePath.size() - 1);
188213
}
189-
return true;
214+
violations.add("Dependency "
215+
+ node.getDependency()
216+
+ dumpIntermediatePath(intermediatePath)
217+
+ " is referenced with a banned dynamic version "
218+
+ versionContraint);
190219
}
191220
}
192221

@@ -195,7 +224,7 @@ public void execute() throws EnforcerRuleException {
195224

196225
try {
197226
DependencyNode rootDependency =
198-
resolverUtil.resolveTransitiveDependencies(excludeOptionals, excludedScopes);
227+
resolverUtil.resolveTransitiveDependencies(verbose, excludeOptionals, excludedScopes);
199228

200229
List<String> violations = collectDependenciesWithBannedDynamicVersions(rootDependency);
201230
if (!violations.isEmpty()) {
@@ -239,23 +268,27 @@ private List<String> collectDependenciesWithBannedDynamicVersions(DependencyNode
239268
} else {
240269
predicate = d -> true;
241270
}
242-
BannedDynamicVersionCollector bannedDynamicVersionCollector = new BannedDynamicVersionCollector(predicate);
243-
DependencyVisitor depVisitor = new TreeDependencyVisitor(bannedDynamicVersionCollector);
244-
rootDependency.accept(depVisitor);
245-
return bannedDynamicVersionCollector.getViolations();
271+
BannedDynamicVersionCollector collector = new BannedDynamicVersionCollector(predicate);
272+
rootDependency.accept(new PathRecordingDependencyVisitor(collector));
273+
return collector.getViolations();
274+
}
275+
276+
public void setVerbose(boolean verbose) {
277+
this.verbose = verbose;
246278
}
247279

248280
@Override
249281
public String toString() {
250282
return String.format(
251-
"BanDynamicVersions[allowSnapshots=%b, allowLatest=%b, allowRelease=%b, allowRanges=%b, allowRangesWithIdenticalBounds=%b, excludeOptionals=%b, excludedScopes=%s, ignores=%s]",
283+
"BanDynamicVersions[allowSnapshots=%b, allowLatest=%b, allowRelease=%b, allowRanges=%b, allowRangesWithIdenticalBounds=%b, excludeOptionals=%b, excludedScopes=%s, ignores=%s, verbose=%b]",
252284
allowSnapshots,
253285
allowLatest,
254286
allowRelease,
255287
allowRanges,
256288
allowRangesWithIdenticalBounds,
257289
excludeOptionals,
258290
excludedScopes,
259-
ignores);
291+
ignores,
292+
verbose);
260293
}
261294
}

enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/ResolverUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ DependencyNode resolveTransitiveDependencies(boolean excludeOptional, List<Strin
110110
return resolveTransitiveDependencies(false, excludeOptional, excludedScopes);
111111
}
112112

113-
private DependencyNode resolveTransitiveDependencies(
114-
boolean verbose, boolean excludeOptional, List<String> excludedScopes) throws EnforcerRuleException {
113+
DependencyNode resolveTransitiveDependencies(boolean verbose, boolean excludeOptional, List<String> excludedScopes)
114+
throws EnforcerRuleException {
115115

116116
try {
117117
RepositorySystemSession repositorySystemSession = session.getRepositorySession();

enforcer-rules/src/site/apt/banDynamicVersions.apt.vm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Ban Dynamic Versions
6969

7070
[]
7171

72+
* <<verbose>> - if <<<true>>> the dependency tree is checked before Maven computes the final dependency tree. Setting this property will make the rule check dependencies before any conflicts are resolved. This is similar to the <<<verbose>>> parameter for the <<<tree>>> goal for <<<maven-dependency-plugin>>>. Default is <<<false>>>.
73+
7274
[]
7375

7476
Sample Plugin Configuration:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
*
20+
-->
21+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
<groupId>org.apache.maven.plugins.enforcer.its</groupId>
24+
<artifactId>menforcer494_dependency</artifactId>
25+
<version>1.0-SNAPSHOT</version>
26+
</project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
*
20+
-->
21+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
<groupId>org.apache.maven.plugins.enforcer.its</groupId>
24+
<artifactId>menforcer494_dependency</artifactId>
25+
<version>2.0</version>
26+
</project>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
*
20+
-->
21+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
<groupId>org.apache.maven.plugins.enforcer.its</groupId>
24+
<artifactId>menforcer494_project</artifactId>
25+
<version>1.0-SNAPSHOT</version>
26+
27+
<dependencies>
28+
<dependency>
29+
<groupId>org.apache.maven.plugins.enforcer.its</groupId>
30+
<artifactId>menforcer494_dependency</artifactId>
31+
<version>1.0-SNAPSHOT</version>
32+
</dependency>
33+
</dependencies>
34+
</project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
invoker.buildResult = failure
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
-->
21+
22+
<project>
23+
<modelVersion>4.0.0</modelVersion>
24+
25+
<groupId>org.apache.maven.its.enforcer</groupId>
26+
<artifactId>ban-dynamic-versions-test</artifactId>
27+
<version>1.0</version>
28+
29+
<build>
30+
<plugins>
31+
<plugin>
32+
<groupId>org.apache.maven.plugins</groupId>
33+
<artifactId>maven-enforcer-plugin</artifactId>
34+
<version>@project.version@</version>
35+
<executions>
36+
<execution>
37+
<id>test</id>
38+
<goals>
39+
<goal>enforce</goal>
40+
</goals>
41+
<configuration>
42+
<rules>
43+
<banDynamicVersions>
44+
<ignores>
45+
<ignore>*:menforcer494_project</ignore>
46+
</ignores>
47+
<verbose>true</verbose>
48+
</banDynamicVersions>
49+
</rules>
50+
</configuration>
51+
</execution>
52+
</executions>
53+
</plugin>
54+
</plugins>
55+
</build>
56+
57+
<dependencies>
58+
<dependency>
59+
<!-- banned SNAPSHOT, but ignored (though it's transitive dependency on menforcerxxx_dependency is not) -->
60+
<groupId>org.apache.maven.plugins.enforcer.its</groupId>
61+
<artifactId>menforcer494_project</artifactId>
62+
<version>1.0-SNAPSHOT</version>
63+
</dependency>
64+
<dependency>
65+
<!-- this overrides the version in menforcerxxx_project, but we still want the failure from menforcerxxx_project to show -->
66+
<groupId>org.apache.maven.plugins.enforcer.its</groupId>
67+
<artifactId>menforcer494_dependency</artifactId>
68+
<version>2.0</version>
69+
</dependency>
70+
</dependencies>
71+
72+
</project>

0 commit comments

Comments
 (0)