diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/BanDynamicVersions.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/BanDynamicVersions.java index 5a827c88..1aa10ff4 100644 --- a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/BanDynamicVersions.java +++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/BanDynamicVersions.java @@ -22,11 +22,9 @@ import javax.inject.Named; import java.text.ChoiceFormat; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.function.Predicate; @@ -40,9 +38,12 @@ import org.apache.maven.project.MavenProject; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.graph.DependencyNode; -import org.eclipse.aether.graph.DependencyVisitor; -import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.visitor.PathRecordingDependencyVisitor; +import org.eclipse.aether.util.version.GenericVersionScheme; +import org.eclipse.aether.version.InvalidVersionSpecificationException; import org.eclipse.aether.version.VersionConstraint; /** @@ -110,6 +111,14 @@ public final class BanDynamicVersions extends AbstractStandardEnforcerRule { */ private List ignores = null; + /** + * {@code true} if dependencies should be 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 {@code verbose} + * parameter for the {@code tree} goal for {@code maven-dependency-plugin}. + */ + private boolean verbose; + private final ResolverUtil resolverUtil; @Inject @@ -118,9 +127,7 @@ public BanDynamicVersions( this.resolverUtil = Objects.requireNonNull(resolverUtil); } - private final class BannedDynamicVersionCollector implements DependencyVisitor { - - private final Deque nodeStack; // all intermediate nodes (without the root node) + private final class BannedDynamicVersionCollector implements DependencyFilter { private boolean isRoot = true; @@ -128,15 +135,16 @@ private final class BannedDynamicVersionCollector implements DependencyVisitor { private final Predicate predicate; + private GenericVersionScheme versionScheme; + public List getViolations() { return violations; } BannedDynamicVersionCollector(Predicate predicate) { - this.nodeStack = new ArrayDeque<>(); this.predicate = predicate; - this.isRoot = true; this.violations = new ArrayList<>(); + this.versionScheme = new GenericVersionScheme(); } private boolean isBannedDynamicVersion(VersionConstraint versionConstraint) { @@ -163,30 +171,51 @@ private boolean isBannedDynamicVersion(VersionConstraint versionConstraint) { } @Override - public boolean visitEnter(DependencyNode node) { + public boolean accept(DependencyNode node, List parents) { if (isRoot) { isRoot = false; - } else { - getLog().debug("Found node " + node + " with version constraint " + node.getVersionConstraint()); - if (predicate.test(node) && isBannedDynamicVersion(node.getVersionConstraint())) { - violations.add("Dependency " - + node.getDependency() - + dumpIntermediatePath(nodeStack) - + " is referenced with a banned dynamic version " - + node.getVersionConstraint()); - return false; + return false; + } + getLog().debug("Found node " + node + " with version constraint " + node.getVersionConstraint()); + if (!predicate.test(node)) { + return false; + } + VersionConstraint versionConstraint = node.getVersionConstraint(); + if (isBannedDynamicVersion(versionConstraint)) { + addViolation(versionConstraint, node, parents); + return true; + } + try { + if (verbose) { + String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node); + if (premanagedVersion != null) { + VersionConstraint premanagedContraint = versionScheme.parseVersionConstraint(premanagedVersion); + if (isBannedDynamicVersion(premanagedContraint)) { + addViolation(premanagedContraint, node, parents); + return true; + } + } } - nodeStack.addLast(node); + } catch (InvalidVersionSpecificationException ex) { + // This should never happen. + throw new RuntimeException("Failed to parse version for " + node, ex); } - return true; + return false; } - @Override - public boolean visitLeave(DependencyNode node) { - if (!nodeStack.isEmpty()) { - nodeStack.removeLast(); + private void addViolation( + VersionConstraint versionContraint, DependencyNode node, List parents) { + List intermediatePath = new ArrayList<>(parents); + if (!intermediatePath.isEmpty()) { + // This project is also included in the path, but we do + // not want that in the report. + intermediatePath.remove(intermediatePath.size() - 1); } - return true; + violations.add("Dependency " + + node.getDependency() + + dumpIntermediatePath(intermediatePath) + + " is referenced with a banned dynamic version " + + versionContraint); } } @@ -195,7 +224,7 @@ public void execute() throws EnforcerRuleException { try { DependencyNode rootDependency = - resolverUtil.resolveTransitiveDependencies(excludeOptionals, excludedScopes); + resolverUtil.resolveTransitiveDependencies(verbose, excludeOptionals, excludedScopes); List violations = collectDependenciesWithBannedDynamicVersions(rootDependency); if (!violations.isEmpty()) { @@ -239,16 +268,19 @@ private List collectDependenciesWithBannedDynamicVersions(DependencyNode } else { predicate = d -> true; } - BannedDynamicVersionCollector bannedDynamicVersionCollector = new BannedDynamicVersionCollector(predicate); - DependencyVisitor depVisitor = new TreeDependencyVisitor(bannedDynamicVersionCollector); - rootDependency.accept(depVisitor); - return bannedDynamicVersionCollector.getViolations(); + BannedDynamicVersionCollector collector = new BannedDynamicVersionCollector(predicate); + rootDependency.accept(new PathRecordingDependencyVisitor(collector)); + return collector.getViolations(); + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; } @Override public String toString() { return String.format( - "BanDynamicVersions[allowSnapshots=%b, allowLatest=%b, allowRelease=%b, allowRanges=%b, allowRangesWithIdenticalBounds=%b, excludeOptionals=%b, excludedScopes=%s, ignores=%s]", + "BanDynamicVersions[allowSnapshots=%b, allowLatest=%b, allowRelease=%b, allowRanges=%b, allowRangesWithIdenticalBounds=%b, excludeOptionals=%b, excludedScopes=%s, ignores=%s, verbose=%b]", allowSnapshots, allowLatest, allowRelease, @@ -256,6 +288,7 @@ public String toString() { allowRangesWithIdenticalBounds, excludeOptionals, excludedScopes, - ignores); + ignores, + verbose); } } diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/ResolverUtil.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/ResolverUtil.java index 774812af..6ccd62b2 100644 --- a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/ResolverUtil.java +++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/dependency/ResolverUtil.java @@ -110,8 +110,8 @@ DependencyNode resolveTransitiveDependencies(boolean excludeOptional, List excludedScopes) throws EnforcerRuleException { + DependencyNode resolveTransitiveDependencies(boolean verbose, boolean excludeOptional, List excludedScopes) + throws EnforcerRuleException { try { RepositorySystemSession repositorySystemSession = session.getRepositorySession(); diff --git a/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm b/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm index 8062cd69..cf8f7b29 100644 --- a/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm +++ b/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm @@ -69,6 +69,8 @@ Ban Dynamic Versions [] + * <> - if <<>> 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 <<>> parameter for the <<>> goal for <<>>. Default is <<>>. + [] Sample Plugin Configuration: diff --git a/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_dependency-1.0-SNAPSHOT.pom b/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_dependency-1.0-SNAPSHOT.pom new file mode 100644 index 00000000..2f20acf6 --- /dev/null +++ b/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_dependency-1.0-SNAPSHOT.pom @@ -0,0 +1,26 @@ + + + + 4.0.0 + org.apache.maven.plugins.enforcer.its + menforcer494_dependency + 1.0-SNAPSHOT + \ No newline at end of file diff --git a/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_dependency-2.0.pom b/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_dependency-2.0.pom new file mode 100644 index 00000000..e254d15d --- /dev/null +++ b/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_dependency-2.0.pom @@ -0,0 +1,26 @@ + + + + 4.0.0 + org.apache.maven.plugins.enforcer.its + menforcer494_dependency + 2.0 + \ No newline at end of file diff --git a/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_project-1.0-SNAPSHOT.pom b/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_project-1.0-SNAPSHOT.pom new file mode 100644 index 00000000..facb0b0b --- /dev/null +++ b/maven-enforcer-plugin/src/it/mrm/repository/menforcer494_project-1.0-SNAPSHOT.pom @@ -0,0 +1,34 @@ + + + + 4.0.0 + org.apache.maven.plugins.enforcer.its + menforcer494_project + 1.0-SNAPSHOT + + + + org.apache.maven.plugins.enforcer.its + menforcer494_dependency + 1.0-SNAPSHOT + + + \ No newline at end of file diff --git a/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/invoker.properties b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/invoker.properties new file mode 100644 index 00000000..58b6526e --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/invoker.properties @@ -0,0 +1,18 @@ +# 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. + +invoker.buildResult = failure diff --git a/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/pom.xml b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/pom.xml new file mode 100644 index 00000000..dc4efcb2 --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/pom.xml @@ -0,0 +1,72 @@ + + + + + + 4.0.0 + + org.apache.maven.its.enforcer + ban-dynamic-versions-test + 1.0 + + + + + org.apache.maven.plugins + maven-enforcer-plugin + @project.version@ + + + test + + enforce + + + + + + *:menforcer494_project + + true + + + + + + + + + + + + + org.apache.maven.plugins.enforcer.its + menforcer494_project + 1.0-SNAPSHOT + + + + org.apache.maven.plugins.enforcer.its + menforcer494_dependency + 2.0 + + + + diff --git a/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/verify.groovy b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/verify.groovy new file mode 100644 index 00000000..32b1276d --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions-verbose/verify.groovy @@ -0,0 +1,22 @@ +/* + * 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. + */ +File buildLog = new File( basedir, 'build.log' ) +assert buildLog.text.contains( '[ERROR] Dependency org.apache.maven.plugins.enforcer.its:menforcer494_dependency:jar:1.0-SNAPSHOT (compile) via org.apache.maven.plugins.enforcer.its:menforcer494_project:jar:1.0-SNAPSHOT is referenced with a banned dynamic version 1.0-SNAPSHOT' ) +assert buildLog.text.contains( '[ERROR] Rule 0: org.apache.maven.enforcer.rules.dependency.BanDynamicVersions failed with message' ) +assert buildLog.text.contains( '[ERROR] Found 1 dependency with dynamic versions.' )