diff --git a/maven-core/src/main/java/org/apache/maven/plugin/PluginValidationManager.java b/maven-core/src/main/java/org/apache/maven/plugin/PluginValidationManager.java new file mode 100644 index 0000000000..aaf0f29c48 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/PluginValidationManager.java @@ -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. + *

+ * 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. + *

+ * 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. + *

+ * This method will record extra information as well, like plugin occurrence or declaration location. + */ + void reportPluginMojoValidationIssue( + MavenSession mavenSession, MojoDescriptor mojoDescriptor, Class mojoClass, String issue); +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDependenciesValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDependenciesValidator.java new file mode 100644 index 0000000000..6f75c0d337 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDependenciesValidator.java @@ -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 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); +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDescriptorSourcedParametersValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDescriptorSourcedParametersValidator.java index 94ca557812..76a0e16a4d 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDescriptorSourcedParametersValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginDescriptorSourcedParametersValidator.java @@ -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 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("${")) { diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginParametersValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginParametersValidator.java index 809a75dfc2..c5eeb72c75 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginParametersValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/AbstractMavenPluginParametersValidator.java @@ -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(); } } diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java index 1e29fec5b3..47bb66dcf3 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java @@ -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 configurationValidators; + private List dependenciesValidators; + private PluginValidationManager pluginValidationManager; private List prerequisitesCheckers; - private final ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder(); private final PluginDescriptorBuilder builder = new PluginDescriptorBuilder(); @@ -165,6 +168,8 @@ public DefaultMavenPluginManager( PluginArtifactsCache pluginArtifactsCache, MavenPluginValidator pluginValidator, List configurationValidators, + List dependencyValidators, + PluginValidationManager pluginValidationManager, List 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 getConfiguredMojo(Class 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 getConfiguredMojo(Class mojoInterface, MavenSession session, Moj } for (MavenPluginConfigurationValidator validator : configurationValidators) { - validator.validate(mojoDescriptor, pomConfiguration, expressionEvaluator); + validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator); } populateMojoExecutionFields( diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginDependenciesResolver.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginDependenciesResolver.java index 4b1120838d..35fc123f7c 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginDependenciesResolver.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginDependenciesResolver.java @@ -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 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()) { diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java new file mode 100644 index 0000000000..57ce4ebab3 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultPluginValidationManager.java @@ -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 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 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 pluginIssues(RepositorySystemSession session) { + return (ConcurrentHashMap) + session.getData().computeIfAbsent(ISSUES_KEY, ConcurrentHashMap::new); + } + + private static class PluginValidationIssues { + private final LinkedHashSet pluginDeclarations; + + private final LinkedHashSet pluginOccurrences; + + private final LinkedHashSet pluginIssues; + + private final LinkedHashMap> 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); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedCoreExpressionValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedCoreExpressionValidator.java index 0eea6f92ab..54bb11434d 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedCoreExpressionValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedCoreExpressionValidator.java @@ -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 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 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) { diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java index 96f94df87b..1fd07a10e8 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DeprecatedPluginValidator.java @@ -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); } } diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/Maven2DependenciesValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/Maven2DependenciesValidator.java new file mode 100644 index 0000000000..7e53bd40d2 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/Maven2DependenciesValidator.java @@ -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 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"); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenMixedDependenciesValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenMixedDependenciesValidator.java new file mode 100644 index 0000000000..e881fc08bc --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenMixedDependenciesValidator.java @@ -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 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); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginConfigurationValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginConfigurationValidator.java index a79550c6fd..cb0e3fb23b 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginConfigurationValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginConfigurationValidator.java @@ -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); } diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginDependenciesValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginDependenciesValidator.java new file mode 100644 index 0000000000..b990bfe58a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenPluginDependenciesValidator.java @@ -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); +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenScopeDependenciesValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenScopeDependenciesValidator.java new file mode 100644 index 0000000000..87aaa8f8e0 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/MavenScopeDependenciesValidator.java @@ -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 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); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/PlexusContainerDefaultDependenciesValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/PlexusContainerDefaultDependenciesValidator.java new file mode 100644 index 0000000000..c326fede5a --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/PlexusContainerDefaultDependenciesValidator.java @@ -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"); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/ReadOnlyPluginParametersValidator.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/ReadOnlyPluginParametersValidator.java index 6c15550797..83df41d0f7 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/ReadOnlyPluginParametersValidator.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/ReadOnlyPluginParametersValidator.java @@ -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)); } } }