gnodet opened a new pull request, #13249:
URL: https://github.com/apache/maven/pull/13249

   ## Overview
   
   This PR introduces four inter-related features for Maven 4.1.0, developed 
together because they share the same extension points and data model:
   
   1. **`reactor.xml`** — a single `.mvn/reactor.xml` file that consolidates 
CLI defaults (replaces `.mvn/maven.config`), core extensions (replaces 
`.mvn/extensions.xml`), named build aliases, and lifecycle phase injections.
   2. **Named build aliases** — a short name on the command line that expands 
to a full argument list, defined in `reactor.xml`.
   3. **Lifecycle execution filter** — a `maven.lifecycle.filter` user property 
that skips mojo executions matching a coordinate or phase predicate.
   4. **Structured build report** — a JSON file written to 
`target/build-reports/` at the end of each build, capturing timing, status, 
per-mojo log output, and environment info; accessible via 
`Session.buildEnvironment()` and a new `BuildReport` API.
   
   ---
   
   ## Feature 1: `reactor.xml`
   
   **Location:** `.mvn/reactor.xml` (project root directory)
   
   **Schema:** `api/maven-api-core/src/main/mdo/reactor.mdo` — generates StAX 
reader/writer via Modello.
   
   **Format:**
   
   ```xml
   <reactor xmlns="http://maven.apache.org/REACTOR/1.0.0";>
     <!-- Replaces .mvn/maven.config -->
     <options>-T4 --no-transfer-progress</options>
   
     <!-- Replaces .mvn/extensions.xml -->
     <extensions>
       <extension groupId="io.takari.maven" artifactId="takari-smart-builder" 
version="0.6.1"/>
     </extensions>
   
     <!-- Named build aliases (see Feature 2) -->
     <aliases>
       <alias name="ci">
         <content>verify -Dmaven.test.skip=true</content>
       </alias>
       <alias name="release">
         <args><arg>-Prelease</arg><arg>deploy</arg></args>
       </alias>
     </aliases>
   
     <!-- Custom lifecycle phase injections -->
     <phases>
       <phase name="pre-integration" after="test" before="integration-test"/>
     </phases>
   </reactor>
   ```
   
   **Key design decisions:**
   - `reactor.xml` replaces both `maven.config` and `extensions.xml` when 
present. Legacy files are still read when `reactor.xml` is absent, preserving 
full backwards compatibility.
   - `<alias name="...">` uses an XML attribute (not a child element) because 
aliases are short and the attribute form is more readable for what is expected 
to be a common file.
   - `<options>` is a whitespace/quote-aware inline string (same tokenization 
as shell); `<optionArgs>` provides a structured alternative when values contain 
spaces.
   - Parsing happens in `BaseParser` before any DI container is started, so 
reactor.xml config (including extensions) is available at the earliest possible 
point.
   
   ---
   
   ## Feature 2: Named Build Aliases
   
   **Parsing:** `MavenParser.expandAlias()` — pure string manipulation before 
any Maven DI or session setup.
   
   **Semantics:**
   - Every argument in the args list is checked against the alias map in order; 
any match is replaced by the alias expansion tokens.
   - Expansion is **not recursive** — tokens produced by an alias are passed 
through as-is.
   - Aliases can be flag-style (e.g. `--skip-tests`) to provide shorthand for 
long `-D` expressions.
   - An empty alias expansion (no `<content>` and no `<args>`) is valid — the 
alias token is simply removed.
   
   **Validation:**
   - Blank alias names are rejected at expansion time with 
`IllegalArgumentException`.
   - Unclosed quotes in `<content>` strings throw `IllegalArgumentException` 
(via `ArgumentTokenizer`).
   
   **Example:**
   ```
   # reactor.xml defines alias "ci" → "verify -Dmaven.test.skip=true"
   mvn ci -B
   # is equivalent to:
   mvn verify -Dmaven.test.skip=true -B
   ```
   
   **Tests:** `MavenParserAliasTest`, `ArgumentTokenizerTest`, IT 
`mng-12537-reactor-xml-alias`.
   
   ---
   
   ## Feature 3: Lifecycle Execution Filter
   
   **Activation:** `-Dmaven.lifecycle.filter=<expression>` (property name: 
`Constants.MAVEN_LIFECYCLE_FILTER`)
   
   **Expression syntax** (comma-separated, OR-ed predicates):
   
   | Token | Meaning |
   |---|---|
   | `*` | Skip every mojo execution |
   | `:A` | Skip by artifactId (e.g. `:maven-enforcer-plugin`) |
   | `G:A` | Skip by groupId:artifactId |
   | `P` | Skip by goal prefix (e.g. `enforcer`) |
   | `P:v:g` | Skip by prefix + version + goal |
   | `P:v:g@e` | Skip by prefix + version + goal + executionId |
   | `phase(name)` | Skip all mojos bound to the named phase |
   
   **Implementation:** `MojoExecutionFilter` (parser) + `CoordinatePredicate` / 
`PhasePredicate` (matchers). Applied in `MojoExecutor` before each mojo 
execution; skipped mojos fire a `MojoSkipped` execution event.
   
   A `DefaultMojoExecution` with a `null` session is created for predicate 
evaluation — only string fields (goal, groupId, artifactId, prefix, 
executionId, phase) are accessed; no session-bound fields are touched.
   
   **Example:**
   ```
   # Skip all test-phase mojos and the enforcer plugin
   mvn verify -Dmaven.lifecycle.filter=phase(test),:maven-enforcer-plugin
   ```
   
   **Tests:** `CoordinatePredicateTest`, `MojoExecutionFilterTest`, 
`PhasePredicateTest`, IT `mng-12538-lifecycle-filter`.
   
   ---
   
   ## Feature 4: Structured Build Report
   
   **Output location:** `<topDirectory>/target/build-reports/`
   - `build-report-<timestamp>Z.json` — timestamped per-build file
   - `build-report-latest.json` — symlink (or copy on Windows) always pointing 
to the most recent report
   
   **Content:**
   
   ```jsonc
   {
     "formatVersion": 1,
     "status": "SUCCESS",          // SUCCESS | FAILURE | ERROR | SKIPPED
     "startTime": "2025-07-29T14:30:00Z",
     "duration": "PT1M23.456S",
     "mavenVersion": "4.1.0-SNAPSHOT",
     "javaVersion": "25.0.1",
     "goals": ["verify"],
     "multiModule": true,
     "threads": 4,
     "environment": {
       "os": "Linux 6.1.0", "arch": "amd64",
       "userProperties": { "-Dmaven.test.skip": "true" },
       "systemProperties": { "java.version": "25.0.1", ... }
     },
     "modules": [
       {
         "groupId": "org.apache.maven",
         "artifactId": "maven-core",
         "status": "SUCCESS",
         "duration": "PT12.3S",
         "mojos": [
           {
             "groupId": "org.apache.maven.plugins",
             "artifactId": "maven-compiler-plugin",
             "goal": "compile",
             "executionId": "default-compile",
             "phase": "compile",
             "status": "SUCCESS",
             "duration": "PT2.1S",
             "output": [
               { "level": "INFO", "timestamp": "...", "message": "Compiling 42 
source files" }
             ]
           }
         ]
       }
     ],
     "failures": [...],
     "output": [...]   // build-level log lines not attributed to a module
   }
   ```
   
   **Implementation:**
   - `BuildReportCollector` — `@Singleton` `ExecutionListener` that captures 
timings and routes log events via `ProjectBuildLogAppender`. Thread-safe: uses 
`ConcurrentHashMap` + per-list `synchronized` blocks (properly guarded — no 
synchronization on the shared `Collections.emptyList()` singleton).
   - `BuildReportJsonWriter` — hand-written JSON serializer (no external JSON 
dependency; produces UTF-8 output compatible with `jq`).
   - `DefaultBuildEnvironment` — captures OS, JVM, Maven version, and 
user/system properties (sensitive keys redacted to `***`).
   
   **API access:** `Session.buildEnvironment()` returns a `BuildEnvironment` 
(lazy, volatile, thread-safe).
   
   **New API types** (`api/maven-api-core`, package 
`org.apache.maven.api.build.report`):
   - `BuildReport` — top-level report
   - `ModuleReport` — per-module timing and mojo list
   - `MojoReport` — per-mojo timing and captured log output
   - `LogEvent` — a single captured log line (level + timestamp + message + 
optional throwable)
   - `FailureReport` — structured failure info (module, mojo, message, stack 
trace excerpt)
   - `BuildStatus` — enum: `SUCCESS`, `FAILURE`, `ERROR`, `SKIPPED`
   - `BuildEnvironment` — OS/JVM/Maven/property snapshot
   
   All API types are immutable records with noun-style accessors (`groupId()`, 
`status()`, `duration()`) and full Javadoc. Lists are defensively copied in 
compact constructors.
   
   **Tests:** `BuildReportCollectorTest`, `BuildReportIntegrationTest`.
   
   ---
   
   ## What Was Not Changed
   
   - `.mvn/maven.config` and `.mvn/extensions.xml` continue to be read when 
`reactor.xml` is absent — no migration required.
   - No existing behaviour changes when these features are not activated.
   - The `maven.lifecycle.filter` property is not set by default — zero 
overhead when unused.
   - Build reports are written on a best-effort basis (failure to write logs a 
`WARN`, build is not aborted).
   
   ---
   
   ## Checklist
   
   - [x] Unit tests for all new classes (`ArgumentTokenizerTest`, 
`MavenParserAliasTest`, `CoordinatePredicateTest`, `MojoExecutionFilterTest`, 
`PhasePredicateTest`, `BuildReportCollectorTest`, `BuildReportIntegrationTest`)
   - [x] Integration tests (`mng-12537-reactor-xml-alias`, 
`mng-12538-lifecycle-filter`)
   - [x] `spotless:apply` applied
   - [x] No new compiler warnings
   - [x] API types follow Maven 4 immutable-type conventions (noun accessors, 
compact constructors, `List.copyOf()` at construction time)
   - [x] Concurrency: `BuildReportCollector` reviewed — `ConcurrentHashMap` + 
per-list `synchronized`, no spurious synchronization on shared singletons
   


-- 
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]

Reply via email to