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 ba1afa8472ed..df0ebc711fbd 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 3b934c922e9b..45731697a77c 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 4fe089e6ceff..d53b81cc4e44 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 cf3144a0023f..9d639fafc62e 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 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 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 000000000000..ec127689a80f --- /dev/null +++ b/impl/maven-core/src/test/resources-project-builder/target-path-regression/pom.xml @@ -0,0 +1,56 @@ + + + + + + 4.0.0 + + org.apache.maven.its.mng + target-path-regression-test + 1.0-SNAPSHOT + jar + + TargetPath Regression Test - MNG-11062 + Test for targetPath parameter in resource bundles ignored regression + + + + + + src/test/resources + ${project.build.directory}/test-classes + + + src/test/data + ${project.build.directory}/test-run + + + + + + + src/main/resources + custom-classes + false + + + + \ 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 dc8aaa206a50..ec35428a516e 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 7db6005447bc..e27cfa3109ce 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 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 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 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 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")); + } }