This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-enforcer.git


The following commit(s) were added to refs/heads/master by this push:
     new 3c74747  [MENFORCER-427] New rule to ban dynamic versions (#187)
3c74747 is described below

commit 3c747479e67eca62af729b9a9dd713ccdc548f17
Author: Konrad Windszus <[email protected]>
AuthorDate: Fri Oct 14 08:40:27 2022 +0200

    [MENFORCER-427] New rule to ban dynamic versions (#187)
---
 .../maven/plugins/enforcer/BanDynamicVersions.java | 382 +++++++++++++++++++++
 .../src/site/apt/banDynamicVersions.apt.vm         | 111 ++++++
 enforcer-rules/src/site/apt/index.apt              |   2 +
 .../src/it/mrm/repository/menforcer427-1.0.pom     |  39 +++
 .../src/it/mrm/repository/menforcer427_a-1.0.pom   |  26 ++
 .../src/it/mrm/repository/menforcer427_b-1.0.pom   |  26 ++
 .../ban-dynamic-versions/invoker.properties        |  18 +
 .../src/it/projects/ban-dynamic-versions/pom.xml   | 114 ++++++
 .../it/projects/ban-dynamic-versions/verify.groovy |  25 ++
 9 files changed, 743 insertions(+)

diff --git 
a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDynamicVersions.java
 
b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDynamicVersions.java
new file mode 100644
index 0000000..c3a5cc0
--- /dev/null
+++ 
b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDynamicVersions.java
@@ -0,0 +1,382 @@
+package org.apache.maven.plugins.enforcer;
+
+/*
+ * 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.
+ */
+
+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;
+import java.util.stream.Collectors;
+
+import org.apache.maven.RepositoryUtils;
+import 
org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.utils.logging.MessageBuilder;
+import org.apache.maven.shared.utils.logging.MessageUtils;
+import 
org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.util.graph.selector.AndDependencySelector;
+import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
+import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
+import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
+import org.eclipse.aether.version.VersionConstraint;
+
+/**
+ * This rule bans dependencies having a version which requires resolution 
(i.e. dynamic versions which might change with
+ * each build). Dynamic versions are either
+ * <ul>
+ * <li>version ranges,</li>
+ * <li>the special placeholders {@code LATEST} or {@code RELEASE} or</li>
+ * <li>versions ending with {@code -SNAPSHOT}.
+ * </ul>
+ * 
+ * @since 3.2.0
+ */
+public class BanDynamicVersions
+    extends AbstractNonCacheableEnforcerRule
+{
+
+    private static final String RELEASE = "RELEASE";
+
+    private static final String LATEST = "LATEST";
+
+    private static final String SNAPSHOT_SUFFIX = "-SNAPSHOT";
+
+    /**
+     * {@code true} if versions ending with {@code -SNAPSHOT} should be allowed
+     */
+    private boolean allowSnapshots;
+
+    /**
+     * {@code true} if versions using {@code LATEST} should be allowed
+     */
+    private boolean allowLatest;
+
+    /**
+     * {@code true} if versions using {@code RELEASE} should be allowed
+     */
+    private boolean allowRelease;
+
+    /**
+     * {@code true} if version ranges should be allowed
+     */
+    private boolean allowRanges;
+
+    /**
+     * {@code true} if ranges having the same upper and lower bound like 
{@code [1.0]} should be allowed.
+     * Only applicable if {@link #allowRanges} is not set to {@code true}.
+     */
+    private boolean allowRangesWithIdenticalBounds;
+
+    /**
+     * {@code true} if optional dependencies should not be checked
+     */
+    private boolean excludeOptionals;
+
+    /**
+     * the scopes of dependencies which should be excluded from this rule
+     */
+    private String[] excludedScopes;
+
+    /**
+     * Specify the ignored dependencies. This can be a list of artifacts in 
the format
+     * 
<code>groupId[:artifactId[:version[:type[:scope:[classifier]]]]]</code>. 
+     * Any of the sections can be a wildcard by using '*' (e.g. {@code 
group:*:1.0}).
+     * <br>
+     * Any of the ignored dependencies may have dynamic versions.
+     * 
+     * @see {@link #setIgnores(List)}
+     */
+    private List<String> ignores = null;
+
+    public void setIgnores( List<String> ignores )
+    {
+        this.ignores = ignores;
+    }
+
+    public void setAllowSnapshots( boolean allowSnapshots )
+    {
+        this.allowSnapshots = allowSnapshots;
+    }
+
+    public void setAllowLatest( boolean allowLatest )
+    {
+        this.allowLatest = allowLatest;
+    }
+
+    public void setAllowRelease( boolean allowRelease )
+    {
+        this.allowRelease = allowRelease;
+    }
+
+    public void setAllowRanges( boolean allowRanges )
+    {
+        this.allowRanges = allowRanges;
+    }
+
+    public void setExcludeOptionals( boolean excludeOptionals )
+    {
+        this.excludeOptionals = excludeOptionals;
+    }
+
+    public void setExcludedScopes( String[] excludedScopes )
+    {
+        this.excludedScopes = excludedScopes;
+    }
+
+    private final class BannedDynamicVersionCollector
+        implements DependencyVisitor
+    {
+
+        private final Log log;
+
+        private final Deque<DependencyNode> nodeStack; // all intermediate 
nodes (without the root node)
+
+        private boolean isRoot = true;
+
+        private int numViolations;
+
+        private final Predicate<DependencyNode> predicate;
+
+        public int getNumViolations()
+        {
+            return numViolations;
+        }
+
+        BannedDynamicVersionCollector( Log log, Predicate<DependencyNode> 
predicate )
+        {
+            this.log = log;
+            nodeStack = new ArrayDeque<>();
+            this.predicate = predicate;
+            this.isRoot = true;
+            numViolations = 0;
+        }
+
+        private boolean isBannedDynamicVersion( VersionConstraint 
versionConstraint )
+        {
+            if ( versionConstraint.getVersion() != null )
+            {
+                if ( versionConstraint.getVersion().toString().equals( LATEST 
) )
+                {
+                    return !allowLatest;
+                }
+                else if ( versionConstraint.getVersion().toString().equals( 
RELEASE ) )
+                {
+                    return !allowRelease;
+                }
+                else if ( versionConstraint.getVersion().toString().endsWith( 
SNAPSHOT_SUFFIX ) )
+                {
+                    return !allowSnapshots;
+                }
+            }
+            else if ( versionConstraint.getRange() != null )
+            {
+                if ( allowRangesWithIdenticalBounds 
+                     && Objects.equals( 
versionConstraint.getRange().getLowerBound(), 
+                                        
versionConstraint.getRange().getUpperBound() ) ) 
+                {
+                        return false;
+                }
+                return !allowRanges;
+            }
+            else
+            {
+                log.warn( "Unexpected version constraint found: " + 
versionConstraint );
+            }
+            return false;
+
+        }
+
+        @Override
+        public boolean visitEnter( DependencyNode node )
+        {
+            if ( isRoot )
+            {
+                isRoot = false;
+            }
+            else
+            {
+                log.debug( "Found node " + node + " with version constraint " 
+ node.getVersionConstraint() );
+                if ( predicate.test( node ) && isBannedDynamicVersion( 
node.getVersionConstraint() ) )
+                {
+                    MessageBuilder msgBuilder = MessageUtils.buffer();
+                    log.warn( msgBuilder.a( "Dependency " )
+                              .strong( node.getDependency() )
+                              .mojo( dumpIntermediatePath( nodeStack ) )
+                              .a( " is referenced with a banned dynamic 
version " + node.getVersionConstraint() )
+                              .toString() );
+                    numViolations++;
+                    return false;
+                }
+                nodeStack.addLast( node );
+            }
+            return true;
+        }
+
+        @Override
+        public boolean visitLeave( DependencyNode node )
+        {
+            if ( !nodeStack.isEmpty() )
+            {
+                nodeStack.removeLast();
+            }
+            return true;
+        }
+    }
+
+    @SuppressWarnings( "unchecked" )
+    @Override
+    public void execute( EnforcerRuleHelper helper )
+        throws EnforcerRuleException
+    {
+        MavenProject project;
+        DefaultRepositorySystemSession newRepoSession;
+        RepositorySystem repoSystem;
+        List<RemoteRepository> remoteRepositories;
+        try
+        {
+            project = (MavenProject) Objects.requireNonNull( helper.evaluate( 
"${project}" ), "${project} is null" );
+            RepositorySystemSession repoSession =
+                (RepositorySystemSession) Objects.requireNonNull( 
helper.evaluate( "${repositorySystemSession}" ),
+                                                                  
"${repositorySystemSession} is null" );
+            // get a new session to be able to tweak the dependency selector
+            newRepoSession = new DefaultRepositorySystemSession( repoSession );
+            remoteRepositories = (List<RemoteRepository>) helper.evaluate( 
"${project.remoteProjectRepositories}" );
+            repoSystem = helper.getComponent( RepositorySystem.class );
+        }
+        catch ( ExpressionEvaluationException eee )
+        {
+            throw new EnforcerRuleException( "Cannot resolve expression", eee 
);
+        }
+        catch ( ComponentLookupException cle )
+        {
+            throw new EnforcerRuleException( "Unable to retrieve component 
RepositorySystem", cle );
+        }
+        Log log = helper.getLog();
+
+        Collection<DependencySelector> depSelectors = new ArrayList<>();
+        depSelectors.add( new ScopeDependencySelector( excludedScopes ) );
+        if ( excludeOptionals )
+        {
+            depSelectors.add( new OptionalDependencySelector() );
+        }
+        newRepoSession.setDependencySelector( new AndDependencySelector( 
depSelectors ) );
+
+        Dependency rootDependency = RepositoryUtils.toDependency( 
project.getArtifact(), null );
+        try
+        {
+            // use root dependency with unresolved direct dependencies
+            int numViolations = emitDependenciesWithBannedDynamicVersions( 
rootDependency, repoSystem, newRepoSession,
+                                                                           
remoteRepositories, log );
+            if ( numViolations > 0 )
+            {
+                ChoiceFormat dependenciesFormat = new ChoiceFormat( 
"1#dependency|1<dependencies" );
+                throw new EnforcerRuleException( "Found " + numViolations + " "
+                    + dependenciesFormat.format( numViolations )
+                    + " with dynamic versions. Look at the warnings emitted 
above for the details." );
+            }
+        }
+        catch ( DependencyCollectionException e )
+        {
+            throw new EnforcerRuleException( "Could not retrieve dependency 
metadata for project",
+                                             e );
+        }
+    }
+
+    private static String dumpIntermediatePath( Collection<DependencyNode> 
path )
+    {
+        if ( path.isEmpty() )
+        {
+            return "";
+        }
+        return " via " + path.stream().map( n -> n.getArtifact().toString() 
).collect( Collectors.joining( " -> " ) );
+    }
+
+    private static final class ExcludeArtifactPatternsPredicate
+        implements Predicate<DependencyNode>
+    {
+
+        private final ArtifactMatcher artifactMatcher;
+
+        ExcludeArtifactPatternsPredicate( List<String> excludes )
+        {
+            this.artifactMatcher = new ArtifactMatcher( excludes, 
Collections.emptyList() );
+        }
+
+        @Override
+        public boolean test( DependencyNode depNode )
+        {
+            try
+            {
+                return artifactMatcher.match( RepositoryUtils.toArtifact( 
depNode.getArtifact() ) );
+            }
+            catch ( InvalidVersionSpecificationException e )
+            {
+                throw new IllegalArgumentException( "Invalid version found for 
dependency node " + depNode, e );
+            }
+        }
+
+    }
+
+    protected int emitDependenciesWithBannedDynamicVersions( 
org.eclipse.aether.graph.Dependency rootDependency,
+                                                             RepositorySystem 
repoSystem,
+                                                             
RepositorySystemSession repoSession,
+                                                             
List<RemoteRepository> remoteRepositories, Log log )
+        throws DependencyCollectionException
+    {
+        CollectRequest collectRequest = new CollectRequest( rootDependency, 
remoteRepositories );
+        CollectResult collectResult = repoSystem.collectDependencies( 
repoSession, collectRequest );
+        Predicate<DependencyNode> predicate;
+        if ( ignores != null && !ignores.isEmpty() )
+        {
+            predicate = new ExcludeArtifactPatternsPredicate( ignores );
+        }
+        else
+        {
+            predicate = d -> true;
+        }
+        BannedDynamicVersionCollector bannedDynamicVersionCollector =
+            new BannedDynamicVersionCollector( log, predicate );
+        DependencyVisitor depVisitor = new TreeDependencyVisitor( 
bannedDynamicVersionCollector );
+        collectResult.getRoot().accept( depVisitor );
+        return bannedDynamicVersionCollector.getNumViolations();
+    }
+
+}
diff --git a/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm 
b/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm
new file mode 100644
index 0000000..806bf27
--- /dev/null
+++ b/enforcer-rules/src/site/apt/banDynamicVersions.apt.vm
@@ -0,0 +1,111 @@
+~~ 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.    
+ 
+  ------
+  Ban Dynamic Versions
+  ------
+  Konrad Windszus
+  ------
+  2022-10-13
+  ------
+
+Ban Dynamic Versions
+
+  This rule bans dependencies having versions which require resolving (i.e. 
dynamic versions which might change with each build and require 
+  lookup of 
{{{https://maven.apache.org/ref/3-LATEST/maven-repository-metadata/repository-metadata.html}repositoy
 metadata}}). Dynamic versions are either 
+  
+  * 
{{{https://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification}version
 ranges}}, i.e. all version strings starting with either <<<[>>> or <<<(>>>,
+  
+  * the special placeholders <<<LATEST>>>/<<<RELEASE>>> or
+  
+  * versions ending with <<<-SNAPSHOT>>>.
+
+  []
+
+  The following parameters are supported by this rule:
+  
+  * <<allowSnapshots>> - if <<<true>>> dependencies with versions ending with 
<<<-SNAPSHOT>>> will not be banned.  Default is <<<false>>>.
+  
+  * <<allowRelease>> - if <<<true>>> dependencies with version placeholder 
<<<RELEASE>>> will not be banned.  Default is <<<false>>>.
+  
+  * <<allowLatest>> - if <<<true>>> dependencies with versions placeholder 
<<<LATEST>>> will not be banned.  Default is <<<false>>>.
+  
+  * <<allowRanges>> - if <<<true>>> versions ending with <<<-SNAPSHOT>>> will 
not be banned.  Default is <<<false>>>.
+  
+  * <<allowRangesWithIdenticalBounds>> - if <<<true>>> ranges having a range 
with same upper and lower bound (always inclusive) will not be banned (although 
they require resolving).
+  
+  * <<excludeOptionals>> - if <<<true>>> optional dependencies won't be 
checked.  Default is <<<false>>>.
+  
+  * <<excludedScopes>> - the list of scopes to exclude. By default no scopes 
are excluded.
+  
+  * <<ignores>> - a list of dependencies to ignore. The format is 
<<<groupId[:artifactId[:version[:type[:scope:[classifier]]]]]>>> where 
<<<artifactId>>>, <<<version>>>, <<<type>>>, <<<scope>>> and <<<classifier>>> 
are optional (but require all previous parts). Wildcards may be used to replace 
an entire or just parts of a section.
+      Examples:
+       
+        * <<<org.apache.maven>>>
+        
+        * <<<org.apache.maven:someArtifact>>>
+        
+        * <<<org.apache.maven:artifact:someVersion>>>
+        
+        * <<<org.apache.maven:*:*:jar:test>>>
+        
+        * <<<*:*:*:jar:compile:tests>>>
+
+        * <<<org.apache.*:maven-*:*>>>
+        
+        []
+   
+  * <<message>> - an optional message to the user if the rule fails.
+  
+  []
+
+   
+  Sample Plugin Configuration:
+  
++---+
+<project>
+  [...]
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <version>${project.version}</version>
+        <executions>
+          <execution>
+            <id>ban-dynamic-versions</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <banDynamicVersions>
+                  <ignores>
+                    <ignore>org.apache.maven</ignore>
+                  </ignore>
+                  <allowSnapshots>true</allowSnapshots>
+                </banDynamicVersions>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  [...]
+</project>
++---+
diff --git a/enforcer-rules/src/site/apt/index.apt 
b/enforcer-rules/src/site/apt/index.apt
index dfd8dec..ac384ba 100644
--- a/enforcer-rules/src/site/apt/index.apt
+++ b/enforcer-rules/src/site/apt/index.apt
@@ -37,6 +37,8 @@ Built-In Rules
   
   * 
{{{./banDuplicatePomDependencyVersions.html}banDuplicatePomDependencyVersions}} 
- enforces that the project doesn't have duplicate declared dependencies.
   
+  * {{{./banDynamicVersions.html}banDynamicVersions}} - bans all dependencies 
requiring version resolution at build time (i.e. version ranges, placeholders 
<<<RELEASE>>>/<<<LATEST>>> or SNAPSHOT versions).
+  
   * {{{./bannedDependencies.html}bannedDependencies}} - enforces that excluded 
dependencies aren't included.
   
   * {{{./bannedPlugins.html}bannedPlugins}} - enforces that specific plugins 
aren't included in the build.
diff --git a/maven-enforcer-plugin/src/it/mrm/repository/menforcer427-1.0.pom 
b/maven-enforcer-plugin/src/it/mrm/repository/menforcer427-1.0.pom
new file mode 100644
index 0000000..46e07d9
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/mrm/repository/menforcer427-1.0.pom
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  * 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. 
+  *
+-->
+<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";>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+  <artifactId>menforcer427</artifactId>
+  <version>1.0</version>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer427-a</artifactId>
+      <version>[1.0,2)</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer427-b</artifactId>
+      <version>[1.0,2)</version>
+    </dependency>
+  </dependencies>
+</project>
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/mrm/repository/menforcer427_a-1.0.pom 
b/maven-enforcer-plugin/src/it/mrm/repository/menforcer427_a-1.0.pom
new file mode 100644
index 0000000..01bf52a
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/mrm/repository/menforcer427_a-1.0.pom
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  * 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. 
+  *
+-->
+<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";>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+  <artifactId>menforcer427-a</artifactId>
+  <version>1.0</version>
+</project>
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/mrm/repository/menforcer427_b-1.0.pom 
b/maven-enforcer-plugin/src/it/mrm/repository/menforcer427_b-1.0.pom
new file mode 100644
index 0000000..10f018b
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/mrm/repository/menforcer427_b-1.0.pom
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  * 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. 
+  *
+-->
+<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";>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+  <artifactId>menforcer427-b</artifactId>
+  <version>1.0</version>
+</project>
\ No newline at end of file
diff --git 
a/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/invoker.properties 
b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/invoker.properties
new file mode 100644
index 0000000..58b6526
--- /dev/null
+++ 
b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/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/pom.xml 
b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/pom.xml
new file mode 100644
index 0000000..71afef5
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.enforcer</groupId>
+  <artifactId>ban-dynamic-versions-test</artifactId>
+  <version>1.0</version>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <version>@project.version@</version>
+        <executions>
+          <execution>
+            <id>test</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <banDynamicVersions>
+                  <excludedScopes>
+                    <excludedScope>test</excludedScope>
+                  </excludedScopes>
+                  
<allowRangesWithIdenticalBounds>true</allowRangesWithIdenticalBounds>
+                </banDynamicVersions>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+        <artifactId>menforcer138_archiver</artifactId>
+        <!-- non applicable dependency management (only affects transitive not 
direct ones) -->
+        <version>2.1.1</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+        <artifactId>menforcer138_utils</artifactId>
+        <version>[1.0,5]</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+        <artifactId>menforcer427-b</artifactId>
+        <version>1.0</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer138_archiver</artifactId>
+      <version>[1.3,2.1.1]</version> <!-- banned dynamic version range -->
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer138_utils</artifactId>
+      <!-- excluded managed scope -->
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer138_io</artifactId>
+      <version>LATEST</version> <!-- banned LATEST -->
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer134_model</artifactId>
+      <version>1.0-SNAPSHOT</version> <!-- banned SNAPSHOT -->
+    </dependency>
+    <dependency>
+      <!-- banned transitive version range -->
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer427</artifactId>
+      <version>1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.plugins.enforcer.its</groupId>
+      <artifactId>menforcer192-a</artifactId>
+      <!--  version range syntax with equal upper and lower bounds -->
+      <version>[1.0]</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git 
a/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/verify.groovy 
b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/verify.groovy
new file mode 100644
index 0000000..c26be3e
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/ban-dynamic-versions/verify.groovy
@@ -0,0 +1,25 @@
+/*
+ * 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( '[WARNING] Dependency 
org.apache.maven.plugins.enforcer.its:menforcer138_archiver:jar:2.1.1 (compile) 
is referenced with a banned dynamic version [1.3,2.1.1]' )
+assert buildLog.text.contains( '[WARNING] Dependency 
org.apache.maven.plugins.enforcer.its:menforcer138_io:jar:LATEST (compile) is 
referenced with a banned dynamic version LATEST' )
+assert buildLog.text.contains( '[WARNING] Dependency 
org.apache.maven.plugins.enforcer.its:menforcer134_model:jar:1.0-SNAPSHOT 
(compile) is referenced with a banned dynamic version 1.0-SNAPSHOT' )
+assert buildLog.text.contains( '[WARNING] Dependency 
org.apache.maven.plugins.enforcer.its:menforcer427-a:jar:1.0 (compile) via 
org.apache.maven.plugins.enforcer.its:menforcer427:jar:1.0 is referenced with a 
banned dynamic version [1.0,2)' )
+assert buildLog.text.contains( '[ERROR] Rule 0: 
org.apache.maven.plugins.enforcer.BanDynamicVersions failed with message' )
+assert buildLog.text.contains( 'Found 4 dependencies with dynamic versions. 
Look at the warnings emitted above for the details.' )

Reply via email to