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 7d80f3ae47..256fe9479e 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 @@ default String getId() { 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 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 5616c3dd93..32baea23e1 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. * - * Implementation note: while this method return type is {@link Map}, the returned map instance - * implements {@link java.util.concurrent.ConcurrentMap} as well. + * Implementation note: 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 a42a655314..fc8418fb6d 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 @@ String + + root + 4.0.0+ + + Since: Maven 4.0.0 + ]]> + + boolean + false + inceptionYear 3.0.0+ 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 6a3f155091..612d8e7dd7 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.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 TestProjectBuilder( RepositorySystem repoSystem, RemoteRepositoryManager repositoryManager, ProjectDependenciesResolver dependencyResolver, - ModelCacheFactory modelCacheFactory) { + ModelCacheFactory modelCacheFactory, + RootLocator rootLocator) { super( modelBuilder, modelProcessor, @@ -54,7 +56,8 @@ public TestProjectBuilder( 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 e0a073b91b..310574f379 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 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 goals; private boolean useReactor = false; @@ -1051,16 +1057,43 @@ public MavenExecutionRequest setToolchains(Map> too 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 9d86df5e8b..66a49a1f20 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 bd4e34ad52..fc430696e9 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 List getProjects() { 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 0951f26f85..c234608ffb 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 boolean isExecutionRoot() { 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 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 05a72ff973..011710778d 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 Path getMultiModuleProjectDirectory() { @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 1b6c5a8d0c..eabbe03f7a 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 void injectTransformedArtifacts(MavenProject project, RepositorySystemSes 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 5bb9dab0f2..027ca74ee6 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.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 DefaultProjectBuilder( 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 DefaultProjectBuilder( this.repositoryManager = repositoryManager; this.dependencyResolver = dependencyResolver; this.modelCacheFactory = modelCacheFactory; + this.rootLocator = rootLocator; } // ---------------------------------------------------------------------- // MavenProjectBuilder Implementation @@ -162,6 +167,11 @@ private ProjectBuildingResult build(File pomFile, ModelSource modelSource, Inter 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 @@ private boolean build( 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 b024001bf1..84a4090b23 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 @@ 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.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 resolvedArtifacts; private ArtifactFilter artifactFilter; @@ -1679,4 +1683,20 @@ public ProjectBuildingRequest getProjectBuildingRequest() { 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 0000000000..dd516c91a3 --- /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 39db5828af..c01c6f56c3 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 @@ 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.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 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 @@ void testShouldExtractPluginArtifacts() throws Exception { 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 @@ private ExpressionEvaluator createExpressionEvaluator( 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/PluginParameterExpressionEvaluatorV4Test.java b/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorV4Test.java new file mode 100644 index 0000000000..aee3309830 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/plugin/PluginParameterExpressionEvaluatorV4Test.java @@ -0,0 +1,468 @@ +/* + * 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.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; +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; +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; +import org.apache.maven.project.MavenProject; +import org.apache.maven.repository.RepositorySystem; +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 + */ +public class PluginParameterExpressionEvaluatorV4Test extends AbstractCoreMavenComponentTestCase { + private static final String FS = File.separator; + + @Inject + private RepositorySystem factory; + + private Path rootDirectory; + + @Test + public void testPluginDescriptorExpressionReference() throws Exception { + MojoExecution exec = newMojoExecution(); + + Session session = newSession(); + + Object result = new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin}"); + + System.out.println("Result: " + result); + + assertSame( + exec.getMojoDescriptor().getPluginDescriptor(), + result, + "${plugin} expression does not return plugin descriptor."); + } + + @Test + public void testPluginArtifactsExpressionReference() throws Exception { + MojoExecution exec = newMojoExecution(); + + Artifact depArtifact = createArtifact("group", "artifact", "1"); + + List deps = new ArrayList<>(); + deps.add(depArtifact); + + exec.getMojoDescriptor().getPluginDescriptor().setArtifacts(deps); + + Session session = newSession(); + + @SuppressWarnings("unchecked") + List depResults = (List) + new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin.artifacts}"); + + System.out.println("Result: " + depResults); + + assertNotNull(depResults); + assertEquals(1, depResults.size()); + assertSame(depArtifact, depResults.get(0), "dependency artifact is wrong."); + } + + @Test + public void testPluginArtifactMapExpressionReference() throws Exception { + MojoExecution exec = newMojoExecution(); + + Artifact depArtifact = createArtifact("group", "artifact", "1"); + + List deps = new ArrayList<>(); + deps.add(depArtifact); + + exec.getMojoDescriptor().getPluginDescriptor().setArtifacts(deps); + + Session session = newSession(); + + @SuppressWarnings("unchecked") + Map depResults = (Map) + new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin.artifactMap}"); + + System.out.println("Result: " + depResults); + + assertNotNull(depResults); + assertEquals(1, depResults.size()); + assertSame( + depArtifact, + depResults.get(ArtifactUtils.versionlessKey(depArtifact)), + "dependency artifact is wrong."); + } + + @Test + public void testPluginArtifactIdExpressionReference() throws Exception { + MojoExecution exec = newMojoExecution(); + + Session session = newSession(); + + Object result = new PluginParameterExpressionEvaluatorV4(session, null, exec).evaluate("${plugin.artifactId}"); + + System.out.println("Result: " + result); + + assertSame( + exec.getMojoDescriptor().getPluginDescriptor().getArtifactId(), + result, + "${plugin.artifactId} expression does not return plugin descriptor's artifactId."); + } + + @Test + public void testValueExtractionWithAPomValueContainingAPath() throws Exception { + String expected = getTestFile("target/test-classes/target/classes").getCanonicalPath(); + + Build build = new Build(); + build.setDirectory(expected.substring(0, expected.length() - "/classes".length())); + + Model model = new Model(); + model.setBuild(build); + + MavenProject project = new MavenProject(model); + project.setFile(new File("pom.xml").getCanonicalFile()); + + ExpressionEvaluator expressionEvaluator = createExpressionEvaluator(project, null, new Properties()); + + Object value = expressionEvaluator.evaluate("${project.build.directory}/classes"); + String actual = new File(value.toString()).getCanonicalPath(); + + assertEquals(expected, actual); + } + + @Test + public void testEscapedVariablePassthrough() throws Exception { + String var = "${var}"; + + Model model = new Model(); + model.setVersion("1"); + + MavenProject project = new MavenProject(model); + + ExpressionEvaluator ee = createExpressionEvaluator(project, null, new Properties()); + + Object value = ee.evaluate("$" + var); + + assertEquals(var, value); + } + + @Test + public void testEscapedVariablePassthroughInLargerExpression() throws Exception { + String var = "${var}"; + String key = var + " with version: ${project.version}"; + + Model model = new Model(); + model.setVersion("1"); + + MavenProject project = new MavenProject(model); + + ExpressionEvaluator ee = createExpressionEvaluator(project, null, new Properties()); + + Object value = ee.evaluate("$" + key); + + assertEquals("${var} with version: 1", value); + } + + @Test + public void testMultipleSubExpressionsInLargerExpression() throws Exception { + String key = "${project.artifactId} with version: ${project.version}"; + + Model model = new Model(); + model.setArtifactId("test"); + model.setVersion("1"); + + MavenProject project = new MavenProject(model); + + ExpressionEvaluator ee = createExpressionEvaluator(project, null, new Properties()); + + Object value = ee.evaluate(key); + + assertEquals("test with version: 1", value); + } + + @Test + public void testMissingPOMPropertyRefInLargerExpression() throws Exception { + String expr = "/path/to/someproject-${baseVersion}"; + + MavenProject project = new MavenProject(new Model()); + + ExpressionEvaluator ee = createExpressionEvaluator(project, null, new Properties()); + + Object value = ee.evaluate(expr); + + assertEquals(expr, value); + } + + @Test + public void testPOMPropertyExtractionWithMissingProject_WithDotNotation() throws Exception { + String key = "m2.name"; + String checkValue = "value"; + + Properties properties = new Properties(); + properties.setProperty(key, checkValue); + + Model model = new Model(); + model.setProperties(properties); + + MavenProject project = new MavenProject(model); + + ExpressionEvaluator ee = createExpressionEvaluator(project, null, new Properties()); + + Object value = ee.evaluate("${" + key + "}"); + + assertEquals(checkValue, value); + } + + @Test + public void testBasedirExtractionWithMissingProject() throws Exception { + ExpressionEvaluator ee = createExpressionEvaluator(null, null, new Properties()); + + Object value = ee.evaluate("${basedir}"); + + assertEquals(System.getProperty("user.dir"), value); + } + + @Test + public void testValueExtractionFromSystemPropertiesWithMissingProject() throws Exception { + String sysprop = "PPEET_sysprop1"; + + Properties executionProperties = new Properties(); + + if (executionProperties.getProperty(sysprop) == null) { + executionProperties.setProperty(sysprop, "value"); + } + + ExpressionEvaluator ee = createExpressionEvaluator(null, null, executionProperties); + + Object value = ee.evaluate("${" + sysprop + "}"); + + assertEquals("value", value); + } + + @Test + public void testValueExtractionFromSystemPropertiesWithMissingProject_WithDotNotation() throws Exception { + String sysprop = "PPEET.sysprop2"; + + Properties executionProperties = new Properties(); + + if (executionProperties.getProperty(sysprop) == null) { + executionProperties.setProperty(sysprop, "value"); + } + + ExpressionEvaluator ee = createExpressionEvaluator(null, null, executionProperties); + + Object value = ee.evaluate("${" + sysprop + "}"); + + assertEquals("value", value); + } + + @SuppressWarnings("deprecation") + private static MavenSession createSession(PlexusContainer container, ArtifactRepository repo, Properties properties) + throws CycleDetectedException, DuplicateProjectException, NoLocalRepositoryManagerException { + MavenExecutionRequest request = new DefaultMavenExecutionRequest() + .setSystemProperties(properties) + .setGoals(Collections.emptyList()) + .setBaseDirectory(new File("")) + .setLocalRepository(repo); + + 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.emptyList()); + return session; + } + + @Test + public void testLocalRepositoryExtraction() throws Exception { + ExpressionEvaluator expressionEvaluator = + createExpressionEvaluator(createDefaultProject(), null, new Properties()); + Object value = expressionEvaluator.evaluate("${localRepository}"); + + assertEquals("local", ((org.apache.maven.api.LocalRepository) value).getId()); + } + + @Test + public void testTwoExpressions() throws Exception { + Build build = new Build(); + build.setDirectory("expected-directory"); + build.setFinalName("expected-finalName"); + + Model model = new Model(); + model.setBuild(build); + + ExpressionEvaluator expressionEvaluator = + createExpressionEvaluator(new MavenProject(model), null, new Properties()); + + Object value = expressionEvaluator.evaluate("${project.build.directory}" + FS + "${project.build.finalName}"); + + assertEquals("expected-directory" + File.separatorChar + "expected-finalName", value); + } + + @Test + public void testShouldExtractPluginArtifacts() throws Exception { + PluginDescriptor pd = new PluginDescriptor(); + + Artifact artifact = createArtifact("testGroup", "testArtifact", "1.0"); + + pd.setArtifacts(Collections.singletonList(artifact)); + + ExpressionEvaluator ee = createExpressionEvaluator(createDefaultProject(), pd, new Properties()); + + Object value = ee.evaluate("${plugin.artifacts}"); + + assertTrue(value instanceof List); + + @SuppressWarnings("unchecked") + List artifacts = (List) value; + + assertEquals(1, artifacts.size()); + + Artifact result = artifacts.get(0); + + 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()); + } + + private ExpressionEvaluator createExpressionEvaluator( + MavenProject project, PluginDescriptor pluginDescriptor, Properties executionProperties) throws Exception { + ArtifactRepository repo = factory.createDefaultLocalRepository(); + + MutablePlexusContainer container = (MutablePlexusContainer) getContainer(); + 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); + mojo.setGoal("goal"); + + MojoExecution mojoExecution = new MojoExecution(mojo); + + return new PluginParameterExpressionEvaluatorV4( + session, project != null ? new DefaultProject(session, project) : null, mojoExecution); + } + + protected Artifact createArtifact(String groupId, String artifactId, String version) throws Exception { + Dependency dependency = new Dependency(); + dependency.setGroupId(groupId); + dependency.setArtifactId(artifactId); + dependency.setVersion(version); + dependency.setType("jar"); + dependency.setScope("compile"); + + return factory.createDependencyArtifact(dependency); + } + + private MojoExecution newMojoExecution() { + PluginDescriptor pd = new PluginDescriptor(); + pd.setArtifactId("my-plugin"); + pd.setGroupId("org.myco.plugins"); + pd.setVersion("1"); + + MojoDescriptor md = new MojoDescriptor(); + md.setPluginDescriptor(pd); + + pd.addComponentDescriptor(md); + + 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); + } + + @Override + protected String getProjectsDirectory() { + // TODO Auto-generated method stub + return 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 bc879c6409..704212be57 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 @@ static Properties getBuildProperties() { } 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 9a315f12ce..29bff7b38a 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 d5fd812158..6e9ae1a0ca 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.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.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.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 @@ void initialize(CliRequest cliRequest) throws ExitException { } } + // 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 @@ private MavenExecutionRequest populateRequest(CliRequest cliRequest, MavenExecut 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 2d58ebeb27..eea6a87f97 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.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.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 @@ void populatePropertiesOverwrite() throws Exception { 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 0000000000..7e5b53c8e0 --- /dev/null +++ b/maven-embedder/src/test/projects/root-attribute/child/pom.xml @@ -0,0 +1,3 @@ + + + \ 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 0000000000..c44d4f0c7c --- /dev/null +++ b/maven-embedder/src/test/projects/root-attribute/pom.xml @@ -0,0 +1,3 @@ + + + \ 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 3ddecdc570..75bc40381a 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.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 DefaultModelBuilderFactory setVersionProcessor(ModelVersionProcessor vers return this; } + public DefaultModelBuilderFactory setRootLocator(RootLocator rootLocator) { + this.rootLocator = rootLocator; + return this; + } + protected ModelProcessor newModelProcessor() { return new DefaultModelProcessor(newModelLocator(), newModelReader()); } @@ -227,7 +236,7 @@ protected ProfileActivator[] newProfileActivators() { } protected ProfileActivationFilePathInterpolator newProfileActivationFilePathInterpolator() { - return new ProfileActivationFilePathInterpolator(newPathTranslator()); + return new ProfileActivationFilePathInterpolator(newPathTranslator(), newRootLocator()); } protected UrlNormalizer newUrlNormalizer() { @@ -238,10 +247,15 @@ protected PathTranslator newPathTranslator() { 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 8e84836998..37e7b02363 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 @@ 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.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 Object getValue(String expression) { 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 88318f7b65..f2a511c03f 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.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 @@ @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 3ec95ab455..1455b7fcc4 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.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 Object getValue(String expression) { 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 0000000000..4b4a36a22a --- /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 0000000000..c1b458db7f --- /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 0000000000..4b81cf5c84 --- /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 270d2a8eb0..38faeff9b3 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: <<>> ({{{./apidocs/org/apache/maven/model/validation/ModelValidator.html}javadoc}}), with its <<>> 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 <<>> - ({{{./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 <<>> + ({{{./apidocs/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.html}javadoc}}, + {{{./xref/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.html}source}}). - Notice that model interpolation happens profile activation, then profile activation doesn't benefit from every values: + Notice that model interpolation happens 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 | <<>>\ <<>> () | the directory containing the <<>> file as URI | <<<$\{project.baseUri\}>>> | *----+------+------+ +| <<>> | the project's root directory (containing a <<<.mvn>>> directory or with the <<>> xml attribute) | <<<$\{project.rootDirectory\}>>> | +*----+------+------+ | <<>>\ <<>> | the UTC timestamp of build start, in <<>> default format, which can be overridden with <<>> 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 b50789efac..44a2a59ef5 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.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 void testBaseUri() throws Exception { 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 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 5c29e87083..02ea9ba6bf 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 @@ 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 39312bfb6a..c2ac10bd1f 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.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 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 @@ void setUp() throws Exception { } } + @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 2b700a564a..617d7d91f0 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 final XmlPullParser get(XmlPullParser orgParser, Path projectPath) { 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/RootXMLFilter.java b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RootXMLFilter.java new file mode 100644 index 0000000000..af40c5c54d --- /dev/null +++ b/maven-model-transform/src/main/java/org/apache/maven/model/transform/RootXMLFilter.java @@ -0,0 +1,53 @@ +/* + * 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.transform; + +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 + * @since 4.0.0 + */ +class RootXMLFilter extends BufferingParser { + RootXMLFilter(XmlPullParser xmlPullParser) { + super(xmlPullParser); + } + + @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 4113771e40..a1aba62ba5 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 @@ protected Event bufferEvent() throws XmlPullParserException { 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 a5b7c7e14b..a061e1b363 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 696cb1c91b..be4f495068 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 f43847aafc..8f04323b2a 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 a204149c3b..903184ce89 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 e968ec1a94..a589b603f6 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