Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,53 @@ class ConnectedResource extends Resource {
.includes(sourceRoot.includes())
.excludes(sourceRoot.excludes())
.filtering(Boolean.toString(sourceRoot.stringFiltering()))
.targetPath(sourceRoot.targetPath().map(Path::toString).orElse(null))
.targetPath(computeRelativeTargetPath(sourceRoot, scope, project))
.build());
this.originalSourceRoot = sourceRoot;
this.scope = scope;
this.project = project;
}

/**
* Computes the targetPath relative to the output directory.
* In Maven 3 API, Resource.getTargetPath() is expected to be relative to the output directory
* (e.g., "custom-output"), while SourceRoot.targetPath() is relative to the project basedir
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while SourceRoot.targetPath() is relative to the project basedir

Actually it was a bug. The specification that we wrote in maven.mdo said that targetPath shall be relative to the target directory. Fixing that bug was the subject of #11322, but that correction was incomplete. We could said that this pull request #11394 completes #11322.

* (e.g., "target/classes/custom-output").
*/
private static String computeRelativeTargetPath(SourceRoot sourceRoot, ProjectScope scope, MavenProject project) {
return sourceRoot
.targetPath()
.map(targetPath -> {
// Get the output directory for this scope
String outputDir = scope == ProjectScope.MAIN
? project.getBuild().getOutputDirectory()
: project.getBuild().getTestOutputDirectory();
Path outputDirPath = Path.of(outputDir);

// If targetPath is absolute, try to make it relative to the output directory
if (targetPath.isAbsolute()) {
if (targetPath.startsWith(outputDirPath)) {
return outputDirPath.relativize(targetPath).toString();
}
return targetPath.toString();
}

// If targetPath is relative, check if it starts with the output directory
// (e.g., "target/classes/custom-output" should become "custom-output")
Path baseDir = project.getBaseDirectory();
Path resolvedTargetPath = baseDir.resolve(targetPath);
Path resolvedOutputDir = baseDir.resolve(outputDirPath);

if (resolvedTargetPath.startsWith(resolvedOutputDir)) {
return resolvedOutputDir.relativize(resolvedTargetPath).toString();
}

// Otherwise, return as-is
return targetPath.toString();
})
.orElse(null);
}

@Override
public void addInclude(String include) {
// Update the underlying Resource model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ void setUp() {
// Set a dummy pom file to establish the base directory
project.setFile(new java.io.File("./pom.xml"));

// Set build output directories
project.getBuild().setOutputDirectory("target/classes");
project.getBuild().setTestOutputDirectory("target/test-classes");

// Add a resource source root to the project
project.addSourceRoot(
new DefaultSourceRoot(ProjectScope.MAIN, Language.RESOURCES, Path.of("src/main/resources")));
Expand Down Expand Up @@ -199,7 +203,7 @@ void testTargetPathPreservedWithConnectedResource() {
resourceWithTarget.setDirectory("src/main/custom");
resourceWithTarget.setTargetPath("custom-output");

// Convert through DefaultSourceRoot to ensure targetPath extraction works
// Convert through DefaultSourceRoot to ensure targetPath is preserved
DefaultSourceRoot sourceRootFromResource =
new DefaultSourceRoot(project.getBaseDirectory(), ProjectScope.MAIN, resourceWithTarget.getDelegate());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,11 @@ void testRelativeTestTargetPath() {
source.targetPath().orElseThrow());
}

/*MNG-11062*/
/*GH-11381*/
@Test
void testExtractsTargetPathFromResource() {
// Test the Resource constructor that was broken in the regression
// Test the Resource constructor with relative targetPath
// targetPath should be kept as relative path
Resource resource = Resource.newBuilder()
.directory("src/test/resources")
.targetPath("test-output")
Expand All @@ -196,13 +197,14 @@ void testExtractsTargetPathFromResource() {

Optional<Path> targetPath = sourceRoot.targetPath();
assertTrue(targetPath.isPresent(), "targetPath should be present");
assertEquals(Path.of("myproject", "test-output"), targetPath.get());
assertEquals(
Path.of("myproject/test-output"), targetPath.get(), "targetPath should be resolved against baseDir");
assertEquals(Path.of("myproject", "src", "test", "resources"), sourceRoot.directory());
assertEquals(ProjectScope.TEST, sourceRoot.scope());
assertEquals(Language.RESOURCES, sourceRoot.language());
}

/*MNG-11062*/
/*GH-11381*/
@Test
void testHandlesNullTargetPathFromResource() {
// Test null targetPath handling
Expand All @@ -216,7 +218,7 @@ void testHandlesNullTargetPathFromResource() {
assertFalse(targetPath.isPresent(), "targetPath should be empty when null");
}

/*MNG-11062*/
/*GH-11381*/
@Test
void testHandlesEmptyTargetPathFromResource() {
// Test empty string targetPath
Expand All @@ -231,7 +233,7 @@ void testHandlesEmptyTargetPathFromResource() {
assertFalse(targetPath.isPresent(), "targetPath should be empty for empty string");
}

/*MNG-11062*/
/*GH-11381*/
@Test
void testHandlesPropertyPlaceholderInTargetPath() {
// Test property placeholder preservation
Expand All @@ -244,10 +246,13 @@ void testHandlesPropertyPlaceholderInTargetPath() {

Optional<Path> targetPath = sourceRoot.targetPath();
assertTrue(targetPath.isPresent(), "Property placeholder targetPath should be present");
assertEquals(Path.of("myproject", "${project.build.directory}/custom"), targetPath.get());
assertEquals(
Path.of("myproject/${project.build.directory}/custom"),
targetPath.get(),
"Property placeholder should be resolved against baseDir");
}

/*MNG-11062*/
/*GH-11381*/
@Test
void testResourceConstructorRequiresNonNullDirectory() {
// Test that null directory throws exception
Expand All @@ -260,7 +265,7 @@ void testResourceConstructorRequiresNonNullDirectory() {
"Should throw exception for null directory");
}

/*MNG-11062*/
/*GH-11381*/
@Test
void testResourceConstructorPreservesOtherProperties() {
// Test that other Resource properties are correctly preserved
Expand All @@ -276,7 +281,9 @@ void testResourceConstructorPreservesOtherProperties() {

// Verify all properties are preserved
assertEquals(
Path.of("myproject", "test-classes"), sourceRoot.targetPath().orElseThrow());
Path.of("myproject/test-classes"),
sourceRoot.targetPath().orElseThrow(),
"targetPath should be resolved against baseDir");
assertTrue(sourceRoot.stringFiltering(), "Filtering should be true");
assertEquals(1, sourceRoot.includes().size());
assertTrue(sourceRoot.includes().contains("*.properties"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.it;

import java.io.File;

import org.junit.jupiter.api.Test;

/**
* This is a test set for <a href="https://github.com/apache/maven/issues/11381">GH-11381</a>.
*
* Verifies that relative targetPath in resources is resolved relative to the output directory
* (target/classes) and not relative to the project base directory, maintaining Maven 3.x behavior.
*
* @since 4.0.0-rc-4
*/
class MavenITgh11381ResourceTargetPathTest extends AbstractMavenIntegrationTestCase {

/**
* Verify that resources with relative targetPath are copied to target/classes/targetPath
* and not to the project root directory.
*
* @throws Exception in case of failure
*/
@Test
void testRelativeTargetPathInResources() throws Exception {
File testDir = extractResources("/gh-11381");

Verifier verifier = newVerifier(testDir.getAbsolutePath());
verifier.setAutoclean(false);
verifier.deleteDirectory("target");
verifier.addCliArgument("process-resources");
verifier.execute();
verifier.verifyErrorFreeLog();

// Verify that resources were copied to target/classes/target-dir (Maven 3.x behavior)
verifier.verifyFilePresent("target/classes/target-dir/test.yml");
verifier.verifyFilePresent("target/classes/target-dir/subdir/another.yml");

// Verify that resources were NOT copied to the project root target-dir directory
verifier.verifyFileNotPresent("target-dir/test.yml");
verifier.verifyFileNotPresent("target-dir/subdir/another.yml");
}
}

44 changes: 44 additions & 0 deletions its/core-it-suite/src/test/resources/gh-11381/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?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.gh11381</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Maven Integration Test :: GH-11381</name>
<description>Test for relative targetPath in resources - should be relative to output directory</description>

<build>
<resources>
<resource>
<directory>${project.basedir}/rest</directory>
<targetPath>target-dir</targetPath>
<includes>
<include>**/*.yml</include>
</includes>
</resource>
</resources>
</build>
</project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Another test YAML file for GH-11381
another:
test: data

4 changes: 4 additions & 0 deletions its/core-it-suite/src/test/resources/gh-11381/rest/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Test YAML file for GH-11381
test:
key: value

Loading