You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2023/04/20 10:58:23 UTC
[maven] branch master updated: [MNG-7038] Introduce public properties to point to the root and top directories of (multi-module) project (#1061)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new 2db7c85b6 [MNG-7038] Introduce public properties to point to the root and top directories of (multi-module) project (#1061)
2db7c85b6 is described below
commit 2db7c85b6494682363c22180f07010813f25b4ed
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Apr 20 12:58:12 2023 +0200
[MNG-7038] Introduce public properties to point to the root and top directories of (multi-module) project (#1061)
This commit introduces three properties:
* project.rootDirectory: the project's directory or parent directory containing a .mvn subdirectory or a pom.xml flagged with the root="true" attribute. If no such directory can be found, accessing the rootDirectory property will throw an IllegalStateException.
* session.topDirectory : the directory of the topmost project being built, usually the current directory or the directory pointed at by the -f/--file command line argument. The topDirectory is similar to the executionRootDirectory property available on the session, but renamed to make it coherent with the new rootDirectory and to avoid using root in its name. The topDirectory property is computed by the CLI as the directory pointed at by the -f/--file command line argument, or the cu [...]
* session.rootDirectory : the rootDirectory for the topDirectory project.
The topDirectory and rootDirectory properties are made available on the MavenSession / Session and deprecate the executionRootDirectory and multiModuleProjectDirectory properties. The rootDirectory should never change for a given project and is thus made available for profile activation and model interpolation (without the project. prefix, similar to basedir). The goal is also to make the rootDirectory property also available during command line arguments interpolation.
A root boolean attribute is also added to the model to indicate that the project is the root project. This attribute is only supported if the buildconsumer feature is active and removed before the pom is installed or deployed. It can be used as an alternative mechanism to the .mvn directory.
---
.../main/java/org/apache/maven/api/Project.java | 37 +++++++
.../main/java/org/apache/maven/api/Session.java | 29 ++++-
api/maven-api-model/src/main/mdo/maven.mdo | 13 +++
.../apache/maven/project/TestProjectBuilder.java | 7 +-
.../execution/DefaultMavenExecutionRequest.java | 33 ++++++
.../maven/execution/MavenExecutionRequest.java | 47 ++++++++
.../org/apache/maven/execution/MavenSession.java | 21 ++++
.../apache/maven/internal/impl/DefaultProject.java | 16 +++
.../apache/maven/internal/impl/DefaultSession.java | 13 ++-
.../ConsumerPomArtifactTransformer.java | 3 +
.../maven/project/DefaultProjectBuilder.java | 14 ++-
.../org/apache/maven/project/MavenProject.java | 20 ++++
.../maven/internal/impl/DefaultSessionTest.java | 62 +++++++++++
.../PluginParameterExpressionEvaluatorTest.java | 32 ++++++
... PluginParameterExpressionEvaluatorV4Test.java} | 119 +++++++++++++++------
.../org/apache/maven/cli/CLIReportingUtils.java | 7 ++
.../main/java/org/apache/maven/cli/CliRequest.java | 5 +
.../main/java/org/apache/maven/cli/MavenCli.java | 54 ++++++++++
.../java/org/apache/maven/cli/MavenCliTest.java | 9 ++
.../src/test/projects/root-attribute/child/pom.xml | 3 +
.../src/test/projects/root-attribute/pom.xml | 3 +
.../model/building/DefaultModelBuilderFactory.java | 18 +++-
.../AbstractStringBasedModelInterpolator.java | 22 +++-
.../StringVisitorModelInterpolator.java | 6 +-
.../ProfileActivationFilePathInterpolator.java | 19 +++-
.../maven/model/root/DefaultRootLocator.java | 59 ++++++++++
.../org/apache/maven/model/root/RootLocator.java | 69 ++++++++++++
.../org.apache.maven.model.root.RootLocator | 1 +
maven-model-builder/src/site/apt/index.apt | 16 +--
.../AbstractModelInterpolatorTest.java | 47 +++++++-
.../StringVisitorModelInterpolatorTest.java | 2 +-
.../activation/FileProfileActivatorTest.java | 27 ++++-
.../RawToConsumerPomXMLFilterFactory.java | 2 +
...PomXMLFilterFactory.java => RootXMLFilter.java} | 41 +++----
.../model/transform/pull/BufferingParser.java | 10 ++
src/mdo/reader-ex.vm | 2 +-
src/mdo/reader-modified.vm | 2 +-
src/mdo/reader.vm | 14 +--
src/mdo/writer-ex.vm | 9 +-
src/mdo/writer.vm | 9 +-
40 files changed, 833 insertions(+), 89 deletions(-)
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
index 7d80f3ae4..256fe9479 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
@@ -86,8 +86,45 @@ public interface Project {
return getModel().getId();
}
+ /**
+ * @deprecated use {@link #isTopProject()} instead
+ */
+ @Deprecated
boolean isExecutionRoot();
+ /**
+ * Returns a boolean indicating if the project is the top level project for
+ * this reactor build. The top level project may be different from the
+ * {@code rootDirectory}, especially if a subtree of the project is being
+ * built, either because Maven has been launched in a subdirectory or using
+ * a {@code -f} option.
+ *
+ * @return {@code true} if the project is the top level project for this build
+ */
+ boolean isTopProject();
+
+ /**
+ * Returns a boolean indicating if the project is a root project,
+ * meaning that the {@link #getRootDirectory()} and {@link #getBasedir()}
+ * points to the same directory, and that either {@link Model#isRoot()}
+ * is {@code true} or that {@code basedir} contains a {@code .mvn} child
+ * directory.
+ *
+ * @return {@code true} if the project is the root project
+ * @see Model#isRoot()
+ */
+ boolean isRootProject();
+
+ /**
+ * Gets the root directory of the project, which is the parent directory
+ * containing the {@code .mvn} directory or flagged with {@code root="true"}.
+ *
+ * @throws IllegalStateException if the root directory could not be found
+ * @see Session#getRootDirectory()
+ */
+ @Nonnull
+ Path getRootDirectory();
+
@Nonnull
Optional<Project> getParent();
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java
index 5616c3dd9..32baea23e 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Session.java
@@ -84,9 +84,34 @@ public interface Session {
@Nonnull
Instant getStartTime();
+ /**
+ * Gets the directory of the topmost project being built, usually the current directory or the
+ * directory pointed at by the {@code -f/--file} command line argument.
+ */
+ @Nonnull
+ Path getTopDirectory();
+
+ /**
+ * Gets the root directory of the session, which is the root directory for the top directory project.
+ *
+ * @throws IllegalStateException if the root directory could not be found
+ * @see #getTopDirectory()
+ * @see Project#getRootDirectory()
+ */
@Nonnull
+ Path getRootDirectory();
+
+ /**
+ * @deprecated use {@link #getRootDirectory()} instead
+ */
+ @Nonnull
+ @Deprecated
Path getMultiModuleProjectDirectory();
+ /**
+ * @deprecated use {@link #getTopDirectory()} instead
+ */
+ @Deprecated
@Nonnull
Path getExecutionRootDirectory();
@@ -97,8 +122,8 @@ public interface Session {
* Returns the plugin context for mojo being executed and the specified
* {@link Project}, never returns {@code null} as if context not present, creates it.
*
- * <strong>Implementation note:</strong> while this method return type is {@link Map}, the returned map instance
- * implements {@link java.util.concurrent.ConcurrentMap} as well.
+ * <strong>Implementation note:</strong> while this method return type is {@link Map}, the
+ * returned map instance implements {@link java.util.concurrent.ConcurrentMap} as well.
*
* @throws org.apache.maven.api.services.MavenException if not called from the within a mojo execution
*/
diff --git a/api/maven-api-model/src/main/mdo/maven.mdo b/api/maven-api-model/src/main/mdo/maven.mdo
index a42a65531..fc8418fb6 100644
--- a/api/maven-api-model/src/main/mdo/maven.mdo
+++ b/api/maven-api-model/src/main/mdo/maven.mdo
@@ -212,6 +212,19 @@
</description>
<type>String</type>
</field>
+ <field xml.attribute="true" xml.tagName="root">
+ <name>root</name>
+ <version>4.0.0+</version>
+ <description>
+ <![CDATA[
+ Indicates that this project is the root project, located in the upper directory of the source tree.
+ This is the directory which may contain the .mvn directory.
+ <br><b>Since</b>: Maven 4.0.0
+ ]]>
+ </description>
+ <type>boolean</type>
+ <defaultValue>false</defaultValue>
+ </field>
<field>
<name>inceptionYear</name>
<version>3.0.0+</version>
diff --git a/maven-compat/src/test/java/org/apache/maven/project/TestProjectBuilder.java b/maven-compat/src/test/java/org/apache/maven/project/TestProjectBuilder.java
index 6a3f15509..612d8e7dd 100644
--- a/maven-compat/src/test/java/org/apache/maven/project/TestProjectBuilder.java
+++ b/maven-compat/src/test/java/org/apache/maven/project/TestProjectBuilder.java
@@ -29,6 +29,7 @@ import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.bridge.MavenRepositorySystem;
import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.model.building.ModelProcessor;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.repository.internal.ModelCacheFactory;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.impl.RemoteRepositoryManager;
@@ -45,7 +46,8 @@ public class TestProjectBuilder extends DefaultProjectBuilder {
RepositorySystem repoSystem,
RemoteRepositoryManager repositoryManager,
ProjectDependenciesResolver dependencyResolver,
- ModelCacheFactory modelCacheFactory) {
+ ModelCacheFactory modelCacheFactory,
+ RootLocator rootLocator) {
super(
modelBuilder,
modelProcessor,
@@ -54,7 +56,8 @@ public class TestProjectBuilder extends DefaultProjectBuilder {
repoSystem,
repositoryManager,
dependencyResolver,
- modelCacheFactory);
+ modelCacheFactory,
+ rootLocator);
}
@Override
diff --git a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
index e0a073b91..310574f37 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
@@ -19,6 +19,7 @@
package org.apache.maven.execution;
import java.io.File;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -30,6 +31,7 @@ import java.util.Properties;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.model.Profile;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.properties.internal.SystemProperties;
@@ -102,6 +104,10 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
private File basedir;
+ private Path rootDirectory;
+
+ private Path topDirectory;
+
private List<String> goals;
private boolean useReactor = false;
@@ -1051,16 +1057,43 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
return this;
}
+ @Deprecated
@Override
public void setMultiModuleProjectDirectory(File directory) {
this.multiModuleProjectDirectory = directory;
}
+ @Deprecated
@Override
public File getMultiModuleProjectDirectory() {
return multiModuleProjectDirectory;
}
+ @Override
+ public Path getRootDirectory() {
+ if (rootDirectory == null) {
+ throw new IllegalStateException(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE);
+ }
+ return rootDirectory;
+ }
+
+ @Override
+ public MavenExecutionRequest setRootDirectory(Path rootDirectory) {
+ this.rootDirectory = rootDirectory;
+ return this;
+ }
+
+ @Override
+ public Path getTopDirectory() {
+ return topDirectory;
+ }
+
+ @Override
+ public MavenExecutionRequest setTopDirectory(Path topDirectory) {
+ this.topDirectory = topDirectory;
+ return this;
+ }
+
@Override
public MavenExecutionRequest setEventSpyDispatcher(EventSpyDispatcher eventSpyDispatcher) {
this.eventSpyDispatcher = eventSpyDispatcher;
diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
index 9d86df5e8..66a49a1f2 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
@@ -19,6 +19,7 @@
package org.apache.maven.execution;
import java.io.File;
+import java.nio.file.Path;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -91,8 +92,17 @@ public interface MavenExecutionRequest {
// ----------------------------------------------------------------------
// Base directory
+
+ /**
+ * @deprecated use {@link #setTopDirectory(Path)} instead
+ */
+ @Deprecated
MavenExecutionRequest setBaseDirectory(File basedir);
+ /**
+ * @deprecated use {@link #getTopDirectory()} instead
+ */
+ @Deprecated
String getBaseDirectory();
// Timing (remove this)
@@ -494,14 +504,51 @@ public interface MavenExecutionRequest {
/**
* @since 3.3.0
+ * @deprecated use {@link #setRootDirectory(Path)} instead
*/
+ @Deprecated
void setMultiModuleProjectDirectory(File file);
/**
* @since 3.3.0
+ * @deprecated use {@link #getRootDirectory()} instead
*/
+ @Deprecated
File getMultiModuleProjectDirectory();
+ /**
+ * Sets the top directory of the project.
+ *
+ * @since 4.0.0
+ */
+ MavenExecutionRequest setTopDirectory(Path topDirectory);
+
+ /**
+ * Gets the directory of the topmost project being built, usually the current directory or the
+ * directory pointed at by the {@code -f/--file} command line argument.
+ *
+ * @since 4.0.0
+ */
+ Path getTopDirectory();
+
+ /**
+ * Sets the root directory of the project.
+ *
+ * @since 4.0.0
+ */
+ MavenExecutionRequest setRootDirectory(Path rootDirectory);
+
+ /**
+ * Gets the root directory of the top project, which is the parent directory containing the {@code .mvn}
+ * directory or a {@code pom.xml} file with the {@code root="true"} attribute.
+ * If there's no such directory, an {@code IllegalStateException} will be thrown.
+ *
+ * @throws IllegalStateException if the root directory could not be found
+ * @see #getTopDirectory()
+ * @since 4.0.0
+ */
+ Path getRootDirectory();
+
/**
* @since 3.3.0
*/
diff --git a/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java b/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
index bd4e34ad5..fc430696e 100644
--- a/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
+++ b/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
@@ -19,6 +19,7 @@
package org.apache.maven.execution;
import java.io.File;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -141,10 +142,30 @@ public class MavenSession implements Cloneable {
return projects;
}
+ /**
+ * @deprecated use {@link #getTopDirectory()} ()}
+ */
+ @Deprecated
public String getExecutionRootDirectory() {
return request.getBaseDirectory();
}
+ /**
+ * @see MavenExecutionRequest#getTopDirectory()
+ * @since 4.0.0
+ */
+ public Path getTopDirectory() {
+ return request.getTopDirectory();
+ }
+
+ /**
+ * @see MavenExecutionRequest#getRootDirectory()
+ * @since 4.0.0
+ */
+ public Path getRootDirectory() {
+ return request.getRootDirectory();
+ }
+
public MavenExecutionRequest getRequest() {
return request;
}
diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java
index 0951f26f8..c234608ff 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java
@@ -129,6 +129,22 @@ public class DefaultProject implements Project {
return project.isExecutionRoot();
}
+ @Override
+ public boolean isTopProject() {
+ return getBasedir().isPresent()
+ && getBasedir().get().equals(getSession().getTopDirectory());
+ }
+
+ @Override
+ public boolean isRootProject() {
+ return getBasedir().isPresent() && getBasedir().get().equals(getRootDirectory());
+ }
+
+ @Override
+ public Path getRootDirectory() {
+ return project.getRootDirectory();
+ }
+
@Override
public Optional<Project> getParent() {
MavenProject parent = project.getParent();
diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java
index 05a72ff97..011710778 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java
@@ -19,7 +19,6 @@
package org.apache.maven.internal.impl;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
@@ -150,7 +149,17 @@ public class DefaultSession extends AbstractSession {
@Nonnull
@Override
public Path getExecutionRootDirectory() {
- return Paths.get(mavenSession.getRequest().getBaseDirectory());
+ return getTopDirectory();
+ }
+
+ @Override
+ public Path getRootDirectory() {
+ return mavenSession.getRequest().getRootDirectory();
+ }
+
+ @Override
+ public Path getTopDirectory() {
+ return mavenSession.getRequest().getTopDirectory();
}
@Nonnull
diff --git a/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java b/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
index 1b6c5a8d0..eabbe03f7 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/transformation/ConsumerPomArtifactTransformer.java
@@ -74,6 +74,9 @@ public final class ConsumerPomArtifactTransformer {
generatedFile = Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER, "pom");
}
project.addAttachedArtifact(new ConsumerPomArtifact(project, generatedFile, session));
+ } else if (project.getModel().isRoot()) {
+ throw new IllegalStateException(
+ "The use of the root attribute on the model requires the buildconsumer feature to be active");
}
}
diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index 5bb9dab0f..027ca74ee 100644
--- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -69,6 +69,7 @@ import org.apache.maven.model.building.StringModelSource;
import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.building.TransformerContextBuilder;
import org.apache.maven.model.resolution.ModelResolver;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.repository.internal.ArtifactDescriptorUtils;
import org.apache.maven.repository.internal.ModelCacheFactory;
import org.codehaus.plexus.util.Os;
@@ -101,6 +102,8 @@ public class DefaultProjectBuilder implements ProjectBuilder {
private final ProjectDependenciesResolver dependencyResolver;
private final ModelCacheFactory modelCacheFactory;
+ private final RootLocator rootLocator;
+
@SuppressWarnings("checkstyle:ParameterNumber")
@Inject
public DefaultProjectBuilder(
@@ -111,7 +114,8 @@ public class DefaultProjectBuilder implements ProjectBuilder {
RepositorySystem repoSystem,
RemoteRepositoryManager repositoryManager,
ProjectDependenciesResolver dependencyResolver,
- ModelCacheFactory modelCacheFactory) {
+ ModelCacheFactory modelCacheFactory,
+ RootLocator rootLocator) {
this.modelBuilder = modelBuilder;
this.modelProcessor = modelProcessor;
this.projectBuildingHelper = projectBuildingHelper;
@@ -120,6 +124,7 @@ public class DefaultProjectBuilder implements ProjectBuilder {
this.repositoryManager = repositoryManager;
this.dependencyResolver = dependencyResolver;
this.modelCacheFactory = modelCacheFactory;
+ this.rootLocator = rootLocator;
}
// ----------------------------------------------------------------------
// MavenProjectBuilder Implementation
@@ -162,6 +167,11 @@ public class DefaultProjectBuilder implements ProjectBuilder {
request.setModelSource(modelSource);
request.setLocationTracking(true);
+ if (pomFile != null) {
+ project.setRootDirectory(
+ rootLocator.findRoot(pomFile.getParentFile().toPath()));
+ }
+
ModelBuildingResult result;
try {
result = modelBuilder.build(request);
@@ -445,6 +455,8 @@ public class DefaultProjectBuilder implements ProjectBuilder {
MavenProject project = new MavenProject();
project.setFile(pomFile);
+ project.setRootDirectory(rootLocator.findRoot(pomFile.getParentFile().toPath()));
+
ModelBuildingRequest request = getModelBuildingRequest(config)
.setPomFile(pomFile)
.setTwoPhaseBuilding(true)
diff --git a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
index b024001bf..84a4090b2 100644
--- a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
+++ b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
@@ -21,6 +21,7 @@ package org.apache.maven.project;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -64,6 +65,7 @@ import org.apache.maven.model.Repository;
import org.apache.maven.model.Resource;
import org.apache.maven.model.Scm;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.apache.maven.project.artifact.MavenMetadataSource;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
@@ -105,6 +107,8 @@ public class MavenProject implements Cloneable {
private File basedir;
+ private Path rootDirectory;
+
private Set<Artifact> resolvedArtifacts;
private ArtifactFilter artifactFilter;
@@ -1679,4 +1683,20 @@ public class MavenProject implements Cloneable {
public void setProjectBuildingRequest(ProjectBuildingRequest projectBuildingRequest) {
this.projectBuilderConfiguration = projectBuildingRequest;
}
+
+ /**
+ * @since 4.0.0
+ * @return the rootDirectory for this project
+ * @throws IllegalStateException if the rootDirectory cannot be found
+ */
+ public Path getRootDirectory() {
+ if (rootDirectory == null) {
+ throw new IllegalStateException(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE);
+ }
+ return rootDirectory;
+ }
+
+ public void setRootDirectory(Path rootDirectory) {
+ this.rootDirectory = rootDirectory;
+ }
}
diff --git a/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultSessionTest.java b/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultSessionTest.java
new file mode 100644
index 000000000..dd516c91a
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultSessionTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.internal.impl;
+
+import java.nio.file.Paths;
+import java.util.Collections;
+
+import org.apache.maven.execution.DefaultMavenExecutionRequest;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.root.RootLocator;
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class DefaultSessionTest {
+
+ @Test
+ void testRootDirectoryWithNull() {
+ RepositorySystemSession rss = MavenRepositorySystemUtils.newSession();
+ DefaultMavenExecutionRequest mer = new DefaultMavenExecutionRequest();
+ MavenSession ms = new MavenSession(null, rss, mer, null);
+ DefaultSession session =
+ new DefaultSession(ms, new DefaultRepositorySystem(), Collections.emptyList(), null, null, null);
+
+ assertEquals(
+ RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE,
+ assertThrows(IllegalStateException.class, session::getRootDirectory)
+ .getMessage());
+ }
+
+ @Test
+ void testRootDirectory() {
+ RepositorySystemSession rss = MavenRepositorySystemUtils.newSession();
+ DefaultMavenExecutionRequest mer = new DefaultMavenExecutionRequest();
+ MavenSession ms = new MavenSession(null, rss, mer, null);
+ ms.getRequest().setRootDirectory(Paths.get("myRootDirectory"));
+ DefaultSession session =
+ new DefaultSession(ms, new DefaultRepositorySystem(), Collections.emptyList(), null, null, null);
+
+ assertEquals(Paths.get("myRootDirectory"), session.getRootDirectory());
+ }
+}
diff --git a/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java b/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java
index 39db5828a..c01c6f56c 100644
--- a/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java
+++ b/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java
@@ -21,6 +21,9 @@ package org.apache.maven.plugin;
import javax.inject.Inject;
import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -38,6 +41,7 @@ import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.DuplicateProjectException;
@@ -51,8 +55,11 @@ import org.junit.jupiter.api.Test;
import static org.codehaus.plexus.testing.PlexusExtension.getTestFile;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -64,6 +71,8 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
@Inject
private RepositorySystem factory;
+ private Path rootDirectory;
+
@Test
void testPluginDescriptorExpressionReference() throws Exception {
MojoExecution exec = newMojoExecution();
@@ -357,6 +366,28 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
assertEquals("testGroup", result.getGroupId());
}
+ @Test
+ void testRootDirectoryNotPrefixed() throws Exception {
+ ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
+ assertNull(ee.evaluate("${rootDirectory}"));
+ }
+
+ @Test
+ void testRootDirectoryWithNull() throws Exception {
+ ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
+ Exception e = assertThrows(Exception.class, () -> ee.evaluate("${session.rootDirectory}"));
+ e = assertInstanceOf(InvocationTargetException.class, e.getCause());
+ e = assertInstanceOf(IllegalStateException.class, e.getCause());
+ assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
+ }
+
+ @Test
+ void testRootDirectory() throws Exception {
+ this.rootDirectory = Paths.get("myRootDirectory");
+ ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
+ assertInstanceOf(Path.class, ee.evaluate("${session.rootDirectory}"));
+ }
+
private MavenProject createDefaultProject() {
return new MavenProject(new Model());
}
@@ -368,6 +399,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
MutablePlexusContainer container = (MutablePlexusContainer) getContainer();
MavenSession session = createSession(container, repo, executionProperties);
session.setCurrentProject(project);
+ session.getRequest().setRootDirectory(rootDirectory);
MojoDescriptor mojo = new MojoDescriptor();
mojo.setPluginDescriptor(pluginDescriptor);
diff --git a/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java b/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorV4Test.java
similarity index 69%
copy from maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java
copy to maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorV4Test.java
index 39db5828a..aee330983 100644
--- a/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorTest.java
+++ b/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorV4Test.java
@@ -21,6 +21,9 @@ package org.apache.maven.plugin;
import javax.inject.Inject;
import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -28,6 +31,7 @@ import java.util.Map;
import java.util.Properties;
import org.apache.maven.AbstractCoreMavenComponentTestCase;
+import org.apache.maven.api.Session;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
@@ -35,9 +39,12 @@ import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
+import org.apache.maven.internal.impl.DefaultProject;
+import org.apache.maven.internal.impl.DefaultSession;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.DuplicateProjectException;
@@ -47,30 +54,40 @@ import org.codehaus.plexus.MutablePlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.util.dag.CycleDetectedException;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
+import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.NoLocalRepositoryManagerException;
import org.junit.jupiter.api.Test;
import static org.codehaus.plexus.testing.PlexusExtension.getTestFile;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Jason van Zyl
*/
-class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentTestCase {
+public class PluginParameterExpressionEvaluatorV4Test extends AbstractCoreMavenComponentTestCase {
private static final String FS = File.separator;
@Inject
private RepositorySystem factory;
+ private Path rootDirectory;
+
@Test
- void testPluginDescriptorExpressionReference() throws Exception {
+ public void testPluginDescriptorExpressionReference() throws Exception {
MojoExecution exec = newMojoExecution();
- MavenSession session = newMavenSession();
+ Session session = newSession();
- Object result = new PluginParameterExpressionEvaluator(session, exec).evaluate("${plugin}");
+ Object result = new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin}");
System.out.println("Result: " + result);
@@ -81,7 +98,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testPluginArtifactsExpressionReference() throws Exception {
+ public void testPluginArtifactsExpressionReference() throws Exception {
MojoExecution exec = newMojoExecution();
Artifact depArtifact = createArtifact("group", "artifact", "1");
@@ -91,11 +108,11 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
exec.getMojoDescriptor().getPluginDescriptor().setArtifacts(deps);
- MavenSession session = newMavenSession();
+ Session session = newSession();
@SuppressWarnings("unchecked")
- List<Artifact> depResults =
- (List<Artifact>) new PluginParameterExpressionEvaluator(session, exec).evaluate("${plugin.artifacts}");
+ List<Artifact> depResults = (List<Artifact>)
+ new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin.artifacts}");
System.out.println("Result: " + depResults);
@@ -105,7 +122,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testPluginArtifactMapExpressionReference() throws Exception {
+ public void testPluginArtifactMapExpressionReference() throws Exception {
MojoExecution exec = newMojoExecution();
Artifact depArtifact = createArtifact("group", "artifact", "1");
@@ -115,11 +132,11 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
exec.getMojoDescriptor().getPluginDescriptor().setArtifacts(deps);
- MavenSession session = newMavenSession();
+ Session session = newSession();
@SuppressWarnings("unchecked")
Map<String, Artifact> depResults = (Map<String, Artifact>)
- new PluginParameterExpressionEvaluator(session, exec).evaluate("${plugin.artifactMap}");
+ new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin.artifactMap}");
System.out.println("Result: " + depResults);
@@ -132,12 +149,12 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testPluginArtifactIdExpressionReference() throws Exception {
+ public void testPluginArtifactIdExpressionReference() throws Exception {
MojoExecution exec = newMojoExecution();
- MavenSession session = newMavenSession();
+ Session session = newSession();
- Object result = new PluginParameterExpressionEvaluator(session, exec).evaluate("${plugin.artifactId}");
+ Object result = new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin.artifactId}");
System.out.println("Result: " + result);
@@ -148,7 +165,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testValueExtractionWithAPomValueContainingAPath() throws Exception {
+ public void testValueExtractionWithAPomValueContainingAPath() throws Exception {
String expected = getTestFile("target/test-classes/target/classes").getCanonicalPath();
Build build = new Build();
@@ -169,7 +186,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testEscapedVariablePassthrough() throws Exception {
+ public void testEscapedVariablePassthrough() throws Exception {
String var = "${var}";
Model model = new Model();
@@ -185,7 +202,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testEscapedVariablePassthroughInLargerExpression() throws Exception {
+ public void testEscapedVariablePassthroughInLargerExpression() throws Exception {
String var = "${var}";
String key = var + " with version: ${project.version}";
@@ -202,7 +219,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testMultipleSubExpressionsInLargerExpression() throws Exception {
+ public void testMultipleSubExpressionsInLargerExpression() throws Exception {
String key = "${project.artifactId} with version: ${project.version}";
Model model = new Model();
@@ -219,7 +236,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testMissingPOMPropertyRefInLargerExpression() throws Exception {
+ public void testMissingPOMPropertyRefInLargerExpression() throws Exception {
String expr = "/path/to/someproject-${baseVersion}";
MavenProject project = new MavenProject(new Model());
@@ -232,7 +249,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testPOMPropertyExtractionWithMissingProject_WithDotNotation() throws Exception {
+ public void testPOMPropertyExtractionWithMissingProject_WithDotNotation() throws Exception {
String key = "m2.name";
String checkValue = "value";
@@ -252,7 +269,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testBasedirExtractionWithMissingProject() throws Exception {
+ public void testBasedirExtractionWithMissingProject() throws Exception {
ExpressionEvaluator ee = createExpressionEvaluator(null, null, new Properties());
Object value = ee.evaluate("${basedir}");
@@ -261,7 +278,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testValueExtractionFromSystemPropertiesWithMissingProject() throws Exception {
+ public void testValueExtractionFromSystemPropertiesWithMissingProject() throws Exception {
String sysprop = "PPEET_sysprop1";
Properties executionProperties = new Properties();
@@ -278,7 +295,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testValueExtractionFromSystemPropertiesWithMissingProject_WithDotNotation() throws Exception {
+ public void testValueExtractionFromSystemPropertiesWithMissingProject_WithDotNotation() throws Exception {
String sysprop = "PPEET.sysprop2";
Properties executionProperties = new Properties();
@@ -296,28 +313,33 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
@SuppressWarnings("deprecation")
private static MavenSession createSession(PlexusContainer container, ArtifactRepository repo, Properties properties)
- throws CycleDetectedException, DuplicateProjectException {
+ throws CycleDetectedException, DuplicateProjectException, NoLocalRepositoryManagerException {
MavenExecutionRequest request = new DefaultMavenExecutionRequest()
.setSystemProperties(properties)
.setGoals(Collections.<String>emptyList())
.setBaseDirectory(new File(""))
.setLocalRepository(repo);
- return new MavenSession(
- container, request, new DefaultMavenExecutionResult(), Collections.<MavenProject>emptyList());
+ DefaultRepositorySystemSession repositorySession = new DefaultRepositorySystemSession();
+ repositorySession.setLocalRepositoryManager(new SimpleLocalRepositoryManagerFactory()
+ .newInstance(repositorySession, new LocalRepository(repo.getUrl())));
+ MavenSession session =
+ new MavenSession(container, repositorySession, request, new DefaultMavenExecutionResult());
+ session.setProjects(Collections.<MavenProject>emptyList());
+ return session;
}
@Test
- void testLocalRepositoryExtraction() throws Exception {
+ public void testLocalRepositoryExtraction() throws Exception {
ExpressionEvaluator expressionEvaluator =
createExpressionEvaluator(createDefaultProject(), null, new Properties());
Object value = expressionEvaluator.evaluate("${localRepository}");
- assertEquals("local", ((ArtifactRepository) value).getId());
+ assertEquals("local", ((org.apache.maven.api.LocalRepository) value).getId());
}
@Test
- void testTwoExpressions() throws Exception {
+ public void testTwoExpressions() throws Exception {
Build build = new Build();
build.setDirectory("expected-directory");
build.setFinalName("expected-finalName");
@@ -334,7 +356,7 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
}
@Test
- void testShouldExtractPluginArtifacts() throws Exception {
+ public void testShouldExtractPluginArtifacts() throws Exception {
PluginDescriptor pd = new PluginDescriptor();
Artifact artifact = createArtifact("testGroup", "testArtifact", "1.0");
@@ -357,6 +379,28 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
assertEquals("testGroup", result.getGroupId());
}
+ @Test
+ void testRootDirectoryNotPrefixed() throws Exception {
+ ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
+ assertNull(ee.evaluate("${rootDirectory}"));
+ }
+
+ @Test
+ void testRootDirectoryWithNull() throws Exception {
+ ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
+ Exception e = assertThrows(Exception.class, () -> ee.evaluate("${session.rootDirectory}"));
+ e = assertInstanceOf(InvocationTargetException.class, e.getCause());
+ e = assertInstanceOf(IllegalStateException.class, e.getCause());
+ assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
+ }
+
+ @Test
+ void testRootDirectory() throws Exception {
+ this.rootDirectory = Paths.get("myRootDirectory");
+ ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), null, new Properties());
+ assertInstanceOf(Path.class, ee.evaluate("${session.rootDirectory}"));
+ }
+
private MavenProject createDefaultProject() {
return new MavenProject(new Model());
}
@@ -366,8 +410,12 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
ArtifactRepository repo = factory.createDefaultLocalRepository();
MutablePlexusContainer container = (MutablePlexusContainer) getContainer();
- MavenSession session = createSession(container, repo, executionProperties);
- session.setCurrentProject(project);
+ MavenSession mavenSession = createSession(container, repo, executionProperties);
+ mavenSession.setCurrentProject(project);
+ mavenSession.getRequest().setRootDirectory(rootDirectory);
+
+ DefaultSession session =
+ new DefaultSession(mavenSession, new DefaultRepositorySystem(), null, null, null, null);
MojoDescriptor mojo = new MojoDescriptor();
mojo.setPluginDescriptor(pluginDescriptor);
@@ -375,7 +423,8 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
MojoExecution mojoExecution = new MojoExecution(mojo);
- return new PluginParameterExpressionEvaluator(session, mojoExecution);
+ return new PluginParameterExpressionEvaluatorV4(
+ session, project != null ? new DefaultProject(session, project) : null, mojoExecution);
}
protected Artifact createArtifact(String groupId, String artifactId, String version) throws Exception {
@@ -403,6 +452,10 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
return new MojoExecution(md);
}
+ private DefaultSession newSession() throws Exception {
+ return new DefaultSession(newMavenSession(), new DefaultRepositorySystem(), null, null, null, null);
+ }
+
private MavenSession newMavenSession() throws Exception {
return createMavenSession(null);
}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java
index bc879c640..704212be5 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/CLIReportingUtils.java
@@ -135,6 +135,13 @@ public final class CLIReportingUtils {
}
public static void showError(Logger logger, String message, Throwable e, boolean showStackTrace) {
+ if (logger == null) {
+ System.err.println(message);
+ if (showStackTrace && e != null) {
+ e.printStackTrace(System.err);
+ }
+ return;
+ }
if (showStackTrace) {
logger.error(message, e);
} else {
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java b/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java
index 9a315f12c..29bff7b38 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java
@@ -19,6 +19,7 @@
package org.apache.maven.cli;
import java.io.File;
+import java.nio.file.Path;
import java.util.Properties;
import org.apache.commons.cli.CommandLine;
@@ -40,6 +41,10 @@ public class CliRequest {
File multiModuleProjectDirectory;
+ Path rootDirectory;
+
+ Path topDirectory;
+
boolean verbose;
boolean quiet;
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index d5fd81215..6e9ae1a0c 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -29,6 +29,8 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -37,6 +39,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
+import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Matcher;
@@ -87,6 +90,7 @@ import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.logwrapper.LogLevelRecorder;
import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory;
import org.apache.maven.model.building.ModelProcessor;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.project.MavenProject;
import org.apache.maven.properties.internal.EnvironmentUtils;
import org.apache.maven.properties.internal.SystemProperties;
@@ -320,6 +324,54 @@ public class MavenCli {
}
}
+ // We need to locate the top level project which may be pointed at using
+ // the -f/--file option. However, the command line isn't parsed yet, so
+ // we need to iterate through the args to find it and act upon it.
+ Path topDirectory = Paths.get(cliRequest.workingDirectory);
+ boolean isAltFile = false;
+ for (String arg : cliRequest.args) {
+ if (isAltFile) {
+ // this is the argument following -f/--file
+ Path path = Paths.get(arg);
+ if (Files.isDirectory(path)) {
+ topDirectory = path;
+ } else if (Files.isRegularFile(topDirectory)) {
+ topDirectory = path.getParent();
+ if (!Files.isDirectory(topDirectory)) {
+ System.err.println("Directory " + topDirectory
+ + " extracted from the -f/--file command-line argument " + arg + " does not exist");
+ throw new ExitException(1);
+ }
+ } else {
+ System.err.println(
+ "POM file " + arg + " specified with the -f/--file command line argument does not exist");
+ throw new ExitException(1);
+ }
+ break;
+ } else {
+ // Check if this is the -f/--file option
+ isAltFile = arg.equals(String.valueOf(CLIManager.ALTERNATE_POM_FILE)) || arg.equals("file");
+ }
+ }
+ try {
+ topDirectory = topDirectory.toAbsolutePath().toRealPath();
+ } catch (IOException e) {
+ System.err.println("Error computing real path from " + topDirectory);
+ throw new ExitException(1);
+ }
+ cliRequest.topDirectory = topDirectory;
+ // We're very early in the process and we don't have the container set up yet,
+ // so we rely on the JDK services to eventually lookup a custom RootLocator.
+ // This is used to compute {@code session.rootDirectory} but all {@code project.rootDirectory}
+ // properties will be compute through the RootLocator found in the container.
+ RootLocator rootLocator =
+ ServiceLoader.load(RootLocator.class).iterator().next();
+ Path rootDirectory = rootLocator.findRoot(topDirectory);
+ if (rootDirectory == null) {
+ System.err.println(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE);
+ }
+ cliRequest.rootDirectory = rootDirectory;
+
//
// Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative
// Windows paths.
@@ -1185,6 +1237,8 @@ public class MavenCli {
request.setSystemProperties(cliRequest.systemProperties);
request.setUserProperties(cliRequest.userProperties);
request.setMultiModuleProjectDirectory(cliRequest.multiModuleProjectDirectory);
+ request.setRootDirectory(cliRequest.rootDirectory);
+ request.setTopDirectory(cliRequest.topDirectory);
request.setPom(determinePom(commandLine, workingDirectory, baseDirectory));
request.setTransferListener(determineTransferListener(quiet, verbose, commandLine, request));
request.setExecutionListener(determineExecutionListener());
diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index 2d58ebeb2..eea6a87f9 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -22,6 +22,8 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
@@ -36,6 +38,7 @@ import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.ProfileActivation;
import org.apache.maven.execution.ProjectActivation;
+import org.apache.maven.model.root.DefaultRootLocator;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.building.ToolchainsBuildingRequest;
@@ -543,6 +546,12 @@ class MavenCliTest {
assertThat(request.getUserProperties().getProperty("x"), is("false"));
}
+ @Test
+ public void findRootProjectWithAttribute() {
+ Path test = Paths.get("src/test/projects/root-attribute");
+ assertEquals(test, new DefaultRootLocator().findRoot(test.resolve("child")));
+ }
+
private MavenProject createMavenProject(String groupId, String artifactId) {
MavenProject project = new MavenProject();
project.setGroupId(groupId);
diff --git a/maven-embedder/src/test/projects/root-attribute/child/pom.xml b/maven-embedder/src/test/projects/root-attribute/child/pom.xml
new file mode 100644
index 000000000..7e5b53c8e
--- /dev/null
+++ b/maven-embedder/src/test/projects/root-attribute/child/pom.xml
@@ -0,0 +1,3 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+
+</project>
\ No newline at end of file
diff --git a/maven-embedder/src/test/projects/root-attribute/pom.xml b/maven-embedder/src/test/projects/root-attribute/pom.xml
new file mode 100644
index 000000000..c44d4f0c7
--- /dev/null
+++ b/maven-embedder/src/test/projects/root-attribute/pom.xml
@@ -0,0 +1,3 @@
+<project root="true" xmlns="http://maven.apache.org/POM/4.0.0">
+
+</project>
\ No newline at end of file
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java
index 3ddecdc57..75bc40381 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilderFactory.java
@@ -64,6 +64,8 @@ import org.apache.maven.model.profile.activation.JdkVersionProfileActivator;
import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator;
import org.apache.maven.model.profile.activation.ProfileActivator;
import org.apache.maven.model.profile.activation.PropertyProfileActivator;
+import org.apache.maven.model.root.DefaultRootLocator;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.model.superpom.DefaultSuperPomProvider;
import org.apache.maven.model.superpom.SuperPomProvider;
import org.apache.maven.model.validation.DefaultModelValidator;
@@ -100,6 +102,8 @@ public class DefaultModelBuilderFactory {
private ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
private ModelVersionProcessor versionProcessor;
+ private RootLocator rootLocator;
+
public DefaultModelBuilderFactory setModelProcessor(ModelProcessor modelProcessor) {
this.modelProcessor = modelProcessor;
return this;
@@ -201,6 +205,11 @@ public class DefaultModelBuilderFactory {
return this;
}
+ public DefaultModelBuilderFactory setRootLocator(RootLocator rootLocator) {
+ this.rootLocator = rootLocator;
+ return this;
+ }
+
protected ModelProcessor newModelProcessor() {
return new DefaultModelProcessor(newModelLocator(), newModelReader());
}
@@ -227,7 +236,7 @@ public class DefaultModelBuilderFactory {
}
protected ProfileActivationFilePathInterpolator newProfileActivationFilePathInterpolator() {
- return new ProfileActivationFilePathInterpolator(newPathTranslator());
+ return new ProfileActivationFilePathInterpolator(newPathTranslator(), newRootLocator());
}
protected UrlNormalizer newUrlNormalizer() {
@@ -238,10 +247,15 @@ public class DefaultModelBuilderFactory {
return new DefaultPathTranslator();
}
+ protected RootLocator newRootLocator() {
+ return new DefaultRootLocator();
+ }
+
protected ModelInterpolator newModelInterpolator() {
UrlNormalizer normalizer = newUrlNormalizer();
PathTranslator pathTranslator = newPathTranslator();
- return new StringVisitorModelInterpolator(pathTranslator, normalizer);
+ RootLocator rootLocator = newRootLocator();
+ return new StringVisitorModelInterpolator(pathTranslator, normalizer, rootLocator);
}
protected ModelVersionProcessor newModelVersionPropertiesProcessor() {
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java b/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java
index 8e8483699..37e7b0236 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java
@@ -21,6 +21,7 @@ package org.apache.maven.model.interpolation;
import javax.inject.Inject;
import java.io.File;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -33,6 +34,7 @@ import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.path.PathTranslator;
import org.apache.maven.model.path.UrlNormalizer;
+import org.apache.maven.model.root.RootLocator;
import org.codehaus.plexus.interpolation.AbstractValueSource;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
@@ -74,10 +76,14 @@ public abstract class AbstractStringBasedModelInterpolator implements ModelInter
private final PathTranslator pathTranslator;
private final UrlNormalizer urlNormalizer;
+ private final RootLocator rootLocator;
+
@Inject
- public AbstractStringBasedModelInterpolator(PathTranslator pathTranslator, UrlNormalizer urlNormalizer) {
+ public AbstractStringBasedModelInterpolator(
+ PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
this.pathTranslator = pathTranslator;
this.urlNormalizer = urlNormalizer;
+ this.rootLocator = rootLocator;
}
@Override
@@ -133,6 +139,20 @@ public abstract class AbstractStringBasedModelInterpolator implements ModelInter
valueSources.add(new BuildTimestampValueSource(config.getBuildStartTime(), modelProperties));
}
+ valueSources.add(new PrefixedValueSourceWrapper(
+ new AbstractValueSource(false) {
+ @Override
+ public Object getValue(String expression) {
+ if ("rootDirectory".equals(expression)) {
+ Path base = projectDir != null ? projectDir.toPath() : null;
+ Path root = rootLocator.findMandatoryRoot(base);
+ return root.toFile().getPath();
+ }
+ return null;
+ }
+ },
+ PROJECT_PREFIXES));
+
valueSources.add(projectPrefixValueSource);
valueSources.add(new MapBasedValueSource(config.getUserProperties()));
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.java b/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.java
index 88318f7b6..f2a511c03 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.java
@@ -35,6 +35,7 @@ import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.path.PathTranslator;
import org.apache.maven.model.path.UrlNormalizer;
+import org.apache.maven.model.root.RootLocator;
import org.apache.maven.model.v4.MavenTransformer;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
@@ -51,8 +52,9 @@ import org.codehaus.plexus.interpolation.ValueSource;
@Singleton
public class StringVisitorModelInterpolator extends AbstractStringBasedModelInterpolator {
@Inject
- public StringVisitorModelInterpolator(PathTranslator pathTranslator, UrlNormalizer urlNormalizer) {
- super(pathTranslator, urlNormalizer);
+ public StringVisitorModelInterpolator(
+ PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
+ super(pathTranslator, urlNormalizer, rootLocator);
}
interface InnerInterpolator {
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/path/ProfileActivationFilePathInterpolator.java b/maven-model-builder/src/main/java/org/apache/maven/model/path/ProfileActivationFilePathInterpolator.java
index 3ec95ab45..1455b7fcc 100644
--- a/maven-model-builder/src/main/java/org/apache/maven/model/path/ProfileActivationFilePathInterpolator.java
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/path/ProfileActivationFilePathInterpolator.java
@@ -23,9 +23,11 @@ import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
+import java.nio.file.Path;
import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.model.profile.ProfileActivationContext;
+import org.apache.maven.model.root.RootLocator;
import org.codehaus.plexus.interpolation.AbstractValueSource;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
@@ -42,9 +44,12 @@ public class ProfileActivationFilePathInterpolator {
private final PathTranslator pathTranslator;
+ private final RootLocator rootLocator;
+
@Inject
- public ProfileActivationFilePathInterpolator(PathTranslator pathTranslator) {
+ public ProfileActivationFilePathInterpolator(PathTranslator pathTranslator, RootLocator rootLocator) {
this.pathTranslator = pathTranslator;
+ this.rootLocator = rootLocator;
}
/**
@@ -79,6 +84,18 @@ public class ProfileActivationFilePathInterpolator {
return null;
}
+ interpolator.addValueSource(new AbstractValueSource(false) {
+ @Override
+ public Object getValue(String expression) {
+ if ("rootDirectory".equals(expression)) {
+ Path base = basedir != null ? basedir.toPath() : null;
+ Path root = rootLocator.findMandatoryRoot(base);
+ return root.toFile().getAbsolutePath();
+ }
+ return null;
+ }
+ });
+
interpolator.addValueSource(new MapBasedValueSource(context.getProjectProperties()));
interpolator.addValueSource(new MapBasedValueSource(context.getUserProperties()));
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/root/DefaultRootLocator.java b/maven-model-builder/src/main/java/org/apache/maven/model/root/DefaultRootLocator.java
new file mode 100644
index 000000000..4b4a36a22
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/root/DefaultRootLocator.java
@@ -0,0 +1,59 @@
+/*
+ * 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.model.root;
+
+import javax.inject.Named;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.codehaus.plexus.util.xml.pull.MXParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+@Named
+public class DefaultRootLocator implements RootLocator {
+
+ public boolean isRootDirectory(Path dir) {
+ if (Files.isDirectory(dir.resolve(".mvn"))) {
+ return true;
+ }
+ // we're too early to use the modelProcessor ...
+ Path pom = dir.resolve("pom.xml");
+ try (InputStream is = Files.newInputStream(pom)) {
+ MXParser parser = new MXParser();
+ parser.setInput(is, null);
+ if (parser.nextTag() == MXParser.START_TAG && parser.getName().equals("project")) {
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ if ("root".equals(parser.getAttributeName(i))) {
+ return Boolean.parseBoolean(parser.getAttributeValue(i));
+ }
+ }
+ }
+ } catch (IOException | XmlPullParserException e) {
+ // The root locator can be used very early during the setup of Maven,
+ // even before the arguments from the command line are parsed. Any exception
+ // that would happen here should cause the build to fail at a later stage
+ // (when actually parsing the POM) and will lead to a better exception being
+ // displayed to the user, so just bail out and return false.
+ }
+ return false;
+ }
+}
diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/root/RootLocator.java b/maven-model-builder/src/main/java/org/apache/maven/model/root/RootLocator.java
new file mode 100644
index 000000000..c1b458db7
--- /dev/null
+++ b/maven-model-builder/src/main/java/org/apache/maven/model/root/RootLocator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.model.root;
+
+import java.nio.file.Path;
+
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.Nullable;
+
+/**
+ * Interface used to locate the root directory for a given project.
+ *
+ * The root locator is usually looked up from the plexus container.
+ * One notable exception is the computation of the early {@code session.rootDirectory}
+ * property which happens very early. The implementation used in this case
+ * will be discovered using the JDK service mechanism.
+ *
+ * The default implementation will look for a {@code .mvn} child directory
+ * or a {@code pom.xml} containing the {@code root="true"} attribute.
+ *
+ * @see DefaultRootLocator
+ */
+public interface RootLocator {
+
+ String UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE = "Unable to find the root directory. "
+ + "Create a .mvn directory in the root directory or add the root=\"true\""
+ + " attribute on the root project's model to identify it.";
+
+ @Nonnull
+ default Path findMandatoryRoot(Path basedir) {
+ Path rootDirectory = findRoot(basedir);
+ if (rootDirectory == null) {
+ throw new IllegalStateException(getNoRootMessage());
+ }
+ return rootDirectory;
+ }
+
+ @Nullable
+ default Path findRoot(Path basedir) {
+ Path rootDirectory = basedir;
+ while (rootDirectory != null && !isRootDirectory(rootDirectory)) {
+ rootDirectory = rootDirectory.getParent();
+ }
+ return rootDirectory;
+ }
+
+ @Nonnull
+ default String getNoRootMessage() {
+ return UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE;
+ }
+
+ boolean isRootDirectory(Path dir);
+}
diff --git a/maven-model-builder/src/main/resources/META-INF/services/org.apache.maven.model.root.RootLocator b/maven-model-builder/src/main/resources/META-INF/services/org.apache.maven.model.root.RootLocator
new file mode 100644
index 000000000..4b81cf5c8
--- /dev/null
+++ b/maven-model-builder/src/main/resources/META-INF/services/org.apache.maven.model.root.RootLocator
@@ -0,0 +1 @@
+org.apache.maven.model.root.DefaultRootLocator
diff --git a/maven-model-builder/src/site/apt/index.apt b/maven-model-builder/src/site/apt/index.apt
index 270d2a8eb..38faeff9b 100644
--- a/maven-model-builder/src/site/apt/index.apt
+++ b/maven-model-builder/src/site/apt/index.apt
@@ -41,7 +41,7 @@ Maven Model Builder
** profile activation: see {{{./apidocs/org/apache/maven/model/profile/activation/package-summary.html}available activators}}.
Notice that model interpolation hasn't happened yet, then interpolation for file-based activation is limited to
- <<<$\{basedir}>>> (since Maven 3), system properties and user properties
+ <<<$\{basedir}>>> (since Maven 3), <<<$\{rootDirectory}>>> (since Maven 4), system properties and user properties
** file model validation: <<<ModelValidator>>> ({{{./apidocs/org/apache/maven/model/validation/ModelValidator.html}javadoc}}),
with its <<<DefaultModelValidator>>> implementation
@@ -51,7 +51,7 @@ Maven Model Builder
* phase 2, with optional plugin processing
- ** Build up a raw model by re-reading the file and enrich it based on information available in the reactor. Some features:
+ ** Build up a raw model by re-reading the file and enriching it based on information available in the reactor. Some features:
*** Resolve version of versionless parents based on relativePath (including ci-friendly versions)
@@ -156,13 +156,13 @@ Maven Model Builder
* Model Interpolation
- Model Interpolation consists in replacing <<<$\{...\}>>> with calculated value. It is done in <<<StringSearchModelInterpolator>>>
- ({{{./apidocs/org/apache/maven/model/interpolation/StringSearchModelInterpolator.html}javadoc}},
- {{{./xref/org/apache/maven/model/interpolation/StringSearchModelInterpolator.html}source}}).
+ Model Interpolation consists in replacing <<<$\{...\}>>> with calculated value. It is done in <<<StringVisitorModelInterpolator>>>
+ ({{{./apidocs/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.html}javadoc}},
+ {{{./xref/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.html}source}}).
- Notice that model interpolation happens <after> profile activation, then profile activation doesn't benefit from every values:
+ Notice that model interpolation happens <after> profile activation, and that profile activation doesn't benefit from every values:
interpolation for file-based activation is limited to <<<$\{basedir}>>> (which was introduced in Maven 3 and is not deprecated
- in this context), system properties and user properties.
+ in this context) and <<<$\{rootDirectory}>>> (introduced in Maven 4), system properties and user properties.
Values are evaluated in sequence from different syntaxes:
@@ -183,6 +183,8 @@ Maven Model Builder
| <<<project.baseUri>>>\
<<<pom.baseUri>>> (<deprecated>) | the directory containing the <<<pom.xml>>> file as URI | <<<$\{project.baseUri\}>>> |
*----+------+------+
+| <<<project.rootDirectory>>> | the project's root directory (containing a <<<.mvn>>> directory or with the <<<root="true">>> xml attribute) | <<<$\{project.rootDirectory\}>>> |
+*----+------+------+
| <<<build.timestamp>>>\
<<<maven.build.timestamp>>> | the UTC timestamp of build start, in <<<yyyy-MM-dd'T'HH:mm:ss'Z'>>> default format, which can be overridden with <<<maven.build.timestamp.format>>> POM property | <<<$\{maven.build.timestamp\}>>> |
*----+------+------+
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/AbstractModelInterpolatorTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/AbstractModelInterpolatorTest.java
index b50789efa..44a2a59ef 100644
--- a/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/AbstractModelInterpolatorTest.java
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/AbstractModelInterpolatorTest.java
@@ -19,6 +19,8 @@
package org.apache.maven.model.interpolation;
import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
@@ -41,11 +43,13 @@ import org.apache.maven.api.model.Scm;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.SimpleProblemCollector;
+import org.apache.maven.model.root.RootLocator;
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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -306,9 +310,50 @@ public abstract class AbstractModelInterpolatorTest {
assertEquals("myBaseUri/temp-repo", (out.getRepositories().get(0)).getUrl());
}
+ @Test
+ void testRootDirectory() throws Exception {
+ Path rootDirectory = Paths.get("myRootDirectory");
+
+ Model model = Model.newBuilder()
+ .version("3.8.1")
+ .artifactId("foo")
+ .repositories(Collections.singletonList(Repository.newBuilder()
+ .url("file:${project.rootDirectory}/temp-repo")
+ .build()))
+ .build();
+
+ ModelInterpolator interpolator = createInterpolator();
+
+ final SimpleProblemCollector collector = new SimpleProblemCollector();
+ Model out = interpolator.interpolateModel(
+ model, rootDirectory.toFile(), createModelBuildingRequest(context), collector);
+ assertProblemFree(collector);
+
+ assertEquals("file:myRootDirectory/temp-repo", (out.getRepositories().get(0)).getUrl());
+ }
+
+ @Test
+ void testRootDirectoryWithNull() throws Exception {
+ Model model = Model.newBuilder()
+ .version("3.8.1")
+ .artifactId("foo")
+ .repositories(Collections.singletonList(Repository.newBuilder()
+ .url("file:///${project.rootDirectory}/temp-repo")
+ .build()))
+ .build();
+
+ ModelInterpolator interpolator = createInterpolator();
+
+ final SimpleProblemCollector collector = new SimpleProblemCollector();
+ IllegalStateException e = assertThrows(
+ IllegalStateException.class,
+ () -> interpolator.interpolateModel(model, null, createModelBuildingRequest(context), collector));
+
+ assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
+ }
+
@Test
public void testEnvars() throws Exception {
- Properties context = new Properties();
context.put("env.HOME", "/path/to/home");
Map<String, String> modelProperties = new HashMap<>();
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolatorTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolatorTest.java
index 5c29e8708..02ea9ba6b 100644
--- a/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolatorTest.java
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/interpolation/StringVisitorModelInterpolatorTest.java
@@ -20,6 +20,6 @@ package org.apache.maven.model.interpolation;
public class StringVisitorModelInterpolatorTest extends AbstractModelInterpolatorTest {
protected ModelInterpolator createInterpolator() {
- return new StringVisitorModelInterpolator(null, null);
+ return new StringVisitorModelInterpolator(null, null, bd -> true);
}
}
diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/profile/activation/FileProfileActivatorTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/profile/activation/FileProfileActivatorTest.java
index 39312bfb6..c2ac10bd1 100644
--- a/maven-model-builder/src/test/java/org/apache/maven/model/profile/activation/FileProfileActivatorTest.java
+++ b/maven-model-builder/src/test/java/org/apache/maven/model/profile/activation/FileProfileActivatorTest.java
@@ -28,11 +28,13 @@ import org.apache.maven.api.model.Profile;
import org.apache.maven.model.path.DefaultPathTranslator;
import org.apache.maven.model.path.ProfileActivationFilePathInterpolator;
import org.apache.maven.model.profile.DefaultProfileActivationContext;
+import org.apache.maven.model.root.RootLocator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Tests {@link FileProfileActivator}.
@@ -49,9 +51,10 @@ class FileProfileActivatorTest extends AbstractProfileActivatorTest<FileProfileA
@BeforeEach
@Override
void setUp() throws Exception {
- activator = new FileProfileActivator(new ProfileActivationFilePathInterpolator(new DefaultPathTranslator()));
+ activator = new FileProfileActivator(
+ new ProfileActivationFilePathInterpolator(new DefaultPathTranslator(), bd -> true));
- context.setProjectDirectory(new File(tempDir.toString()));
+ context.setProjectDirectory(tempDir.toFile());
File file = new File(tempDir.resolve("file.txt").toString());
if (!file.createNewFile()) {
@@ -59,6 +62,26 @@ class FileProfileActivatorTest extends AbstractProfileActivatorTest<FileProfileA
}
}
+ @Test
+ void testRootDirectoryWithNull() {
+ context.setProjectDirectory(null);
+
+ IllegalStateException e = assertThrows(
+ IllegalStateException.class,
+ () -> assertActivation(false, newExistsProfile("${rootDirectory}"), context));
+ assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
+ }
+
+ @Test
+ void testRootDirectory() {
+ assertActivation(false, newExistsProfile("${rootDirectory}/someFile.txt"), context);
+ assertActivation(true, newMissingProfile("${rootDirectory}/someFile.txt"), context);
+ assertActivation(true, newExistsProfile("${rootDirectory}"), context);
+ assertActivation(true, newExistsProfile("${rootDirectory}/" + "file.txt"), context);
+ assertActivation(false, newMissingProfile("${rootDirectory}"), context);
+ assertActivation(false, newMissingProfile("${rootDirectory}/" + "file.txt"), context);
+ }
+
@Test
void testIsActiveNoFile() {
assertActivation(false, newExistsProfile(null), context);
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
index 2b700a564..617d7d91f 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
@@ -40,6 +40,8 @@ public class RawToConsumerPomXMLFilterFactory {
parser = buildPomXMLFilterFactory.get(parser, projectPath);
+ // Remove root model attribute
+ parser = new RootXMLFilter(parser);
// Strip modules
parser = new ModulesXMLFilter(parser);
// Adjust relativePath
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RootXMLFilter.java
similarity index 50%
copy from maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
copy to maven-model-transform/src/main/java/org/apache/maven/model/transform/RootXMLFilter.java
index 2b700a564..af40c5c54 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/RawToConsumerPomXMLFilterFactory.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RootXMLFilter.java
@@ -18,33 +18,36 @@
*/
package org.apache.maven.model.transform;
-import java.nio.file.Path;
+import java.io.IOException;
+import java.util.stream.Stream;
+import org.apache.maven.model.transform.pull.BufferingParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
+ * Remove the root attribute on the model
+ *
* @author Guillaume Nodet
- * @author Robert Scholte
* @since 4.0.0
*/
-public class RawToConsumerPomXMLFilterFactory {
- private BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory;
-
- public RawToConsumerPomXMLFilterFactory(BuildToRawPomXMLFilterFactory buildPomXMLFilterFactory) {
- this.buildPomXMLFilterFactory = buildPomXMLFilterFactory;
+class RootXMLFilter extends BufferingParser {
+ RootXMLFilter(XmlPullParser xmlPullParser) {
+ super(xmlPullParser);
}
- public final XmlPullParser get(XmlPullParser orgParser, Path projectPath) {
- // Ensure that xs:any elements aren't touched by next filters
- XmlPullParser parser = orgParser instanceof FastForwardFilter ? orgParser : new FastForwardFilter(orgParser);
-
- parser = buildPomXMLFilterFactory.get(parser, projectPath);
-
- // Strip modules
- parser = new ModulesXMLFilter(parser);
- // Adjust relativePath
- parser = new RelativePathXMLFilter(parser);
-
- return parser;
+ @Override
+ protected boolean accept() throws XmlPullParserException, IOException {
+ if (xmlPullParser.getEventType() == XmlPullParser.START_TAG) {
+ if (xmlPullParser.getDepth() == 1 && "project".equals(xmlPullParser.getName())) {
+ Event event = bufferEvent();
+ event.attributes = Stream.of(event.attributes)
+ .filter(a -> !"root".equals(a.name))
+ .toArray(Attribute[]::new);
+ pushEvent(event);
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java
index 4113771e4..a1aba62ba 100644
--- a/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java
+++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/pull/BufferingParser.java
@@ -395,6 +395,16 @@ public class BufferingParser implements XmlPullParser {
event.prefix = pp.getPrefix();
event.empty = pp.isEmptyElementTag();
event.text = pp.getText();
+ event.attributes = new Attribute[pp.getAttributeCount()];
+ for (int i = 0; i < pp.getAttributeCount(); i++) {
+ Attribute attr = new Attribute();
+ attr.name = pp.getAttributeName(i);
+ attr.namespace = pp.getAttributeNamespace(i);
+ attr.value = pp.getAttributeValue(i);
+ attr.type = pp.getAttributeType(i);
+ attr.isDefault = pp.isAttributeDefault(i);
+ event.attributes[i] = attr;
+ }
break;
case END_TAG:
event.name = pp.getName();
diff --git a/src/mdo/reader-ex.vm b/src/mdo/reader-ex.vm
index a5b7c7e14..a061e1b36 100644
--- a/src/mdo/reader-ex.vm
+++ b/src/mdo/reader-ex.vm
@@ -207,7 +207,7 @@ public class ${className}
#if ( $field.type == "String" )
${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) );
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
- ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( value, "$fieldTagName" ), "$fieldTagName", parser, strict, "${field.defaultValue}" ) );
+ ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( value, "$fieldTagName" ), "$fieldTagName", parser, ${field.defaultValue} ) );
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
diff --git a/src/mdo/reader-modified.vm b/src/mdo/reader-modified.vm
index 696cb1c91..be4f49506 100644
--- a/src/mdo/reader-modified.vm
+++ b/src/mdo/reader-modified.vm
@@ -236,7 +236,7 @@ public class ${className}
#if ( $field.type == "String" )
${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) );
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
- ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( value, "$fieldTagName" ), "$fieldTagName", parser, "${field.defaultValue}" ) );
+ ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( value, "$fieldTagName" ), "$fieldTagName", parser, ${field.defaultValue} ) );
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
diff --git a/src/mdo/reader.vm b/src/mdo/reader.vm
index f43847aaf..8f04323b2 100644
--- a/src/mdo/reader.vm
+++ b/src/mdo/reader.vm
@@ -175,7 +175,7 @@ public class ${className}
private boolean getBooleanValue( String s, String attribute, XmlPullParser parser )
throws XmlPullParserException
{
- return getBooleanValue( s, attribute, parser, null );
+ return getBooleanValue( s, attribute, parser, false );
} //-- boolean getBooleanValue( String, String, XmlPullParser )
/**
@@ -189,18 +189,14 @@ public class ${className}
* any.
* @return boolean
*/
- private boolean getBooleanValue( String s, String attribute, XmlPullParser parser, String defaultValue )
+ private boolean getBooleanValue( String s, String attribute, XmlPullParser parser, boolean defaultValue )
throws XmlPullParserException
{
if ( s != null && s.length() != 0 )
{
return Boolean.valueOf( s ).booleanValue();
}
- if ( defaultValue != null )
- {
- return Boolean.valueOf( defaultValue ).booleanValue();
- }
- return false;
+ return defaultValue;
} //-- boolean getBooleanValue( String, String, XmlPullParser, String )
/**
@@ -709,7 +705,7 @@ public class ${className}
#if ( $field.type == "String" )
${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) );
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
- ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( value, "$fieldTagName" ), "$fieldTagName", parser, "${field.defaultValue}" ) );
+ ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( value, "$fieldTagName" ), "$fieldTagName", parser, ${field.defaultValue} ) );
#else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
@@ -749,7 +745,7 @@ public class ${className}
${classLcapName}.${field.name}( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ) );
break;
#elseif ( $field.type == "boolean" || $field.type == "Boolean" )
- ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ), "${fieldTagName}", parser, "${field.defaultValue}" ) );
+ ${classLcapName}.${field.name}( getBooleanValue( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ), "${fieldTagName}", parser, ${field.defaultValue} ) );
break;
#elseif ( $field.type == "int" )
${classLcapName}.${field.name}( getIntegerValue( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ), "${fieldTagName}", parser, strict, ${field.defaultValue} ) );
diff --git a/src/mdo/writer-ex.vm b/src/mdo/writer-ex.vm
index a204149c3..903184ce8 100644
--- a/src/mdo/writer-ex.vm
+++ b/src/mdo/writer-ex.vm
@@ -210,8 +210,15 @@ public class ${className}
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
#if ( $field.type == "String" )
writeAttr( "$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer );
+ #elseif ( $field.type == "boolean" )
+ #set ( $def = ${field.defaultValue} )
+ #if ( ${def} == "true" )
+ writeAttr( "$fieldTagName", ${classLcapName}.is${fieldCapName}() ? null : "false", serializer );
+ #else
+ writeAttr( "$fieldTagName", ${classLcapName}.is${fieldCapName}() ? "true" : null, serializer );
+ #end
#else
- // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
+ // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
#end
#end
diff --git a/src/mdo/writer.vm b/src/mdo/writer.vm
index e968ec1a9..a589b603f 100644
--- a/src/mdo/writer.vm
+++ b/src/mdo/writer.vm
@@ -192,8 +192,15 @@ public class ${className}
#set ( $fieldCapName = $Helper.capitalise( $field.name ) )
#if ( $field.type == "String" )
writeAttr( "$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer );
+ #elseif ( $field.type == "boolean" )
+ #set ( $def = ${field.defaultValue} )
+ #if ( ${def} == "true" )
+ writeAttr( "$fieldTagName", ${classLcapName}.is${fieldCapName}() ? null : "false", serializer );
+ #else
+ writeAttr( "$fieldTagName", ${classLcapName}.is${fieldCapName}() ? "true" : null, serializer );
+ #end
#else
- // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
+ // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end
#end
#end