Skip to content

Commit 7d9a345

Browse files
gnodetclaude
andcommitted
Fix Source targetPath incorrectly aligned to project basedir (PR #12178)
Cherry-pick from fix/resource-targetpath branch (squashed commit). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 74b04b7 commit 7d9a345

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
@@ -1000,4 +1000,99 @@ void testDuplicateEnabledSources() throws Exception {
10001000
testJavaRoots.get(0).module().orElse(null),
10011001
"Test source root should be for com.example.dup module");
10021002
}
1003+
1004+
@Test
1005+
void testSourceTargetPathRemainsRelative() throws Exception {
1006+
File pom = getProject("source-target-path");
1007+
1008+
MavenSession mavenSession = createMavenSession(null);
1009+
ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest();
1010+
configuration.setRepositorySession(mavenSession.getRepositorySession());
1011+
1012+
ProjectBuildingResult result = getContainer()
1013+
.lookup(org.apache.maven.project.ProjectBuilder.class)
1014+
.build(pom, configuration);
1015+
1016+
MavenProject project = result.getProject();
1017+
1018+
// Verify main resources have relative targetPath preserved
1019+
List<SourceRoot> mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
1020+
.toList();
1021+
assertEquals(1, mainResources.size());
1022+
assertTrue(mainResources.get(0).targetPath().isPresent());
1023+
assertEquals(Path.of(".grammar"), mainResources.get(0).targetPath().get());
1024+
1025+
// Verify test resources have relative targetPath preserved
1026+
List<SourceRoot> testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)
1027+
.toList();
1028+
assertEquals(1, testResources.size());
1029+
assertTrue(testResources.get(0).targetPath().isPresent());
1030+
assertEquals(Path.of("META-INF/test"), testResources.get(0).targetPath().get());
1031+
1032+
// Verify the compat layer also returns relative targetPath
1033+
List<org.apache.maven.model.Resource> resources = project.getResources();
1034+
assertEquals(1, resources.size());
1035+
assertEquals(".grammar", resources.get(0).getTargetPath());
1036+
1037+
List<org.apache.maven.model.Resource> testResourceList = project.getTestResources();
1038+
assertEquals(1, testResourceList.size());
1039+
assertEquals(
1040+
"META-INF" + File.separator + "test", testResourceList.get(0).getTargetPath());
1041+
}
1042+
1043+
@Test
1044+
void testResourceTargetPathRemainsRelativeInCompatLayer() throws Exception {
1045+
File pom = getProject("resource-target-path");
1046+
1047+
MavenSession mavenSession = createMavenSession(null);
1048+
ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest();
1049+
configuration.setRepositorySession(mavenSession.getRepositorySession());
1050+
1051+
ProjectBuildingResult result = getContainer()
1052+
.lookup(org.apache.maven.project.ProjectBuilder.class)
1053+
.build(pom, configuration);
1054+
1055+
MavenProject project = result.getProject();
1056+
1057+
// Verify main resources via SourceRoot API
1058+
List<SourceRoot> mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES)
1059+
.toList();
1060+
assertEquals(1, mainResources.size());
1061+
assertTrue(mainResources.get(0).targetPath().isPresent());
1062+
assertFalse(
1063+
mainResources.get(0).targetPath().get().isAbsolute(),
1064+
"SourceRoot targetPath must be relative, got: "
1065+
+ mainResources.get(0).targetPath().get());
1066+
assertEquals(
1067+
Path.of("META-INF/tags/rdc"), mainResources.get(0).targetPath().get());
1068+
1069+
// Verify test resources via SourceRoot API
1070+
List<SourceRoot> testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES)
1071+
.toList();
1072+
assertEquals(1, testResources.size());
1073+
assertTrue(testResources.get(0).targetPath().isPresent());
1074+
assertFalse(
1075+
testResources.get(0).targetPath().get().isAbsolute(),
1076+
"SourceRoot targetPath must be relative, got: "
1077+
+ testResources.get(0).targetPath().get());
1078+
assertEquals(
1079+
Path.of("org/apache/maven/messages"),
1080+
testResources.get(0).targetPath().get());
1081+
1082+
// Verify compat layer: MavenProject.getResources() must return relative targetPath
1083+
List<org.apache.maven.model.Resource> resources = project.getResources();
1084+
assertEquals(1, resources.size());
1085+
assertEquals(
1086+
"META-INF" + File.separator + "tags" + File.separator + "rdc",
1087+
resources.get(0).getTargetPath(),
1088+
"Resource targetPath from getResources() must remain relative");
1089+
1090+
// Verify compat layer: MavenProject.getTestResources() must return relative targetPath
1091+
List<org.apache.maven.model.Resource> testResourceList = project.getTestResources();
1092+
assertEquals(1, testResourceList.size());
1093+
assertEquals(
1094+
"org" + File.separator + "apache" + File.separator + "maven" + File.separator + "messages",
1095+
testResourceList.get(0).getTargetPath(),
1096+
"Test resource targetPath from getTestResources() must remain relative");
1097+
}
10031098
}
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)