From ba00e3c6caf15e3f45b29d9c6ab0ef34125c1976 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 2 Jun 2026 13:08:02 +0200 Subject: [PATCH] Fix Source.targetPath incorrectly aligned to basedir The DefaultModelPathTranslator was aligning Source.targetPath to the project basedir, converting relative paths like "META-INF/tags/rdc" into absolute paths. This caused maven-resources-plugin to copy resources to wrong locations (e.g., /META-INF/tags/rdc instead of target/classes/META-INF/tags/rdc). The targetPath is relative to the output directory (target/classes or target/test-classes), not the project basedir, so it must not be resolved against basedir during model path translation. Co-Authored-By: Claude Opus 4.6 --- .../maven/project/ProjectBuilderTest.java | 95 ++++++++++++++++ .../resource-target-path/pom.xml | 45 ++++++++ .../source-target-path/pom.xml | 46 ++++++++ .../model/DefaultModelPathTranslator.java | 8 +- .../model/DefaultModelPathTranslatorTest.java | 107 ++++++++++++++++++ 5 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml create mode 100644 impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml create mode 100644 impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java index c79d8cd9aaf6..26df7b1da5c2 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuilderTest.java @@ -996,4 +996,99 @@ void testDuplicateEnabledSources() throws Exception { testJavaRoots.get(0).module().orElse(null), "Test source root should be for com.example.dup module"); } + + @Test + void testResourceTargetPathRemainsRelativeInCompatLayer() throws Exception { + File pom = getProject("resource-target-path"); + + MavenSession mavenSession = createMavenSession(null); + ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest(); + configuration.setRepositorySession(mavenSession.getRepositorySession()); + + ProjectBuildingResult result = getContainer() + .lookup(org.apache.maven.project.ProjectBuilder.class) + .build(pom, configuration); + + MavenProject project = result.getProject(); + + // Verify main resources via SourceRoot API + List mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) + .toList(); + assertEquals(1, mainResources.size()); + assertTrue(mainResources.get(0).targetPath().isPresent(), "Main resource should have a targetPath"); + assertFalse( + mainResources.get(0).targetPath().get().isAbsolute(), + "SourceRoot targetPath must be relative, got: " + + mainResources.get(0).targetPath().get()); + assertEquals( + Path.of("META-INF/tags/rdc"), mainResources.get(0).targetPath().get()); + + // Verify test resources via SourceRoot API + List testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES) + .toList(); + assertEquals(1, testResources.size()); + assertTrue(testResources.get(0).targetPath().isPresent(), "Test resource should have a targetPath"); + assertFalse( + testResources.get(0).targetPath().get().isAbsolute(), + "SourceRoot targetPath must be relative, got: " + + testResources.get(0).targetPath().get()); + assertEquals( + Path.of("org/apache/maven/messages"), + testResources.get(0).targetPath().get()); + + // Verify compat layer: MavenProject.getResources() must return relative targetPath + List resources = project.getResources(); + assertEquals(1, resources.size()); + assertFalse( + Path.of(resources.get(0).getTargetPath()).isAbsolute(), + "Resource targetPath from getResources() must be relative, got: " + + resources.get(0).getTargetPath()); + + // Verify compat layer: MavenProject.getTestResources() must return relative targetPath + List testResourceList = project.getTestResources(); + assertEquals(1, testResourceList.size()); + assertFalse( + Path.of(testResourceList.get(0).getTargetPath()).isAbsolute(), + "Test resource targetPath from getTestResources() must be relative, got: " + + testResourceList.get(0).getTargetPath()); + } + + @Test + void testSourceTargetPathRemainsRelative() throws Exception { + File pom = getProject("source-target-path"); + + MavenSession mavenSession = createMavenSession(null); + ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest(); + configuration.setRepositorySession(mavenSession.getRepositorySession()); + + ProjectBuildingResult result = getContainer() + .lookup(org.apache.maven.project.ProjectBuilder.class) + .build(pom, configuration); + + MavenProject project = result.getProject(); + + // Verify main resources have relative targetPath preserved + List mainResources = project.getEnabledSourceRoots(ProjectScope.MAIN, Language.RESOURCES) + .toList(); + assertEquals(1, mainResources.size()); + assertTrue(mainResources.get(0).targetPath().isPresent()); + assertEquals(Path.of(".grammar"), mainResources.get(0).targetPath().get()); + + // Verify test resources have relative targetPath preserved + List testResources = project.getEnabledSourceRoots(ProjectScope.TEST, Language.RESOURCES) + .toList(); + assertEquals(1, testResources.size()); + assertTrue(testResources.get(0).targetPath().isPresent()); + assertEquals(Path.of("META-INF/test"), testResources.get(0).targetPath().get()); + + // Verify the compat layer also returns relative targetPath + List resources = project.getResources(); + assertEquals(1, resources.size()); + assertEquals(".grammar", resources.get(0).getTargetPath()); + + List testResourceList = project.getTestResources(); + assertEquals(1, testResourceList.size()); + assertEquals( + "META-INF" + File.separator + "test", testResourceList.get(0).getTargetPath()); + } } diff --git a/impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml b/impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml new file mode 100644 index 000000000000..15203c271f36 --- /dev/null +++ b/impl/maven-core/src/test/projects/project-builder/resource-target-path/pom.xml @@ -0,0 +1,45 @@ + + + + + + 4.0.0 + + org.apache.maven.its.mng + resource-target-path-test + 1.0-SNAPSHOT + jar + + + + + src/main/resources + META-INF/tags/rdc + + + + + src/test/resources + org/apache/maven/messages + + + + diff --git a/impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml b/impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml new file mode 100644 index 000000000000..204a01358613 --- /dev/null +++ b/impl/maven-core/src/test/projects/project-builder/source-target-path/pom.xml @@ -0,0 +1,46 @@ + + + + + + 4.1.0 + + org.apache.maven.its.mng + source-target-path-test + 1.0-SNAPSHOT + jar + + + + + resources + src/main/resources + .grammar + + + resources + test + src/test/resources + META-INF/test + + + + diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java index ad4135d94b96..d6164b86bb86 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelPathTranslator.java @@ -134,11 +134,9 @@ private Source alignToBaseDirectory(Source source, Path basedir) { if (newDir != oldDir) { source = source.withDirectory(newDir); } - oldDir = source.getTargetPath(); - newDir = alignToBaseDirectory(oldDir, basedir); - if (newDir != oldDir) { - source = source.withTargetPath(newDir); - } + // targetPath is intentionally NOT aligned to basedir. + // It is relative to the output directory (target/classes or target/test-classes), + // not to the project base directory. } return source; } diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java new file mode 100644 index 000000000000..be71eff2d5ec --- /dev/null +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelPathTranslatorTest.java @@ -0,0 +1,107 @@ +/* + * 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 + * + * http://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. + */ +package org.apache.maven.impl.model; + +import java.nio.file.Path; + +import org.apache.maven.api.model.Build; +import org.apache.maven.api.model.Model; +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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DefaultModelPathTranslatorTest { + + private DefaultModelPathTranslator translator; + private Path basedir; + + @BeforeEach + void setUp() { + translator = new DefaultModelPathTranslator(new DefaultPathTranslator()); + basedir = Path.of("/home/user/myproject").toAbsolutePath(); + } + + @Test + void sourceDirectoryIsAlignedToBasedir() { + Source source = Source.newBuilder().directory("src/main/java").build(); + Model model = Model.newBuilder() + .build(Build.newBuilder().sources(java.util.List.of(source)).build()) + .build(); + + Model result = translator.alignToBaseDirectory(model, basedir, null); + + String dir = result.getBuild().getSources().get(0).getDirectory(); + assertTrue(Path.of(dir).isAbsolute(), "directory should be absolute after alignment"); + assertTrue(Path.of(dir).endsWith(Path.of("src/main/java")), "directory should end with original path"); + } + + @Test + void sourceTargetPathIsNotAlignedToBasedir() { + Source source = Source.newBuilder() + .directory("src/main/resources") + .targetPath("META-INF/resources") + .build(); + Model model = Model.newBuilder() + .build(Build.newBuilder().sources(java.util.List.of(source)).build()) + .build(); + + Model result = translator.alignToBaseDirectory(model, basedir, null); + + String targetPath = result.getBuild().getSources().get(0).getTargetPath(); + assertEquals("META-INF/resources", targetPath, "targetPath should remain relative"); + } + + @Test + void sourceTargetPathDotPrefixRemainsRelative() { + Source source = Source.newBuilder() + .directory("src/main/resources") + .targetPath(".grammar") + .build(); + Model model = Model.newBuilder() + .build(Build.newBuilder().sources(java.util.List.of(source)).build()) + .build(); + + Model result = translator.alignToBaseDirectory(model, basedir, null); + + String targetPath = result.getBuild().getSources().get(0).getTargetPath(); + assertEquals(".grammar", targetPath, "dot-prefixed targetPath should remain relative"); + } + + @Test + void resourceDirectoryIsAlignedButTargetPathIsNot() { + Resource resource = Resource.newBuilder() + .directory("src/main/resources") + .targetPath("custom-output") + .build(); + Model model = Model.newBuilder() + .build(Build.newBuilder().resources(java.util.List.of(resource)).build()) + .build(); + + Model result = translator.alignToBaseDirectory(model, basedir, null); + + Resource aligned = result.getBuild().getResources().get(0); + assertTrue( + Path.of(aligned.getDirectory()).isAbsolute(), "resource directory should be absolute after alignment"); + assertEquals("custom-output", aligned.getTargetPath(), "resource targetPath should remain relative"); + } +}