Skip to content

Commit e4579e9

Browse files
mbauringnodet
andauthored
Fix targetPath parameter ignored in resource bundles (fixes #11062) (#11063)
Co-authored-by: Guillaume Nodet <[email protected]>
1 parent 4b686c5 commit e4579e9

File tree

7 files changed

+275
-1
lines changed

7 files changed

+275
-1
lines changed

impl/maven-core/src/main/java/org/apache/maven/project/ConnectedResource.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.apache.maven.project;
2020

21+
import java.nio.file.Path;
2122
import java.util.ArrayList;
2223
import java.util.List;
2324

@@ -41,6 +42,7 @@ class ConnectedResource extends Resource {
4142
.includes(sourceRoot.includes())
4243
.excludes(sourceRoot.excludes())
4344
.filtering(Boolean.toString(sourceRoot.stringFiltering()))
45+
.targetPath(sourceRoot.targetPath().map(Path::toString).orElse(null))
4446
.build());
4547
this.originalSourceRoot = sourceRoot;
4648
this.scope = scope;

impl/maven-core/src/main/java/org/apache/maven/project/MavenProject.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ private static Resource toResource(SourceRoot sourceRoot) {
828828
.includes(sourceRoot.includes())
829829
.excludes(sourceRoot.excludes())
830830
.filtering(Boolean.toString(sourceRoot.stringFiltering()))
831+
.targetPath(sourceRoot.targetPath().map(Path::toString).orElse(null))
831832
.build());
832833
}
833834

impl/maven-core/src/test/java/org/apache/maven/project/PomConstructionTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,25 @@ void testCompleteModelWithParent() throws Exception {
12251225
testCompleteModel(pom);
12261226
}
12271227

1228+
/*MNG-11062*/
1229+
@Test
1230+
void testTargetPathResourceRegression() throws Exception {
1231+
PomTestWrapper pom = buildPom("target-path-regression");
1232+
1233+
// Verify main resources targetPath is preserved
1234+
assertEquals(1, ((List<?>) pom.getValue("build/resources")).size());
1235+
assertEquals("custom-classes", pom.getValue("build/resources[1]/targetPath"));
1236+
assertPathSuffixEquals("src/main/resources", pom.getValue("build/resources[1]/directory"));
1237+
1238+
// Verify testResources targetPath with property interpolation is preserved
1239+
assertEquals(2, ((List<?>) pom.getValue("build/testResources")).size());
1240+
String buildPath = pom.getBasedir().toPath().resolve("target").toString();
1241+
assertEquals(buildPath + "/test-classes", pom.getValue("build/testResources[1]/targetPath"));
1242+
assertPathSuffixEquals("src/test/resources", pom.getValue("build/testResources[1]/directory"));
1243+
assertEquals(buildPath + "/test-run", pom.getValue("build/testResources[2]/targetPath"));
1244+
assertPathSuffixEquals("src/test/data", pom.getValue("build/testResources[2]/directory"));
1245+
}
1246+
12281247
@SuppressWarnings("checkstyle:MethodLength")
12291248
private void testCompleteModel(PomTestWrapper pom) throws Exception {
12301249
assertEquals("4.0.0", pom.getValue("modelVersion"));

impl/maven-core/src/test/java/org/apache/maven/project/ResourceIncludeTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.apache.maven.project;
2020

21+
import java.io.File;
2122
import java.nio.file.Path;
2223
import java.util.List;
2324

@@ -29,6 +30,7 @@
2930
import org.junit.jupiter.api.Test;
3031

3132
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
import static org.junit.jupiter.api.Assertions.assertNull;
3234
import static org.junit.jupiter.api.Assertions.assertTrue;
3335

3436
/**
@@ -188,4 +190,91 @@ void testUnderlyingSourceRootsUpdated() {
188190
org.apache.maven.api.SourceRoot sourceRoot = sourceRootsList.get(0);
189191
assertTrue(sourceRoot.includes().contains("*.xml"), "Underlying SourceRoot should contain the include");
190192
}
193+
194+
/*MNG-11062*/
195+
@Test
196+
void testTargetPathPreservedWithConnectedResource() {
197+
// Create resource with targetPath using Resource constructor pattern
198+
Resource resourceWithTarget = new Resource();
199+
resourceWithTarget.setDirectory("src/main/custom");
200+
resourceWithTarget.setTargetPath("custom-output");
201+
202+
// Convert through DefaultSourceRoot to ensure targetPath extraction works
203+
DefaultSourceRoot sourceRootFromResource =
204+
new DefaultSourceRoot(project.getBaseDirectory(), ProjectScope.MAIN, resourceWithTarget.getDelegate());
205+
206+
project.addSourceRoot(sourceRootFromResource);
207+
208+
// Get resources - this creates ConnectedResource instances
209+
List<Resource> resources = project.getResources();
210+
assertEquals(2, resources.size(), "Should have two resources now");
211+
212+
// Find the resource with the custom directory
213+
Resource customResource = resources.stream()
214+
.filter(r -> r.getDirectory().endsWith("custom"))
215+
.findFirst()
216+
.orElseThrow(() -> new AssertionError("Custom resource not found"));
217+
218+
// Verify targetPath was preserved through conversion chain
219+
assertEquals(
220+
"custom-output", customResource.getTargetPath(), "targetPath should be preserved in ConnectedResource");
221+
222+
// Test that includes modification preserves targetPath (tests ConnectedResource functionality)
223+
customResource.addInclude("*.properties");
224+
assertEquals(
225+
"custom-output", customResource.getTargetPath(), "targetPath should survive includes modification");
226+
assertEquals(1, customResource.getIncludes().size(), "Should have one include");
227+
228+
// Verify persistence after getting resources again
229+
Resource persistedResource = project.getResources().stream()
230+
.filter(r -> r.getDirectory().endsWith("custom"))
231+
.findFirst()
232+
.orElseThrow();
233+
assertEquals(
234+
"custom-output",
235+
persistedResource.getTargetPath(),
236+
"targetPath should persist after resource retrieval");
237+
assertTrue(persistedResource.getIncludes().contains("*.properties"), "Include should persist with targetPath");
238+
}
239+
240+
/*MNG-11062*/
241+
@Test
242+
void testTargetPathEdgeCases() {
243+
// Test null targetPath (should be handled gracefully)
244+
Resource nullTargetResource = new Resource();
245+
nullTargetResource.setDirectory("src/test/null-target");
246+
// targetPath is null by default
247+
248+
DefaultSourceRoot nullTargetSourceRoot =
249+
new DefaultSourceRoot(project.getBaseDirectory(), ProjectScope.MAIN, nullTargetResource.getDelegate());
250+
project.addSourceRoot(nullTargetSourceRoot);
251+
252+
List<Resource> resources = project.getResources();
253+
Resource nullTargetResult = resources.stream()
254+
.filter(r -> r.getDirectory().endsWith("null-target"))
255+
.findFirst()
256+
.orElseThrow();
257+
258+
// null targetPath should remain null (not cause errors)
259+
assertNull(nullTargetResult.getTargetPath(), "Null targetPath should remain null");
260+
261+
// Test property placeholder in targetPath
262+
Resource placeholderResource = new Resource();
263+
placeholderResource.setDirectory("src/test/placeholder");
264+
placeholderResource.setTargetPath("${project.build.directory}/custom");
265+
266+
DefaultSourceRoot placeholderSourceRoot =
267+
new DefaultSourceRoot(project.getBaseDirectory(), ProjectScope.MAIN, placeholderResource.getDelegate());
268+
project.addSourceRoot(placeholderSourceRoot);
269+
270+
Resource placeholderResult = project.getResources().stream()
271+
.filter(r -> r.getDirectory().endsWith("placeholder"))
272+
.findFirst()
273+
.orElseThrow();
274+
275+
assertEquals(
276+
"${project.build.directory}" + File.separator + "custom",
277+
placeholderResult.getTargetPath(),
278+
"Property placeholder in targetPath should be preserved");
279+
}
191280
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
https://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
-->
21+
22+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
24+
<modelVersion>4.0.0</modelVersion>
25+
26+
<groupId>org.apache.maven.its.mng</groupId>
27+
<artifactId>target-path-regression-test</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
<packaging>jar</packaging>
30+
31+
<name>TargetPath Regression Test - MNG-11062</name>
32+
<description>Test for targetPath parameter in resource bundles ignored regression</description>
33+
34+
<build>
35+
<!-- Copy test resources and data to their respective directories -->
36+
<testResources>
37+
<testResource>
38+
<directory>src/test/resources</directory>
39+
<targetPath>${project.build.directory}/test-classes</targetPath>
40+
</testResource>
41+
<testResource>
42+
<directory>src/test/data</directory>
43+
<targetPath>${project.build.directory}/test-run</targetPath>
44+
</testResource>
45+
</testResources>
46+
47+
<!-- Also test main resources with custom targetPath -->
48+
<resources>
49+
<resource>
50+
<directory>src/main/resources</directory>
51+
<targetPath>custom-classes</targetPath>
52+
<filtering>false</filtering>
53+
</resource>
54+
</resources>
55+
</build>
56+
</project>

impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultSourceRoot.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resour
117117
this.scope = scope;
118118
language = Language.RESOURCES;
119119
targetVersion = null;
120-
targetPath = null;
120+
value = nonBlank(resource.getTargetPath());
121+
targetPath = (value != null) ? baseDir.resolve(value).normalize() : null;
121122
}
122123

123124
/**

impl/maven-impl/src/test/java/org/apache/maven/impl/DefaultSourceRootTest.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
package org.apache.maven.impl;
2020

2121
import java.nio.file.Path;
22+
import java.util.List;
23+
import java.util.Optional;
2224

2325
import org.apache.maven.api.Language;
2426
import org.apache.maven.api.ProjectScope;
2527
import org.apache.maven.api.Session;
28+
import org.apache.maven.api.model.Resource;
2629
import org.apache.maven.api.model.Source;
2730
import org.junit.jupiter.api.BeforeEach;
2831
import org.junit.jupiter.api.Test;
@@ -33,6 +36,8 @@
3336
import org.mockito.stubbing.LenientStubber;
3437

3538
import static org.junit.jupiter.api.Assertions.assertEquals;
39+
import static org.junit.jupiter.api.Assertions.assertFalse;
40+
import static org.junit.jupiter.api.Assertions.assertThrows;
3641
import static org.junit.jupiter.api.Assertions.assertTrue;
3742
import static org.mockito.ArgumentMatchers.eq;
3843

@@ -116,4 +121,105 @@ void testModuleTestDirectory() {
116121
assertEquals(Path.of("myproject", "src", "org.foo.bar", "test", "java"), source.directory());
117122
assertTrue(source.targetVersion().isEmpty());
118123
}
124+
125+
/*MNG-11062*/
126+
@Test
127+
void testExtractsTargetPathFromResource() {
128+
// Test the Resource constructor that was broken in the regression
129+
Resource resource = Resource.newBuilder()
130+
.directory("src/test/resources")
131+
.targetPath("test-output")
132+
.build();
133+
134+
DefaultSourceRoot sourceRoot = new DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
135+
136+
Optional<Path> targetPath = sourceRoot.targetPath();
137+
assertTrue(targetPath.isPresent(), "targetPath should be present");
138+
assertEquals(Path.of("myproject", "test-output"), targetPath.get());
139+
assertEquals(Path.of("myproject", "src", "test", "resources"), sourceRoot.directory());
140+
assertEquals(ProjectScope.TEST, sourceRoot.scope());
141+
assertEquals(Language.RESOURCES, sourceRoot.language());
142+
}
143+
144+
/*MNG-11062*/
145+
@Test
146+
void testHandlesNullTargetPathFromResource() {
147+
// Test null targetPath handling
148+
Resource resource =
149+
Resource.newBuilder().directory("src/test/resources").build();
150+
// targetPath is null by default
151+
152+
DefaultSourceRoot sourceRoot = new DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
153+
154+
Optional<Path> targetPath = sourceRoot.targetPath();
155+
assertFalse(targetPath.isPresent(), "targetPath should be empty when null");
156+
}
157+
158+
/*MNG-11062*/
159+
@Test
160+
void testHandlesEmptyTargetPathFromResource() {
161+
// Test empty string targetPath
162+
Resource resource = Resource.newBuilder()
163+
.directory("src/test/resources")
164+
.targetPath("")
165+
.build();
166+
167+
DefaultSourceRoot sourceRoot = new DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
168+
169+
Optional<Path> targetPath = sourceRoot.targetPath();
170+
assertFalse(targetPath.isPresent(), "targetPath should be empty for empty string");
171+
}
172+
173+
/*MNG-11062*/
174+
@Test
175+
void testHandlesPropertyPlaceholderInTargetPath() {
176+
// Test property placeholder preservation
177+
Resource resource = Resource.newBuilder()
178+
.directory("src/test/resources")
179+
.targetPath("${project.build.directory}/custom")
180+
.build();
181+
182+
DefaultSourceRoot sourceRoot = new DefaultSourceRoot(Path.of("myproject"), ProjectScope.MAIN, resource);
183+
184+
Optional<Path> targetPath = sourceRoot.targetPath();
185+
assertTrue(targetPath.isPresent(), "Property placeholder targetPath should be present");
186+
assertEquals(Path.of("myproject", "${project.build.directory}/custom"), targetPath.get());
187+
}
188+
189+
/*MNG-11062*/
190+
@Test
191+
void testResourceConstructorRequiresNonNullDirectory() {
192+
// Test that null directory throws exception
193+
Resource resource = Resource.newBuilder().build();
194+
// directory is null by default
195+
196+
assertThrows(
197+
IllegalArgumentException.class,
198+
() -> new DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource),
199+
"Should throw exception for null directory");
200+
}
201+
202+
/*MNG-11062*/
203+
@Test
204+
void testResourceConstructorPreservesOtherProperties() {
205+
// Test that other Resource properties are correctly preserved
206+
Resource resource = Resource.newBuilder()
207+
.directory("src/test/resources")
208+
.targetPath("test-classes")
209+
.filtering("true")
210+
.includes(List.of("*.properties"))
211+
.excludes(List.of("*.tmp"))
212+
.build();
213+
214+
DefaultSourceRoot sourceRoot = new DefaultSourceRoot(Path.of("myproject"), ProjectScope.TEST, resource);
215+
216+
// Verify all properties are preserved
217+
assertEquals(
218+
Path.of("myproject", "test-classes"), sourceRoot.targetPath().orElseThrow());
219+
assertTrue(sourceRoot.stringFiltering(), "Filtering should be true");
220+
assertEquals(1, sourceRoot.includes().size());
221+
assertTrue(sourceRoot.includes().contains("*.properties"));
222+
assertEquals(1, sourceRoot.excludes().size());
223+
assertTrue(sourceRoot.excludes().contains("*.tmp"));
224+
}
119225
}

0 commit comments

Comments
 (0)