You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by de...@apache.org on 2025/10/26 17:22:22 UTC
(maven) 03/04: When the value of `` is a relative directory, the specification in `maven.mdo` requires that we resolve against `${project.build.outputDirectory}`, which is not `baseDir`. Also modify the specification for resolving against `${project.build.testOutputDirectory}` if the scope is test and `${project.build.directory}` is the scope is neither main or test.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
commit e69bff99d8efb869b29dfe888302039d01f8333d
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Oct 25 17:08:29 2025 +0200
When the value of `<targetPath>` is a relative directory, the specification in `maven.mdo`
requires that we resolve against `${project.build.outputDirectory}`, which is not `baseDir`.
Also modify the specification for resolving against `${project.build.testOutputDirectory}`
if the scope is test and `${project.build.directory}` is the scope is neither main or test.
---
.../main/java/org/apache/maven/api/SourceRoot.java | 35 +++++++++
.../java/org/apache/maven/api/SourceRootTest.java | 87 ++++++++++++++++++++++
api/maven-api-model/src/main/mdo/maven.mdo | 12 ++-
.../maven/project/DefaultProjectBuilder.java | 12 ++-
.../org/apache/maven/impl/DefaultSourceRoot.java | 16 ++--
.../apache/maven/impl/DefaultSourceRootTest.java | 65 +++++++++++++++-
6 files changed, 217 insertions(+), 10 deletions(-)
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
index 6904b76d84..c8b4d3b771 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/SourceRoot.java
@@ -24,6 +24,9 @@
import java.util.List;
import java.util.Optional;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.model.Build;
+
/**
* A root directory of source files.
* The sources may be Java main classes, test classes, resources or anything else identified by the scope.
@@ -151,6 +154,38 @@ default Optional<Path> targetPath() {
return Optional.empty();
}
+ /**
+ * {@return the explicit target path resolved against the default target path}
+ * If the {@linkplain #targetPath() explicit target path} is present and absolute, then it is returned as-is.
+ * If absent, then the one of the following value is returned by default:
+ *
+ * <ul>
+ * <li>{@link Build#getOutputDirectory()} if the scope is {@link ProjectScope#MAIN},</li>
+ * <li>{@link Build#getTestOutputDirectory()} if the scope is {@link ProjectScope#TEST},</li>
+ * <li>{@link Build#getDirectory()} otherwise.</li>
+ * </ul>
+ *
+ * If the {@linkplain #targetPath() explicit target path} is present but relative,
+ * then it is resolved against the above-cited default directory.
+ *
+ * @param project the project to use for getting default directories
+ */
+ @Nonnull
+ default Path targetPath(@Nonnull Project project) {
+ Build build = project.getBuild();
+ ProjectScope scope = scope();
+ String base;
+ if (scope == ProjectScope.MAIN) {
+ base = build.getOutputDirectory();
+ } else if (scope == ProjectScope.TEST) {
+ base = build.getTestOutputDirectory();
+ } else {
+ base = build.getDirectory();
+ }
+ Path dir = project.getBasedir().resolve(base);
+ return targetPath().map(dir::resolve).orElse(dir);
+ }
+
/**
* {@return whether resources are filtered to replace tokens with parameterized values}
* The default value is {@code false}.
diff --git a/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java b/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
new file mode 100644
index 0000000000..9f65f5d0af
--- /dev/null
+++ b/api/maven-api-core/src/test/java/org/apache/maven/api/SourceRootTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.api;
+
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.Collection;
+import java.util.Optional;
+
+import org.apache.maven.api.model.Build;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SourceRootTest implements SourceRoot {
+ private ProjectScope scope;
+
+ private Language language;
+
+ private String moduleName;
+
+ @Override
+ public ProjectScope scope() {
+ return (scope != null) ? scope : SourceRoot.super.scope();
+ }
+
+ @Override
+ public Language language() {
+ return (language != null) ? language : SourceRoot.super.language();
+ }
+
+ @Override
+ public Optional<String> module() {
+ return Optional.ofNullable(moduleName);
+ }
+
+ @Override
+ public PathMatcher matcher(Collection<String> defaultIncludes, boolean useDefaultExcludes) {
+ return null; // Not used for this test.
+ }
+
+ @Test
+ void testDirectory() {
+ assertEquals(Path.of("src", "main", "java"), directory());
+
+ scope = ProjectScope.TEST;
+ assertEquals(Path.of("src", "test", "java"), directory());
+
+ moduleName = "org.foo";
+ assertEquals(Path.of("src", "org.foo", "test", "java"), directory());
+ }
+
+ @Test
+ void testTargetPath() {
+ Build build = mock(Build.class);
+ when(build.getDirectory()).thenReturn("target");
+ when(build.getOutputDirectory()).thenReturn("target/classes");
+ when(build.getTestOutputDirectory()).thenReturn("target/test-classes");
+
+ Project project = mock(Project.class);
+ when(project.getBuild()).thenReturn(build);
+ when(project.getBasedir()).thenReturn(Path.of("myproject"));
+
+ assertEquals(Path.of("myproject", "target", "classes"), targetPath(project));
+
+ scope = ProjectScope.TEST;
+ assertEquals(Path.of("myproject", "target", "test-classes"), targetPath(project));
+ }
+}
diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo
index 83c3c06534..3a18900ac3 100644
--- a/api/maven-api-model/src/main/mdo/maven.mdo
+++ b/api/maven-api-model/src/main/mdo/maven.mdo
@@ -2160,8 +2160,16 @@
<description>
<![CDATA[
Specifies an explicit target path, overriding the default value.
- The path is relative to the {@code ${project.build.outputDirectory}} directory,
- which is typically {@code target/classes} in a Java project.
+ If unspecified, then the default value is one of the following:
+
+ <ul>
+ <li>{@code ${project.build.outputDirectory}} (typically {@code target/classes}) if {@code scope} is "main",</li>
+ <li>{@code ${project.build.testOutputDirectory}} (typically {@code target/test-classes}) if {@code scope} is "test",</li>
+ <li>{@code ${project.build.directory}} (typically {@code target}) otherwise.</li>
+ </ul>
+
+ <p>If this property is specified but is a relative path,
+ then the path is resolved against the above-cited default value.</p>
<p>When a target path is explicitly specified, the values of the {@code module} and {@code targetVersion}
elements are not used for inferring the path (they are still used as compiler options however).
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index f18d590b61..245d85117d 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -40,6 +40,7 @@
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -650,11 +651,20 @@ private void initProject(MavenProject project, ModelBuilderResult result) {
Build build = project.getBuild().getDelegate();
List<org.apache.maven.api.model.Source> sources = build.getSources();
Path baseDir = project.getBaseDirectory();
+ Function<ProjectScope, String> outputDirectory = (scope) -> {
+ if (scope == ProjectScope.MAIN) {
+ return build.getOutputDirectory();
+ } else if (scope == ProjectScope.TEST) {
+ return build.getTestOutputDirectory();
+ } else {
+ return build.getDirectory();
+ }
+ };
boolean hasScript = false;
boolean hasMain = false;
boolean hasTest = false;
for (var source : sources) {
- var src = DefaultSourceRoot.fromModel(session, baseDir, source);
+ var src = DefaultSourceRoot.fromModel(session, baseDir, outputDirectory, source);
project.addSourceRoot(src);
Language language = src.language();
if (Language.JAVA_FAMILY.equals(language)) {
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 57994c0fe1..6ffec2ce88 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
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.Function;
import org.apache.maven.api.Language;
import org.apache.maven.api.ProjectScope;
@@ -114,11 +115,13 @@ public DefaultSourceRoot(
/**
* Creates a new instance from the given model.
*
- * @param session the session of resolving extensible enumerations
- * @param baseDir the base directory for resolving relative paths
- * @param source a source element from the model
+ * @param session the session of resolving extensible enumerations
+ * @param baseDir the base directory for resolving relative paths
+ * @param outputDir supplier of output directory relative to {@code baseDir}
+ * @param source a source element from the model
*/
- public static DefaultSourceRoot fromModel(final Session session, final Path baseDir, final Source source) {
+ public static DefaultSourceRoot fromModel(
+ Session session, Path baseDir, Function<ProjectScope, String> outputDir, Source source) {
ProjectScope scope =
nonBlank(source.getScope()).map(session::requireProjectScope).orElse(ProjectScope.MAIN);
Language language =
@@ -139,7 +142,10 @@ public static DefaultSourceRoot fromModel(final Session session, final Path base
source.getIncludes(),
source.getExcludes(),
source.isStringFiltering(),
- nonBlank(source.getTargetPath()).map(baseDir::resolve).orElse(null),
+ nonBlank(source.getTargetPath())
+ .map((targetPath) ->
+ baseDir.resolve(outputDir.apply(scope)).resolve(targetPath))
+ .orElse(null),
source.isEnabled());
}
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 446a0315e9..6ceedfea56 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
@@ -21,6 +21,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
+import java.util.function.Function;
import org.apache.maven.api.Language;
import org.apache.maven.api.ProjectScope;
@@ -56,10 +57,28 @@ public void setup() {
stub.when(session.requireLanguage(eq("resources"))).thenReturn(Language.RESOURCES);
}
+ /**
+ * Returns the output directory relative to the base directory.
+ */
+ private static Function<ProjectScope, String> outputDirectory() {
+ return (scope) -> {
+ if (scope == ProjectScope.MAIN) {
+ return "target/classes";
+ } else if (scope == ProjectScope.TEST) {
+ return "target/test-classes";
+ } else {
+ return "target";
+ }
+ };
+ }
+
@Test
void testMainJavaDirectory() {
var source = DefaultSourceRoot.fromModel(
- session, Path.of("myproject"), Source.newBuilder().build());
+ session,
+ Path.of("myproject"),
+ outputDirectory(),
+ Source.newBuilder().build());
assertTrue(source.module().isEmpty());
assertEquals(ProjectScope.MAIN, source.scope());
@@ -71,7 +90,10 @@ void testMainJavaDirectory() {
@Test
void testTestJavaDirectory() {
var source = DefaultSourceRoot.fromModel(
- session, Path.of("myproject"), Source.newBuilder().scope("test").build());
+ session,
+ Path.of("myproject"),
+ outputDirectory(),
+ Source.newBuilder().scope("test").build());
assertTrue(source.module().isEmpty());
assertEquals(ProjectScope.TEST, source.scope());
@@ -85,6 +107,7 @@ void testTestResourceDirectory() {
var source = DefaultSourceRoot.fromModel(
session,
Path.of("myproject"),
+ outputDirectory(),
Source.newBuilder().scope("test").lang("resources").build());
assertTrue(source.module().isEmpty());
@@ -99,6 +122,7 @@ void testModuleMainDirectory() {
var source = DefaultSourceRoot.fromModel(
session,
Path.of("myproject"),
+ outputDirectory(),
Source.newBuilder().module("org.foo.bar").build());
assertEquals("org.foo.bar", source.module().orElseThrow());
@@ -113,6 +137,7 @@ void testModuleTestDirectory() {
var source = DefaultSourceRoot.fromModel(
session,
Path.of("myproject"),
+ outputDirectory(),
Source.newBuilder().module("org.foo.bar").scope("test").build());
assertEquals("org.foo.bar", source.module().orElseThrow());
@@ -122,6 +147,42 @@ void testModuleTestDirectory() {
assertTrue(source.targetVersion().isEmpty());
}
+ /**
+ * Tests that relative target paths are resolved against the right base directory.
+ */
+ @Test
+ void testRelativeMainTargetPath() {
+ var source = DefaultSourceRoot.fromModel(
+ session,
+ Path.of("myproject"),
+ outputDirectory(),
+ Source.newBuilder().targetPath("user-output").build());
+
+ assertEquals(ProjectScope.MAIN, source.scope());
+ assertEquals(Language.JAVA_FAMILY, source.language());
+ assertEquals(
+ Path.of("myproject", "target", "classes", "user-output"),
+ source.targetPath().orElseThrow());
+ }
+
+ /**
+ * Tests that relative target paths are resolved against the right base directory.
+ */
+ @Test
+ void testRelativeTestTargetPath() {
+ var source = DefaultSourceRoot.fromModel(
+ session,
+ Path.of("myproject"),
+ outputDirectory(),
+ Source.newBuilder().targetPath("user-output").scope("test").build());
+
+ assertEquals(ProjectScope.TEST, source.scope());
+ assertEquals(Language.JAVA_FAMILY, source.language());
+ assertEquals(
+ Path.of("myproject", "target", "test-classes", "user-output"),
+ source.targetPath().orElseThrow());
+ }
+
/*MNG-11062*/
@Test
void testExtractsTargetPathFromResource() {