From 252ca09f37cf5103d561772809b63233807b03fc Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 31 Jul 2026 17:27:30 +0200 Subject: [PATCH 1/6] Report structured BuilderProblems from compiler diagnostics Map javax.tools.Diagnostic to BuilderProblem and report via the new DiagnosticReporter service, so compiler warnings and errors appear with structured keys, source locations, and severity in the build report and mvnlog --diagnostics output. - Add @Inject DiagnosticReporter to AbstractCompilerMojo - Pass DiagnosticReporter to DiagnosticLogger constructor - Map each Diagnostic to BuilderProblem with key "compiler:", severity from Diagnostic.Kind, and source file/line/column - Use per-type key for dedup (e.g. "compiler:compiler.warn.unchecked" counts all unchecked warnings as one summary entry) - Bump mavenVersion to 4.1.0-SNAPSHOT for DiagnosticReporter API - Update test imports for maven-testing package relocation - Add no-op DiagnosticReporter provider in test configuration Co-Authored-By: Claude Opus 4.6 --- pom.xml | 2 +- .../plugin/compiler/AbstractCompilerMojo.java | 12 ++++ .../plugin/compiler/DiagnosticLogger.java | 68 ++++++++++++++++++- .../maven/plugin/compiler/ToolExecutor.java | 2 +- .../plugin/compiler/CompilerMojoTestCase.java | 25 ++++--- 5 files changed, 96 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 75b3e25f6..8927b8137 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ under the License. 17 - 4.0.0-rc-4 + 4.1.0-SNAPSHOT 9.10.1 7.0.0 diff --git a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java index cac92f902..521b861bd 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java @@ -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; @@ -942,6 +943,17 @@ final void amendincrementalCompilation(EnumSet 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. diff --git a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java index 9bce7a546..625f82206 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java +++ b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java @@ -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 */ @@ -49,6 +52,12 @@ final class DiagnosticLogger implements DiagnosticListener { */ 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. */ @@ -75,16 +84,24 @@ final class DiagnosticLogger implements DiagnosticListener { 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<>(); @@ -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. * @@ -176,6 +204,40 @@ public void report(Diagnostic 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}. + *

+ * Each diagnostic is reported with a per-type key ({@code "compiler:"}) + * 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 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()); } /** diff --git a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java index 907d32390..bd8bb26b0 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java +++ b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java @@ -225,7 +225,7 @@ protected ToolExecutor(final AbstractCompilerMojo mojo, DiagnosticListener {}; + } + @Provides @Singleton @SuppressWarnings("unused") From 92a9030787925a9b58dd11f37f49c6ed519f1f6a Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sun, 2 Aug 2026 21:25:30 +0200 Subject: [PATCH 2/6] Use per-location dedup keys for compiler diagnostics Change the BuilderProblem key from per-type (compiler:) to per-location (compiler:::) so each unique file+line keeps its own entry in the build report. Warnings of the same type in different files are no longer collapsed into a single entry. Co-Authored-By: Claude Opus 4.6 --- .../plugin/compiler/DiagnosticLogger.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java index 625f82206..aee644ab9 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java +++ b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java @@ -211,10 +211,10 @@ public void report(Diagnostic diagnostic) { /** * Reports a structured {@link BuilderProblem} to the {@link DiagnosticReporter}. *

- * Each diagnostic is reported with a per-type key ({@code "compiler:"}) - * 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. + * Each diagnostic is reported with a per-location key + * ({@code "compiler:::"}) so each unique file+line + * gets its own entry in the build report. Warnings of the same type in + * different files are kept as separate entries. */ private void reportToBuildReport(Diagnostic diagnostic, String message, String code) { if (diagnosticReporter == null || code == null) { @@ -222,21 +222,25 @@ private void reportToBuildReport(Diagnostic diagnostic } 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) + .message(message); + // Build a per-location key so each unique file+line keeps its own entry JavaFileObject sourceFile = diagnostic.getSource(); + StringBuilder keyBuilder = new StringBuilder("compiler:").append(code); if (sourceFile != null) { - builder.source(relativize(sourceFile.getName())); + String relPath = relativize(sourceFile.getName()); + builder.source(relPath); + keyBuilder.append(':').append(relPath); long line = diagnostic.getLineNumber(); if (line != Diagnostic.NOPOS) { builder.lineNumber((int) line); + keyBuilder.append(':').append(line); } long column = diagnostic.getColumnNumber(); if (column != Diagnostic.NOPOS) { builder.columnNumber((int) column); } } + builder.key(keyBuilder.toString()); diagnosticReporter.report(builder.build()); } From 590e426e1845e8ac6a801f2647b74263a8a79819 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 7 Aug 2026 00:08:26 +0200 Subject: [PATCH 3/6] Use Log.child() and Log.problem() for hierarchical logging and structured diagnostics Refactor DiagnosticLogger, Options, and ByteCodeTransformer to use hierarchical child loggers (e.g. "compiler:compile.diagnostics", "compiler:compile.options") via Log.child(), and report compiler warnings/errors through Log.problem() instead of the separate DiagnosticReporter service. This: - Gives each sub-component a distinct logger name for filtering - Avoids double-counting in BuildReportCollector by leveraging the STRUCTURED_PROBLEM_ACTIVE thread-local flag set by Log.problem() - Removes the DiagnosticReporter dependency from the plugin (the Log's problem sink, wired by DefaultMavenPluginManager, handles reporting) - Simplifies DiagnosticLogger by removing MessageBuilder formatting (source location is included as plain text in the problem message and captured structurally via source/lineNumber/columnNumber fields) Requires Maven core 4.1.0-SNAPSHOT with Log.child() and Log.problem() API. Co-Authored-By: Claude Opus 4.6 --- .../plugin/compiler/AbstractCompilerMojo.java | 24 +-- .../plugin/compiler/DiagnosticLogger.java | 196 ++++++++---------- .../maven/plugin/compiler/ToolExecutor.java | 2 +- .../plugin/compiler/CompilerMojoTestCase.java | 14 +- 4 files changed, 104 insertions(+), 132 deletions(-) diff --git a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java index 521b861bd..de7c1a51b 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java @@ -70,10 +70,10 @@ import org.apache.maven.api.plugin.MojoException; import org.apache.maven.api.plugin.annotations.Parameter; import org.apache.maven.api.services.ArtifactManager; +import org.apache.maven.api.services.BuilderProblem; 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; @@ -943,20 +943,13 @@ final void amendincrementalCompilation(EnumSet 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. + * Also used for structured diagnostic reporting via {@link Log#problem(BuilderProblem)}, + * which feeds into the build report, {@code mvnlog --diagnostics}, and warning suppression. + * + *

Sub-components receive child loggers (e.g. {@code logger.child("diagnostics")}, + * {@code logger.child("options")}) for hierarchical naming in the log output.

* *

Thread safety

* This logger should be thread-safe if the {@link ToolExecutor} is executed in a background thread. @@ -1345,7 +1338,7 @@ public Options parseParameters(final OptionChecker compiler) { * For example, Maven will check for illegal values in the "-g" option only if the compiler rejected * the fully formatted option (e.g. "-g:vars,lines") that we provided to it. */ - final var configuration = new Options(compiler, logger); + final var configuration = new Options(compiler, logger.child("options")); configuration.addIfNonBlank("--source", getSource()); targetOrReleaseSet = configuration.addIfNonBlank("--target", getTarget()); targetOrReleaseSet |= configuration.setRelease(getRelease()); @@ -1475,7 +1468,8 @@ private void compile(final JavaCompiler compiler, final Options configuration) t Path moduleDescriptor = executor.outputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX); if (Files.isRegularFile(moduleDescriptor)) { byte[] oridinal = Files.readAllBytes(moduleDescriptor); - byte[] modified = ByteCodeTransformer.patchJdkModuleVersion(oridinal, getRelease(), logger); + byte[] modified = + ByteCodeTransformer.patchJdkModuleVersion(oridinal, getRelease(), logger.child("bytecode")); if (modified != null) { Files.write(moduleDescriptor, modified); } diff --git a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java index aee644ab9..f5b51a2af 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java +++ b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java @@ -31,33 +31,23 @@ 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 sends the messages to the Maven logger - * and reports structured {@link BuilderProblem}s to the {@link DiagnosticReporter}. + * A Java compiler diagnostic listener which sends the messages to the Maven logger. + * When the logger supports structured problem reporting ({@link Log#problem(BuilderProblem)}), + * each diagnostic is also reported as a {@link BuilderProblem} with a per-location dedup key, + * enabling the build report, {@code mvnlog --diagnostics}, and warning suppression. * * @author Martin Desruisseaux */ final class DiagnosticLogger implements DiagnosticListener { /** - * The logger where to send diagnostics. + * The logger where to send diagnostics and structured problems. + * This should be a child logger (e.g. {@code "compiler:compile.diagnostics"}) + * obtained via {@link Log#child(String)}. */ private final Log logger; - /** - * The factory for creating message builders. - */ - 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. */ @@ -84,24 +74,17 @@ final class DiagnosticLogger implements DiagnosticListener { private String firstError; /** - * Creates a listener which will send the diagnostics to the given logger - * and to the given diagnostic reporter. + * Creates a listener which will send the diagnostics to the given logger. + * Structured problems are reported via {@link Log#problem(BuilderProblem)}, + * which handles dedup, suppression, and thread-safe interaction with the + * {@code BuildReportCollector} automatically. * - * @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 logger the logger where to send diagnostics (typically a child logger) * @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, - DiagnosticReporter diagnosticReporter, - Locale locale, - Path directory) { + DiagnosticLogger(Log logger, Locale locale, Path directory) { this.logger = logger; - this.messageBuilderFactory = messageBuilderFactory; - this.diagnosticReporter = diagnosticReporter; this.locale = locale; this.directory = directory; codeCount = new LinkedHashMap<>(); @@ -125,20 +108,30 @@ private String relativize(String file) { } /** - * Maps a {@link Diagnostic.Kind} to a {@link BuilderProblem.Severity}. + * Maps a {@link Diagnostic.Kind} to a {@link BuilderProblem.Severity}, + * or {@code null} if the kind has no corresponding severity (e.g. {@code NOTE}). */ 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; + default -> null; }; } /** - * Invoked when the compiler emitted a warning. + * Invoked when the compiler emitted a diagnostic. + *

+ * When the diagnostic has a code and a mappable severity (error or warning), + * a structured {@link BuilderProblem} is reported via {@link Log#problem(BuilderProblem)} + * with a per-location dedup key ({@code "compiler:::"}). + * This avoids double-counting with the {@code BuildReportCollector}'s WARN auto-promotion, + * because {@code Log.problem()} sets the structured-problem flag internally. + *

+ * Informational diagnostics (notes, other) are logged at INFO level without + * creating a structured problem, since they are not actionable warnings. * - * @param diagnostic the warning emitted by the Java compiler + * @param diagnostic the diagnostic emitted by the Java compiler */ @Override public void report(Diagnostic diagnostic) { @@ -146,57 +139,24 @@ public void report(Diagnostic diagnostic) { if (message == null || message.isBlank()) { return; } - MessageBuilder record = messageBuilderFactory.builder(); - record.a(message); - JavaFileObject source = diagnostic.getSource(); Diagnostic.Kind kind = diagnostic.getKind(); - String style; - switch (kind) { - case ERROR: - style = ".error:-bold,f:red"; - break; - case MANDATORY_WARNING: - case WARNING: - style = ".warning:-bold,f:yellow"; - break; - default: - style = ".info:-bold,f:blue"; - if (diagnostic.getLineNumber() == Diagnostic.NOPOS) { - source = null; // Some messages are generic, e.g. "Recompile with -Xlint:deprecation". - } - break; - } - if (source != null) { - record.newline().a(" at ").a(relativize(source.getName())); - long line = diagnostic.getLineNumber(); - long column = diagnostic.getColumnNumber(); - if (line != Diagnostic.NOPOS || column != Diagnostic.NOPOS) { - record.style(style).a('['); - if (line != Diagnostic.NOPOS) { - record.a(line); - } - if (column != Diagnostic.NOPOS) { - record.a(',').a(column); - } - record.a(']').resetStyle(); - } - } - String log = record.toString(); + JavaFileObject source = diagnostic.getSource(); + // Track counts for the summary switch (kind) { case ERROR: if (firstError == null) { firstError = message; } - logger.error(log); numErrors++; break; case MANDATORY_WARNING: case WARNING: - logger.warn(log); numWarnings++; break; default: - logger.info(log); + if (diagnostic.getLineNumber() == Diagnostic.NOPOS) { + source = null; // Some messages are generic, e.g. "Recompile with -Xlint:deprecation". + } break; } // Statistics @@ -204,44 +164,65 @@ public void report(Diagnostic diagnostic) { if (code != null) { codeCount.merge(code, 1, (old, initial) -> old + 1); } - // Report structured diagnostic to the build report - reportToBuildReport(diagnostic, message, code); + // Report as a structured problem when we have a diagnostic code and a mappable severity. + // Log.problem() handles both the console log and the build report, + // setting the STRUCTURED_PROBLEM_ACTIVE flag to prevent double-counting. + BuilderProblem.Severity severity = mapSeverity(kind); + if (code != null && severity != null) { + logger.problem(buildProblem(diagnostic, message, code, source, severity)); + } else { + // Informational diagnostic or no code — fall back to plain logging + switch (kind) { + case ERROR -> logger.error(message); + case MANDATORY_WARNING, WARNING -> logger.warn(message); + default -> logger.info(message); + } + } } /** - * Reports a structured {@link BuilderProblem} to the {@link DiagnosticReporter}. + * Builds a structured {@link BuilderProblem} from the given compiler diagnostic. + * Each diagnostic gets a per-location key ({@code "compiler:::"}) + * so each unique file+line keeps its own entry in the build report. *

- * Each diagnostic is reported with a per-location key - * ({@code "compiler:::"}) so each unique file+line - * gets its own entry in the build report. Warnings of the same type in - * different files are kept as separate entries. + * The message includes the source location as plain text (e.g. + * {@code "unchecked cast\n at src/main/java/Foo.java[42,10]"}) + * so it remains visible in the console output. */ - private void reportToBuildReport(Diagnostic diagnostic, String message, String code) { - if (diagnosticReporter == null || code == null) { - return; - } - BuilderProblem.Builder builder = BuilderProblem.builder() - .severity(mapSeverity(diagnostic.getKind())) - .message(message); - // Build a per-location key so each unique file+line keeps its own entry - JavaFileObject sourceFile = diagnostic.getSource(); - StringBuilder keyBuilder = new StringBuilder("compiler:").append(code); - if (sourceFile != null) { - String relPath = relativize(sourceFile.getName()); + private BuilderProblem buildProblem( + Diagnostic diagnostic, + String message, + String code, + JavaFileObject source, + BuilderProblem.Severity severity) { + BuilderProblem.Builder builder = BuilderProblem.builder().severity(severity); + // Build message with source location and per-location dedup key + var fullMessage = new StringBuilder(message); + var keyBuilder = new StringBuilder("compiler:").append(code); + if (source != null) { + String relPath = relativize(source.getName()); builder.source(relPath); keyBuilder.append(':').append(relPath); long line = diagnostic.getLineNumber(); - if (line != Diagnostic.NOPOS) { - builder.lineNumber((int) line); - keyBuilder.append(':').append(line); - } long column = diagnostic.getColumnNumber(); - if (column != Diagnostic.NOPOS) { - builder.columnNumber((int) column); + fullMessage.append(System.lineSeparator()).append(" at ").append(relPath); + if (line != Diagnostic.NOPOS || column != Diagnostic.NOPOS) { + fullMessage.append('['); + if (line != Diagnostic.NOPOS) { + fullMessage.append(line); + builder.lineNumber((int) line); + keyBuilder.append(':').append(line); + } + if (column != Diagnostic.NOPOS) { + fullMessage.append(',').append(column); + builder.columnNumber((int) column); + } + fullMessage.append(']'); } } - builder.key(keyBuilder.toString()); - diagnosticReporter.report(builder.build()); + return builder.key(keyBuilder.toString()) + .message(fullMessage.toString()) + .build(); } /** @@ -257,23 +238,24 @@ Optional firstError(Throwable cause) { * Reports summary after the compilation finished. */ void logSummary() { - MessageBuilder message = messageBuilderFactory.builder(); + var message = new StringBuilder(); final String patternForCount; if (!codeCount.isEmpty()) { @SuppressWarnings("unchecked") Map.Entry[] entries = codeCount.entrySet().toArray(Map.Entry[]::new); Arrays.sort(entries, (a, b) -> Integer.compare(b.getValue(), a.getValue())); patternForCount = patternForCount(Math.max(entries[0].getValue(), Math.max(numWarnings, numErrors))); - message.strong("Summary of compiler messages:").newline(); + message.append("Summary of compiler messages:").append(System.lineSeparator()); for (Map.Entry entry : entries) { int count = entry.getValue(); - message.format(patternForCount, count, entry.getKey()).newline(); + message.append(String.format(patternForCount, count, entry.getKey())) + .append(System.lineSeparator()); } } else { patternForCount = patternForCount(Math.max(numWarnings, numErrors)); } if ((numWarnings | numErrors) != 0) { - message.strong("Total:"); + message.append("Total:"); } if (numWarnings != 0) { writeCount(message, patternForCount, numWarnings, "warning"); @@ -281,7 +263,7 @@ void logSummary() { if (numErrors != 0) { writeCount(message, patternForCount, numErrors, "error"); } - logger.info(message.toString()); + logger.info(message); } /** @@ -296,9 +278,9 @@ private static String patternForCount(int n) { /** * Appends the count of warnings or errors, making them plural if needed. */ - private static void writeCount(MessageBuilder message, String patternForCount, int count, String name) { - message.newline(); - message.format(patternForCount, count, name); + private static void writeCount(StringBuilder message, String patternForCount, int count, String name) { + message.append(System.lineSeparator()); + message.append(String.format(patternForCount, count, name)); if (count > 1) { message.append('s'); } diff --git a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java index bd8bb26b0..cd136cd81 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java +++ b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java @@ -225,7 +225,7 @@ protected ToolExecutor(final AbstractCompilerMojo mojo, DiagnosticListener {}; - } - @Provides @Singleton @SuppressWarnings("unused") From 4f68578dd04ce7e7374d77a053c9ee042d42de61 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 7 Aug 2026 07:55:37 +0200 Subject: [PATCH 4/6] Revert "Use Log.child() and Log.problem() for hierarchical logging and structured diagnostics" This reverts commit 590e426e1845e8ac6a801f2647b74263a8a79819. --- .../plugin/compiler/AbstractCompilerMojo.java | 24 ++- .../plugin/compiler/DiagnosticLogger.java | 196 ++++++++++-------- .../maven/plugin/compiler/ToolExecutor.java | 2 +- .../plugin/compiler/CompilerMojoTestCase.java | 14 +- 4 files changed, 132 insertions(+), 104 deletions(-) diff --git a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java index de7c1a51b..521b861bd 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java @@ -70,10 +70,10 @@ import org.apache.maven.api.plugin.MojoException; import org.apache.maven.api.plugin.annotations.Parameter; import org.apache.maven.api.services.ArtifactManager; -import org.apache.maven.api.services.BuilderProblem; 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; @@ -944,12 +944,19 @@ final void amendincrementalCompilation(EnumSet aspects, protected MessageBuilderFactory messageBuilderFactory; /** - * The logger for reporting information or warnings to the user. - * Also used for structured diagnostic reporting via {@link Log#problem(BuilderProblem)}, - * which feeds into the build report, {@code mvnlog --diagnostics}, and warning suppression. + * 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. * - *

Sub-components receive child loggers (e.g. {@code logger.child("diagnostics")}, - * {@code logger.child("options")}) for hierarchical naming in the log output.

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

Thread safety

* This logger should be thread-safe if the {@link ToolExecutor} is executed in a background thread. @@ -1338,7 +1345,7 @@ public Options parseParameters(final OptionChecker compiler) { * For example, Maven will check for illegal values in the "-g" option only if the compiler rejected * the fully formatted option (e.g. "-g:vars,lines") that we provided to it. */ - final var configuration = new Options(compiler, logger.child("options")); + final var configuration = new Options(compiler, logger); configuration.addIfNonBlank("--source", getSource()); targetOrReleaseSet = configuration.addIfNonBlank("--target", getTarget()); targetOrReleaseSet |= configuration.setRelease(getRelease()); @@ -1468,8 +1475,7 @@ private void compile(final JavaCompiler compiler, final Options configuration) t Path moduleDescriptor = executor.outputDirectory.resolve(MODULE_INFO + CLASS_FILE_SUFFIX); if (Files.isRegularFile(moduleDescriptor)) { byte[] oridinal = Files.readAllBytes(moduleDescriptor); - byte[] modified = - ByteCodeTransformer.patchJdkModuleVersion(oridinal, getRelease(), logger.child("bytecode")); + byte[] modified = ByteCodeTransformer.patchJdkModuleVersion(oridinal, getRelease(), logger); if (modified != null) { Files.write(moduleDescriptor, modified); } diff --git a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java index f5b51a2af..aee644ab9 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java +++ b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java @@ -31,23 +31,33 @@ 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 sends the messages to the Maven logger. - * When the logger supports structured problem reporting ({@link Log#problem(BuilderProblem)}), - * each diagnostic is also reported as a {@link BuilderProblem} with a per-location dedup key, - * enabling the build report, {@code mvnlog --diagnostics}, and warning suppression. + * 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 */ final class DiagnosticLogger implements DiagnosticListener { /** - * The logger where to send diagnostics and structured problems. - * This should be a child logger (e.g. {@code "compiler:compile.diagnostics"}) - * obtained via {@link Log#child(String)}. + * The logger where to send diagnostics. */ private final Log logger; + /** + * The factory for creating message builders. + */ + 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. */ @@ -74,17 +84,24 @@ final class DiagnosticLogger implements DiagnosticListener { private String firstError; /** - * Creates a listener which will send the diagnostics to the given logger. - * Structured problems are reported via {@link Log#problem(BuilderProblem)}, - * which handles dedup, suppression, and thread-safe interaction with the - * {@code BuildReportCollector} automatically. + * 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 (typically a child logger) + * @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, 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<>(); @@ -108,30 +125,20 @@ private String relativize(String file) { } /** - * Maps a {@link Diagnostic.Kind} to a {@link BuilderProblem.Severity}, - * or {@code null} if the kind has no corresponding severity (e.g. {@code NOTE}). + * 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 -> null; + default -> BuilderProblem.Severity.INFO; }; } /** - * Invoked when the compiler emitted a diagnostic. - *

- * When the diagnostic has a code and a mappable severity (error or warning), - * a structured {@link BuilderProblem} is reported via {@link Log#problem(BuilderProblem)} - * with a per-location dedup key ({@code "compiler:::"}). - * This avoids double-counting with the {@code BuildReportCollector}'s WARN auto-promotion, - * because {@code Log.problem()} sets the structured-problem flag internally. - *

- * Informational diagnostics (notes, other) are logged at INFO level without - * creating a structured problem, since they are not actionable warnings. + * Invoked when the compiler emitted a warning. * - * @param diagnostic the diagnostic emitted by the Java compiler + * @param diagnostic the warning emitted by the Java compiler */ @Override public void report(Diagnostic diagnostic) { @@ -139,24 +146,57 @@ public void report(Diagnostic diagnostic) { if (message == null || message.isBlank()) { return; } - Diagnostic.Kind kind = diagnostic.getKind(); + MessageBuilder record = messageBuilderFactory.builder(); + record.a(message); JavaFileObject source = diagnostic.getSource(); - // Track counts for the summary + Diagnostic.Kind kind = diagnostic.getKind(); + String style; + switch (kind) { + case ERROR: + style = ".error:-bold,f:red"; + break; + case MANDATORY_WARNING: + case WARNING: + style = ".warning:-bold,f:yellow"; + break; + default: + style = ".info:-bold,f:blue"; + if (diagnostic.getLineNumber() == Diagnostic.NOPOS) { + source = null; // Some messages are generic, e.g. "Recompile with -Xlint:deprecation". + } + break; + } + if (source != null) { + record.newline().a(" at ").a(relativize(source.getName())); + long line = diagnostic.getLineNumber(); + long column = diagnostic.getColumnNumber(); + if (line != Diagnostic.NOPOS || column != Diagnostic.NOPOS) { + record.style(style).a('['); + if (line != Diagnostic.NOPOS) { + record.a(line); + } + if (column != Diagnostic.NOPOS) { + record.a(',').a(column); + } + record.a(']').resetStyle(); + } + } + String log = record.toString(); switch (kind) { case ERROR: if (firstError == null) { firstError = message; } + logger.error(log); numErrors++; break; case MANDATORY_WARNING: case WARNING: + logger.warn(log); numWarnings++; break; default: - if (diagnostic.getLineNumber() == Diagnostic.NOPOS) { - source = null; // Some messages are generic, e.g. "Recompile with -Xlint:deprecation". - } + logger.info(log); break; } // Statistics @@ -164,65 +204,44 @@ public void report(Diagnostic diagnostic) { if (code != null) { codeCount.merge(code, 1, (old, initial) -> old + 1); } - // Report as a structured problem when we have a diagnostic code and a mappable severity. - // Log.problem() handles both the console log and the build report, - // setting the STRUCTURED_PROBLEM_ACTIVE flag to prevent double-counting. - BuilderProblem.Severity severity = mapSeverity(kind); - if (code != null && severity != null) { - logger.problem(buildProblem(diagnostic, message, code, source, severity)); - } else { - // Informational diagnostic or no code — fall back to plain logging - switch (kind) { - case ERROR -> logger.error(message); - case MANDATORY_WARNING, WARNING -> logger.warn(message); - default -> logger.info(message); - } - } + // Report structured diagnostic to the build report + reportToBuildReport(diagnostic, message, code); } /** - * Builds a structured {@link BuilderProblem} from the given compiler diagnostic. - * Each diagnostic gets a per-location key ({@code "compiler:::"}) - * so each unique file+line keeps its own entry in the build report. + * Reports a structured {@link BuilderProblem} to the {@link DiagnosticReporter}. *

- * The message includes the source location as plain text (e.g. - * {@code "unchecked cast\n at src/main/java/Foo.java[42,10]"}) - * so it remains visible in the console output. + * Each diagnostic is reported with a per-location key + * ({@code "compiler:::"}) so each unique file+line + * gets its own entry in the build report. Warnings of the same type in + * different files are kept as separate entries. */ - private BuilderProblem buildProblem( - Diagnostic diagnostic, - String message, - String code, - JavaFileObject source, - BuilderProblem.Severity severity) { - BuilderProblem.Builder builder = BuilderProblem.builder().severity(severity); - // Build message with source location and per-location dedup key - var fullMessage = new StringBuilder(message); - var keyBuilder = new StringBuilder("compiler:").append(code); - if (source != null) { - String relPath = relativize(source.getName()); + private void reportToBuildReport(Diagnostic diagnostic, String message, String code) { + if (diagnosticReporter == null || code == null) { + return; + } + BuilderProblem.Builder builder = BuilderProblem.builder() + .severity(mapSeverity(diagnostic.getKind())) + .message(message); + // Build a per-location key so each unique file+line keeps its own entry + JavaFileObject sourceFile = diagnostic.getSource(); + StringBuilder keyBuilder = new StringBuilder("compiler:").append(code); + if (sourceFile != null) { + String relPath = relativize(sourceFile.getName()); builder.source(relPath); keyBuilder.append(':').append(relPath); long line = diagnostic.getLineNumber(); + if (line != Diagnostic.NOPOS) { + builder.lineNumber((int) line); + keyBuilder.append(':').append(line); + } long column = diagnostic.getColumnNumber(); - fullMessage.append(System.lineSeparator()).append(" at ").append(relPath); - if (line != Diagnostic.NOPOS || column != Diagnostic.NOPOS) { - fullMessage.append('['); - if (line != Diagnostic.NOPOS) { - fullMessage.append(line); - builder.lineNumber((int) line); - keyBuilder.append(':').append(line); - } - if (column != Diagnostic.NOPOS) { - fullMessage.append(',').append(column); - builder.columnNumber((int) column); - } - fullMessage.append(']'); + if (column != Diagnostic.NOPOS) { + builder.columnNumber((int) column); } } - return builder.key(keyBuilder.toString()) - .message(fullMessage.toString()) - .build(); + builder.key(keyBuilder.toString()); + diagnosticReporter.report(builder.build()); } /** @@ -238,24 +257,23 @@ Optional firstError(Throwable cause) { * Reports summary after the compilation finished. */ void logSummary() { - var message = new StringBuilder(); + MessageBuilder message = messageBuilderFactory.builder(); final String patternForCount; if (!codeCount.isEmpty()) { @SuppressWarnings("unchecked") Map.Entry[] entries = codeCount.entrySet().toArray(Map.Entry[]::new); Arrays.sort(entries, (a, b) -> Integer.compare(b.getValue(), a.getValue())); patternForCount = patternForCount(Math.max(entries[0].getValue(), Math.max(numWarnings, numErrors))); - message.append("Summary of compiler messages:").append(System.lineSeparator()); + message.strong("Summary of compiler messages:").newline(); for (Map.Entry entry : entries) { int count = entry.getValue(); - message.append(String.format(patternForCount, count, entry.getKey())) - .append(System.lineSeparator()); + message.format(patternForCount, count, entry.getKey()).newline(); } } else { patternForCount = patternForCount(Math.max(numWarnings, numErrors)); } if ((numWarnings | numErrors) != 0) { - message.append("Total:"); + message.strong("Total:"); } if (numWarnings != 0) { writeCount(message, patternForCount, numWarnings, "warning"); @@ -263,7 +281,7 @@ void logSummary() { if (numErrors != 0) { writeCount(message, patternForCount, numErrors, "error"); } - logger.info(message); + logger.info(message.toString()); } /** @@ -278,9 +296,9 @@ private static String patternForCount(int n) { /** * Appends the count of warnings or errors, making them plural if needed. */ - private static void writeCount(StringBuilder message, String patternForCount, int count, String name) { - message.append(System.lineSeparator()); - message.append(String.format(patternForCount, count, name)); + private static void writeCount(MessageBuilder message, String patternForCount, int count, String name) { + message.newline(); + message.format(patternForCount, count, name); if (count > 1) { message.append('s'); } diff --git a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java index cd136cd81..bd8bb26b0 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java +++ b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java @@ -225,7 +225,7 @@ protected ToolExecutor(final AbstractCompilerMojo mojo, DiagnosticListener {}; + } + @Provides @Singleton @SuppressWarnings("unused") From fdaafdf06109b4e58a82ebff531a5dfb8573f7ae Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 7 Aug 2026 07:57:39 +0200 Subject: [PATCH 5/6] Revert "Use per-location dedup keys for compiler diagnostics" This reverts commit 92a9030787925a9b58dd11f37f49c6ed519f1f6a. --- .../plugin/compiler/DiagnosticLogger.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java index aee644ab9..625f82206 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java +++ b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java @@ -211,10 +211,10 @@ public void report(Diagnostic diagnostic) { /** * Reports a structured {@link BuilderProblem} to the {@link DiagnosticReporter}. *

- * Each diagnostic is reported with a per-location key - * ({@code "compiler:::"}) so each unique file+line - * gets its own entry in the build report. Warnings of the same type in - * different files are kept as separate entries. + * Each diagnostic is reported with a per-type key ({@code "compiler:"}) + * 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 diagnostic, String message, String code) { if (diagnosticReporter == null || code == null) { @@ -222,25 +222,21 @@ private void reportToBuildReport(Diagnostic diagnostic } BuilderProblem.Builder builder = BuilderProblem.builder() .severity(mapSeverity(diagnostic.getKind())) - .message(message); - // Build a per-location key so each unique file+line keeps its own entry + .message(message) + .key("compiler:" + code); + // Attach source location from the first occurrence (collector deduplicates by key) JavaFileObject sourceFile = diagnostic.getSource(); - StringBuilder keyBuilder = new StringBuilder("compiler:").append(code); if (sourceFile != null) { - String relPath = relativize(sourceFile.getName()); - builder.source(relPath); - keyBuilder.append(':').append(relPath); + builder.source(relativize(sourceFile.getName())); long line = diagnostic.getLineNumber(); if (line != Diagnostic.NOPOS) { builder.lineNumber((int) line); - keyBuilder.append(':').append(line); } long column = diagnostic.getColumnNumber(); if (column != Diagnostic.NOPOS) { builder.columnNumber((int) column); } } - builder.key(keyBuilder.toString()); diagnosticReporter.report(builder.build()); } From 4196eb58fc680f07f8dcb770da56a8fed7bbcd55 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 7 Aug 2026 07:57:39 +0200 Subject: [PATCH 6/6] Revert "Report structured BuilderProblems from compiler diagnostics" This reverts commit 252ca09f37cf5103d561772809b63233807b03fc. --- pom.xml | 2 +- .../plugin/compiler/AbstractCompilerMojo.java | 12 ---- .../plugin/compiler/DiagnosticLogger.java | 68 +------------------ .../maven/plugin/compiler/ToolExecutor.java | 2 +- .../plugin/compiler/CompilerMojoTestCase.java | 25 +++---- 5 files changed, 13 insertions(+), 96 deletions(-) diff --git a/pom.xml b/pom.xml index 8927b8137..75b3e25f6 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ under the License. 17 - 4.1.0-SNAPSHOT + 4.0.0-rc-4 9.10.1 7.0.0 diff --git a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java index 521b861bd..cac92f902 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java +++ b/src/main/java/org/apache/maven/plugin/compiler/AbstractCompilerMojo.java @@ -73,7 +73,6 @@ 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; @@ -943,17 +942,6 @@ final void amendincrementalCompilation(EnumSet 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. diff --git a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java index 625f82206..9bce7a546 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java +++ b/src/main/java/org/apache/maven/plugin/compiler/DiagnosticLogger.java @@ -30,14 +30,11 @@ 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 sends the messages to the Maven logger - * and reports structured {@link BuilderProblem}s to the {@link DiagnosticReporter}. + * A Java compiler diagnostic listener which send the messages to the Maven logger. * * @author Martin Desruisseaux */ @@ -52,12 +49,6 @@ final class DiagnosticLogger implements DiagnosticListener { */ 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. */ @@ -84,24 +75,16 @@ final class DiagnosticLogger implements DiagnosticListener { private String firstError; /** - * Creates a listener which will send the diagnostics to the given logger - * and to the given diagnostic reporter. + * Creates a listener which will send the diagnostics to the given logger. * * @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, - DiagnosticReporter diagnosticReporter, - Locale locale, - Path directory) { + DiagnosticLogger(Log logger, MessageBuilderFactory messageBuilderFactory, Locale locale, Path directory) { this.logger = logger; this.messageBuilderFactory = messageBuilderFactory; - this.diagnosticReporter = diagnosticReporter; this.locale = locale; this.directory = directory; codeCount = new LinkedHashMap<>(); @@ -124,17 +107,6 @@ 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. * @@ -204,40 +176,6 @@ public void report(Diagnostic 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}. - *

- * Each diagnostic is reported with a per-type key ({@code "compiler:"}) - * 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 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()); } /** diff --git a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java index bd8bb26b0..907d32390 100644 --- a/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java +++ b/src/main/java/org/apache/maven/plugin/compiler/ToolExecutor.java @@ -225,7 +225,7 @@ protected ToolExecutor(final AbstractCompilerMojo mojo, DiagnosticListener {}; - } - @Provides @Singleton @SuppressWarnings("unused")