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/sling-maven-enforcer-rules.git


The following commit(s) were added to refs/heads/master by this push:
     new 1672fb3  consider versions in the check
1672fb3 is described below

commit 1672fb31874f966ad09b447ba2363eb537055b6b
Author: Konrad Windszus <[email protected]>
AuthorDate: Thu Jun 9 09:43:09 2022 +0200

    consider versions in the check
---
 README.md                                          |  6 +-
 pom.xml                                            | 17 +++++
 .../verify.groovy                                  |  7 +-
 ...uireProvidedDependenciesInRuntimeClasspath.java | 74 +++++++++++++---------
 ...ProvidedDependenciesInRuntimeClasspathTest.java | 49 ++++++++++++++
 5 files changed, 118 insertions(+), 35 deletions(-)

diff --git a/README.md b/README.md
index bf2612a..a081b5e 100644
--- a/README.md
+++ b/README.md
@@ -19,10 +19,12 @@ It provides additional [Maven 
Enforcer](https://maven.apache.org/enforcer/maven-
 ### Require Provided Dependencies in Runtime Classpath
 
 Checks that the runtime classpath (e.g. used by Maven Plugins via the 
-[Plugin 
Classloader](https://maven.apache.org/guides/mini/guide-maven-classloading.html#3-plugin-classloaders)
 or by the [Appassembler Maven Plugin's `assemble` 
goal](http://www.mojohaus.org/appassembler/appassembler-maven-plugin/assemble-mojo.html))
 contains all [provided 
dependencies](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope)
 both direct and transitive ones.
+[Plugin 
Classloader](https://maven.apache.org/guides/mini/guide-maven-classloading.html#3-plugin-classloaders)
 or by the [Appassembler Maven Plugin's `assemble` 
goal](http://www.mojohaus.org/appassembler/appassembler-maven-plugin/assemble-mojo.html))
 contains all [provided 
dependencies](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope).
 
 As those are not transitively inherited they need to be declared explicitly in 
the pom.xml of the using Maven project.
 
+The check assumes semantic versioning, i.e. for provided dependencies without 
a version range all compatible runtime dependencies are accepted (i.e. ones 
that share groupId, artifactId, classifier and extension, and have the same 
major version and minor version which is equal or higher to the one of the 
provided dependency.
+
 #### Parameters
 
 All parameters are optional.
@@ -32,7 +34,7 @@ All parameters are optional.
      * `org.apache.maven:myArtifact`
      * `org.apache.maven:*:jar`
  * `includeOptionalDependencies` - whether to include optional dependencies in 
the check. Either `true` or `false`. By default no optional dependencies are 
checked.
- * `includeDirectDependencies` - whether to include direct (provided) 
dependencies in the check. Either `true` or `false`. By default no direct 
provided dependencies are checked.
+ * `includeDirectDependencies` - whether to include direct (provided) 
dependencies in the check. Either `true` or `false`. By default no direct 
provided dependencies are checked, i.e. only transitive ones are considered.
 
 #### Sample Plugin Configuration:
 
diff --git a/pom.xml b/pom.xml
index 1c2f544..c6c6e28 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,18 @@
         <project.build.outputTimestamp>10</project.build.outputTimestamp>
     </properties>
 
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.junit</groupId>
+                <artifactId>junit-bom</artifactId>
+                <version>5.8.2</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
     <dependencies>
         <dependency>
             <groupId>org.apache.maven.enforcer</groupId>
@@ -85,6 +97,11 @@
             <version>3.0.2</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/it/require-provided-deps-in-runtime-classpath/verify.groovy 
b/src/it/require-provided-deps-in-runtime-classpath/verify.groovy
index c00b952..14cfcf9 100644
--- a/src/it/require-provided-deps-in-runtime-classpath/verify.groovy
+++ b/src/it/require-provided-deps-in-runtime-classpath/verify.groovy
@@ -17,7 +17,8 @@
  * under the License.
  */
 File buildLog = new File(basedir, 'build.log')
-assert buildLog.text.contains('Found 16 missing runtime dependencies')
-assert buildLog.text.contains('Provided dependency 
org.osgi:org.osgi.framework:jar:1.8.0 (via 
org.apache.jackrabbit.vault:vault-cli:jar:3.6.0 -> 
org.apache.jackrabbit.vault:org.apache.jackrabbit.vault:jar:3.6.0) not found as 
runtime dependency!')
-assert buildLog.text.contains('Provided dependency 
com.google.code.findbugs:jsr305:jar:3.0.2 (direct) not found as runtime 
dependency!')
+assert buildLog.text.contains('Found 17 missing runtime dependencies')
+assert buildLog.text.contains('[WARNING] Dependency 
org.osgi:org.osgi.framework:jar:1.8.0 (provided) via 
org.apache.jackrabbit.vault:vault-cli:jar:3.6.0 -> 
org.apache.jackrabbit.vault:org.apache.jackrabbit.vault:jar:3.6.0 not found as 
runtime dependency!')
+assert buildLog.text.contains('[WARNING] Dependency 
com.google.code.findbugs:jsr305:jar:3.0.2 (provided) not found as runtime 
dependency!')
+assert buildLog.text.contains('[WARNING] Found provided dependency 
org.apache.jackrabbit:oak-jackrabbit-api:jar:1.42.0 only with potentially 
incompatible version 1.40.0 in runtime classpath')
 assert true
\ No newline at end of file
diff --git 
a/src/main/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspath.java
 
b/src/main/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspath.java
index 2d89f8d..aa6ee5e 100644
--- 
a/src/main/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspath.java
+++ 
b/src/main/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspath.java
@@ -33,6 +33,10 @@ import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 
 import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import 
org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.VersionRange;
 import org.apache.maven.enforcer.rule.api.EnforcerRule2;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
@@ -53,7 +57,6 @@ import 
org.eclipse.aether.collection.DependencyCollectionContext;
 import org.eclipse.aether.collection.DependencyCollectionException;
 import org.eclipse.aether.collection.DependencySelector;
 import org.eclipse.aether.graph.Dependency;
-import org.eclipse.aether.graph.DependencyFilter;
 import org.eclipse.aether.graph.DependencyNode;
 import org.eclipse.aether.graph.DependencyVisitor;
 import org.eclipse.aether.graph.Exclusion;
@@ -150,7 +153,7 @@ public class RequireProvidedDependenciesInRuntimeClasspath
                     rootDependency, repoSystem, newRepoSession, 
remoteRepositories, log);
             int numViolations = checkForMissingArtifacts(rootDependencyNode, 
runtimeArtifacts, log);
             if (numViolations > 0) {
-                throw new EnforcerRuleException("Found " + numViolations + " 
missing runtime dependencies. Look at the errors emitted above for the 
details.");
+                throw new EnforcerRuleException("Found " + numViolations + " 
missing runtime dependencies. Look at the warnings emitted above for the 
details.");
             }
         } catch (DependencyCollectionException e) {
             // draw graph
@@ -274,10 +277,14 @@ public class RequireProvidedDependenciesInRuntimeClasspath
             if (isRoot) {
                 isRoot = false;
             } else {
-                if (!isArtifactContainedInList(dependencyNode.getArtifact(), 
artifacts)) {
-                    MessageBuilder msgBuilder = MessageUtils.buffer();
-                    log.error(msgBuilder.a("Provided dependency 
").strong(dependencyNode.getArtifact()).mojo(" (" + 
dumpIntermediatePath(nodeStack) + ")").a(" not found as runtime 
dependency!").toString());
-                    numMissingArtifacts++;
+                try {
+                    if 
(!isCompatibleArtifactContainedInList(dependencyNode.getArtifact(), artifacts, 
log)) {
+                        MessageBuilder msgBuilder = MessageUtils.buffer();
+                        log.warn(msgBuilder.a("Dependency 
").strong(dependencyNode.getDependency()).mojo(dumpIntermediatePath(nodeStack)).a("
 not found as runtime dependency!").toString());
+                        numMissingArtifacts++;
+                    }
+                } catch (InvalidVersionSpecificationException e) {
+                    log.error("Invalid version given for artifact " + 
dependencyNode.getArtifact(), e);
                 }
                 nodeStack.addLast(dependencyNode);
             }
@@ -295,6 +302,15 @@ public class RequireProvidedDependenciesInRuntimeClasspath
         public int getNumMissingArtifacts() {
             return numMissingArtifacts;
         }
+
+        private static String dumpIntermediatePath(Collection<DependencyNode> 
path) {
+            if (path.isEmpty()) {
+                return "";
+            }
+            return " via " + path.stream()
+                    .map(n -> n.getArtifact().toString())
+                    .collect(Collectors.joining(" -> "));
+        }
     }
 
     protected DependencyNode collectTransitiveDependencies(
@@ -322,25 +338,35 @@ public class RequireProvidedDependenciesInRuntimeClasspath
         return depVisitor.getNumMissingArtifacts();
     }
 
-    private static String dumpIntermediatePath(Collection<DependencyNode> 
path) {
-        if (path.isEmpty()) {
-            return "direct";
-        }
-        return "via " + path.stream()
-                .map(n -> n.getArtifact().toString())
-                .collect(Collectors.joining(" -> "));
-    }
-
-    protected static boolean isArtifactContainedInList(Artifact artifact,
-            List<Artifact> artifacts) {
+    protected static boolean isCompatibleArtifactContainedInList(Artifact 
artifact,
+            List<Artifact> artifacts, Log log) throws 
InvalidVersionSpecificationException {
         for (Artifact artifactInList : artifacts) {
             if (areArtifactsEqualDisregardingVersion(artifact, 
artifactInList)) {
-                return true;
+                // check version compatibility
+                if (isVersionCompatible(artifact.getVersion(), 
artifactInList.getVersion())) {
+                    return true;
+                } else {
+                    MessageBuilder msgBuilder = MessageUtils.buffer();
+                    log.warn("Found provided dependency " + 
msgBuilder.strong(artifact).a(" only with potentially incompatible version 
").strong(artifactInList.getVersion()).toString() + " in runtime classpath");
+                }
             }
         }
         return false;
     }
 
+    protected static boolean isVersionCompatible(String requiredVersion, 
String providedVersion) throws InvalidVersionSpecificationException {
+       ArtifactVersion provided = new DefaultArtifactVersion(providedVersion);
+       VersionRange required = 
VersionRange.createFromVersionSpec(requiredVersion);
+       
+       // is it really a range?
+       if (required.getRecommendedVersion() == null) {
+           return required.containsVersion(provided);
+       } else {
+           // if only one version assume that versions with a higher minor 
version are compatible
+           return required.getRecommendedVersion().getMajorVersion() == 
provided.getMajorVersion() && 
required.getRecommendedVersion().getMinorVersion() <= 
provided.getMinorVersion();
+       }
+    }
+
     /**
      * 
      * @param artifact1 one artifact
@@ -354,18 +380,6 @@ public class RequireProvidedDependenciesInRuntimeClasspath
                 && artifact1.getExtension().equals(artifact2.getExtension()));
     }
 
-    private static final class SingleArtifactFilter implements 
DependencyFilter {
-        private final org.eclipse.aether.artifact.Artifact artifact;
-
-        public SingleArtifactFilter(org.eclipse.aether.artifact.Artifact 
artifact) {
-            this.artifact = artifact;
-        }
-        @Override
-        public boolean accept(DependencyNode node, List<DependencyNode> 
parents) {
-            return node.getDependency().getArtifact().equals(artifact);
-        }
-    }
-
     public void setExcludes(List<String> theExcludes) {
         this.excludes = theExcludes;
     }
diff --git 
a/src/test/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspathTest.java
 
b/src/test/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspathTest.java
new file mode 100644
index 0000000..5ad472e
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspathTest.java
@@ -0,0 +1,49 @@
+package org.apache.sling.maven.enforcer;
+
+import 
org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.junit.jupiter.api.Assertions;
+
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+class RequireProvidedDependenciesInRuntimeClasspathTest {
+
+    @Test
+    void testIsVersionCompatible() throws InvalidVersionSpecificationException 
{
+        
Assertions.assertTrue(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("1.0.0",
 "1.0.0"));
+        
Assertions.assertTrue(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("[,1.0.0]",
 "1.0.0"));
+        
Assertions.assertTrue(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("[1,2)",
 "1.1.0"));
+        
Assertions.assertTrue(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("1.0.0",
 "1.1.0"));
+        
Assertions.assertFalse(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("[1,2)",
 "2.0"));
+        
Assertions.assertFalse(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("1.1",
 "1.0"));
+        
Assertions.assertFalse(RequireProvidedDependenciesInRuntimeClasspath.isVersionCompatible("1.1",
 "2.0"));
+    }
+
+    @Test
+    void testAreArtifactsEqualDisregardingVersion() {
+        
Assertions.assertTrue(RequireProvidedDependenciesInRuntimeClasspath.areArtifactsEqualDisregardingVersion(new
 DefaultArtifact("myArtifact:myGroup:1.0.0"), new 
DefaultArtifact("myArtifact:myGroup:2.0.0")));
+        
Assertions.assertTrue(RequireProvidedDependenciesInRuntimeClasspath.areArtifactsEqualDisregardingVersion(new
 DefaultArtifact("myArtifact:myGroup:myClassifier:myExtension:1.0.0"), new 
DefaultArtifact("myArtifact:myGroup:myClassifier:myExtension:2.0.0")));
+        
Assertions.assertFalse(RequireProvidedDependenciesInRuntimeClasspath.areArtifactsEqualDisregardingVersion(new
 DefaultArtifact("myArtifact:myGroup:myClassifier:myExtension:1.0.0"), new 
DefaultArtifact("myArtifact:myGroup:myClassifier:myExtension1:2.0.0")));
+        
Assertions.assertFalse(RequireProvidedDependenciesInRuntimeClasspath.areArtifactsEqualDisregardingVersion(new
 DefaultArtifact("myArtifact:myGroup:myClassifier:myExtension:1.0.0"), new 
DefaultArtifact("myArtifact:myGroup:myClassifier1:myExtension:2.0.0")));
+    }
+}

Reply via email to