[MENFORCER-423] Add rule to enforce an explicit dependency scope
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AbstractStandardEnforcerRule.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AbstractStandardEnforcerRule.java
index fecdb09..f369259 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AbstractStandardEnforcerRule.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AbstractStandardEnforcerRule.java
@@ -21,6 +21,8 @@
import org.apache.maven.enforcer.rule.api.EnforcerLevel;
import org.apache.maven.enforcer.rule.api.EnforcerRule2;
+import org.apache.maven.model.InputLocation;
+import org.apache.maven.project.MavenProject;
/**
* The Class AbstractStandardEnforcerRule.
@@ -60,4 +62,70 @@
this.level = level;
}
+ /**
+ * Returns an identifier of a given project.
+ * @param project the project
+ * @return the identifier of the project in the format {@code <groupId>:<artifactId>:<version>}
+ */
+ private static String getProjectId( MavenProject project )
+ {
+ StringBuilder buffer = new StringBuilder( 128 );
+
+ buffer.append( ( project.getGroupId() != null && project.getGroupId().length() > 0 ) ? project.getGroupId()
+ : "[unknown-group-id]" );
+ buffer.append( ':' );
+ buffer.append( ( project.getArtifactId() != null && project.getArtifactId().length() > 0 )
+ ? project.getArtifactId()
+ : "[unknown-artifact-id]" );
+ buffer.append( ':' );
+ buffer.append( ( project.getVersion() != null && project.getVersion().length() > 0 ) ? project.getVersion()
+ : "[unknown-version]" );
+
+ return buffer.toString();
+ }
+
+ /**
+ * Creates a string with line/column information for problems originating directly from this POM. Inspired by
+ * {@code o.a.m.model.building.ModelProblemUtils.formatLocation(...)}.
+ *
+ * @param project the current project.
+ * @param location The location which should be formatted, must not be {@code null}.
+ * @return The formatted problem location or an empty string if unknown, never {@code null}.
+ */
+ protected static String formatLocation( MavenProject project, InputLocation location )
+ {
+ StringBuilder buffer = new StringBuilder();
+
+ if ( !location.getSource().getModelId().equals( getProjectId( project ) ) )
+ {
+ buffer.append( location.getSource().getModelId() );
+
+ if ( location.getSource().getLocation().length() > 0 )
+ {
+ if ( buffer.length() > 0 )
+ {
+ buffer.append( ", " );
+ }
+ buffer.append( location.getSource().getLocation() );
+ }
+ }
+ if ( location.getLineNumber() > 0 )
+ {
+ if ( buffer.length() > 0 )
+ {
+ buffer.append( ", " );
+ }
+ buffer.append( "line " ).append( location.getLineNumber() );
+ }
+ if ( location.getColumnNumber() > 0 )
+ {
+ if ( buffer.length() > 0 )
+ {
+ buffer.append( ", " );
+ }
+ buffer.append( "column " ).append( location.getColumnNumber() );
+ }
+ return buffer.toString();
+ }
+
}
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDependencyManagementScope.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDependencyManagementScope.java
index 94fe177..06b9461 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDependencyManagementScope.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/BanDependencyManagementScope.java
@@ -29,7 +29,6 @@
import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
-import org.apache.maven.model.InputLocation;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher;
import org.apache.maven.project.MavenProject;
@@ -81,7 +80,6 @@
List<Dependency> violatingDependencies = getViolatingDependencies( logger, depMgmt );
if ( !violatingDependencies.isEmpty() )
{
- String projectId = getProjectId( project );
String message = getMessage();
StringBuilder buf = new StringBuilder();
if ( message == null )
@@ -91,7 +89,7 @@
buf.append( message + System.lineSeparator() );
for ( Dependency violatingDependency : violatingDependencies )
{
- buf.append( getErrorMessage( projectId, violatingDependency ) );
+ buf.append( getErrorMessage( project, violatingDependency ) );
}
throw new EnforcerRuleException( buf.toString() );
}
@@ -136,76 +134,14 @@
return violatingDependencies;
}
- private static CharSequence getErrorMessage( String projectId, Dependency violatingDependency )
+ private static CharSequence getErrorMessage( MavenProject project, Dependency violatingDependency )
{
return "Banned scope '" + violatingDependency.getScope() + "' used on dependency '"
+ violatingDependency.getManagementKey() + "' @ "
- + formatLocation( projectId, violatingDependency.getLocation( "" ) )
+ + formatLocation( project, violatingDependency.getLocation( "" ) )
+ System.lineSeparator();
}
- // Get the identifier of the POM in the format <groupId>:<artifactId>:<version>.
- protected static String getProjectId( MavenProject project )
- {
- StringBuilder buffer = new StringBuilder( 128 );
-
- buffer.append( ( project.getGroupId() != null && project.getGroupId().length() > 0 ) ? project.getGroupId()
- : "[unknown-group-id]" );
- buffer.append( ':' );
- buffer.append( ( project.getArtifactId() != null && project.getArtifactId().length() > 0 )
- ? project.getArtifactId()
- : "[unknown-artifact-id]" );
- buffer.append( ':' );
- buffer.append( ( project.getVersion() != null && project.getVersion().length() > 0 ) ? project.getVersion()
- : "[unknown-version]" );
-
- return buffer.toString();
- }
-
- /**
- * Creates a string with line/column information for problems originating directly from this POM. Inspired by
- * {@code o.a.m.model.building.ModelProblemUtils.formatLocation(...)}.
- *
- * @param projectId the id of the current project's pom.
- * @param location The location which should be formatted, must not be {@code null}.
- * @return The formatted problem location or an empty string if unknown, never {@code null}.
- */
- protected static String formatLocation( String projectId, InputLocation location )
- {
- StringBuilder buffer = new StringBuilder();
-
- if ( !location.getSource().getModelId().equals( projectId ) )
- {
- buffer.append( location.getSource().getModelId() );
-
- if ( location.getSource().getLocation().length() > 0 )
- {
- if ( buffer.length() > 0 )
- {
- buffer.append( ", " );
- }
- buffer.append( location.getSource().getLocation() );
- }
- }
- if ( location.getLineNumber() > 0 )
- {
- if ( buffer.length() > 0 )
- {
- buffer.append( ", " );
- }
- buffer.append( "line " ).append( location.getLineNumber() );
- }
- if ( location.getColumnNumber() > 0 )
- {
- if ( buffer.length() > 0 )
- {
- buffer.append( ", " );
- }
- buffer.append( "column " ).append( location.getColumnNumber() );
- }
- return buffer.toString();
- }
-
public void setExcludes( List<String> theExcludes )
{
this.excludes = theExcludes;
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireExplicitDependencyScope.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireExplicitDependencyScope.java
new file mode 100644
index 0000000..2a5b96e
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireExplicitDependencyScope.java
@@ -0,0 +1,97 @@
+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.List;
+
+import org.apache.maven.enforcer.rule.api.EnforcerLevel;
+import org.apache.maven.enforcer.rule.api.EnforcerRule2;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
+import org.apache.maven.model.Dependency;
+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;
+
+/**
+ * Checks that all dependencies have an explicitly declared scope in the non-effective pom (i.e. without taking
+ * inheritance or dependency management into account).
+ */
+public class RequireExplicitDependencyScope
+ extends AbstractNonCacheableEnforcerRule
+ implements EnforcerRule2
+{
+
+ @Override
+ public void execute( EnforcerRuleHelper helper )
+ throws EnforcerRuleException
+ {
+ try
+ {
+ int numMissingDependencyScopes = 0;
+ MavenProject project = (MavenProject) helper.evaluate( "${project}" );
+ if ( project == null )
+ {
+ throw new ExpressionEvaluationException( "${project} is null" );
+ }
+ List<Dependency> dependencies = project.getOriginalModel().getDependencies(); // this is the non-effective
+ // model but the original one
+ // without inheritance and
+ // interpolation resolved
+ // check scope without considering inheritance
+ for ( Dependency dependency : dependencies )
+ {
+ helper.getLog().debug( "Found dependency " + dependency );
+ if ( dependency.getScope() == null )
+ {
+ MessageBuilder msgBuilder = MessageUtils.buffer();
+ msgBuilder
+ .a( "Dependency " ).strong( dependency.getManagementKey() )
+ .a( " @ " ).strong( formatLocation( project, dependency.getLocation( "" ) ) )
+ .a( " does not have an explicit scope defined!" ).toString();
+ if ( getLevel() == EnforcerLevel.ERROR )
+ {
+ helper.getLog().error( msgBuilder.toString() );
+ }
+ else
+ {
+ helper.getLog().warn( msgBuilder.toString() );
+ }
+ numMissingDependencyScopes++;
+ }
+ }
+ if ( numMissingDependencyScopes > 0 )
+ {
+ ChoiceFormat scopesFormat = new ChoiceFormat( "1#scope|1<scopes" );
+ String logCategory = getLevel() == EnforcerLevel.ERROR ? "errors" : "warnings";
+ throw new EnforcerRuleException( "Found " + numMissingDependencyScopes + " missing dependency "
+ + scopesFormat.format( numMissingDependencyScopes )
+ + ". Look at the " + logCategory + " emitted above for the details." );
+ }
+ }
+ catch ( ExpressionEvaluationException eee )
+ {
+ throw new EnforcerRuleException( "Cannot resolve expression: " + eee.getCause(), eee );
+ }
+ }
+
+}
diff --git a/enforcer-rules/src/site/apt/index.apt b/enforcer-rules/src/site/apt/index.apt
index c56d818..dfd8dec 100644
--- a/enforcer-rules/src/site/apt/index.apt
+++ b/enforcer-rules/src/site/apt/index.apt
@@ -56,7 +56,9 @@
* {{{./requireActiveProfile.html}requireActiveProfile}} - enforces one or more active profiles.
* {{{./requireEnvironmentVariable.html}requireEnvironmentVariable}} - enforces the existence of an environment variable.
-
+
+ * {{{./requireExplicitDependencyScope.html}requireExplicitDependencyScope}} - enforces that all dependencies have an explicit scope.
+
* {{{./requireFileChecksum.html}requireFileChecksum}} - enforces that the specified file has a certain checksum.
* {{{./requireFilesDontExist.html}requireFilesDontExist}} - enforces that the list of files does not exist.
diff --git a/enforcer-rules/src/site/apt/requireExplicitDependencyScope.apt.vm b/enforcer-rules/src/site/apt/requireExplicitDependencyScope.apt.vm
new file mode 100644
index 0000000..14d3b92
--- /dev/null
+++ b/enforcer-rules/src/site/apt/requireExplicitDependencyScope.apt.vm
@@ -0,0 +1,62 @@
+~~ 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.
+
+ ------
+ Require Explicit Dependency Scope
+ ------
+ Konrad Windszus
+ ------
+ 2022-08-03
+ ------
+
+Require Explicit Dependency Scope
+
+ This rule enforces that all dependencies have an explicitly declared scope in the non-effective pom (i.e. without taking inheritance or dependency management into account).
+ Useful when the scope is no longer part of the <<<dependencyManagement>>> or in general to force making developers a distinct decision (prevents the default scope <<<compile>>> being used for test dependencies by accident)
+
+ The rule does not support parameters.
+
+ 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>require-explicit-dependency-scope</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireExplicitDependencyScope />
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ [...]
+</project>
++---+
diff --git a/maven-enforcer-plugin/src/it/projects/require-dependency-scope/invoker.properties b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/invoker.properties
new file mode 100644
index 0000000..e64d99e
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/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/require-dependency-scope/pom.xml b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/pom.xml
new file mode 100644
index 0000000..c80b13d
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/pom.xml
@@ -0,0 +1,79 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.its.enforcer</groupId>
+ <artifactId>ban-dependency-management-scope-fail-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>require-dependency-scope</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireExplicitDependencyScope />
+ </rules>
+ <fail>true</fail>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.jackrabbit.vault</groupId>
+ <artifactId>vault-cli</artifactId>
+ <version>3.6.0</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- missing explicit scope -->
+ <dependency>
+ <groupId>org.apache.jackrabbit.vault</groupId>
+ <artifactId>vault-cli</artifactId>
+ </dependency>
+
+ <!-- test scope -->
+ <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/maven-enforcer-plugin/src/it/projects/require-dependency-scope/verify.groovy b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/verify.groovy
new file mode 100644
index 0000000..f4871fd
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/verify.groovy
@@ -0,0 +1,21 @@
+/*
+ * 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.jackrabbit.vault:vault-cli:jar @ line 65, column 21 does not have an explicit scope defined!')
+assert buildLog.text.contains('Found 1 missing dependency scope. Look at the errors emitted above for the details.')