gnodet commented on issue #11381:
URL: https://github.com/apache/maven/issues/11381#issuecomment-3502314999
## Implementation Approach: Maven 4 API Conformance with Maven 3
Compatibility
I've enhanced the javadoc to clarify how `targetPath` handling works in the
Maven 4 API while preserving full backward compatibility with Maven 3. Here's
the design rationale:
### Core Design Principle
The implementation follows a **separation of concerns** pattern:
1. **Storage Layer** (`SourceRoot.targetPath()`): Stores the path **as-is**
from the configuration (relative or absolute)
2. **Resolution Layer** (`SourceRoot.targetPath(Project)`): Resolves
relative paths against the output directory to produce absolute paths
### Maven 4 API Conformance
#### `SourceRoot.targetPath()` - The Storage Method
This method returns the `targetPath` exactly as specified in the
configuration:
- **Relative paths** (e.g., `"META-INF/resources"`) are stored as relative
paths
- **Absolute paths** (e.g., `"/tmp/custom"`) are stored as absolute paths
- **Empty/null** means no explicit target path was specified
The javadoc now explicitly documents:
```java
/**
* <strong>Path Resolution Semantics:</strong>
* <ul>
* <li><strong>Relative paths</strong> are resolved relative to the output
directory
* for the given scope (target/classes for MAIN, target/test-classes
for TEST)</li>
* <li><strong>Absolute paths</strong> are used as-is without any
resolution</li>
* <li><strong>Empty/null</strong> means files are copied to the output
directory root</li>
* </ul>
*/
```
#### `SourceRoot.targetPath(Project)` - The Resolution Method
This method performs the actual path resolution:
```java
default Path targetPath(@Nonnull Project project) {
Optional<Path> targetPath = targetPath();
return targetPath.filter(Path::isAbsolute).orElseGet(() -> {
Path base = project.getOutputDirectory(scope());
return targetPath.map(base::resolve).orElse(base);
});
}
```
**Resolution Algorithm:**
1. Get the output directory for the source root's scope from the project
2. If `targetPath()` is empty → return the output directory
3. If `targetPath()` is absolute → return it unchanged
4. If `targetPath()` is relative → resolve it against the output directory
#### `Project.getOutputDirectory(ProjectScope)` - The Base Directory Provider
This method returns the scope-specific output directory:
- `ProjectScope.MAIN` → `target/classes` (for compiled classes AND resources)
- `ProjectScope.TEST` → `target/test-classes` (for test classes AND
resources)
- Other/null → `target` (the build directory)
The javadoc now explicitly states its role in path resolution and Maven 3
compatibility.
### Maven 3 Backward Compatibility
#### How It Preserves Maven 3 Behavior
In Maven 3, the maven-resources-plugin receives the `targetPath` as a
**relative path** and resolves it against `project.build.outputDirectory` or
`project.build.testOutputDirectory`.
Our implementation maintains this by:
1. **`DefaultSourceRoot`** stores `targetPath` as-is (relative):
```java
nonBlank(resource.getTargetPath()).map(Path::of).orElse(null)
```
2. **`ConnectedResource`** extracts `targetPath` as a string (still
relative):
```java
sourceRoot.targetPath().map(Path::toString).orElse(null)
```
3. **maven-resources-plugin** receives the relative path and resolves it
against the output directory (exactly as in Maven 3)
#### Why This Works
The key insight is that **we don't resolve the path at storage time**.
Instead:
- The `targetPath` is stored as a **relative path** (e.g., `"target-dir"`)
- When maven-resources-plugin reads it via `resource.getTargetPath()`, it
gets `"target-dir"` (a relative path string)
- The plugin then resolves it: `outputDirectory.resolve("target-dir")` →
`target/classes/target-dir`
This is **identical** to Maven 3 behavior!
### Implementation Details
#### `DefaultSourceRoot` Constructor from Resource
```java
public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource
resource) {
this(
scope,
Language.RESOURCES,
null,
null,
baseDir.resolve(resource.getDirectory()), // ← directory IS
resolved against baseDir
resource.getIncludes(),
resource.getExcludes(),
Boolean.parseBoolean(resource.getFiltering()),
nonBlank(resource.getTargetPath()).map(Path::of).orElse(null), // ←
targetPath is NOT resolved
true);
}
```
**Important distinction:**
- `directory` is resolved against `baseDir` because it's a source location
(needs to be absolute for file system access)
- `targetPath` is NOT resolved because it's a destination specification
(resolution happens later based on scope)
The javadoc now explicitly documents this:
```java
/**
* @param baseDir the base directory for resolving relative paths (used only
for the source directory)
*/
```
#### `ConnectedResource` Wrapper
This class bridges the Maven 4 API (`SourceRoot`) with the Maven 3 API
(`Resource`):
```java
ConnectedResource(SourceRoot sourceRoot, ProjectScope scope, MavenProject
project) {
super(org.apache.maven.api.model.Resource.newBuilder()
.directory(sourceRoot.directory().toString())
.targetPath(sourceRoot.targetPath().map(Path::toString).orElse(null)) // ←
relative path
.build());
}
```
The `targetPath` is extracted as a string representation of the relative
path, which is exactly what maven-resources-plugin expects.
### Examples
Given this configuration:
```xml
<resource>
<directory>${project.basedir}/rest</directory>
<targetPath>target-dir</targetPath>
</resource>
```
**Storage:**
- `sourceRoot.directory()` → `/home/user/myproject/rest` (absolute)
- `sourceRoot.targetPath()` → `Optional.of(Path.of("target-dir"))` (relative)
**Resolution (Maven 4 API):**
- `sourceRoot.targetPath(project)` →
`/home/user/myproject/target/classes/target-dir` (absolute)
**Legacy API (Maven 3 compatibility):**
- `resource.getDirectory()` → `/home/user/myproject/rest` (absolute path as
string)
- `resource.getTargetPath()` → `"target-dir"` (relative path as string)
- maven-resources-plugin resolves: `target/classes` + `target-dir` →
`target/classes/target-dir` ✅
### Summary
This implementation:
✅ **Conforms to Maven 4 API** - Clear separation between storage
(`targetPath()`) and resolution (`targetPath(Project)`)
✅ **Preserves Maven 3 compatibility** - Relative paths are stored as-is and
resolved by plugins against output directory
✅ **Well-documented** - Comprehensive javadoc explains semantics, resolution
algorithm, and compatibility guarantees
✅ **Consistent** - All three methods (`SourceRoot.targetPath()`,
`SourceRoot.targetPath(Project)`, `Project.getOutputDirectory()`) have coherent
documentation with cross-references
The key to maintaining compatibility was recognizing that Maven 3's behavior
of resolving `targetPath` relative to the output directory should be preserved
by **not resolving it at storage time**, but instead letting the resolution
happen either:
1. Via `SourceRoot.targetPath(Project)` in Maven 4 API consumers
2. Via maven-resources-plugin's own resolution logic in Maven 3
compatibility mode
--
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]