Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MENFORCER-423] Add rule to enforce an explicit dependency scope #179

Merged
merged 3 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 <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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,7 +80,6 @@ public void execute( EnforcerRuleHelper helper )
List<Dependency> violatingDependencies = getViolatingDependencies( logger, depMgmt );
if ( !violatingDependencies.isEmpty() )
{
String projectId = getProjectId( project );
String message = getMessage();
StringBuilder buf = new StringBuilder();
if ( message == null )
Expand All @@ -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() );
}
Expand Down Expand Up @@ -136,76 +134,14 @@ protected List<Dependency> 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 <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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 );
}
}

}
4 changes: 3 additions & 1 deletion enforcer-rules/src/site/apt/index.apt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ Built-In Rules
* {{{./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.
Expand Down
62 changes: 62 additions & 0 deletions enforcer-rules/src/site/apt/requireExplicitDependencyScope.apt.vm
Original file line number Diff line number Diff line change
@@ -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>
+---+
Original file line number Diff line number Diff line change
@@ -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
Loading