From 4eee3680246000244984a08b90dd16e2e6538928 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Mon, 22 Aug 2022 16:28:33 +0200 Subject: [PATCH] [MENFORCER-423] Add rule to enforce an explicit dependency scope --- .../AbstractStandardEnforcerRule.java | 68 +++++++++++++ .../BanDependencyManagementScope.java | 70 +------------ .../RequireExplicitDependencyScope.java | 97 +++++++++++++++++++ enforcer-rules/src/site/apt/index.apt | 4 +- .../apt/requireExplicitDependencyScope.apt.vm | 62 ++++++++++++ .../invoker.properties | 18 ++++ .../projects/require-dependency-scope/pom.xml | 79 +++++++++++++++ .../require-dependency-scope/verify.groovy | 21 ++++ 8 files changed, 351 insertions(+), 68 deletions(-) create mode 100644 enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/RequireExplicitDependencyScope.java create mode 100644 enforcer-rules/src/site/apt/requireExplicitDependencyScope.apt.vm create mode 100644 maven-enforcer-plugin/src/it/projects/require-dependency-scope/invoker.properties create mode 100644 maven-enforcer-plugin/src/it/projects/require-dependency-scope/pom.xml create mode 100644 maven-enforcer-plugin/src/it/projects/require-dependency-scope/verify.groovy 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 fecdb090..f3692597 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 @@ public void setLevel( EnforcerLevel level ) this.level = level; } + /** + * Returns an identifier of a given project. + * @param project the project + * @return the identifier of the project in the format {@code ::} + */ + 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 94fe177a..06b94610 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 @@ public void execute( EnforcerRuleHelper helper ) List 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 @@ public void execute( EnforcerRuleHelper helper ) 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 @@ protected List getViolatingDependencies( Log logger, DependencyManag 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 ::. - 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 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 00000000..2a5b96e6 --- /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 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>> or in general to force making developers a distinct decision (prevents the default scope <<>> being used for test dependencies by accident) + + The rule does not support parameters. + + Sample Plugin Configuration: + ++---+ + + [...] + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${project.version} + + + require-explicit-dependency-scope + + enforce + + + + + + + + + + + + [...] + ++---+ 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 00000000..e64d99e0 --- /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 00000000..c80b13d1 --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/require-dependency-scope/pom.xml @@ -0,0 +1,79 @@ + + + + 4.0.0 + + org.apache.maven.its.enforcer + ban-dependency-management-scope-fail-test + 1.0 + + + + + org.apache.maven.plugins + maven-enforcer-plugin + @project.version@ + + + require-dependency-scope + + enforce + + + + + + true + + + + + + + + + + + org.apache.jackrabbit.vault + vault-cli + 3.6.0 + provided + + + + + + + + org.apache.jackrabbit.vault + vault-cli + + + + + org.hamcrest + hamcrest + 2.2 + test + + + + \ 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 00000000..f4871fd6 --- /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.')