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")); + } }