[MNG-7754] Improvement and extension of plugin validation (#1079)

This is general rework of current Maven 3.9.x line how it handles plugin and mojo validations.

Changes:
* added plugin validations for dependencies
* introduced pluginValidationManager that gathers violations
* manager creates a report at build end, with dense and non repeating data
* this is in spirit to lessen already too verbose logging, as current solution would report violations as many times plugin is used in reactor, and that can be many (ie. a plugin from parent for each module)

Example report of Maven 3.9.x build: https://gist.github.com/cstamas/b62fdcd53eaf316123cf183f5a24e6a5

---

https://issues.apache.org/jira/browse/MNG-7754
This commit is contained in:
Tamas Cservenak 2023-04-12 11:42:30 +02:00
parent b2ee29e03e
commit 53b6473237
16 changed files with 793 additions and 39 deletions

View File

@ -0,0 +1,55 @@
/*
* 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.
*/
package org.apache.maven.plugin;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
/**
* Component collecting plugin validation issues and reporting them.
*
* @since 3.9.2
*/
public interface PluginValidationManager {
/**
* Reports plugin issues applicable to the plugin as a whole.
* <p>
* This method should be used in "early" phase of plugin execution, possibly even when plugin or mojo descriptor
* does not exist yet. In turn, this method will not record extra information like plugin occurrence or declaration
* location as those are not yet available.
*/
void reportPluginValidationIssue(RepositorySystemSession session, Artifact pluginArtifact, String issue);
/**
* Reports plugin issues applicable to the plugin as a whole.
* <p>
* This method will record extra information as well, like plugin occurrence or declaration location.
*/
void reportPluginValidationIssue(MavenSession mavenSession, MojoDescriptor mojoDescriptor, String issue);
/**
* Reports plugin Mojo issues applicable to the Mojo itself.
* <p>
* This method will record extra information as well, like plugin occurrence or declaration location.
*/
void reportPluginMojoValidationIssue(
MavenSession mavenSession, MojoDescriptor mojoDescriptor, Class<?> mojoClass, String issue);
}

View File

@ -0,0 +1,55 @@
/*
* 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.
*/
package org.apache.maven.plugin.internal;
import java.util.Arrays;
import java.util.List;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import static java.util.Objects.requireNonNull;
/**
* Service responsible for validating plugin dependencies.
*
* @since 3.9.2
*/
abstract class AbstractMavenPluginDependenciesValidator implements MavenPluginDependenciesValidator {
protected final List<String> expectedProvidedScopeExclusions = Arrays.asList(
"org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr", "org.apache.maven:plexus-utils");
protected final PluginValidationManager pluginValidationManager;
protected AbstractMavenPluginDependenciesValidator(PluginValidationManager pluginValidationManager) {
this.pluginValidationManager = requireNonNull(pluginValidationManager);
}
@Override
public void validate(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
if (mojoDescriptor.getPluginDescriptor() != null
&& mojoDescriptor.getPluginDescriptor().getDependencies() != null) {
doValidate(mavenSession, mojoDescriptor);
}
}
protected abstract void doValidate(MavenSession mavenSession, MojoDescriptor mojoDescriptor);
}

View File

@ -21,6 +21,8 @@
import java.util.Arrays;
import java.util.List;
import org.apache.maven.plugin.PluginValidationManager;
/**
* Common implementations for plugin parameters configuration validation that relies on Mojo descriptor (leaves out
* core parameters by default).
@ -48,6 +50,10 @@ abstract class AbstractMavenPluginDescriptorSourcedParametersValidator extends A
private static final List<String> IGNORED_PROPERTY_PREFIX =
Arrays.asList("mojo.", "pom.", "plugin.", "project.", "session.", "settings.");
protected AbstractMavenPluginDescriptorSourcedParametersValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected boolean isIgnoredProperty(String strValue) {
if (!strValue.startsWith("${")) {

View File

@ -18,15 +18,15 @@
*/
package org.apache.maven.plugin.internal;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
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.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.requireNonNull;
/**
* Common implementations for plugin parameters configuration validation.
@ -35,7 +35,11 @@
*/
abstract class AbstractMavenPluginParametersValidator implements MavenPluginConfigurationValidator {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected final PluginValidationManager pluginValidationManager;
protected AbstractMavenPluginParametersValidator(PluginValidationManager pluginValidationManager) {
this.pluginValidationManager = requireNonNull(pluginValidationManager);
}
protected boolean isValueSet(PlexusConfiguration config, ExpressionEvaluator expressionEvaluator) {
if (config == null) {
@ -73,18 +77,18 @@ protected boolean isValueSet(PlexusConfiguration config, ExpressionEvaluator exp
@Override
public final void validate(
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator) {
if (!logger.isWarnEnabled()) {
return;
}
doValidate(mojoDescriptor, pomConfiguration, expressionEvaluator);
doValidate(mavenSession, mojoDescriptor, mojoClass, pomConfiguration, expressionEvaluator);
}
protected abstract void doValidate(
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator);
@ -94,19 +98,19 @@ protected boolean isIgnoredProperty(String strValue) {
protected abstract String getParameterLogReason(Parameter parameter);
protected void logParameter(Parameter parameter) {
MessageBuilder messageBuilder = MessageUtils.buffer()
.warning("Parameter '")
.warning(parameter.getName())
.warning('\'');
protected String formatParameter(Parameter parameter) {
StringBuilder stringBuilder = new StringBuilder()
.append("Parameter '")
.append(parameter.getName())
.append('\'');
if (parameter.getExpression() != null) {
String userProperty = parameter.getExpression().replace("${", "'").replace('}', '\'');
messageBuilder.warning(" (user property ").warning(userProperty).warning(")");
stringBuilder.append(" (user property ").append(userProperty).append(")");
}
messageBuilder.warning(" ").warning(getParameterLogReason(parameter));
stringBuilder.append(" ").append(getParameterLogReason(parameter));
logger.warn(messageBuilder.toString());
return stringBuilder.toString();
}
}

View File

@ -71,6 +71,7 @@
import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
@ -99,6 +100,7 @@
import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfigurationException;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.RepositorySystemSession;
@ -146,8 +148,9 @@ public class DefaultMavenPluginManager implements MavenPluginManager {
private PluginArtifactsCache pluginArtifactsCache;
private MavenPluginValidator pluginValidator;
private List<MavenPluginConfigurationValidator> configurationValidators;
private List<MavenPluginDependenciesValidator> dependenciesValidators;
private PluginValidationManager pluginValidationManager;
private List<MavenPluginPrerequisitesChecker> prerequisitesCheckers;
private final ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
private final PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
@ -165,6 +168,8 @@ public DefaultMavenPluginManager(
PluginArtifactsCache pluginArtifactsCache,
MavenPluginValidator pluginValidator,
List<MavenPluginConfigurationValidator> configurationValidators,
List<MavenPluginDependenciesValidator> dependencyValidators,
PluginValidationManager pluginValidationManager,
List<MavenPluginPrerequisitesChecker> prerequisitesCheckers) {
this.container = container;
this.classRealmManager = classRealmManager;
@ -177,6 +182,8 @@ public DefaultMavenPluginManager(
this.pluginArtifactsCache = pluginArtifactsCache;
this.pluginValidator = pluginValidator;
this.configurationValidators = configurationValidators;
this.dependenciesValidators = dependencyValidators;
this.pluginValidationManager = pluginValidationManager;
this.prerequisitesCheckers = prerequisitesCheckers;
}
@ -559,6 +566,18 @@ public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, Moj
((Mojo) mojo).setLog(new MojoLogWrapper(mojoLogger));
}
if (mojo instanceof Contextualizable) {
pluginValidationManager.reportPluginMojoValidationIssue(
session,
mojoDescriptor,
mojo.getClass(),
"Implements `Contextualizable` interface from Plexus Container, which is EOL.");
}
for (MavenPluginDependenciesValidator validator : dependenciesValidators) {
validator.validate(session, mojoDescriptor);
}
XmlNode dom = mojoExecution.getConfiguration() != null
? mojoExecution.getConfiguration().getDom()
: null;
@ -582,7 +601,7 @@ public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, Moj
}
for (MavenPluginConfigurationValidator validator : configurationValidators) {
validator.validate(mojoDescriptor, pomConfiguration, expressionEvaluator);
validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
}
populateMojoExecutionFields(

View File

@ -32,6 +32,7 @@
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.PluginValidationManager;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
@ -77,9 +78,13 @@ public class DefaultPluginDependenciesResolver implements PluginDependenciesReso
private final RepositorySystem repoSystem;
private final PluginValidationManager pluginValidationManager;
@Inject
public DefaultPluginDependenciesResolver(RepositorySystem repoSystem) {
public DefaultPluginDependenciesResolver(
RepositorySystem repoSystem, PluginValidationManager pluginValidationManager) {
this.repoSystem = repoSystem;
this.pluginValidationManager = pluginValidationManager;
}
private Artifact toArtifact(Plugin plugin, RepositorySystemSession session) {
@ -107,6 +112,19 @@ public Artifact resolve(Plugin plugin, List<RemoteRepository> repositories, Repo
request.setTrace(trace);
ArtifactDescriptorResult result = repoSystem.readArtifactDescriptor(pluginSession, request);
if (result.getDependencies() != null) {
for (org.eclipse.aether.graph.Dependency dependency : result.getDependencies()) {
if ("org.apache.maven".equals(dependency.getArtifact().getGroupId())
&& "maven-compat".equals(dependency.getArtifact().getArtifactId())
&& !JavaScopes.TEST.equals(dependency.getScope())) {
pluginValidationManager.reportPluginValidationIssue(
session,
pluginArtifact,
"Plugin depends on the deprecated Maven 2.x compatibility layer, which may not be supported in Maven 4.x");
}
}
}
pluginArtifact = result.getArtifact();
if (logger.isWarnEnabled()) {

View File

@ -0,0 +1,270 @@
/*
* 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.
*/
package org.apache.maven.plugin.internal;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.InputLocation;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.util.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
@Named
public final class DefaultPluginValidationManager extends AbstractMavenLifecycleParticipant
implements PluginValidationManager {
private static final String ISSUES_KEY = DefaultPluginValidationManager.class.getName() + ".issues";
private static final String MAVEN_PLUGIN_VALIDATION_KEY = "maven.plugin.validation";
private enum ValidationLevel {
DISABLED,
ENABLED,
VERBOSE
}
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void afterSessionEnd(MavenSession session) {
reportSessionCollectedValidationIssues(session);
}
private ValidationLevel validationLevel(RepositorySystemSession session) {
String level = ConfigUtils.getString(session, null, MAVEN_PLUGIN_VALIDATION_KEY);
if (level == null || level.isEmpty()) {
return ValidationLevel.ENABLED;
}
try {
return ValidationLevel.valueOf(level.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
logger.warn(
"Invalid value specified for property {}: '{}'. Supported values are (case insensitive): {}",
MAVEN_PLUGIN_VALIDATION_KEY,
level,
Arrays.toString(ValidationLevel.values()));
return ValidationLevel.ENABLED;
}
}
private String pluginKey(String groupId, String artifactId, String version) {
return groupId + ":" + artifactId + ":" + version;
}
private String pluginKey(MojoDescriptor mojoDescriptor) {
PluginDescriptor pd = mojoDescriptor.getPluginDescriptor();
return pluginKey(pd.getGroupId(), pd.getArtifactId(), pd.getVersion());
}
private String pluginKey(Artifact pluginArtifact) {
return pluginKey(pluginArtifact.getGroupId(), pluginArtifact.getArtifactId(), pluginArtifact.getVersion());
}
@Override
public void reportPluginValidationIssue(RepositorySystemSession session, Artifact pluginArtifact, String issue) {
String pluginKey = pluginKey(pluginArtifact);
PluginValidationIssues pluginIssues =
pluginIssues(session).computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
pluginIssues.reportPluginIssue(null, null, issue);
}
@Override
public void reportPluginValidationIssue(MavenSession mavenSession, MojoDescriptor mojoDescriptor, String issue) {
String pluginKey = pluginKey(mojoDescriptor);
PluginValidationIssues pluginIssues = pluginIssues(mavenSession.getRepositorySession())
.computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
pluginIssues.reportPluginIssue(
pluginDeclaration(mavenSession, mojoDescriptor), pluginOccurrence(mavenSession), issue);
}
@Override
public void reportPluginMojoValidationIssue(
MavenSession mavenSession, MojoDescriptor mojoDescriptor, Class<?> mojoClass, String issue) {
String pluginKey = pluginKey(mojoDescriptor);
PluginValidationIssues pluginIssues = pluginIssues(mavenSession.getRepositorySession())
.computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
pluginIssues.reportPluginMojoIssue(
pluginDeclaration(mavenSession, mojoDescriptor),
pluginOccurrence(mavenSession),
mojoInfo(mojoDescriptor, mojoClass),
issue);
}
private void reportSessionCollectedValidationIssues(MavenSession mavenSession) {
ValidationLevel validationLevel = validationLevel(mavenSession.getRepositorySession());
ConcurrentHashMap<String, PluginValidationIssues> issuesMap = pluginIssues(mavenSession.getRepositorySession());
if (!issuesMap.isEmpty()) {
logger.warn("");
logger.warn("Plugin validation issues were detected in {} plugin(s)", issuesMap.size());
logger.warn("");
if (validationLevel == ValidationLevel.DISABLED || !logger.isWarnEnabled()) {
return;
}
for (Map.Entry<String, PluginValidationIssues> entry : issuesMap.entrySet()) {
logger.warn("Plugin {}", entry.getKey());
PluginValidationIssues issues = entry.getValue();
if (validationLevel == ValidationLevel.VERBOSE && !issues.pluginDeclarations.isEmpty()) {
logger.warn(" Declared at location(s):");
for (String pluginDeclaration : issues.pluginDeclarations) {
logger.warn(" * {}", pluginDeclaration);
}
}
if (validationLevel == ValidationLevel.VERBOSE && !issues.pluginOccurrences.isEmpty()) {
logger.warn(" Used in module(s):");
for (String pluginOccurrence : issues.pluginOccurrences) {
logger.warn(" * {}", pluginOccurrence);
}
}
if (!issues.pluginIssues.isEmpty()) {
logger.warn(" Plugin issue(s):");
for (String pluginIssue : issues.pluginIssues) {
logger.warn(" * {}", pluginIssue);
}
}
if (!issues.mojoIssues.isEmpty()) {
logger.warn(" Mojo issue(s):");
for (String mojoInfo : issues.mojoIssues.keySet()) {
logger.warn(" * Mojo {}", mojoInfo);
for (String mojoIssue : issues.mojoIssues.get(mojoInfo)) {
logger.warn(" - {}", mojoIssue);
}
}
}
logger.warn("");
}
logger.warn("");
logger.warn(
"To fix these issues, please upgrade above listed plugins, or, notify their maintainers about reported issues.");
logger.warn("");
logger.warn(
"For more or less details, use 'maven.plugin.validation' property with one of the values (case insensitive): {}",
Arrays.toString(ValidationLevel.values()));
logger.warn("");
}
}
private String pluginDeclaration(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
InputLocation inputLocation =
mojoDescriptor.getPluginDescriptor().getPlugin().getLocation("");
if (inputLocation != null && inputLocation.getSource() != null) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(inputLocation.getSource().getModelId());
String location = inputLocation.getSource().getLocation();
if (location != null) {
if (location.contains("://")) {
stringBuilder.append(" (").append(location).append(")");
} else {
File rootBasedir = mavenSession.getTopLevelProject().getBasedir();
File locationFile = new File(location);
if (location.startsWith(rootBasedir.getPath())) {
stringBuilder
.append(" (")
.append(rootBasedir.toPath().relativize(locationFile.toPath()))
.append(")");
} else {
stringBuilder.append(" (").append(location).append(")");
}
}
}
stringBuilder.append(" @ line ").append(inputLocation.getLineNumber());
return stringBuilder.toString();
} else {
return "unknown";
}
}
private String pluginOccurrence(MavenSession mavenSession) {
MavenProject prj = mavenSession.getCurrentProject();
String result = prj.getGroupId() + ":" + prj.getArtifactId() + ":" + prj.getVersion();
File currentPom = prj.getFile();
if (currentPom != null) {
File rootBasedir = mavenSession.getTopLevelProject().getBasedir();
result += " (" + rootBasedir.toPath().relativize(currentPom.toPath()) + ")";
}
return result;
}
private String mojoInfo(MojoDescriptor mojoDescriptor, Class<?> mojoClass) {
return mojoDescriptor.getFullGoalName() + " (" + mojoClass.getName() + ")";
}
@SuppressWarnings("unchecked")
private ConcurrentHashMap<String, PluginValidationIssues> pluginIssues(RepositorySystemSession session) {
return (ConcurrentHashMap<String, PluginValidationIssues>)
session.getData().computeIfAbsent(ISSUES_KEY, ConcurrentHashMap::new);
}
private static class PluginValidationIssues {
private final LinkedHashSet<String> pluginDeclarations;
private final LinkedHashSet<String> pluginOccurrences;
private final LinkedHashSet<String> pluginIssues;
private final LinkedHashMap<String, LinkedHashSet<String>> mojoIssues;
private PluginValidationIssues() {
this.pluginDeclarations = new LinkedHashSet<>();
this.pluginOccurrences = new LinkedHashSet<>();
this.pluginIssues = new LinkedHashSet<>();
this.mojoIssues = new LinkedHashMap<>();
}
private synchronized void reportPluginIssue(String pluginDeclaration, String pluginOccurrence, String issue) {
if (pluginDeclaration != null) {
pluginDeclarations.add(pluginDeclaration);
}
if (pluginOccurrence != null) {
pluginOccurrences.add(pluginOccurrence);
}
pluginIssues.add(issue);
}
private synchronized void reportPluginMojoIssue(
String pluginDeclaration, String pluginOccurrence, String mojoInfo, String issue) {
if (pluginDeclaration != null) {
pluginDeclarations.add(pluginDeclaration);
}
if (pluginOccurrence != null) {
pluginOccurrences.add(pluginOccurrence);
}
mojoIssues.computeIfAbsent(mojoInfo, k -> new LinkedHashSet<>()).add(issue);
}
}
}

View File

@ -18,12 +18,15 @@
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.HashMap;
import java.util.Objects;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
@ -40,7 +43,7 @@ class DeprecatedCoreExpressionValidator extends AbstractMavenPluginParametersVal
private static final HashMap<String, String> DEPRECATED_CORE_PARAMETERS;
private static final String ARTIFACT_REPOSITORY_REASON =
"Avoid use of ArtifactRepository type. If you need access to local repository, switch to '${repositorySystemSession}' expression and get LRM from it instead.";
"ArtifactRepository type is deprecated and its use in Mojos should be avoided.";
static {
HashMap<String, String> deprecatedCoreParameters = new HashMap<>();
@ -49,21 +52,33 @@ class DeprecatedCoreExpressionValidator extends AbstractMavenPluginParametersVal
DEPRECATED_CORE_PARAMETERS = deprecatedCoreParameters;
}
@Inject
DeprecatedCoreExpressionValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected String getParameterLogReason(Parameter parameter) {
return "is deprecated core expression; " + DEPRECATED_CORE_PARAMETERS.get(parameter.getDefaultValue());
return "uses deprecated parameter expression '" + parameter.getDefaultValue() + "': "
+ DEPRECATED_CORE_PARAMETERS.get(parameter.getDefaultValue());
}
@Override
protected void doValidate(
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator) {
if (mojoDescriptor.getParameters() == null) {
return;
}
mojoDescriptor.getParameters().stream().filter(this::isDeprecated).forEach(this::logParameter);
mojoDescriptor.getParameters().stream()
.filter(this::isDeprecated)
.map(this::formatParameter)
.forEach(m -> pluginValidationManager.reportPluginMojoValidationIssue(
mavenSession, mojoDescriptor, mojoClass, m));
}
private boolean isDeprecated(Parameter parameter) {

View File

@ -18,9 +18,12 @@
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.shared.utils.logging.MessageUtils;
@ -35,6 +38,12 @@
@Singleton
@Named
class DeprecatedPluginValidator extends AbstractMavenPluginDescriptorSourcedParametersValidator {
@Inject
DeprecatedPluginValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected String getParameterLogReason(Parameter parameter) {
return "is deprecated: " + parameter.getDeprecated();
@ -42,36 +51,46 @@ protected String getParameterLogReason(Parameter parameter) {
@Override
protected void doValidate(
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator) {
if (mojoDescriptor.getDeprecated() != null) {
logDeprecatedMojo(mojoDescriptor);
pluginValidationManager.reportPluginMojoValidationIssue(
mavenSession, mojoDescriptor, mojoClass, logDeprecatedMojo(mojoDescriptor));
}
mojoDescriptor.getParameters().stream()
.filter(parameter -> parameter.getDeprecated() != null)
.filter(Parameter::isEditable)
.forEach(parameter -> checkParameter(parameter, pomConfiguration, expressionEvaluator));
if (mojoDescriptor.getParameters() != null) {
mojoDescriptor.getParameters().stream()
.filter(parameter -> parameter.getDeprecated() != null)
.filter(Parameter::isEditable)
.forEach(parameter -> checkParameter(
mavenSession, mojoDescriptor, mojoClass, parameter, pomConfiguration, expressionEvaluator));
}
}
private void checkParameter(
Parameter parameter, PlexusConfiguration pomConfiguration, ExpressionEvaluator expressionEvaluator) {
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
Parameter parameter,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator) {
PlexusConfiguration config = pomConfiguration.getChild(parameter.getName(), false);
if (isValueSet(config, expressionEvaluator)) {
logParameter(parameter);
pluginValidationManager.reportPluginMojoValidationIssue(
mavenSession, mojoDescriptor, mojoClass, formatParameter(parameter));
}
}
private void logDeprecatedMojo(MojoDescriptor mojoDescriptor) {
String message = MessageUtils.buffer()
private String logDeprecatedMojo(MojoDescriptor mojoDescriptor) {
return MessageUtils.buffer()
.warning("Goal '")
.warning(mojoDescriptor.getGoal())
.warning("' is deprecated: ")
.warning(mojoDescriptor.getDeprecated())
.toString();
logger.warn(message);
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.codehaus.plexus.component.repository.ComponentDependency;
/**
* Detects Maven2 plugins.
*
* @since 3.9.2
*/
@Singleton
@Named
class Maven2DependenciesValidator extends AbstractMavenPluginDependenciesValidator {
@Inject
Maven2DependenciesValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected void doValidate(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
Set<String> maven2Versions = mojoDescriptor.getPluginDescriptor().getDependencies().stream()
.filter(d -> "org.apache.maven".equals(d.getGroupId()))
.filter(d -> !expectedProvidedScopeExclusions.contains(d.getGroupId() + ":" + d.getArtifactId()))
.map(ComponentDependency::getVersion)
.filter(v -> v.startsWith("2."))
.collect(Collectors.toSet());
if (!maven2Versions.isEmpty()) {
pluginValidationManager.reportPluginValidationIssue(
mavenSession,
mojoDescriptor,
"Plugin is a Maven 2.x plugin, which will be not supported in Maven 4.x");
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.codehaus.plexus.component.repository.ComponentDependency;
/**
* Detects mixed Maven versions in plugins.
*
* @since 3.9.2
*/
@Singleton
@Named
class MavenMixedDependenciesValidator extends AbstractMavenPluginDependenciesValidator {
@Inject
MavenMixedDependenciesValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected void doValidate(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
Set<String> mavenVersions = mojoDescriptor.getPluginDescriptor().getDependencies().stream()
.filter(d -> "org.apache.maven".equals(d.getGroupId()))
.filter(d -> !expectedProvidedScopeExclusions.contains(d.getGroupId() + ":" + d.getArtifactId()))
.map(ComponentDependency::getVersion)
.collect(Collectors.toSet());
if (mavenVersions.size() > 1) {
pluginValidationManager.reportPluginValidationIssue(
mavenSession, mojoDescriptor, "Plugin mixes multiple Maven versions: " + mavenVersions);
}
}
}

View File

@ -18,6 +18,7 @@
*/
package org.apache.maven.plugin.internal;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
@ -29,10 +30,12 @@
*/
interface MavenPluginConfigurationValidator {
/**
* Check mojo configuration.
* Checks mojo configuration issues.
*/
void validate(
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator);
}

View File

@ -0,0 +1,34 @@
/*
* 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.
*/
package org.apache.maven.plugin.internal;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
/**
* Service responsible for validating plugin dependencies.
*
* @since 3.9.2
*/
interface MavenPluginDependenciesValidator {
/**
* Checks mojo dependency issues.
*/
void validate(MavenSession mavenSession, MojoDescriptor mojoDescriptor);
}

View File

@ -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.
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
/**
* Detects Maven3 artifacts in bad scope in plugins.
*
* @since 3.9.2
*/
@Singleton
@Named
class MavenScopeDependenciesValidator extends AbstractMavenPluginDependenciesValidator {
@Inject
MavenScopeDependenciesValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected void doValidate(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
Set<String> mavenArtifacts = mojoDescriptor.getPluginDescriptor().getDependencies().stream()
.filter(d -> "org.apache.maven".equals(d.getGroupId()))
.filter(d -> !expectedProvidedScopeExclusions.contains(d.getGroupId() + ":" + d.getArtifactId()))
.filter(d -> d.getVersion().startsWith("3."))
.map(d -> d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion())
.collect(Collectors.toSet());
if (!mavenArtifacts.isEmpty()) {
pluginValidationManager.reportPluginValidationIssue(
mavenSession,
mojoDescriptor,
"Plugin should declare these Maven artifacts in `provided` scope: " + mavenArtifacts);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
/**
* Detects Plexus Container Default in plugins.
*
* @since 3.9.2
*/
@Singleton
@Named
class PlexusContainerDefaultDependenciesValidator extends AbstractMavenPluginDependenciesValidator {
@Inject
PlexusContainerDefaultDependenciesValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
protected void doValidate(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
boolean pcdPresent = mojoDescriptor.getPluginDescriptor().getDependencies().stream()
.filter(d -> "org.codehaus.plexus".equals(d.getGroupId()))
.anyMatch(d -> "plexus-container-default".equals(d.getArtifactId()));
if (pcdPresent) {
pluginValidationManager.reportPluginValidationIssue(
mavenSession, mojoDescriptor, "Plugin depends on plexus-container-default, which is EOL");
}
}
}

View File

@ -18,9 +18,12 @@
*/
package org.apache.maven.plugin.internal;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
@ -34,6 +37,12 @@
@Named
@Singleton
class ReadOnlyPluginParametersValidator extends AbstractMavenPluginDescriptorSourcedParametersValidator {
@Inject
ReadOnlyPluginParametersValidator(PluginValidationManager pluginValidationManager) {
super(pluginValidationManager);
}
@Override
protected String getParameterLogReason(Parameter parameter) {
return "is read-only, must not be used in configuration";
@ -41,7 +50,9 @@ protected String getParameterLogReason(Parameter parameter) {
@Override
protected void doValidate(
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator) {
if (mojoDescriptor.getParameters() == null) {
@ -50,15 +61,22 @@ protected void doValidate(
mojoDescriptor.getParameters().stream()
.filter(parameter -> !parameter.isEditable())
.forEach(parameter -> checkParameter(parameter, pomConfiguration, expressionEvaluator));
.forEach(parameter -> checkParameter(
mavenSession, mojoDescriptor, mojoClass, parameter, pomConfiguration, expressionEvaluator));
}
private void checkParameter(
Parameter parameter, PlexusConfiguration pomConfiguration, ExpressionEvaluator expressionEvaluator) {
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
Parameter parameter,
PlexusConfiguration pomConfiguration,
ExpressionEvaluator expressionEvaluator) {
PlexusConfiguration config = pomConfiguration.getChild(parameter.getName(), false);
if (isValueSet(config, expressionEvaluator)) {
logParameter(parameter);
pluginValidationManager.reportPluginMojoValidationIssue(
mavenSession, mojoDescriptor, mojoClass, formatParameter(parameter));
}
}
}