Fix targetPath parameter ignored in resource bundles (fixes #11062) (#11063)
Co-authored-by: Guillaume Nodet <gnodet@gmail.com>
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 ba1afa8..df0ebc7 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 3b934c9..4573169 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 4fe089e..d53b81c 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 cf3144a..9d639fa 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 0000000..ec12768
--- /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 dc8aaa2..ec35428 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 7db6005..e27cfa3 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"));
+ }
}