Skip to content
Draft
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 @@ -76,6 +76,7 @@
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.impl.SettingsUtilsV4;
import org.apache.maven.internal.build.DefaultDiagnosticCollector;
import org.apache.maven.jline.FastTerminal;
import org.apache.maven.jline.MessageUtils;
import org.apache.maven.logging.BuildEventListener;
Expand Down Expand Up @@ -747,6 +748,13 @@ protected Runnable settings(C context, boolean emitSettingsWarnings, SettingsBui
}
}
context.logger.info("");

// Pipe structured problems directly to DiagnosticCollector so that
// key, suggestion, documentationUrl, and source location are preserved
// in the build report (instead of being lost to plain-text logging).
// This runs before SessionStarted, so the SLF4J auto-collection hook
// is not active yet — no double-counting risk.
pipeSettingsProblems(context, settingsResult);
}
return () -> {
context.installationSettingsPath = null;
Expand All @@ -758,6 +766,20 @@ protected Runnable settings(C context, boolean emitSettingsWarnings, SettingsBui
};
}

/**
* Pipes structured settings validation problems to the DiagnosticCollector.
* This preserves key, suggestion, documentationUrl, and source location
* that would otherwise be lost when problems are logged as plain text.
*/
private void pipeSettingsProblems(C context, SettingsBuilderResult settingsResult) {
context.lookup.lookupOptional(DefaultDiagnosticCollector.class).ifPresent(collector -> {
for (BuilderProblem problem :
settingsResult.getProblems().problems().toList()) {
collector.report(problem);
}
});
}

protected void customizeSettingsRequest(C context, SettingsBuilderRequest settingsBuilderRequest)
throws Exception {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.execution.ProfileActivation;
import org.apache.maven.execution.ProjectActivation;
import org.apache.maven.internal.build.DefaultDiagnosticCollector;
import org.apache.maven.jline.MessageUtils;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.logging.BuildEventListener;
Expand Down Expand Up @@ -220,6 +221,17 @@ protected void toolchains(MavenContext context, MavenExecutionRequest request) t
}

context.logger.info("");

// Pipe structured problems directly to DiagnosticCollector so that
// key, suggestion, documentationUrl, and source location are preserved
// in the build report. This runs before SessionStarted, so the SLF4J
// auto-collection hook is not active yet — no double-counting risk.
context.lookup.lookupOptional(DefaultDiagnosticCollector.class).ifPresent(collector -> {
for (BuilderProblem problem :
toolchainsResult.getProblems().problems().toList()) {
collector.report(problem);
}
});
}
}

Expand Down
2 changes: 2 additions & 0 deletions impl/maven-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ under the License.
<exclude>org.apache.maven.toolchain.ToolchainManagerPrivate</exclude>
<exclude>org.apache.maven.toolchain.ToolchainPrivate</exclude>
<exclude>org.apache.maven.toolchain.ToolchainsBuilder</exclude>
<!-- PluginValidationManager: String issue → BuilderProblem problem (4.1.0) -->
<exclude>org.apache.maven.plugin.PluginValidationManager</exclude>
</excludes>
</parameter>
</configuration>
Expand Down
34 changes: 33 additions & 1 deletion impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Prerequisites;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.Lookup;
import org.apache.maven.api.services.LookupException;
import org.apache.maven.artifact.ArtifactUtils;
Expand All @@ -59,6 +60,7 @@
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.graph.GraphBuilder;
import org.apache.maven.graph.ProjectSelector;
import org.apache.maven.internal.build.DefaultDiagnosticCollector;
import org.apache.maven.internal.impl.DefaultSessionFactory;
import org.apache.maven.internal.impl.InternalMavenSession;
import org.apache.maven.lifecycle.LifecycleExecutionException;
Expand Down Expand Up @@ -113,6 +115,8 @@ public class DefaultMaven implements Maven {

private final ProjectSelector projectSelector;

private final DefaultDiagnosticCollector diagnosticCollector;

@Inject
@SuppressWarnings("checkstyle:ParameterNumber")
public DefaultMaven(
Expand All @@ -126,7 +130,8 @@ public DefaultMaven(
BuildResumptionDataRepository buildResumptionDataRepository,
SuperPomProvider superPomProvider,
DefaultSessionFactory defaultSessionFactory,
@Nullable @Named("ide") WorkspaceReader ideWorkspaceReader) {
@Nullable @Named("ide") WorkspaceReader ideWorkspaceReader,
DefaultDiagnosticCollector diagnosticCollector) {
this.lookup = lookup;
this.eventCatapult = eventCatapult;
this.legacySupport = legacySupport;
Expand All @@ -138,6 +143,7 @@ public DefaultMaven(
this.superPomProvider = superPomProvider;
this.ideWorkspaceReader = ideWorkspaceReader;
this.defaultSessionFactory = defaultSessionFactory;
this.diagnosticCollector = diagnosticCollector;
this.projectSelector = new ProjectSelector(); // if necessary switch to DI
}

Expand Down Expand Up @@ -648,6 +654,10 @@ private Result<? extends ProjectDependencyGraph> buildGraph(MavenSession session
} else {
logger.error(problem.getMessage());
}
// Pipe structured problem directly to DiagnosticCollector so that
// source location and severity are preserved in the build report.
// The SLF4J hook excludes this logger to avoid double-counting.
diagnosticCollector.report(toBuilderProblem(problem));
}

if (!graphResult.hasErrors()) {
Expand All @@ -660,6 +670,28 @@ private Result<? extends ProjectDependencyGraph> buildGraph(MavenSession session
return graphResult;
}

/**
* Converts a compat {@link ModelProblem} to the Maven 4 {@link BuilderProblem} API,
* preserving source, line, column, severity, and message.
*/
private static BuilderProblem toBuilderProblem(ModelProblem problem) {
BuilderProblem.Severity severity =
switch (problem.getSeverity()) {
case FATAL -> BuilderProblem.Severity.FATAL;
case ERROR -> BuilderProblem.Severity.ERROR;
default -> BuilderProblem.Severity.WARNING;
};
return BuilderProblem.builder()
.source(problem.getSource())
.lineNumber(problem.getLineNumber())
.columnNumber(problem.getColumnNumber())
.exception(problem.getException())
.message(problem.getMessage())
.severity(severity)
.key("model:" + problem.getMessage().hashCode())
.build();
}

@Deprecated
// 5 January 2014
protected Logger getLogger() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ public final class BuildReportCollector extends AbstractEventSpy {

private static final int MAX_STACKTRACE_LINES = 30;

/**
* Logger names excluded from SLF4J auto-collection because these classes
* already pipe structured {@link org.apache.maven.api.services.BuilderProblem}
* objects directly to the {@link DefaultDiagnosticCollector}. Without this
* exclusion, each problem would be counted twice: once from the direct pipe
* and once from the SLF4J WARN interception.
*/
private static final Set<String> EXCLUDED_LOGGERS = Set.of(
BuildReportCollector.class.getName(),
"org.apache.maven.DefaultMaven",
"org.apache.maven.project.collector.DefaultProjectsSelector",
"org.apache.maven.plugin.internal.DefaultPluginValidationManager");

private final DefaultDiagnosticCollector diagnosticCollector;

@Inject
Expand Down Expand Up @@ -353,10 +366,10 @@ private void captureLogEvent(int level, String loggerName, String message, Throw

// Auto-collect WARN-level log events as build problems, giving Maven 3 plugins
// automatic deduplication and summary at end of build without code changes.
// Skip our own logger to avoid feedback loops from problem summary printing.
if (level == LocationAwareLogger.WARN_INT
&& message != null
&& !loggerName.equals(BuildReportCollector.class.getName())) {
// Skip loggers that already pipe structured BuilderProblems directly to the
// DiagnosticCollector (avoiding double-counting), and our own logger to avoid
// feedback loops from problem summary printing.
if (level == LocationAwareLogger.WARN_INT && message != null && !EXCLUDED_LOGGERS.contains(loggerName)) {
String syntheticKey = syntheticDiagnosticKey(loggerName, message);
diagnosticCollector.report(BuilderProblem.builder()
.source(loggerName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.maven.plugin;

import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.eclipse.aether.RepositorySystemSession;
Expand Down Expand Up @@ -54,27 +55,94 @@ enum IssueLocality {
* 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.
*
* @since 4.1.0
*/
void reportPluginValidationIssue(
IssueLocality locality, RepositorySystemSession session, Artifact pluginArtifact, String issue);
IssueLocality locality, RepositorySystemSession session, Artifact pluginArtifact, BuilderProblem problem);

/**
* 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.
*
* @since 4.1.0
*/
void reportPluginValidationIssue(
IssueLocality locality, MavenSession mavenSession, MojoDescriptor mojoDescriptor, String issue);
IssueLocality locality, MavenSession mavenSession, MojoDescriptor mojoDescriptor, BuilderProblem problem);

/**
* Reports plugin Mojo issues applicable to the Mojo itself.
* <p>
* This method will record extra information as well, like plugin occurrence or declaration location.
*
* @since 4.1.0
*/
void reportPluginMojoValidationIssue(
IssueLocality locality,
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
String issue);
BuilderProblem problem);

// ---- Deprecated String-based adapters for external callers ----

/**
* @deprecated Use {@link #reportPluginValidationIssue(IssueLocality, RepositorySystemSession, Artifact,
* BuilderProblem)} instead.
*/
@Deprecated(since = "4.1.0", forRemoval = true)
default void reportPluginValidationIssue(
IssueLocality locality, RepositorySystemSession session, Artifact pluginArtifact, String issue) {
reportPluginValidationIssue(
locality,
session,
pluginArtifact,
BuilderProblem.builder()
.message(issue)
.severity(BuilderProblem.Severity.WARNING)
.key("plugin-validation:" + issue.hashCode())
.build());
}

/**
* @deprecated Use {@link #reportPluginValidationIssue(IssueLocality, MavenSession, MojoDescriptor,
* BuilderProblem)} instead.
*/
@Deprecated(since = "4.1.0", forRemoval = true)
default void reportPluginValidationIssue(
IssueLocality locality, MavenSession mavenSession, MojoDescriptor mojoDescriptor, String issue) {
reportPluginValidationIssue(
locality,
mavenSession,
mojoDescriptor,
BuilderProblem.builder()
.message(issue)
.severity(BuilderProblem.Severity.WARNING)
.key("plugin-validation:" + issue.hashCode())
.build());
}

/**
* @deprecated Use {@link #reportPluginMojoValidationIssue(IssueLocality, MavenSession, MojoDescriptor, Class,
* BuilderProblem)} instead.
*/
@Deprecated(since = "4.1.0", forRemoval = true)
default void reportPluginMojoValidationIssue(
IssueLocality locality,
MavenSession mavenSession,
MojoDescriptor mojoDescriptor,
Class<?> mojoClass,
String issue) {
reportPluginMojoValidationIssue(
locality,
mavenSession,
mojoDescriptor,
mojoClass,
BuilderProblem.builder()
.message(issue)
.severity(BuilderProblem.Severity.WARNING)
.key("plugin-validation:" + issue.hashCode())
.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.maven.plugin.internal;

import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.PluginValidationManager;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
Expand Down Expand Up @@ -97,6 +98,11 @@ protected boolean isIgnoredProperty(String strValue) {

protected abstract String getParameterLogReason(Parameter parameter);

/**
* Returns the validation key prefix for this validator (e.g. "deprecated-param", "readonly-param").
*/
protected abstract String getValidationKeyPrefix();

protected String formatParameter(Parameter parameter) {
StringBuilder stringBuilder = new StringBuilder()
.append("Parameter '")
Expand All @@ -112,4 +118,12 @@ protected String formatParameter(Parameter parameter) {

return stringBuilder.toString();
}

protected BuilderProblem buildParameterProblem(Parameter parameter) {
return BuilderProblem.builder()
.message(formatParameter(parameter))
.severity(BuilderProblem.Severity.WARNING)
.key("plugin-validation:" + getValidationKeyPrefix() + ":" + parameter.getName())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.apache.maven.api.Service;
import org.apache.maven.api.Session;
import org.apache.maven.api.plugin.descriptor.Resolution;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.DependencyResolver;
import org.apache.maven.api.services.DependencyResolverResult;
import org.apache.maven.api.services.PathScopeRegistry;
Expand Down Expand Up @@ -751,7 +752,13 @@ private <T> T loadV3Mojo(
session,
mojoDescriptor,
mojo.getClass(),
"Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.");
BuilderProblem.builder()
.message(
"Mojo implements `Contextualizable` interface from Plexus Container, which is EOL.")
.severity(BuilderProblem.Severity.WARNING)
.key("plugin-validation:contextualizable")
.suggestion("Migrate from Contextualizable to javax.inject dependency injection")
.build());
}

XmlNode dom = mojoExecution.getConfiguration() != null
Expand Down
Loading