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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ under the License.

<properties>
<javaVersion>17</javaVersion>
<mavenVersion>4.0.0-rc-4</mavenVersion>
<mavenVersion>4.1.0-SNAPSHOT</mavenVersion>

<asmVersion>9.10.1</asmVersion>
<guiceVersion>7.0.0</guiceVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import org.apache.maven.api.services.DependencyResolver;
import org.apache.maven.api.services.DependencyResolverRequest;
import org.apache.maven.api.services.DependencyResolverResult;
import org.apache.maven.api.services.DiagnosticReporter;
import org.apache.maven.api.services.MavenException;
import org.apache.maven.api.services.MessageBuilder;
import org.apache.maven.api.services.MessageBuilderFactory;
Expand Down Expand Up @@ -942,6 +943,17 @@ final void amendincrementalCompilation(EnumSet<IncrementalBuild.Aspect> aspects,
@Inject
protected MessageBuilderFactory messageBuilderFactory;

/**
* Service for reporting structured build diagnostics to the build report.
* Compiler warnings and errors are reported through this service so they
* appear with structured keys, source locations, and suggestions in
* {@code mvnlog --diagnostics} and the JSON build report.
*
* @since 4.0.0-beta-5
*/
@Inject
protected DiagnosticReporter diagnosticReporter;

/**
* The logger for reporting information or warnings to the user.
* Currently, this is also used for console output.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@
import java.util.Optional;

import org.apache.maven.api.plugin.Log;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.DiagnosticReporter;
import org.apache.maven.api.services.MessageBuilder;
import org.apache.maven.api.services.MessageBuilderFactory;

/**
* A Java compiler diagnostic listener which send the messages to the Maven logger.
* A Java compiler diagnostic listener which sends the messages to the Maven logger
* and reports structured {@link BuilderProblem}s to the {@link DiagnosticReporter}.
*
* @author Martin Desruisseaux
*/
Expand All @@ -49,6 +52,12 @@ final class DiagnosticLogger implements DiagnosticListener<JavaFileObject> {
*/
private final MessageBuilderFactory messageBuilderFactory;

/**
* The service for reporting structured diagnostics to the build report.
* May be {@code null} if no reporter is available (e.g. Maven 4.0.x).
*/
private final DiagnosticReporter diagnosticReporter;

/**
* The locale for compiler message.
*/
Expand All @@ -75,16 +84,24 @@ final class DiagnosticLogger implements DiagnosticListener<JavaFileObject> {
private String firstError;

/**
* Creates a listener which will send the diagnostics to the given logger.
* Creates a listener which will send the diagnostics to the given logger
* and to the given diagnostic reporter.
*
* @param logger the logger where to send diagnostics
* @param messageBuilderFactory the factory for creating message builders
* @param diagnosticReporter the reporter for structured build diagnostics, or {@code null}
* @param locale the locale for compiler message
* @param directory the base directory with which to relativize the paths to source files
*/
DiagnosticLogger(Log logger, MessageBuilderFactory messageBuilderFactory, Locale locale, Path directory) {
DiagnosticLogger(
Log logger,
MessageBuilderFactory messageBuilderFactory,
DiagnosticReporter diagnosticReporter,
Locale locale,
Path directory) {
this.logger = logger;
this.messageBuilderFactory = messageBuilderFactory;
this.diagnosticReporter = diagnosticReporter;
this.locale = locale;
this.directory = directory;
codeCount = new LinkedHashMap<>();
Expand All @@ -107,6 +124,17 @@ private String relativize(String file) {
return file;
}

/**
* Maps a {@link Diagnostic.Kind} to a {@link BuilderProblem.Severity}.
*/
private static BuilderProblem.Severity mapSeverity(Diagnostic.Kind kind) {
return switch (kind) {
case ERROR -> BuilderProblem.Severity.ERROR;
case WARNING, MANDATORY_WARNING -> BuilderProblem.Severity.WARNING;
default -> BuilderProblem.Severity.INFO;
};
}

/**
* Invoked when the compiler emitted a warning.
*
Expand Down Expand Up @@ -176,6 +204,40 @@ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (code != null) {
codeCount.merge(code, 1, (old, initial) -> old + 1);
}
// Report structured diagnostic to the build report
reportToBuildReport(diagnostic, message, code);
}

/**
* Reports a structured {@link BuilderProblem} to the {@link DiagnosticReporter}.
* <p>
* Each diagnostic is reported with a per-type key ({@code "compiler:<code>"})
* so that the build report deduplicates by diagnostic kind. For example, 50
* unchecked warnings produce a single entry with count=50 in the summary.
* Individual per-file details remain in the build log.
*/
private void reportToBuildReport(Diagnostic<? extends JavaFileObject> diagnostic, String message, String code) {
if (diagnosticReporter == null || code == null) {
return;
}
BuilderProblem.Builder builder = BuilderProblem.builder()
.severity(mapSeverity(diagnostic.getKind()))
.message(message)
.key("compiler:" + code);
// Attach source location from the first occurrence (collector deduplicates by key)
JavaFileObject sourceFile = diagnostic.getSource();
if (sourceFile != null) {
builder.source(relativize(sourceFile.getName()));
long line = diagnostic.getLineNumber();
if (line != Diagnostic.NOPOS) {
builder.lineNumber((int) line);
}
long column = diagnostic.getColumnNumber();
if (column != Diagnostic.NOPOS) {
builder.columnNumber((int) column);
}
}
diagnosticReporter.report(builder.build());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ protected ToolExecutor(final AbstractCompilerMojo mojo, DiagnosticListener<? sup
logger = mojo.logger;
if (listener == null) {
Path root = mojo.project.getRootDirectory();
listener = new DiagnosticLogger(logger, mojo.messageBuilderFactory, LOCALE, root);
listener = new DiagnosticLogger(logger, mojo.messageBuilderFactory, mojo.diagnosticReporter, LOCALE, root);
}
this.listener = listener;
encoding = mojo.charset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,21 @@
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.plugin.Log;
import org.apache.maven.api.plugin.testing.Basedir;
import org.apache.maven.api.plugin.testing.InjectMojo;
import org.apache.maven.api.plugin.testing.MojoExtension;
import org.apache.maven.api.plugin.testing.MojoParameter;
import org.apache.maven.api.plugin.testing.MojoTest;
import org.apache.maven.api.plugin.testing.stubs.ProducedArtifactStub;
import org.apache.maven.api.plugin.testing.stubs.ProjectStub;
import org.apache.maven.api.plugin.testing.stubs.SessionMock;
import org.apache.maven.api.services.ArtifactManager;
import org.apache.maven.api.services.DiagnosticReporter;
import org.apache.maven.api.services.MessageBuilderFactory;
import org.apache.maven.api.services.ToolchainManager;
import org.apache.maven.impl.DefaultMessageBuilderFactory;
import org.apache.maven.impl.InternalSession;
import org.apache.maven.plugin.compiler.stubs.CompilerStub;
import org.apache.maven.testing.plugin.Basedir;
import org.apache.maven.testing.plugin.InjectMojo;
import org.apache.maven.testing.plugin.MojoExtension;
import org.apache.maven.testing.plugin.MojoParameter;
import org.apache.maven.testing.plugin.MojoTest;
import org.apache.maven.testing.plugin.stubs.ProducedArtifactStub;
import org.apache.maven.testing.plugin.stubs.ProjectStub;
import org.apache.maven.testing.plugin.stubs.SessionMock;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
Expand Down Expand Up @@ -474,6 +475,14 @@ private static InternalSession createSession() {
return session;
}

@Provides
@Singleton
@SuppressWarnings("unused")
private static DiagnosticReporter createDiagnosticReporter() {
// No-op reporter for tests — diagnostics are verified via Log mock
return problem -> {};
}

@Provides
@Singleton
@SuppressWarnings("unused")
Expand Down
Loading