Conversation
bae1db9 to
5a5af1e
Compare
602fffb to
056dcf0
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
056dcf0 to
a1ee585
Compare
e64db31 to
de8044a
Compare
a1ee585 to
0f31ce2
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8083c89 to
170bd72
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
170bd72 to
af0cc72
Compare
Add the --console CLI flag with four output modes: - plain: compact one-line-per-module output for CI - rich: JLine status bar with live reactor progress - verbose: full mojo-level output (current default) - machine: JSON lines for piping to external tools Part 3 of the #12572 split (depends on build report PR #12695). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8baa65a to
02ac855
Compare
af0cc72 to
3658983
Compare
gnodet
left a comment
There was a problem hiding this comment.
Well-designed build report feature with clean API/impl separation and comprehensive tests. A few issues noted below.
Also noted:
- The architecture is solid: clean API interfaces in maven-api-core, record-based implementations in maven-core, EventSpy pattern for automatic discovery, thread-based log routing for parallel-build safety, atomic file writes with symlink swap.
- The PR correctly depends on PR #12694 (logging foundation) — should not be merged until #12694 lands.
- No test for the
captureLogEventrouting logic (mojo-level vs module-level vs build-level buffers). This is the core routing mechanism and warrants at least one test exercising the dispatch.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of gnodet
| * Maximum number of log events captured per scope (mojo, module, or build). | ||
| * Beyond this, events are dropped and a truncation notice is appended. | ||
| */ | ||
| static final int MAX_LOG_EVENTS_PER_SCOPE = 500; |
There was a problem hiding this comment.
The Javadoc says "Beyond this, events are dropped and a truncation notice is appended" but captureLogEvent silently drops events without appending any truncation notice. Either append a synthetic LogEvent indicating truncation (e.g. with level WARN and message "... N events truncated"), or update the Javadoc to say events are silently dropped.
| sb.append('}'); | ||
| } | ||
|
|
||
| private static void writeModule(StringBuilder sb, ModuleReport module, int indent) { |
There was a problem hiding this comment.
The hasMore parameter is unused (annotated @SuppressWarnings("unused")) and all call sites pass true. If trailing comma control is no longer needed, the parameter should be removed to reduce confusion.
| private static void writeModule(StringBuilder sb, ModuleReport module, int indent) { | |
| private static void writeNullableField(StringBuilder sb, int indent, String key, String value) { |
gnodet
left a comment
There was a problem hiding this comment.
Well-designed build report feature with clean API/impl separation, solid thread safety, and comprehensive tests. A few issues worth addressing:
Confirmed findings (verified independently):
-
[Medium]
BuildReportCollector.java— The Javadoc onMAX_LOG_EVENTS_PER_SCOPEstates "events are dropped and a truncation notice is appended," butcaptureLogEventsilently drops events without ever appending a truncation notice. Either implement the truncation notice (e.g., append a synthetic LogEvent like "... N events truncated") or correct the Javadoc to say events are silently dropped. -
[Low]
BuildReportJsonWriter.java— ThewriteNullableFieldmethod has an unusedboolean hasMoreparameter annotated with@SuppressWarnings("unused"). The parameter is never read and the method always emits a trailing comma regardless. Remove it to avoid confusion. -
[Low]
BuildReportJsonWriter.java—writeProblemusessb.lastIndexOf(",\n")to remove trailing commas (searches entire buffer backwards), whilewriteLogEventuses the dedicatedremoveTrailingCommahelper (checks only last two characters). UseremoveTrailingCommaconsistently — it's safer sincelastIndexOfcould theoretically match an earlier,\nif future refactors change field order.
This review was generated by an AI agent (Claude Code) and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
| /** | ||
| * Maximum number of log events captured per scope (mojo, module, or build). | ||
| * Beyond this, events are dropped and a truncation notice is appended. | ||
| */ |
There was a problem hiding this comment.
[Medium] The Javadoc here says "events are dropped and a truncation notice is appended," but captureLogEvent (below) silently drops events when the limit is reached — no truncation notice is ever appended.
Either implement the truncation notice (e.g., append a synthetic LogEvent like "... N events truncated") or correct this Javadoc to say events are silently dropped.
| } | ||
|
|
||
| private static void writeNullableField( | ||
| StringBuilder sb, int indent, String key, String value, @SuppressWarnings("unused") boolean hasMore) { |
There was a problem hiding this comment.
[Low] The hasMore parameter is annotated @SuppressWarnings("unused") and is indeed never read — the method always emits a trailing comma regardless of its value. Consider removing it:
| StringBuilder sb, int indent, String key, String value, @SuppressWarnings("unused") boolean hasMore) { | |
| private static void writeNullableField(StringBuilder sb, int indent, String key, String value) { |
gnodet
left a comment
There was a problem hiding this comment.
Well-structured addition of a JSON build report feature with clean API design, good test coverage (17 tests), and defensive error handling. The zero-dependency JSON writer is appropriate for Maven's philosophy. A few design items:
Medium severity:
-
No opt-out mechanism (
BuildReportCollector.java): The collector is unconditionally active for every build — everymvninvocation writes a JSON file to disk with no system property to disable it. Consider adding-Dmaven.build.report.skip=truefor environments where this is undesirable (read-only filesystems, embedded invocations, CI runners). -
MojoSkipped events not handled (
BuildReportCollector.java):ExecutionEvent.Type.MojoSkipped(fired e.g. when a mojo requires online mode but Maven is offline) silently vanishes from the report. Inconsistent withProjectSkippedwhich IS handled. Skipped mojos should be tracked withBuildStatus.SKIPPED.
Low severity:
-
Failure timestamp inaccuracy (
BuildReportCollector.javaline 1110):failureTimestampis set toMonotonicClock.now()at report-assembly time, not at actual failure time. The mojo's timing data does capture the real timing — worth documenting in theFailureReport.timestamp()Javadoc. -
Inconsistent trailing comma removal (
BuildReportJsonWriter.java):writeProblemusessb.lastIndexOf(",\n")which searches backwards through the entire buffer, whilewriteLogEventuses the more robustremoveTrailingComma(sb)which checks only the end. Consider usingremoveTrailingCommaconsistently. -
Unused
hasMoreparameter (BuildReportJsonWriter.javaline 1553): Annotated@SuppressWarnings("unused")and never referenced. Either use it to control comma behavior or remove it. -
~70 lines of duplicated test helpers:
BuildReportCollectorTestandBuildReportIntegrationTestshare identicalcreateProject,createSession,createMojoExecution, andcreateEventmethods. Consider extracting to a shared test utility.
The API design (immutable interfaces in maven-api-core, record implementations in maven-core, @Experimental markers) follows Maven's established patterns. Thread safety approach is sound. The atomic file write + symlink pattern is well-implemented with proper fallbacks.
This review was generated by an AI agent (Claude Code) and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
3658983 to
c51ee87
Compare
02ac855 to
812a842
Compare
| * @since 4.1.0 | ||
| */ | ||
| default long sequenceNumber() { | ||
| return -1; |
There was a problem hiding this comment.
[Medium, RERAISED] Javadoc contract violation — says "always non-negative" but default returns -1.
Line 161: @return the sequence number, always non-negative
Line 165: return -1; // violates the stated contract
This mismatch was raised in the prior review and remains unaddressed in the squash. The default value of -1 is used by DefaultLogEvent's convenience constructor (tests, programmatic construction) — so -1 is a legitimate sentinel meaning "not assigned".
Fix the Javadoc: change to @return the sequence number, or {@code -1} if not available.
| } | ||
|
|
||
| @Override | ||
| public void mojoSkipped(ExecutionEvent event) { |
There was a problem hiding this comment.
[Medium, RERAISED] mojoSkipped() still does not call setMojoId(null) — mojo ID leaked on skip.
The base branch called ProjectBuildLogAppender.setMojoId(null) in mojoSkipped() (mirroring mojoSucceeded and mojoFailed). This PR removed that call.
With this gap: if a mojo is skipped, the MOJO_ID thread-local is never cleared. Any subsequent log event on that thread (e.g. from a later mojo startup or a project-level log) will be misrouted into the skipped mojo's scope in the build report.
mojoSucceeded and mojoFailed both correctly call setMojoId(null) (lines 132, 139). mojoSkipped must do the same:
@Override
public void mojoSkipped(ExecutionEvent event) {
setMdc(event);
ProjectBuildLogAppender.setMojoId(null); // missing
delegate.mojoSkipped(event);
}
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squash commit (7d1e5cb6) — six issues carried forward, none resolved.
Resolved in this squash:
DefaultLog.withMetadata()StackWalker guard correctly restored viahasReportCapture()✅projectId()/mojoId()added toLogEventAPI and populated inProjectBuildLogAppender✅FORKING_MOJO_IDthread-local added, fork restore logic mirrors the project ID pattern ✅
Still unresolved (6 findings):
- [High]
Log.java—isTraceEnabled()and all 5trace()overloads losedefault, breaking any plugin that implementsLog(binary-incompatible;AbstractMethodErrorat runtime). - [Medium]
BuildReportCollector.java—MAX_LOG_EVENTS_PER_SCOPEJavadoc says "a truncation notice is appended" but none is ever appended. - [Medium]
LoggingExecutionListener.java—mojoSkipped()does not callsetMojoId(null); mojo ID leaks to subsequent events on the same thread. - [Medium]
Session.java— Javadoc saysbuildEnvironment()returns "the same object recorded in the structured build report" butDefaultSessionconstructs a freshDefaultBuildEnvironmenton every call. - [Medium]
LogEvent.java—sequenceNumber()Javadoc says "always non-negative" but default returns-1. - [Medium]
BuildEnvironment.java— "What is not yet captured" section still listsbatchModeandnoTransferProgresseven though both are now exposed. - [Low]
BuildReportJsonWriter.java— deadhasMoreparameter with@SuppressWarnings("unused").
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| default boolean isTraceEnabled() { | ||
| return false; | ||
| } | ||
| boolean isTraceEnabled(); |
There was a problem hiding this comment.
[High, RERAISED] isTraceEnabled() and all six trace methods changed from default to abstract — binary-incompatible breaking change.
In feature/logging-foundation (the base branch), all six methods had default implementations:
default boolean isTraceEnabled() { return false; }
default void trace(CharSequence content) {}
default void trace(CharSequence content, Throwable error) {}
default void trace(Throwable error) {}
default void trace(Supplier<String> content) {}
default void trace(Supplier<String> content, Throwable error) {}This PR makes them all abstract. Any existing class that implements Log and relied on those defaults — third-party plugin frameworks, mocking adapters, test doubles — will fail with AbstractMethodError at runtime without a recompile.
Note: the Javadoc on trace(Supplier<String>) (line 75–81) still says "The default implementation is a no-op for backward compatibility" — but there is no longer a default.
The base branch intentionally provided default {} bodies to allow incremental adoption. Either keep them as defaults, or add a clear migration note explaining why the break is intentional and what implementors must do.
| @Override | ||
| public void mojoSkipped(ExecutionEvent event) { | ||
| setMdc(event); | ||
| delegate.mojoSkipped(event); |
There was a problem hiding this comment.
[Medium] Two issues in mojoSkipped and fork mojo ID lifecycle.
1. mojoSkipped still does not clear the mojo ID (RERAISED).
mojoSucceeded (line 132) and mojoFailed (line 139) both call ProjectBuildLogAppender.setMojoId(null). mojoSkipped does not. When a mojo is skipped (e.g. maven-surefire-plugin with -DskipTests), the MDC and MOJO_ID ThreadLocal are left pointing at the skipped mojo. Any log emitted by the next lifecycle phase on the same thread will be misrouted in the build report.
| delegate.mojoSkipped(event); | |
| public void mojoSkipped(ExecutionEvent event) { | |
| setMdc(event); | |
| ProjectBuildLogAppender.setMojoId(null); | |
| delegate.mojoSkipped(event); | |
| } |
2. FORKING_MOJO_ID restore path is dead code (new).
setForkingMojoId() was added to ProjectBuildLogAppender and the restore logic was wired into setMojoId(null) — but setForkingMojoId() is never called in this squashed commit. The calls from forkStarted/forkSucceeded/forkFailed were removed. FORKING_MOJO_ID will always be null, making the restore branch in setMojoId() permanently dead.
Either restore the calls in LoggingExecutionListener.forkStarted() / forkSucceeded() / forkFailed(), or remove the dead FORKING_MOJO_ID ThreadLocal and setForkingMojoId() method entirely.
7d1e5cb to
bd0bdf3
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squash commit bd0bdf3d.
Resolved since prior reviews:
Log.javatrace methods restored todefault— binary-compat issue fixed ✅LoggingExecutionListener.mojoSkipped()now callssetMojoId(null)— mojo ID leak fixed ✅LogEvent.sequenceNumber()Javadoc now correctly saysor {@code -1} if unavailable✅
Still unresolved (4 findings): see inline comments.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
bd0bdf3 to
ee2e6c4
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squashed commit bd0bdf3d. New content: BuildEnvironment API with batchMode/noTransferProgress, BuildReport/ModuleReport/MojoReport hierarchy, BuildReportCollector (EventSpy), BuildReportJsonWriter, comprehensive tests.
Five issues. Four are reraises that remain unaddressed after multiple rounds; one is new.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of commit ee2e6c44 — import cleanup in ApiRunner.java only. Four findings from prior review still unresolved.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squash commit ee2e6c44 — import cleanup in ApiRunner.java only.
The four findings from the prior review remain unaddressed.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
ee2e6c4 to
df6ed07
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squash commit df6ed07b.
All 9 prior findings resolved — full tally below. One new finding in this commit.
Resolved since prior reviews:
Log.javatrace methods —defaultkeyword restored ✅LoggingExecutionListener.mojoSkipped()—setMojoId(null)added ✅LogEvent.sequenceNumber()Javadoc — fixed to "or -1 if unavailable" ✅BuildEnvironment.javastale Javadoc —batchMode/noTransferProgressremoved from "not yet captured" ✅BuildReportCollectortruncation Javadoc — fixed to "silently dropped" ✅BuildReportJsonWriter.writeNullableFielddeadhasMoreparameter — removed ✅writeProblemnow usesremoveTrailingCommaconsistently ✅Session.java"same object" Javadoc claim — removed ✅FORKING_MOJO_IDThreadLocal — now properly set inforkStartedand cleared inforkSucceeded/forkFailed✅
One new finding: see inline comment.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| private final MavenRepositorySystem mavenRepositorySystem; | ||
| private final RuntimeInformation runtimeInformation; | ||
| private final Map<String, Project> allProjects = new ConcurrentHashMap<>(); | ||
| private BuildEnvironment buildEnvironment; |
There was a problem hiding this comment.
[Medium] Non-volatile lazy-init field on a @ThreadSafe class — data race.
Session is annotated @ThreadSafe. DefaultSession.buildEnvironment is a plain private field with a non-atomic lazy-init pattern:
if (buildEnvironment == null) {
buildEnvironment = BuildReportCollector.buildEnvironment(getMavenSession());
}
return buildEnvironment;Under the Java Memory Model, a write to a non-volatile field by thread A is not guaranteed to be visible to thread B. Two threads calling session.buildEnvironment() concurrently before the field is initialized can each see null, each compute a distinct DefaultBuildEnvironment, and each write their own — leaving the field in a state where different threads see different instances. This is exactly why BuildReportCollector correctly declares its counterpart field private volatile BuildEnvironment buildEnvironment.
Note: for this to be data-race-free, either use volatile (JMM guarantees visibility) or synchronize. Since DefaultBuildEnvironment is an immutable record, multiple initializations produce equivalent objects — but the racy read/write still constitutes a data race under JMM, which is undefined behavior regardless of whether the result is logically correct.
| private BuildEnvironment buildEnvironment; | |
| private volatile BuildEnvironment buildEnvironment; |
- Add BuildReport, ModuleReport, MojoReport, FailureReport, BuildStatus to maven-api-core - Add BuildEnvironment to Session API and BuildReport - Add BuildReportCollector (maven-core): per-mojo log capture via ProjectBuildLogAppender - Add BuildReportJsonWriter: serializes structured report to JSON file - Add noTransferProgress to MavenExecutionRequest and BuildEnvironment - Remove DefaultLogTest (replaced by BuildReportCollectorTest)
df6ed07 to
7eaa538
Compare
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squash commit df6ed07b.
Resolved since prior review (ee2e6c44):
- Stale Javadoc (
batchMode/noTransferProgresslisted as "not yet captured") — removed ✅ Session.buildEnvironment()Javadoc "same object" false claim — removed ✅- Truncation notice Javadoc (
"appended"→"silently dropped") ✅ - Dead
hasMoreparameter with@SuppressWarnings("unused")— removed ✅ writeProblemlastIndexOfinconsistency → now usesremoveTrailingComma✅DefaultSession.buildEnvironment()re-allocates on every call — lazy-init caching added ✅
One new finding in this commit:
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| private final MavenRepositorySystem mavenRepositorySystem; | ||
| private final RuntimeInformation runtimeInformation; | ||
| private final Map<String, Project> allProjects = new ConcurrentHashMap<>(); | ||
| private BuildEnvironment buildEnvironment; |
There was a problem hiding this comment.
[Medium] Missing volatile on lazily-initialized field — data race in a @ThreadSafe class.
DefaultSession implements Session, which carries @ThreadSafe. The new buildEnvironment field is lazily initialized with a plain check-then-act:
if (buildEnvironment == null) {
buildEnvironment = BuildReportCollector.buildEnvironment(getMavenSession());
}
return buildEnvironment;Without volatile, the JMM offers no happens-before guarantee between the write in thread A and the read in thread B. Thread B may observe a stale null and re-compute, or (worse) observe a partially-constructed DefaultBuildEnvironment record. Note that BuildReportCollector itself uses private volatile BuildEnvironment buildEnvironment on its own caching field — the same pattern, done correctly.
Fix: add volatile.
| private BuildEnvironment buildEnvironment; | |
| private volatile BuildEnvironment buildEnvironment; |
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of commit 7eaa538877 — two changes: volatile on buildEnvironment in DefaultSession, two new LogSink tests in MavenSimpleLoggerTest.
All prior findings resolved:
-
✅
DefaultSession.buildEnvironmentfield is nowvolatile— visibility guaranteed. The lazy-init check-then-act (if (buildEnvironment == null) { buildEnvironment = ... }) is a benign race:buildEnvironment()is a pure factory reading from a frozenMavenExecutionRequest, so two threads computing simultaneously produce equivalent values. Functionally correct. -
✅
MAX_LOG_EVENTS_PER_SCOPEJavadoc now says "silently dropped to prevent unbounded memory growth" — no longer claims a truncation notice is appended. -
✅ Dead
hasMoreparameter removed fromwriteNullableField— all call sites updated to 4-arg form.
New tests (MavenSimpleLoggerTest): writeRoutesToSinkWhenInstalled and writeRoutesThrowableToSink correctly verify that LogSink receives the level, logger name, clean message, formatted string (with throwable rendering), and the original Throwable. Both tests clean up the sink in finally blocks. No issues.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squash commit 7eaa5388.
Resolved since prior reviews:
MAX_LOG_EVENTS_PER_SCOPEJavadoc now says "silently dropped" ✅- Dead
hasMoreparameter inwriteNullableFieldremoved ✅ batchMode/noTransferProgressremoved from "What is not yet captured" Javadoc ✅Session.buildEnvironment()Javadoc no longer claims "same object as in report" ✅writeProblemuses indexed loop with explicit comma —lastIndexOfgone ✅buildEnvironmentfield is nowvolatilein bothBuildReportCollectorandDefaultSession✅
Two findings remain.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| @Override | ||
| public BuildEnvironment buildEnvironment() { | ||
| if (buildEnvironment == null) { | ||
| buildEnvironment = BuildReportCollector.buildEnvironment(getMavenSession()); |
There was a problem hiding this comment.
[Medium] Single-check lazy-init on volatile field — still racy under concurrent access.
volatile prevents the visibility problem, but two threads can still both observe buildEnvironment == null and both enter the body, constructing two separate DefaultBuildEnvironment instances (each reading system properties independently). DefaultSession is @ThreadSafe, so this is a contract violation.
The straightforward fix is double-checked locking:
| buildEnvironment = BuildReportCollector.buildEnvironment(getMavenSession()); | |
| if (buildEnvironment == null) { | |
| synchronized (this) { | |
| if (buildEnvironment == null) { | |
| buildEnvironment = BuildReportCollector.buildEnvironment(getMavenSession()); | |
| } | |
| } |
Alternatively — since BuildReportCollector.onSessionStarted() already initialises its own volatile BuildEnvironment field at session start, the fallback here only fires before the first event fires. If that timing is intentional (e.g. for ApiRunner / standalone mode), the DCL approach above is the right fix. If it isn't intentional, the fallback can simply call BuildReportCollector.buildEnvironment() once and cache via DCL.
| minimal.trace(() -> "no-op"); | ||
| minimal.trace(() -> "no-op", new RuntimeException()); | ||
| } | ||
| } |
There was a problem hiding this comment.
[Medium] DefaultLogTest.java deleted — coverage regression with no replacement.
This file is on the base branch (feature/logging-foundation) and contains 6 tests:
warnWithSupplierAndThrowableDelegatesToWarn— regression guard for thewarn(Supplier, Throwable)→logger.info()buglogApiMetadataIsClearedAfterCall— verifies no metadata leaks across log callstraceMethodsDelegateToSlf4jTrace— verifies thetrace()delegation pathtraceIsNoOpWhenDisabled— verifies trace is suppressed when trace level is offchildCreatesSubLogger— verifiesLog.child()prefix behaviourdefaultTraceIsDisabled— verifiesLogimplementors don't getAbstractMethodErrorfrom trace defaults (the exact binary-compat regression guard raised in earlier reviews)
None of these are replaced by BuildReportCollectorTest, BuildReportJsonWriterTest, or BuildReportIntegrationTest. If the tests are now redundant because the base branch covers them, they should still remain on the base branch — but if this PR removes them from the merge chain, the guard is gone. Restore DefaultLogTest.java or move the tests to BuildReportCollectorTest.
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review of squashed commit 7eaa538877 — all prior findings resolved.
Resolved since last review (ee2e6c44):
MAX_LOG_EVENTS_PER_SCOPEJavadoc now says "silently dropped" — no longer claims a truncation notice is appended ✅- Dead
hasMoreparameter removed fromBuildReportJsonWriter;removeTrailingComma()used consistently throughout includingwriteProblem()✅ mojoSkipped()now callsProjectBuildLogAppender.setMojoId(null)— mojo ID no longer leaked on skip ✅DefaultSession.buildEnvironmentfield is nowvolatilewith lazy initialization — no longer re-allocates on every call ✅BuildEnvironmentclass-level Javadoc updated —batchMode/noTransferProgressno longer listed under "not yet captured" ✅LogEvent.sequenceNumber()Javadoc now says "or -1 if unavailable" — no longer contradicts the default return value ✅Session.buildEnvironment()Javadoc no longer claims the returned object "is the same object recorded in the build report" ✅ApiRunner.javaanonymousBuildEnvironmentimplementation is complete includingnoTransferProgress()andbatchMode()✅
No new findings. The design is solid: EventSpy pattern, thread-based log routing with ConcurrentHashMap, atomic file writes, and comprehensive test coverage (17+ tests). Approved.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
Summary
Part 2 of the logging feature chain (depends on #12694 — logging foundation).
Adds a structured build report that captures per-module and per-mojo execution results, timing, log events, and failures as a JSON file at the end of every build. Also adds
BuildEnvironmentto the Maven API, exposed viaSession.buildEnvironment()and frozen inBuildReport.environment().What's in this PR
BuildReport,BuildStatus,ModuleReport,MojoReport,FailureReportBuildEnvironmento.a.m.apicapturing the invocation contextSession.buildEnvironment()DefaultBuildReport,DefaultModuleReport,DefaultMojoReport,DefaultFailureReport,DefaultBuildEnvironmentBuildReportCollectorEventSpythat tracks lifecycle events and captures log output viaLogEventSink, routing to mojo/module/build-level buffersBuildReportJsonWriterKey design decisions
BuildReportCollectoris a@Named @Singletonthat extendsAbstractEventSpy, discovered automatically — no wiring changes neededConcurrentHashMap<Long, String>(thread ID → mojo/project key) to associate log events with the correct scope in parallel buildsLogEventSink(4-arg) independently from the existingLogSink(5-arg) used byProjectBuildLogAppender— no interference with console outputbuild-report-latest.jsonsymlinkonSessionEndedwraps report generation in try-catch so report failures never crash the buildBuildEnvironment
BuildEnvironment(ino.a.m.api) captures the full invocation context atSessionStarted:password,token,secret,passphrase,apikey), curated system info (OS, JVM, Maven home, available processors)-Ponly), selected projects (-pl), resume-from (-rf)Session.buildEnvironment()gives plugins live access to the same data.BuildReport.environment()carries a frozen snapshot in the JSON output.Also fixes a gap in
MavenExecutionRequest:noTransferProgresswas consumed byMavenInvokerto pick aTransferListenerbut never stored on the request. AddedisNoTransferProgress()/setNoTransferProgress().What's NOT in this PR (deferred to later PRs)
--warning-modeCLI flag — Warning mode, diagnostic collector, BuilderProblem enrichments #12698--console=plain/rich/machine) — Console modes: --console=plain/rich/verbose/machine #13180mvnlogviewer tool — mvnlog: build log viewer, integration tests, script routing #12699args[]),--also-make/--also-make-dependentsinBuildEnvironment— pending further API workPR chain
mvnlogviewerTest plan
BuildEnvironment