This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new e4579e9b2e Fix targetPath parameter ignored in resource bundles (fixes 
#11062) (#11063)
e4579e9b2e is described below

commit e4579e9b2e392487f71fd08976b37f016e3750f2
Author: Mathieu Baurin <86267428+mbau...@users.noreply.github.com>
AuthorDate: Fri Aug 29 14:47:20 2025 +0200

    Fix targetPath parameter ignored in resource bundles (fixes #11062) (#11063)
    
    Co-authored-by: Guillaume Nodet <gno...@gmail.com>
---
 .../apache/maven/project/ConnectedResource.java    |   2 +
 .../org/apache/maven/project/MavenProject.java     |   1 +
 .../apache/maven/project/PomConstructionTest.java  |  19 ++++
 .../apache/maven/project/ResourceIncludeTest.java  |  89 +++++++++++++++++
 .../target-path-regression/pom.xml                 |  56 +++++++++++
 .../org/apache/maven/impl/DefaultSourceRoot.java   |   3 +-
 .../apache/maven/impl/DefaultSourceRootTest.java   | 106 +++++++++++++++++++++
 7 files changed, 275 insertions(+), 1 deletion(-)

diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java 
b/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java
index ba1afa8472..df0ebc711f 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java
@@ -18,6 +18,7 @@
  */
 package org.apache.maven.project;
 
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -41,6 +42,7 @@ class ConnectedResource extends Resource {
                 .includes(sourceRoot.includes())
                 .excludes(sourceRoot.excludes())
                 .filtering(Boolean.toString(sourceRoot.stringFiltering()))
+                
.targetPath(sourceRoot.targetPath().map(Path::toString).orElse(null))
                 .build());
         this.originalSourceRoot = sourceRoot;
         this.scope = scope;
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java 
b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
index 3b934c922e..45731697a7 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
@@ -828,6 +828,7 @@ private static Resource toResource(SourceRoot sourceRoot) {
                 .includes(sourceRoot.includes())
                 .excludes(sourceRoot.excludes())
                 .filtering(Boolean.toString(sourceRoot.stringFiltering()))
+                
.targetPath(sourceRoot.targetPath().map(Path::toString).orElse(null))
                 .build());
     }
 
diff --git 
a/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java
 
b/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java
index 4fe089e6ce..d53b81cc4e 100644
--- 
a/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java
+++ 
b/impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java
@@ -1225,6 +1225,25 @@ void testCompleteModelWithParent() throws Exception {
         testCompleteModel(pom);
     }
 
+    /*MNG-11062*/
+    @Test
+    void testTargetPathResourceRegression() throws Exception {
+        PomTestWrapper pom = buildPom("target-path-regression");
+
+        // Verify main resources targetPath is preserved
+        assertEquals(1, ((List<?>) pom.getValue("build/resources")).size());
+        assertEquals("custom-classes", 
pom.getValue("build/resources[1]/targetPath"));
+        assertPathSuffixEquals("src/main/resources", 
pom.getValue("build/resources[1]/directory"));
+
+        // Verify testResources targetPath with property interpolation is 
preserved
+        assertEquals(2, ((List<?>) 
pom.getValue("build/testResources")).size());
+        String buildPath = 
pom.getBasedir().toPath().resolve("target").toString();
+        assertEquals(buildPath + "/test-classes", 
pom.getValue("build/testResources[1]/targetPath"));
+        assertPathSuffixEquals("src/test/resources", 
pom.getValue("build/testResources[1]/directory"));
+        assertEquals(buildPath + "/test-run", 
pom.getValue("build/testResources[2]/targetPath"));
+        assertPathSuffixEquals("src/test/data", 
pom.getValue("build/testResources[2]/directory"));
+    }
+
     @SuppressWarnings("checkstyle:MethodLength")
     private void testCompleteModel(PomTestWrapper pom) throws Exception {
         assertEquals("4.0.0", pom.getValue("modelVersion"));
diff --git 
a/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
 
b/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
index cf3144a002..9d639fafc6 100644
--- 
a/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
+++ 
b/impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.maven.project;
 
+import java.io.File;
 import java.nio.file.Path;
 import java.util.List;
 
@@ -29,6 +30,7 @@
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -188,4 +190,91 @@ void testUnderlyingSourceRootsUpdated() {
         org.apache.maven.api.SourceRoot sourceRoot = sourceRootsList.get(0);
         assertTrue(sourceRoot.includes().contains("*.xml"), "Underlying 
SourceRoot should contain the include");
     }
+
+    /*MNG-11062*/
+    @Test
+    void testTargetPathPreservedWithConnectedResource() {
+        // Create resource with targetPath using Resource constructor pattern
+        Resource resourceWithTarget = new Resource();
+        resourceWithTarget.setDirectory("src/main/custom");
+        resourceWithTarget.setTargetPath("custom-output");
+
+        // Convert through DefaultSourceRoot to ensure targetPath extraction 
works
+        DefaultSourceRoot sourceRootFromResource =
+                new DefaultSourceRoot(project.getBaseDirectory(), 
ProjectScope.MAIN, resourceWithTarget.getDelegate());
+
+        project.addSourceRoot(sourceRootFromResource);
+
+        // Get resources - this creates ConnectedResource instances
+        List<Resource> resources = project.getResources();
+        assertEquals(2, resources.size(), "Should have two resources now");
+
+        // Find the resource with the custom directory
+        Resource customResource = resources.stream()
+                .filter(r -> r.getDirectory().endsWith("custom"))
+                .findFirst()
+                .orElseThrow(() -> new AssertionError("Custom resource not 
found"));
+
+        // Verify targetPath was preserved through conversion chain
+        assertEquals(
+                "custom-output", customResource.getTargetPath(), "targetPath 
should be preserved in ConnectedResource");
+
+        // Test that includes modification preserves targetPath (tests 
ConnectedResource functionality)
+        customResource.addInclude("*.properties");
+        assertEquals(
+                "custom-output", customResource.getTargetPath(), "targetPath 
should survive includes modification");
+        assertEquals(1, customResource.getIncludes().size(), "Should have one 
include");
+
+        // Verify persistence after getting resources again
+        Resource persistedResource = project.getResources().stream()
+                .filter(r -> r.getDirectory().endsWith("custom"))
+                .findFirst()
+                .orElseThrow();
+        assertEquals(
+                "custom-output",
+                persistedResource.getTargetPath(),
+                "targetPath should persist after resource retrieval");
+        assertTrue(persistedResource.getIncludes().contains("*.properties"), 
"Include should persist with targetPath");
+    }
+
+    /*MNG-11062*/
+    @Test
+    void testTargetPathEdgeCases() {
+        // Test null targetPath (should be handled gracefully)
+        Resource nullTargetResource = new Resource();
+        nullTargetResource.setDirectory("src/test/null-target");
+        // targetPath is null by default
+
+        DefaultSourceRoot nullTargetSourceRoot =
+                new DefaultSourceRoot(project.getBaseDirectory(), 
ProjectScope.MAIN, nullTargetResource.getDelegate());
+        project.addSourceRoot(nullTargetSourceRoot);
+
+        List<Resource> resources = project.getResources();
+        Resource nullTargetResult = resources.stream()
+                .filter(r -> r.getDirectory().endsWith("null-target"))
+                .findFirst()
+                .orElseThrow();
+
+        // null targetPath should remain null (not cause errors)
+        assertNull(nullTargetResult.getTargetPath(), "Null targetPath should 
remain null");
+
+        // Test property placeholder in targetPath
+        Resource placeholderResource = new Resource();
+        placeholderResource.setDirectory("src/test/placeholder");
+        placeholderResource.setTargetPath("${project.build.directory}/custom");
+
+        DefaultSourceRoot placeholderSourceRoot =
+                new DefaultSourceRoot(project.getBaseDirectory(), 
ProjectScope.MAIN, placeholderResource.getDelegate());
+        project.addSourceRoot(placeholderSourceRoot);
+
+        Resource placeholderResult = project.getResources().stream()
+                .filter(r -> r.getDirectory().endsWith("placeholder"))
+                .findFirst()
+                .orElseThrow();
+
+        assertEquals(
+                "${project.build.directory}" + File.separator + "custom",
+                placeholderResult.getTargetPath(),
+                "Property placeholder in targetPath should be preserved");
+    }
 }
diff --git 
a/impl/maven-core/src/test/resources-project-builder/target-path-regression/pom.xml
 
b/impl/maven-core/src/test/resources-project-builder/target-path-regression/pom.xml
new file mode 100644
index 0000000000..ec127689a8
--- /dev/null
+++ 
b/impl/maven-core/src/test/resources-project-builder/target-path-regression/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.mng</groupId>
+  <artifactId>target-path-regression-test</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>TargetPath Regression Test - MNG-11062</name>
+  <description>Test for targetPath parameter in resource bundles ignored 
regression</description>
+
+  <build>
+    <!-- Copy test resources and data to their respective directories -->
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+        <targetPath>${project.build.directory}/test-classes</targetPath>
+      </testResource>
+      <testResource>
+        <directory>src/test/data</directory>
+        <targetPath>${project.build.directory}/test-run</targetPath>
+      </testResource>
+    </testResources>
+    
+    <!-- Also test main resources with custom targetPath -->
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <targetPath>custom-classes</targetPath>
+        <filtering>false</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>
\ No newline at end of file
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
index dc8aaa206a..ec35428a51 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java
@@ -117,7 +117,8 @@ public DefaultSourceRoot(final Path baseDir, ProjectScope 
scope, Resource resour
         this.scope = scope;
         language = Language.RESOURCES;
         targetVersion = null;
-        targetPath = null;
+        value = nonBlank(resource.getTargetPath());
+        targetPath = (value != null) ? baseDir.resolve(value).normalize() : 
null;
     }
 
     /**
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
index 7db6005447..e27cfa3109 100644
--- 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
+++ 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java
@@ -19,10 +19,13 @@
 package org.apache.maven.impl;
 
 import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
 
 import org.apache.maven.api.Language;
 import org.apache.maven.api.ProjectScope;
 import org.apache.maven.api.Session;
+import org.apache.maven.api.model.Resource;
 import org.apache.maven.api.model.Source;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -33,6 +36,8 @@
 import org.mockito.stubbing.LenientStubber;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 
@@ -116,4 +121,105 @@ void testModuleTestDirectory() {
         assertEquals(Path.of("myproject", "src", "org.foo.bar", "test", 
"java"), source.directory());
         assertTrue(source.targetVersion().isEmpty());
     }
+
+    /*MNG-11062*/
+    @Test
+    void testExtractsTargetPathFromResource() {
+        // Test the Resource constructor that was broken in the regression
+        Resource resource = Resource.newBuilder()
+                .directory("src/test/resources")
+                .targetPath("test-output")
+                .build();
+
+        DefaultSourceRoot sourceRoot = new 
DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
+
+        Optional<Path> targetPath = sourceRoot.targetPath();
+        assertTrue(targetPath.isPresent(), "targetPath should be present");
+        assertEquals(Path.of("myproject", "test-output"), targetPath.get());
+        assertEquals(Path.of("myproject", "src", "test", "resources"), 
sourceRoot.directory());
+        assertEquals(ProjectScope.TEST, sourceRoot.scope());
+        assertEquals(Language.RESOURCES, sourceRoot.language());
+    }
+
+    /*MNG-11062*/
+    @Test
+    void testHandlesNullTargetPathFromResource() {
+        // Test null targetPath handling
+        Resource resource =
+                Resource.newBuilder().directory("src/test/resources").build();
+        // targetPath is null by default
+
+        DefaultSourceRoot sourceRoot = new 
DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
+
+        Optional<Path> targetPath = sourceRoot.targetPath();
+        assertFalse(targetPath.isPresent(), "targetPath should be empty when 
null");
+    }
+
+    /*MNG-11062*/
+    @Test
+    void testHandlesEmptyTargetPathFromResource() {
+        // Test empty string targetPath
+        Resource resource = Resource.newBuilder()
+                .directory("src/test/resources")
+                .targetPath("")
+                .build();
+
+        DefaultSourceRoot sourceRoot = new 
DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
+
+        Optional<Path> targetPath = sourceRoot.targetPath();
+        assertFalse(targetPath.isPresent(), "targetPath should be empty for 
empty string");
+    }
+
+    /*MNG-11062*/
+    @Test
+    void testHandlesPropertyPlaceholderInTargetPath() {
+        // Test property placeholder preservation
+        Resource resource = Resource.newBuilder()
+                .directory("src/test/resources")
+                .targetPath("${project.build.directory}/custom")
+                .build();
+
+        DefaultSourceRoot sourceRoot = new 
DefaultSourceRoot(Path.of("myproject"), ProjectScope.MAIN, resource);
+
+        Optional<Path> targetPath = sourceRoot.targetPath();
+        assertTrue(targetPath.isPresent(), "Property placeholder targetPath 
should be present");
+        assertEquals(Path.of("myproject", 
"${project.build.directory}/custom"), targetPath.get());
+    }
+
+    /*MNG-11062*/
+    @Test
+    void testResourceConstructorRequiresNonNullDirectory() {
+        // Test that null directory throws exception
+        Resource resource = Resource.newBuilder().build();
+        // directory is null by default
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new DefaultSourceRoot(Path.of("myproject"), 
ProjectScope.TEST, resource),
+                "Should throw exception for null directory");
+    }
+
+    /*MNG-11062*/
+    @Test
+    void testResourceConstructorPreservesOtherProperties() {
+        // Test that other Resource properties are correctly preserved
+        Resource resource = Resource.newBuilder()
+                .directory("src/test/resources")
+                .targetPath("test-classes")
+                .filtering("true")
+                .includes(List.of("*.properties"))
+                .excludes(List.of("*.tmp"))
+                .build();
+
+        DefaultSourceRoot sourceRoot = new 
DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
+
+        // Verify all properties are preserved
+        assertEquals(
+                Path.of("myproject", "test-classes"), 
sourceRoot.targetPath().orElseThrow());
+        assertTrue(sourceRoot.stringFiltering(), "Filtering should be true");
+        assertEquals(1, sourceRoot.includes().size());
+        assertTrue(sourceRoot.includes().contains("*.properties"));
+        assertEquals(1, sourceRoot.excludes().size());
+        assertTrue(sourceRoot.excludes().contains("*.tmp"));
+    }
 }

Reply via email to