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]

Reply via email to