slachiewicz opened a new issue, #12704:
URL: https://github.com/apache/maven/issues/12704
### Affected version
4.0.0-rc-4 through 4.0.0-rc-6, `maven-4.0.x`, and `master`. Verified by
running a locally built `4.1.0-SNAPSHOT` from `master`; the rc range is
established by tag containment and code inspection, not by running the released
rc binaries.
### Bug description
Under the concurrent builder, when one module fails and the reactor halts,
an **unrelated** module that was already in flight is reported as skipped —
even when it completed all of its requested work successfully — and the log
states a reason that is not true:
```
X was not built because a module it depends on failed to build.
```
Both halves of that sentence can be false: `X` may have no dependency on the
failed module, and `X` may in fact have been built.
#### Reproduction
Two sibling modules with no dependency between them. `slow` has 4000
generated sources so its `compile` takes ~1.5s; `fails` has one source with a
syntax error so its `compile` fails almost immediately.
```
root/pom.xml <modules>slow, fails</modules>, packaging pom
root/slow/pom.xml plain jar module, no dependencies
root/fails/pom.xml plain jar module, no dependencies
```
```bash
mkdir -p slow/src/main/java/gen fails/src/main/java
for i in $(seq 0 3999); do
printf 'package gen;\npublic class C%s { public int v() { return %s; }
}\n' $i $i \
> slow/src/main/java/gen/C$i.java
done
printf 'public class Bad implements int {}\n' > fails/src/main/java/Bad.java
mvn compile -b concurrent -T5
```
Result (5 runs out of 5 — it is a race, but one that is won consistently):
```
[INFO] --- compiler:3.13.0:compile (default-compile) @ slow ---
[INFO] --- compiler:3.13.0:compile (default-compile) @ fails ---
[INFO] Compiling 1 source file with javac ... to target/classes
[INFO] Compiling 4001 source files with javac ... to target/classes
[ERROR] .../fails/src/main/java/Bad.java:[1,29] unexpected type
[INFO]
------------------------------------------------------------------------
[INFO] Skipping slow
[INFO] slow was not built because a module it depends on failed to build.
[INFO]
------------------------------------------------------------------------
```
`slow` compiled all 4001 sources and `slow/target/classes/gen` contains 4000
fresh `.class` files (the `target` directories were removed before every run).
It nonetheless gets `ProjectSkipped` and no `BuildSummary`.
The same project under the legacy builder (`mvn compile -T5`) prints no
`Skipping` line: the in-flight project runs to completion and is reported as
succeeded.
#### Mechanism
All references are to
`impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java`
on `master`.
1. `fails` throws a `LifecycleExecutionException`, so `handleBuildError`
takes the `REACTOR_FAIL_FAST` branch and calls `ReactorBuildStatus.halt()`
(line 881).
2. `processStep` then evaluates every remaining `CREATED` regular step of
**every** project, including unrelated ones that are mid-build:
```java
shouldExecute = !status.isHalted() && !status.isBlackListed(step.project)
&& allPredecessorsExecuted; // :423
```
`isHalted()` is now true, so each step transitions `CREATED -> SKIPPED`
(line 452). With `-X`:
```
[DEBUG] Skipping step BuildStep[project=...:slow, phase=ready] because
the build is halted
[DEBUG] Skipping step BuildStep[project=...:slow, phase=test-compile]
because the build is halted
[DEBUG] Skipping step BuildStep[project=...:slow, phase=package] because
the build is halted
[DEBUG] Skipping step BuildStep[project=...:slow, phase=install] because
the build is halted
[DEBUG] Skipping step BuildStep[project=...:slow, phase=deploy] because
the build is halted
```
3. `TEARDOWN` always runs (line 416) and computes:
```java
allStepsExecuted &= step == projectStep || projectStep.status.get() ==
EXECUTED; // :535
```
`calculateLifecycleMappings` creates before/at/after steps for **every**
lifecycle phase regardless of the requested goal. Steps outside the requested
scope are passed through `BuildStep.skip()`, which only sets a flag and clears
the mojo list (`BuildStep.java:106-109`) — the step stays in the plan with
status `CREATED` and normally executes as an empty no-op. So for `mvn compile`,
`slow`'s plan still holds `ready`, `test-compile`, `test`, `package`, `install`
and `deploy` steps. After a halt these become `SKIPPED` rather than `EXECUTED`,
`allStepsExecuted` is false, and with `failures == null` the code falls through
to `ProjectSkipped` (line 558) instead of `ProjectSucceeded` (line 556).
In other words `allStepsExecuted` currently means *"the project traversed
the entire lifecycle plan"*, not *"the requested work completed"*.
4. `ExecutionEventLogger.projectSkipped`
(`impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java:319`)
then prints the single hardcoded reason, because the event carries no reason
of its own.
#### Why the legacy builder does not show this
`LifecycleModuleBuilder` fires `ProjectSkipped` *before* `ProjectStarted`
and only for projects that never started (`LifecycleModuleBuilder.java:91-94`).
The concurrent builder introduced a state that did not previously exist:
started, ran its requested goals fine, then got cut off. (The message was never
fully accurate even in the legacy builder — under fail-fast a halted project
also gets the "a module it depends on failed" text — but it was at least
limited to projects that genuinely produced nothing.)
#### Provenance
`ProjectSkipped` was added to `BuildPlanExecutor` by 3706aa1e5c, "[MNG-8670]
Fix concurrent builder missing/wrong project events" (#2251, for #9636), first
released in 4.0.0-rc-4. Before that commit `TEARDOWN` fired `ProjectSucceeded`
unconditionally, so this is a regression of the reporting for
halted-but-complete projects rather than a pre-existing gap.
### Impact on the IT suite
This makes `MavenITmng8648ProjectEventsTest` flaky. The test builds a
five-module reactor with `compile -b concurrent -T5` where `subproject-c` fails
on purpose, and asserts:
```java
verifier.verifyTextInLog("org.apache.maven.its.mng8648:subproject-b:jar:1-SNAPSHOT
ProjectSucceeded"); // :52
```
`subproject-b` does not depend on `subproject-c` (only `subproject-d` does),
so whether `b` gets `ProjectSucceeded` or `ProjectSkipped` depends on whether
`c` fails before `b`'s empty compile finishes. It went the wrong way on
`integration-tests (macos-latest, 21)` in
https://github.com/apache/maven/actions/runs/31221127749/job/93006825497
(attempt 1 of that run; attempt 2 was green, which is why the run now shows as
successful):
```
subproject-b:jar:1-SNAPSHOT ProjectStarted
[INFO] --- compiler:3.13.0:compile (default-compile) @ subproject-b ---
subproject-c:jar:1-SNAPSHOT ProjectFailed
[INFO] No sources to compile
subproject-b:jar:1-SNAPSHOT ProjectSkipped
[INFO] Skipping subproject-b
[INFO] subproject-b was not built because a module it depends on failed to
build.
```
Only 1 of 12 integration-test matrix jobs failed in that run.
### Suggested direction
1. **Event.** Base the outcome on whether the project's *in-scope* work
completed, i.e. compute `allStepsExecuted` over steps that were not
`skip()`-flagged (or equivalently require every mojo-bearing step to have
reached `EXECUTED`). A project halted with in-scope mojos still pending
genuinely did not complete and should stay `ProjectSkipped`; one that ran
everything asked of it should be `ProjectSucceeded`. This keeps the MNG-8670
use case intact — `subproject-d`, which really was never built, still gets
`ProjectStarted` + `ProjectSkipped` — and it matches what the MNG-8670 IT
itself asserts for `subproject-b`.
2. **Message.** `ProjectSkipped` now has at least two causes (blacklisted
because a dependency failed vs. reactor halted). The logger cannot tell them
apart today and always claims the first. The reason needs to reach
`ExecutionEventLogger`, so a halted project reads "the build was halted after
an earlier failure" instead.
3. **IT.** Fixing the event alone does not fully deflake
`MavenITmng8648ProjectEventsTest`: if `c` fails before `b`'s steps are even
scheduled, `b` is legitimately skipped and the assertion still fails. The test
needs an ordering guarantee — e.g. make `subproject-c` fail slowly so `b`
reliably finishes first — rather than a loosened assertion.
I am happy to put up a PR for 1 and 2 if the direction looks right.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]