Skip to content

Commit 11c30c2

Browse files
gnodetclaude
andcommitted
Fix Source targetPath incorrectly aligned to project basedir
DefaultModelPathTranslator.alignToBaseDirectory(Source) was resolving Source.targetPath against the project basedir, turning relative paths like "META-INF/tags/rdc" into absolute paths. This caused maven-resources-plugin to write files to incorrect absolute locations instead of relative to the output directory (target/classes). Removed the targetPath alignment from Source path translation, matching the existing correct behavior for Resource.targetPath. Added test coverage for both Source (4.1.0 model) and legacy Resource (4.0.0 model) targetPath through the compat layer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 52c4d43 commit 11c30c2

5 files changed

Lines changed: 302 additions & 5 deletions

File tree

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,4 +996,99 @@ void testDuplicateEnabledSources() throws Exception {
996996
testJavaRoots.get(0).module().orElse(null),
997997
"Test source root should be for com.example.dup module");
998998
}
999+
1000+
@Test
1001+
void testSourceTargetPathRemainsRelative() throws Exception {
1002+
File pom = getProject("source-target-path");
1003+
1004+
MavenSession mavenSession = createMavenSession(null);
1005+
ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest();
1006+
configuration.setRepositorySession(mavenSession.getRepositorySession());
1007+
1008+
ProjectBuildingResult result = getContainer()
1009+
.lookup(org.apache.maven.project.ProjectBuilder.class)
1010+
.build(pom, configuration);
1011+
1012+
MavenProject project = result.getProject();
1013+
1014+
// Verify main resources have relative targetPath preserved
1015+
List<SourceRoot> mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
1016+
.toList();
1017+
assertEquals(1, mainResources.size());
1018+
assertTrue(mainResources.get(0).targetPath().isPresent());
1019+
assertEquals(Path.of(".grammar"), mainResources.get(0).targetPath().get());
1020+
1021+
// Verify test resources have relative targetPath preserved
1022+
List<SourceRoot> testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)
1023+
.toList();
1024+
assertEquals(1, testResources.size());
1025+
assertTrue(testResources.get(0).targetPath().isPresent());
1026+
assertEquals(Path.of("META-INF/test"), testResources.get(0).targetPath().get());
1027+
1028+
// Verify the compat layer also returns relative targetPath
1029+
List<org.apache.maven.model.Resource> resources = project.getResources();
1030+
assertEquals(1, resources.size());
1031+
assertEquals(".grammar", resources.get(0).getTargetPath());
1032+
1033+
List<org.apache.maven.model.Resource> testResourceList = project.getTestResources();
1034+
assertEquals(1, testResourceList.size());
1035+
assertEquals(
1036+
"META-INF" + File.separator + "test", testResourceList.get(0).getTargetPath());
1037+
}
1038+
1039+
@Test
1040+
void testResourceTargetPathRemainsRelativeInCompatLayer() throws Exception {
1041+
File pom = getProject("resource-target-path");
1042+
1043+
MavenSession mavenSession = createMavenSession(null);
1044+
ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest();
1045+
configuration.setRepositorySession(mavenSession.getRepositorySession());
1046+
1047+
ProjectBuildingResult result = getContainer()
1048+
.lookup(org.apache.maven.project.ProjectBuilder.class)
1049+
.build(pom, configuration);
1050+
1051+
MavenProject project = result.getProject();
1052+
1053+
// Verify main resources via SourceRoot API
1054+
List<SourceRoot> mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
1055+
.toList();
1056+
assertEquals(1, mainResources.size());
1057+
assertTrue(mainResources.get(0).targetPath().isPresent());
1058+
assertFalse(
1059+
mainResources.get(0).targetPath().get().isAbsolute(),
1060+
"SourceRoot targetPath must be relative, got: "
1061+
+ mainResources.get(0).targetPath().get());
1062+
assertEquals(
1063+
Path.of("META-INF/tags/rdc"), mainResources.get(0).targetPath().get());
1064+
1065+
// Verify test resources via SourceRoot API
1066+
List<SourceRoot> testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)
1067+
.toList();
1068+
assertEquals(1, testResources.size());
1069+
assertTrue(testResources.get(0).targetPath().isPresent());
1070+
assertFalse(
1071+
testResources.get(0).targetPath().get().isAbsolute(),
1072+
"SourceRoot targetPath must be relative, got: "
1073+
+ testResources.get(0).targetPath().get());
1074+
assertEquals(
1075+
Path.of("org/apache/maven/messages"),
1076+
testResources.get(0).targetPath().get());
1077+
1078+
// Verify compat layer: MavenProject.getResources() must return relative targetPath
1079+
List<org.apache.maven.model.Resource> resources = project.getResources();
1080+
assertEquals(1, resources.size());
1081+
assertEquals(
1082+
"META-INF" + File.separator + "tags" + File.separator + "rdc",
1083+
resources.get(0).getTargetPath(),
1084+
"Resource targetPath from getResources() must remain relative");
1085+
1086+
// Verify compat layer: MavenProject.getTestResources() must return relative targetPath
1087+
List<org.apache.maven.model.Resource> testResourceList = project.getTestResources();
1088+
assertEquals(1, testResourceList.size());
1089+
assertEquals(
1090+
"org" + File.separator + "apache" + File.separator + "maven" + File.separator + "messages",
1091+
testResourceList.get(0).getTargetPath(),
1092+
"Test resource targetPath from getTestResources() must remain relative");
1093+
}
9991094
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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>resource-target-path-test</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
<packaging>jar</packaging>
30+
31+
<name>Resource TargetPath Compat Test</name>
32+
<description>Test that targetPath in legacy resource elements remains relative through the compat layer</description>
33+
34+
<build>
35+
<resources>
36+
<resource>
37+
<directory>src/main/resources</directory>
38+
<targetPath>META-INF/tags/rdc</targetPath>
39+
</resource>
40+
</resources>
41+
<testResources>
42+
<testResource>
43+
<directory>src/test/resources</directory>
44+
<targetPath>org/apache/maven/messages</targetPath>
45+
</testResource>
46+
</testResources>
47+
</build>
48+
</project>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd">
24+
<modelVersion>4.1.0</modelVersion>
25+
26+
<groupId>org.apache.maven.its.mng</groupId>
27+
<artifactId>source-target-path-test</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
<packaging>jar</packaging>
30+
31+
<name>Source TargetPath Test</name>
32+
<description>Test that targetPath in source elements remains relative to output directory</description>
33+
34+
<build>
35+
<sources>
36+
<source>
37+
<lang>resources</lang>
38+
<directory>src/main/resources</directory>
39+
<targetPath>.grammar</targetPath>
40+
</source>
41+
<source>
42+
<lang>resources</lang>
43+
<scope>test</scope>
44+
<directory>src/test/resources</directory>
45+
<targetPath>META-INF/test</targetPath>
46+
</source>
47+
</sources>
48+
</build>
49+
</project>

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,9 @@ private Source alignToBaseDirectory(Source source, Path basedir) {
134134
if (newDir != oldDir) {
135135
source = source.withDirectory(newDir);
136136
}
137-
oldDir = source.getTargetPath();
138-
newDir = alignToBaseDirectory(oldDir, basedir);
139-
if (newDir != oldDir) {
140-
source = source.withTargetPath(newDir);
141-
}
137+
// Note: targetPath is intentionally NOT aligned to basedir.
138+
// It is relative to the output directory (target/classes or target/test-classes),
139+
// not to the project base directory. See maven.mdo Source.targetPath documentation.
142140
}
143141
return source;
144142
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.impl.model;
20+
21+
import java.nio.file.Path;
22+
23+
import org.apache.maven.api.model.Build;
24+
import org.apache.maven.api.model.Model;
25+
import org.apache.maven.api.model.Resource;
26+
import org.apache.maven.api.model.Source;
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertTrue;
32+
33+
class DefaultModelPathTranslatorTest {
34+
35+
private DefaultModelPathTranslator translator;
36+
private Path basedir;
37+
38+
@BeforeEach
39+
void setUp() {
40+
translator = new DefaultModelPathTranslator(new DefaultPathTranslator());
41+
basedir = Path.of("/home/user/myproject").toAbsolutePath();
42+
}
43+
44+
@Test
45+
void sourceDirectoryIsAlignedToBasedir() {
46+
Source source = Source.newBuilder().directory("src/main/java").build();
47+
Model model = Model.newBuilder()
48+
.build(Build.newBuilder().sources(java.util.List.of(source)).build())
49+
.build();
50+
51+
Model result = translator.alignToBaseDirectory(model, basedir, null);
52+
53+
String dir = result.getBuild().getSources().get(0).getDirectory();
54+
assertTrue(Path.of(dir).isAbsolute(), "directory should be absolute after alignment");
55+
assertTrue(Path.of(dir).endsWith(Path.of("src/main/java")), "directory should end with original path");
56+
}
57+
58+
@Test
59+
void sourceTargetPathIsNotAlignedToBasedir() {
60+
Source source = Source.newBuilder()
61+
.directory("src/main/resources")
62+
.targetPath("META-INF/resources")
63+
.build();
64+
Model model = Model.newBuilder()
65+
.build(Build.newBuilder().sources(java.util.List.of(source)).build())
66+
.build();
67+
68+
Model result = translator.alignToBaseDirectory(model, basedir, null);
69+
70+
String targetPath = result.getBuild().getSources().get(0).getTargetPath();
71+
assertEquals("META-INF/resources", targetPath, "targetPath should remain relative");
72+
}
73+
74+
@Test
75+
void sourceTargetPathDotPrefixRemainsRelative() {
76+
Source source = Source.newBuilder()
77+
.directory("src/main/resources")
78+
.targetPath(".grammar")
79+
.build();
80+
Model model = Model.newBuilder()
81+
.build(Build.newBuilder().sources(java.util.List.of(source)).build())
82+
.build();
83+
84+
Model result = translator.alignToBaseDirectory(model, basedir, null);
85+
86+
String targetPath = result.getBuild().getSources().get(0).getTargetPath();
87+
assertEquals(".grammar", targetPath, "dot-prefixed targetPath should remain relative");
88+
}
89+
90+
@Test
91+
void resourceDirectoryIsAlignedButTargetPathIsNot() {
92+
Resource resource = Resource.newBuilder()
93+
.directory("src/main/resources")
94+
.targetPath("custom-output")
95+
.build();
96+
Model model = Model.newBuilder()
97+
.build(Build.newBuilder().resources(java.util.List.of(resource)).build())
98+
.build();
99+
100+
Model result = translator.alignToBaseDirectory(model, basedir, null);
101+
102+
Resource aligned = result.getBuild().getResources().get(0);
103+
assertTrue(
104+
Path.of(aligned.getDirectory()).isAbsolute(), "resource directory should be absolute after alignment");
105+
assertEquals("custom-output", aligned.getTargetPath(), "resource targetPath should remain relative");
106+
}
107+
}

0 commit comments

Comments
 (0)