You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2022/06/08 12:37:21 UTC
[sling-maven-enforcer-rules] branch master updated: SLING-11369 add flags for optional and direct dependencies
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 d66bcfb SLING-11369 add flags for optional and direct dependencies
d66bcfb is described below
commit d66bcfb9a32dc923f5c692b4c23bd4641c50a00d
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Wed Jun 8 14:37:16 2022 +0200
SLING-11369 add flags for optional and direct dependencies
make exclusions also exclude their dependencies
extend IT
---
README.md | 9 +-
pom.xml | 16 +-
.../invoker.properties | 0
.../pom.xml | 23 ++-
.../verify.groovy | 0
...uireProvidedDependenciesInRuntimeClasspath.java | 189 ++++++++++++++++-----
6 files changed, 189 insertions(+), 48 deletions(-)
diff --git a/README.md b/README.md
index d353fda..bf2612a 100644
--- a/README.md
+++ b/README.md
@@ -25,11 +25,14 @@ As those are not transitively inherited they need to be declared explicitly in t
#### Parameters
- * `excludes` - a list of dependencies to skip. Their transitive dependencies are not evaluated either. The format is `groupId[:artifactId][:version][:type][:scope][:classifier]` where `artifactId`, `version`, `type`, `scope` and `classifier` are optional. Wild cards (`*` for arbitrarily many characters or `?` for an arbitrary single character) may be used to replace an entire or just parts of a section. *Examples*:
+All parameters are optional.
+
+ * `excludes` - a list of dependencies to skip. Their transitive dependencies are not evaluated either. The format is `<groupId>[:<artifactId>[:<extension>[:<classifier>]]]`. Wild cards (`*`) may be used to replace an entire part of a section. *Examples*:
* `org.apache.maven` (everything with the given group)
* `org.apache.maven:myArtifact`
- * `org.apache.maven:*:1.2` (exclude version 1.2 and above, equivalent to [1.2,) )
- * `org.apache.maven:*:[1.2]` (explicit exclude of version 1.2)
+ * `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.
#### Sample Plugin Configuration:
diff --git a/pom.xml b/pom.xml
index ef0a356..1c2f544 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.sling</groupId>
<artifactId>sling</artifactId>
- <version>47</version>
+ <version>48</version>
</parent>
<artifactId>maven-enforcer-rules</artifactId>
<version>0.0.1-SNAPSHOT</version>
@@ -40,7 +40,7 @@
<properties>
<sling.java.version>8</sling.java.version>
<api.version>3.0.0</api.version>
- <maven.version>3.3.1</maven.version>
+ <maven.version>3.1.1</maven.version><!-- must be the same version as used by enforcer-api due to shared classloader -->
<project.build.outputTimestamp>10</project.build.outputTimestamp>
</properties>
@@ -54,6 +54,12 @@
<groupId>org.apache.maven.enforcer</groupId>
<artifactId>enforcer-rules</artifactId>
<version>${api.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
@@ -67,6 +73,11 @@
<version>${maven.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven.shared</groupId>
+ <artifactId>maven-shared-utils</artifactId>
+ <version>3.3.4</version>
+ </dependency>
<!-- as Maven Enforcer API still uses JSR305 null annotations we cannot yet switch to Jetbrains null annotations -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
@@ -90,6 +101,7 @@
</pomIncludes>
<postBuildHookScript>verify.groovy</postBuildHookScript>
<streamLogsOnFailures>true</streamLogsOnFailures>
+ <debug>true</debug>
</configuration>
<executions>
<execution>
diff --git a/src/it/require-transitive-provided-deps-in-runtime-classpath/invoker.properties b/src/it/require-provided-deps-in-runtime-classpath/invoker.properties
similarity index 100%
rename from src/it/require-transitive-provided-deps-in-runtime-classpath/invoker.properties
rename to src/it/require-provided-deps-in-runtime-classpath/invoker.properties
diff --git a/src/it/require-transitive-provided-deps-in-runtime-classpath/pom.xml b/src/it/require-provided-deps-in-runtime-classpath/pom.xml
similarity index 78%
rename from src/it/require-transitive-provided-deps-in-runtime-classpath/pom.xml
rename to src/it/require-provided-deps-in-runtime-classpath/pom.xml
index 61a85de..de5d56d 100644
--- a/src/it/require-transitive-provided-deps-in-runtime-classpath/pom.xml
+++ b/src/it/require-provided-deps-in-runtime-classpath/pom.xml
@@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.sling.maven.enforcer.it</groupId>
- <artifactId>require-transitive-provided-deps-in-runtime-classpath</artifactId>
+ <artifactId>require-provided-deps-in-runtime-classpath</artifactId>
<packaging>pom</packaging>
<version>@project.version@</version>
@@ -52,8 +52,10 @@
<excludes>
<exclude>*:h2</exclude>
<exclude>org.codehaus.woodstox</exclude>
- <exclude>javax.servlet:javax.servlet-api:3.1.0</exclude>
+ <exclude>javax.servlet:javax.servlet-api</exclude>
+ <exclude>*:org.apache.felix.scr.annotations</exclude>
</excludes>
+ <includeDirectDependencies>true</includeDirectDependencies>
</requireProvidedDependenciesInRuntimeClasspath>
</rules>
<fail>true</fail>
@@ -71,12 +73,27 @@
<artifactId>vault-cli</artifactId>
<version>3.6.0</version>
</dependency>
- <!-- direct provided dependency -->
+ <!-- direct provided dependency excluded by default -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<scope>provided</scope>
</dependency>
+ <!-- provided but excluded dependency with further transitive dependencies -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <version>1.9.8</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- test scope is excluded by default -->
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest</artifactId>
+ <version>2.2</version>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
</project>
\ No newline at end of file
diff --git a/src/it/require-transitive-provided-deps-in-runtime-classpath/verify.groovy b/src/it/require-provided-deps-in-runtime-classpath/verify.groovy
similarity index 100%
rename from src/it/require-transitive-provided-deps-in-runtime-classpath/verify.groovy
rename to src/it/require-provided-deps-in-runtime-classpath/verify.groovy
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 545238e..9f6a37e 100644
--- a/src/main/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspath.java
+++ b/src/main/java/org/apache/sling/maven/enforcer/RequireProvidedDependenciesInRuntimeClasspath.java
@@ -21,6 +21,7 @@ package org.apache.sling.maven.enforcer;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
@@ -41,12 +42,17 @@ import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.enforcer.AbstractNonCacheableEnforcerRule;
import org.apache.maven.plugins.enforcer.utils.ArtifactUtils;
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.DependencyCollectionContext;
+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;
@@ -56,13 +62,11 @@ import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
-import org.eclipse.aether.util.filter.OrDependencyFilter;
-import org.eclipse.aether.util.filter.PatternInclusionsDependencyFilter;
-import org.eclipse.aether.util.filter.ScopeDependencyFilter;
import org.eclipse.aether.util.graph.selector.AndDependencySelector;
import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
+import org.eclipse.aether.util.graph.selector.StaticDependencySelector;
import org.eclipse.aether.util.graph.visitor.PathRecordingDependencyVisitor;
import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
@@ -77,13 +81,28 @@ import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;
public class RequireProvidedDependenciesInRuntimeClasspath
extends AbstractNonCacheableEnforcerRule implements EnforcerRule2 {
- /** Specify the banned dependencies. This can be a list of artifacts in the format <code>groupId[:artifactId][:version]</code>. Any of
- * the sections can be a wildcard by using '*' (ie group:*:1.0) <br>
- * The rule will fail if any dependency matches any exclude, unless it also matches an include rule.
+ /**
+ * Specify the banned dependencies. This is a list of artifacts in format {@code <groupId>[:<artifactId>[:<extension>[:<classifier>]]]}.
+ * Excluded dependencies are not traversed, i.e. their transitive dependencies are not considered.
*
* @see {@link #setExcludes(List)} */
private List<String> excludes = null;
+ /**
+ * Whether to include optional dependencies in the check. Default = false.
+ *
+ * @see {@link #setIncludeOptionalDependencies(boolean)}
+ */
+ private boolean includeOptionals;
+
+ /**
+ * Whether to include direct (i.e. non transitive) provided dependencies in the check. Default = false.
+ *
+ * @see {@link #setIncludeDirectDependencies(boolean)}
+ */
+ private boolean includeDirects;
+
+ @SuppressWarnings("unchecked")
@Override
public void execute(@Nonnull EnforcerRuleHelper helper) throws EnforcerRuleException {
MavenProject project;
@@ -103,31 +122,48 @@ public class RequireProvidedDependenciesInRuntimeClasspath
throw new EnforcerRuleException("Unable to retrieve component RepositorySystem", cle);
}
Log log = helper.getLog();
- // make sure to transitively also collect dependencies of provided scope
- newRepoSession.setDependencySelector(new AndDependencySelector(
- new OptionalDependencySelector(),
- new ScopeDependencySelector("test"),
- new ExclusionDependencySelector(Collections.singleton(new Exclusion("*", "*", "*", "pom")))));
+
+ Collection<DependencySelector> depSelectors = new ArrayList<>();
+ depSelectors.add(new ScopeDependencySelector("test")); // exclude transitive and direct "test" dependencies of the rootDependency (i.e. the current project)
+ // add also the exclude patterns
+ if (excludes != null && !excludes.isEmpty()) {
+ Collection<Exclusion> exclusions = excludes.stream().map(RequireProvidedDependenciesInRuntimeClasspath::convertPatternToExclusion).collect(Collectors.toCollection(LinkedList::new));
+ exclusions.add(new Exclusion("*", "*", "*", "pom"));
+ depSelectors.add(new ExclusionDependencySelector(exclusions));
+ }
+ if (!includeOptionals) {
+ depSelectors.add(new OptionalDependencySelector());
+ }
+ if (!includeDirects) {
+ depSelectors.add(new LevelAndScopeExclusionSelector(1, "provided"));
+ }
+ newRepoSession.setDependencySelector(new AndDependencySelector(depSelectors));
+
// use the ones for https://maven.apache.org/guides/mini/guide-maven-classloading.html#3-plugin-classloaders
+ @SuppressWarnings("deprecation")
List<Artifact> runtimeArtifacts = project.getRuntimeArtifacts();
+ if (log.isDebugEnabled()) {
+ log.debug("Collected " + runtimeArtifacts.size()+ " runtime dependencies ");
+ for (Artifact runtimeArtifact : runtimeArtifacts) {
+ log.debug(runtimeArtifact.toString());
+ }
+ }
+ Dependency rootDependency = RepositoryUtils.toDependency(project.getArtifact(), null);
try {
Map<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> artifactMap = collectTransitiveDependencies(
- RepositoryUtils.toDependency(project.getArtifact(), null),
- repoSystem, newRepoSession, remoteRepositories);
+ rootDependency, repoSystem, newRepoSession, remoteRepositories, log);
if (log.isDebugEnabled()) {
log.debug("Collected " + artifactMap.size()+ " transitive dependencies: ");
for (Entry<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> artifactResult : artifactMap.entrySet()) {
log.debug(artifactResult.getKey().toString()
+ " (" + dumpPaths(artifactResult.getValue()) + ")");
}
-
}
// collect all violations
- Collection<String> violationMessages = new LinkedList<>();
- checkForMissingArtifacts(artifactMap, runtimeArtifacts, violationMessages, log);
- if (!violationMessages.isEmpty()) {
- throw new EnforcerRuleException("Found " + violationMessages.size() + " missing runtime dependencies:" + System.lineSeparator() + String.join(System.lineSeparator(), violationMessages));
+ int numViolations = checkForMissingArtifacts(artifactMap, runtimeArtifacts, log);
+ if (numViolations > 0) {
+ throw new EnforcerRuleException("Found " + numViolations + " missing runtime dependencies. Look at the errors emitted above for the details.");
}
} catch (DependencyResolutionException e) {
// draw graph
@@ -138,10 +174,74 @@ public class RequireProvidedDependenciesInRuntimeClasspath
throw new EnforcerRuleException("Could not retrieve dependency metadata for project : "
+ e.getMessage() + ". Partial dependency tree: " + writer.toString(), e);
}
+ }
+
+
+ /**
+ *
+ * @param pattern string in in the format {@code <groupId>[:<artifactId>[:<extension>[:<classifier>]]]}
+ * @return the exclusion
+ */
+ private static Exclusion convertPatternToExclusion(String pattern) {
+ String[] parts = pattern.split(":");
+ if (parts.length > 4) {
+ throw new IllegalArgumentException("Pattern must contain at most three colons, but contains " + parts + ": " + pattern);
+ }
+ String groupId = parts[0];
+ String artifactId = "*";
+ String extension = "*";
+ String classifier = "*";
+ if (parts.length > 1) {
+ artifactId = parts[1];
+ }
+ if (parts.length > 2) {
+ extension = parts[2];
+ }
+ if (parts.length > 3) {
+ classifier = parts[3];
+ }
+ return new Exclusion(groupId, artifactId, classifier, extension);
+ }
+
+ private static final class LevelAndScopeExclusionSelector implements DependencySelector {
+
+ private final int targetLevel;
+ private final String targetScope;
+ private final int currentLevel;
+
+ private static final DependencySelector ALL_SELECTOR = new StaticDependencySelector(true);
+
+ public LevelAndScopeExclusionSelector(int targetLevel, String targetScope) {
+ this(targetLevel, targetScope, 0);
+ }
+
+ private LevelAndScopeExclusionSelector(int targetLevel, String targetScope, int currentLevel) {
+ this.targetLevel = targetLevel;
+ this.targetScope = Objects.requireNonNull(targetScope);
+ this.currentLevel = currentLevel;
+ }
+ @Override
+ public boolean selectDependency(Dependency dependency) {
+ if (currentLevel == targetLevel) {
+ return !targetScope.equals(dependency.getScope());
+ }
+ return true;
+ }
+
+ @Override
+ public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
+ if (currentLevel < targetLevel) {
+ return new LevelAndScopeExclusionSelector(targetLevel, targetScope, currentLevel+1);
+ } else {
+ // org.eclipse.aether:aether-util:jar:0.9.0.M2 used at runtime doesn't yet support null for no restrictions
+ return ALL_SELECTOR;
+ }
+ }
}
- private final class DependencyVisitorPrinter implements DependencyVisitor {
+
+ private static final class DependencyVisitorPrinter implements DependencyVisitor {
private final PrintWriter printWriter;
private String indent = "";
@@ -169,19 +269,23 @@ public class RequireProvidedDependenciesInRuntimeClasspath
}
}
- protected void checkForMissingArtifacts(Map<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> artifactMap, List<Artifact> runtimeArtifacts,
- Collection<String> violationMessages, Log log) throws EnforcerRuleException {
+ protected int checkForMissingArtifacts(Map<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> artifactMap, List<Artifact> runtimeArtifacts,
+ Log log) throws EnforcerRuleException {
+ int numViolations = 0;
+
for (Entry<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> artifactResult : artifactMap.entrySet()) {
Artifact dependency = RepositoryUtils.toArtifact(artifactResult.getKey());
if (ArtifactUtils.checkDependencies(Collections.singleton(dependency), excludes) == null) {
if (!isArtifactContainedInList(dependency, runtimeArtifacts)) {
- violationMessages.add("Provided dependency " + dependency
- + " (" + dumpPaths(artifactResult.getValue()) + ") not found as runtime dependency!");
+ MessageBuilder msgBuilder = MessageUtils.buffer();
+ log.error(msgBuilder.a("Provided dependency ").strong(dependency).mojo(" (" + dumpPaths(artifactResult.getValue()) + ")").a(" not found as runtime dependency!").toString());
+ numViolations++;
}
} else {
log.debug("Skip excluded dependency " + dependency);
}
}
+ return numViolations;
}
private static String dumpPaths(List<List<DependencyNode>> paths) {
@@ -196,6 +300,9 @@ public class RequireProvidedDependenciesInRuntimeClasspath
}
private static String dumpPath(List<DependencyNode> path) {
+ if (path.size() <= 2) {
+ return "";
+ }
return path.stream()
.skip(1) // first entry is the project itself
.limit(path.size() - 2l) // last entry is the dependency (which is logged separately)
@@ -217,22 +324,22 @@ public class RequireProvidedDependenciesInRuntimeClasspath
return false;
}
- protected static Map<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> collectTransitiveDependencies(
+ protected Map<org.eclipse.aether.artifact.Artifact, List<List<DependencyNode>>> collectTransitiveDependencies(
org.eclipse.aether.graph.Dependency rootDependency,
RepositorySystem repoSystem, RepositorySystemSession repoSession,
- List<RemoteRepository> remoteRepositories)
+ List<RemoteRepository> remoteRepositories, Log log)
throws DependencyResolutionException {
CollectRequest collectRequest = new CollectRequest(rootDependency, remoteRepositories);
- // project itself (independent of scope) + transitive provided ones
- DependencyFilter depFilter = new OrDependencyFilter(
- new PatternInclusionsDependencyFilter(
- rootDependency.getArtifact().getGroupId() + ":"
- + rootDependency.getArtifact().getArtifactId()),
- new ScopeDependencyFilter(Collections.singleton("provided"),
- Collections.<String> emptySet()));
- // filtering applied only for DependencyResult.getArtifactResults() but not for DependencyResult.getRoot()!
- DependencyRequest req = new DependencyRequest(collectRequest, depFilter);
+ DependencyRequest req = new DependencyRequest(collectRequest, null);
DependencyResult resolutionResult = repoSystem.resolveDependencies(repoSession, req);
+ if (log.isDebugEnabled()) {
+ // draw full dependency graph
+ StringWriter writer = new StringWriter();
+ DependencyVisitor depVisitor = new TreeDependencyVisitor(
+ new DependencyVisitorPrinter(new PrintWriter(writer)));
+ resolutionResult.getRoot().accept(depVisitor);
+ log.debug("dependency tree: " + writer.toString());
+ }
// generate a map with key = artifact, value = all paths to it
return resolutionResult.getArtifactResults().stream()
.filter(a -> !a.getArtifact().toString().equals(rootDependency.getArtifact().toString())) // remove rootDependency itself
@@ -245,7 +352,7 @@ public class RequireProvidedDependenciesInRuntimeClasspath
return visitor.getPaths();
}
- static final class SingleArtifactFilter implements DependencyFilter {
+ private static final class SingleArtifactFilter implements DependencyFilter {
private final org.eclipse.aether.artifact.Artifact artifact;
public SingleArtifactFilter(org.eclipse.aether.artifact.Artifact artifact) {
@@ -257,13 +364,15 @@ public class RequireProvidedDependenciesInRuntimeClasspath
}
}
- /** Specify the banned dependencies. This can be a list of artifacts in the format <code>groupId[:artifactId][:version]</code>. Any of
- * the sections can be a wildcard by using '*' (ie group:*:1.0) <br>
- * The rule will fail if any dependency matches any exclude, unless it also matches an include rule.
- *
- * @param theExcludes the excludes to set */
public void setExcludes(List<String> theExcludes) {
this.excludes = theExcludes;
}
+ public void setIncludeOptionalDependencies(boolean includeOptionals) {
+ this.includeOptionals = includeOptionals;
+ }
+
+ public void setIncludeDirectDependencies(boolean includeDirects) {
+ this.includeDirects = includeDirects;
+ }
}
\ No newline at end of file