[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 current directory if there's no such argument.

 * 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.
This commit is contained in:
Guillaume Nodet 2023-04-20 12:58:12 +02:00 committed by GitHub
parent 9e51a8fe95
commit 2db7c85b64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1246 additions and 37 deletions

View File

@ -86,8 +86,45 @@ default String getId() {
return getModel().getId(); return getModel().getId();
} }
/**
* @deprecated use {@link #isTopProject()} instead
*/
@Deprecated
boolean isExecutionRoot(); 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 @Nonnull
Optional<Project> getParent(); Optional<Project> getParent();

View File

@ -84,9 +84,34 @@ public interface Session {
@Nonnull @Nonnull
Instant getStartTime(); 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 @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(); Path getMultiModuleProjectDirectory();
/**
* @deprecated use {@link #getTopDirectory()} instead
*/
@Deprecated
@Nonnull @Nonnull
Path getExecutionRootDirectory(); Path getExecutionRootDirectory();
@ -97,8 +122,8 @@ public interface Session {
* Returns the plugin context for mojo being executed and the specified * Returns the plugin context for mojo being executed and the specified
* {@link Project}, never returns {@code null} as if context not present, creates it. * {@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 * <strong>Implementation note:</strong> while this method return type is {@link Map}, the
* implements {@link java.util.concurrent.ConcurrentMap} as well. * 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 * @throws org.apache.maven.api.services.MavenException if not called from the within a mojo execution
*/ */

View File

@ -212,6 +212,19 @@
</description> </description>
<type>String</type> <type>String</type>
</field> </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> <field>
<name>inceptionYear</name> <name>inceptionYear</name>
<version>3.0.0+</version> <version>3.0.0+</version>

View File

@ -29,6 +29,7 @@
import org.apache.maven.bridge.MavenRepositorySystem; import org.apache.maven.bridge.MavenRepositorySystem;
import org.apache.maven.model.building.ModelBuilder; import org.apache.maven.model.building.ModelBuilder;
import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.model.root.RootLocator;
import org.apache.maven.repository.internal.ModelCacheFactory; import org.apache.maven.repository.internal.ModelCacheFactory;
import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.impl.RemoteRepositoryManager; import org.eclipse.aether.impl.RemoteRepositoryManager;
@ -45,7 +46,8 @@ public TestProjectBuilder(
RepositorySystem repoSystem, RepositorySystem repoSystem,
RemoteRepositoryManager repositoryManager, RemoteRepositoryManager repositoryManager,
ProjectDependenciesResolver dependencyResolver, ProjectDependenciesResolver dependencyResolver,
ModelCacheFactory modelCacheFactory) { ModelCacheFactory modelCacheFactory,
RootLocator rootLocator) {
super( super(
modelBuilder, modelBuilder,
modelProcessor, modelProcessor,
@ -54,7 +56,8 @@ public TestProjectBuilder(
repoSystem, repoSystem,
repositoryManager, repositoryManager,
dependencyResolver, dependencyResolver,
modelCacheFactory); modelCacheFactory,
rootLocator);
} }
@Override @Override

View File

@ -19,6 +19,7 @@
package org.apache.maven.execution; package org.apache.maven.execution;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -30,6 +31,7 @@
import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.model.Profile; import org.apache.maven.model.Profile;
import org.apache.maven.model.root.RootLocator;
import org.apache.maven.project.DefaultProjectBuildingRequest; import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.properties.internal.SystemProperties; import org.apache.maven.properties.internal.SystemProperties;
@ -102,6 +104,10 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
private File basedir; private File basedir;
private Path rootDirectory;
private Path topDirectory;
private List<String> goals; private List<String> goals;
private boolean useReactor = false; private boolean useReactor = false;
@ -1051,16 +1057,43 @@ public MavenExecutionRequest setToolchains(Map<String, List<ToolchainModel>> too
return this; return this;
} }
@Deprecated
@Override @Override
public void setMultiModuleProjectDirectory(File directory) { public void setMultiModuleProjectDirectory(File directory) {
this.multiModuleProjectDirectory = directory; this.multiModuleProjectDirectory = directory;
} }
@Deprecated
@Override @Override
public File getMultiModuleProjectDirectory() { public File getMultiModuleProjectDirectory() {
return multiModuleProjectDirectory; 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 @Override
public MavenExecutionRequest setEventSpyDispatcher(EventSpyDispatcher eventSpyDispatcher) { public MavenExecutionRequest setEventSpyDispatcher(EventSpyDispatcher eventSpyDispatcher) {
this.eventSpyDispatcher = eventSpyDispatcher; this.eventSpyDispatcher = eventSpyDispatcher;

View File

@ -19,6 +19,7 @@
package org.apache.maven.execution; package org.apache.maven.execution;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -91,8 +92,17 @@ public interface MavenExecutionRequest {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Base directory // Base directory
/**
* @deprecated use {@link #setTopDirectory(Path)} instead
*/
@Deprecated
MavenExecutionRequest setBaseDirectory(File basedir); MavenExecutionRequest setBaseDirectory(File basedir);
/**
* @deprecated use {@link #getTopDirectory()} instead
*/
@Deprecated
String getBaseDirectory(); String getBaseDirectory();
// Timing (remove this) // Timing (remove this)
@ -494,14 +504,51 @@ public interface MavenExecutionRequest {
/** /**
* @since 3.3.0 * @since 3.3.0
* @deprecated use {@link #setRootDirectory(Path)} instead
*/ */
@Deprecated
void setMultiModuleProjectDirectory(File file); void setMultiModuleProjectDirectory(File file);
/** /**
* @since 3.3.0 * @since 3.3.0
* @deprecated use {@link #getRootDirectory()} instead
*/ */
@Deprecated
File getMultiModuleProjectDirectory(); 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 * @since 3.3.0
*/ */

View File

@ -19,6 +19,7 @@
package org.apache.maven.execution; package org.apache.maven.execution;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -141,10 +142,30 @@ public List<MavenProject> getProjects() {
return projects; return projects;
} }
/**
* @deprecated use {@link #getTopDirectory()} ()}
*/
@Deprecated
public String getExecutionRootDirectory() { public String getExecutionRootDirectory() {
return request.getBaseDirectory(); 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() { public MavenExecutionRequest getRequest() {
return request; return request;
} }

View File

@ -129,6 +129,22 @@ public boolean isExecutionRoot() {
return project.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 @Override
public Optional<Project> getParent() { public Optional<Project> getParent() {
MavenProject parent = project.getParent(); MavenProject parent = project.getParent();

View File

@ -19,7 +19,6 @@
package org.apache.maven.internal.impl; package org.apache.maven.internal.impl;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant; import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -150,7 +149,17 @@ public Path getMultiModuleProjectDirectory() {
@Nonnull @Nonnull
@Override @Override
public Path getExecutionRootDirectory() { 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 @Nonnull

View File

@ -74,6 +74,9 @@ public void injectTransformedArtifacts(MavenProject project, RepositorySystemSes
generatedFile = Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER, "pom"); generatedFile = Files.createTempFile(buildDir, CONSUMER_POM_CLASSIFIER, "pom");
} }
project.addAttachedArtifact(new ConsumerPomArtifact(project, generatedFile, session)); 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");
} }
} }

View File

@ -69,6 +69,7 @@
import org.apache.maven.model.building.TransformerContext; import org.apache.maven.model.building.TransformerContext;
import org.apache.maven.model.building.TransformerContextBuilder; import org.apache.maven.model.building.TransformerContextBuilder;
import org.apache.maven.model.resolution.ModelResolver; 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.ArtifactDescriptorUtils;
import org.apache.maven.repository.internal.ModelCacheFactory; import org.apache.maven.repository.internal.ModelCacheFactory;
import org.codehaus.plexus.util.Os; import org.codehaus.plexus.util.Os;
@ -101,6 +102,8 @@ public class DefaultProjectBuilder implements ProjectBuilder {
private final ProjectDependenciesResolver dependencyResolver; private final ProjectDependenciesResolver dependencyResolver;
private final ModelCacheFactory modelCacheFactory; private final ModelCacheFactory modelCacheFactory;
private final RootLocator rootLocator;
@SuppressWarnings("checkstyle:ParameterNumber") @SuppressWarnings("checkstyle:ParameterNumber")
@Inject @Inject
public DefaultProjectBuilder( public DefaultProjectBuilder(
@ -111,7 +114,8 @@ public DefaultProjectBuilder(
RepositorySystem repoSystem, RepositorySystem repoSystem,
RemoteRepositoryManager repositoryManager, RemoteRepositoryManager repositoryManager,
ProjectDependenciesResolver dependencyResolver, ProjectDependenciesResolver dependencyResolver,
ModelCacheFactory modelCacheFactory) { ModelCacheFactory modelCacheFactory,
RootLocator rootLocator) {
this.modelBuilder = modelBuilder; this.modelBuilder = modelBuilder;
this.modelProcessor = modelProcessor; this.modelProcessor = modelProcessor;
this.projectBuildingHelper = projectBuildingHelper; this.projectBuildingHelper = projectBuildingHelper;
@ -120,6 +124,7 @@ public DefaultProjectBuilder(
this.repositoryManager = repositoryManager; this.repositoryManager = repositoryManager;
this.dependencyResolver = dependencyResolver; this.dependencyResolver = dependencyResolver;
this.modelCacheFactory = modelCacheFactory; this.modelCacheFactory = modelCacheFactory;
this.rootLocator = rootLocator;
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// MavenProjectBuilder Implementation // MavenProjectBuilder Implementation
@ -162,6 +167,11 @@ private ProjectBuildingResult build(File pomFile, ModelSource modelSource, Inter
request.setModelSource(modelSource); request.setModelSource(modelSource);
request.setLocationTracking(true); request.setLocationTracking(true);
if (pomFile != null) {
project.setRootDirectory(
rootLocator.findRoot(pomFile.getParentFile().toPath()));
}
ModelBuildingResult result; ModelBuildingResult result;
try { try {
result = modelBuilder.build(request); result = modelBuilder.build(request);
@ -445,6 +455,8 @@ private boolean build(
MavenProject project = new MavenProject(); MavenProject project = new MavenProject();
project.setFile(pomFile); project.setFile(pomFile);
project.setRootDirectory(rootLocator.findRoot(pomFile.getParentFile().toPath()));
ModelBuildingRequest request = getModelBuildingRequest(config) ModelBuildingRequest request = getModelBuildingRequest(config)
.setPomFile(pomFile) .setPomFile(pomFile)
.setTwoPhaseBuilding(true) .setTwoPhaseBuilding(true)

View File

@ -21,6 +21,7 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -64,6 +65,7 @@
import org.apache.maven.model.Resource; import org.apache.maven.model.Resource;
import org.apache.maven.model.Scm; import org.apache.maven.model.Scm;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer; 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.InvalidDependencyVersionException;
import org.apache.maven.project.artifact.MavenMetadataSource; import org.apache.maven.project.artifact.MavenMetadataSource;
import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.ClassRealm;
@ -105,6 +107,8 @@ public class MavenProject implements Cloneable {
private File basedir; private File basedir;
private Path rootDirectory;
private Set<Artifact> resolvedArtifacts; private Set<Artifact> resolvedArtifacts;
private ArtifactFilter artifactFilter; private ArtifactFilter artifactFilter;
@ -1679,4 +1683,20 @@ public ProjectBuildingRequest getProjectBuildingRequest() {
public void setProjectBuildingRequest(ProjectBuildingRequest projectBuildingRequest) { public void setProjectBuildingRequest(ProjectBuildingRequest projectBuildingRequest) {
this.projectBuilderConfiguration = 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;
}
} }

View File

@ -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());
}
}

View File

@ -21,6 +21,9 @@
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -38,6 +41,7 @@
import org.apache.maven.model.Build; import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency; import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model; 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.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.DuplicateProjectException; import org.apache.maven.project.DuplicateProjectException;
@ -51,8 +55,11 @@
import static org.codehaus.plexus.testing.PlexusExtension.getTestFile; import static org.codehaus.plexus.testing.PlexusExtension.getTestFile;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
@ -64,6 +71,8 @@ class PluginParameterExpressionEvaluatorTest extends AbstractCoreMavenComponentT
@Inject @Inject
private RepositorySystem factory; private RepositorySystem factory;
private Path rootDirectory;
@Test @Test
void testPluginDescriptorExpressionReference() throws Exception { void testPluginDescriptorExpressionReference() throws Exception {
MojoExecution exec = newMojoExecution(); MojoExecution exec = newMojoExecution();
@ -357,6 +366,28 @@ void testShouldExtractPluginArtifacts() throws Exception {
assertEquals("testGroup", result.getGroupId()); 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() { private MavenProject createDefaultProject() {
return new MavenProject(new Model()); return new MavenProject(new Model());
} }
@ -368,6 +399,7 @@ private ExpressionEvaluator createExpressionEvaluator(
MutablePlexusContainer container = (MutablePlexusContainer) getContainer(); MutablePlexusContainer container = (MutablePlexusContainer) getContainer();
MavenSession session = createSession(container, repo, executionProperties); MavenSession session = createSession(container, repo, executionProperties);
session.setCurrentProject(project); session.setCurrentProject(project);
session.getRequest().setRootDirectory(rootDirectory);
MojoDescriptor mojo = new MojoDescriptor(); MojoDescriptor mojo = new MojoDescriptor();
mojo.setPluginDescriptor(pluginDescriptor); mojo.setPluginDescriptor(pluginDescriptor);

View File

@ -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<Artifact> deps = new ArrayList<>();
deps.add(depArtifact);
exec.getMojoDescriptor().getPluginDescriptor().setArtifacts(deps);
Session session = newSession();
@SuppressWarnings("unchecked")
List<Artifact> depResults = (List<Artifact>)
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<Artifact> deps = new ArrayList<>();
deps.add(depArtifact);
exec.getMojoDescriptor().getPluginDescriptor().setArtifacts(deps);
Session session = newSession();
@SuppressWarnings("unchecked")
Map<String, Artifact> depResults = (Map<String, Artifact>)
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.<String>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.<MavenProject>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<Artifact> artifacts = (List<Artifact>) 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;
}
}

View File

@ -135,6 +135,13 @@ static Properties getBuildProperties() {
} }
public static void showError(Logger logger, String message, Throwable e, boolean showStackTrace) { 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) { if (showStackTrace) {
logger.error(message, e); logger.error(message, e);
} else { } else {

View File

@ -19,6 +19,7 @@
package org.apache.maven.cli; package org.apache.maven.cli;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
@ -40,6 +41,10 @@ public class CliRequest {
File multiModuleProjectDirectory; File multiModuleProjectDirectory;
Path rootDirectory;
Path topDirectory;
boolean verbose; boolean verbose;
boolean quiet; boolean quiet;

View File

@ -29,6 +29,8 @@
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -37,6 +39,7 @@
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -87,6 +90,7 @@
import org.apache.maven.logwrapper.LogLevelRecorder; import org.apache.maven.logwrapper.LogLevelRecorder;
import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory;
import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.model.root.RootLocator;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.properties.internal.EnvironmentUtils; import org.apache.maven.properties.internal.EnvironmentUtils;
import org.apache.maven.properties.internal.SystemProperties; 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 // Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative
// Windows paths. // Windows paths.
@ -1185,6 +1237,8 @@ private MavenExecutionRequest populateRequest(CliRequest cliRequest, MavenExecut
request.setSystemProperties(cliRequest.systemProperties); request.setSystemProperties(cliRequest.systemProperties);
request.setUserProperties(cliRequest.userProperties); request.setUserProperties(cliRequest.userProperties);
request.setMultiModuleProjectDirectory(cliRequest.multiModuleProjectDirectory); request.setMultiModuleProjectDirectory(cliRequest.multiModuleProjectDirectory);
request.setRootDirectory(cliRequest.rootDirectory);
request.setTopDirectory(cliRequest.topDirectory);
request.setPom(determinePom(commandLine, workingDirectory, baseDirectory)); request.setPom(determinePom(commandLine, workingDirectory, baseDirectory));
request.setTransferListener(determineTransferListener(quiet, verbose, commandLine, request)); request.setTransferListener(determineTransferListener(quiet, verbose, commandLine, request));
request.setExecutionListener(determineExecutionListener()); request.setExecutionListener(determineExecutionListener());

View File

@ -22,6 +22,8 @@
import java.io.File; import java.io.File;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -36,6 +38,7 @@
import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.ProfileActivation; import org.apache.maven.execution.ProfileActivation;
import org.apache.maven.execution.ProjectActivation; import org.apache.maven.execution.ProjectActivation;
import org.apache.maven.model.root.DefaultRootLocator;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.logging.MessageUtils; import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.building.ToolchainsBuildingRequest; import org.apache.maven.toolchain.building.ToolchainsBuildingRequest;
@ -543,6 +546,12 @@ void populatePropertiesOverwrite() throws Exception {
assertThat(request.getUserProperties().getProperty("x"), is("false")); 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) { private MavenProject createMavenProject(String groupId, String artifactId) {
MavenProject project = new MavenProject(); MavenProject project = new MavenProject();
project.setGroupId(groupId); project.setGroupId(groupId);

View File

@ -0,0 +1,3 @@
<project xmlns="http://maven.apache.org/POM/4.0.0">
</project>

View File

@ -0,0 +1,3 @@
<project root="true" xmlns="http://maven.apache.org/POM/4.0.0">
</project>

View File

@ -64,6 +64,8 @@
import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator; import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator;
import org.apache.maven.model.profile.activation.ProfileActivator; import org.apache.maven.model.profile.activation.ProfileActivator;
import org.apache.maven.model.profile.activation.PropertyProfileActivator; 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.DefaultSuperPomProvider;
import org.apache.maven.model.superpom.SuperPomProvider; import org.apache.maven.model.superpom.SuperPomProvider;
import org.apache.maven.model.validation.DefaultModelValidator; import org.apache.maven.model.validation.DefaultModelValidator;
@ -100,6 +102,8 @@ public class DefaultModelBuilderFactory {
private ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator; private ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
private ModelVersionProcessor versionProcessor; private ModelVersionProcessor versionProcessor;
private RootLocator rootLocator;
public DefaultModelBuilderFactory setModelProcessor(ModelProcessor modelProcessor) { public DefaultModelBuilderFactory setModelProcessor(ModelProcessor modelProcessor) {
this.modelProcessor = modelProcessor; this.modelProcessor = modelProcessor;
return this; return this;
@ -201,6 +205,11 @@ public DefaultModelBuilderFactory setVersionProcessor(ModelVersionProcessor vers
return this; return this;
} }
public DefaultModelBuilderFactory setRootLocator(RootLocator rootLocator) {
this.rootLocator = rootLocator;
return this;
}
protected ModelProcessor newModelProcessor() { protected ModelProcessor newModelProcessor() {
return new DefaultModelProcessor(newModelLocator(), newModelReader()); return new DefaultModelProcessor(newModelLocator(), newModelReader());
} }
@ -227,7 +236,7 @@ protected ProfileActivator[] newProfileActivators() {
} }
protected ProfileActivationFilePathInterpolator newProfileActivationFilePathInterpolator() { protected ProfileActivationFilePathInterpolator newProfileActivationFilePathInterpolator() {
return new ProfileActivationFilePathInterpolator(newPathTranslator()); return new ProfileActivationFilePathInterpolator(newPathTranslator(), newRootLocator());
} }
protected UrlNormalizer newUrlNormalizer() { protected UrlNormalizer newUrlNormalizer() {
@ -238,10 +247,15 @@ protected PathTranslator newPathTranslator() {
return new DefaultPathTranslator(); return new DefaultPathTranslator();
} }
protected RootLocator newRootLocator() {
return new DefaultRootLocator();
}
protected ModelInterpolator newModelInterpolator() { protected ModelInterpolator newModelInterpolator() {
UrlNormalizer normalizer = newUrlNormalizer(); UrlNormalizer normalizer = newUrlNormalizer();
PathTranslator pathTranslator = newPathTranslator(); PathTranslator pathTranslator = newPathTranslator();
return new StringVisitorModelInterpolator(pathTranslator, normalizer); RootLocator rootLocator = newRootLocator();
return new StringVisitorModelInterpolator(pathTranslator, normalizer, rootLocator);
} }
protected ModelVersionProcessor newModelVersionPropertiesProcessor() { protected ModelVersionProcessor newModelVersionPropertiesProcessor() {

View File

@ -21,6 +21,7 @@
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -33,6 +34,7 @@
import org.apache.maven.model.building.ModelProblemCollector; import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.path.PathTranslator; import org.apache.maven.model.path.PathTranslator;
import org.apache.maven.model.path.UrlNormalizer; 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.AbstractValueSource;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor; import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
import org.codehaus.plexus.interpolation.MapBasedValueSource; import org.codehaus.plexus.interpolation.MapBasedValueSource;
@ -74,10 +76,14 @@ public abstract class AbstractStringBasedModelInterpolator implements ModelInter
private final PathTranslator pathTranslator; private final PathTranslator pathTranslator;
private final UrlNormalizer urlNormalizer; private final UrlNormalizer urlNormalizer;
private final RootLocator rootLocator;
@Inject @Inject
public AbstractStringBasedModelInterpolator(PathTranslator pathTranslator, UrlNormalizer urlNormalizer) { public AbstractStringBasedModelInterpolator(
PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
this.pathTranslator = pathTranslator; this.pathTranslator = pathTranslator;
this.urlNormalizer = urlNormalizer; this.urlNormalizer = urlNormalizer;
this.rootLocator = rootLocator;
} }
@Override @Override
@ -133,6 +139,20 @@ public Object getValue(String expression) {
valueSources.add(new BuildTimestampValueSource(config.getBuildStartTime(), modelProperties)); 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(projectPrefixValueSource);
valueSources.add(new MapBasedValueSource(config.getUserProperties())); valueSources.add(new MapBasedValueSource(config.getUserProperties()));

View File

@ -35,6 +35,7 @@
import org.apache.maven.model.building.ModelProblemCollectorRequest; import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.path.PathTranslator; import org.apache.maven.model.path.PathTranslator;
import org.apache.maven.model.path.UrlNormalizer; import org.apache.maven.model.path.UrlNormalizer;
import org.apache.maven.model.root.RootLocator;
import org.apache.maven.model.v4.MavenTransformer; import org.apache.maven.model.v4.MavenTransformer;
import org.codehaus.plexus.interpolation.InterpolationException; import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.InterpolationPostProcessor; import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
@ -51,8 +52,9 @@
@Singleton @Singleton
public class StringVisitorModelInterpolator extends AbstractStringBasedModelInterpolator { public class StringVisitorModelInterpolator extends AbstractStringBasedModelInterpolator {
@Inject @Inject
public StringVisitorModelInterpolator(PathTranslator pathTranslator, UrlNormalizer urlNormalizer) { public StringVisitorModelInterpolator(
super(pathTranslator, urlNormalizer); PathTranslator pathTranslator, UrlNormalizer urlNormalizer, RootLocator rootLocator) {
super(pathTranslator, urlNormalizer, rootLocator);
} }
interface InnerInterpolator { interface InnerInterpolator {

View File

@ -23,9 +23,11 @@
import javax.inject.Singleton; import javax.inject.Singleton;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import org.apache.maven.api.model.ActivationFile; import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.model.profile.ProfileActivationContext; 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.AbstractValueSource;
import org.codehaus.plexus.interpolation.InterpolationException; import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.MapBasedValueSource; import org.codehaus.plexus.interpolation.MapBasedValueSource;
@ -42,9 +44,12 @@ public class ProfileActivationFilePathInterpolator {
private final PathTranslator pathTranslator; private final PathTranslator pathTranslator;
private final RootLocator rootLocator;
@Inject @Inject
public ProfileActivationFilePathInterpolator(PathTranslator pathTranslator) { public ProfileActivationFilePathInterpolator(PathTranslator pathTranslator, RootLocator rootLocator) {
this.pathTranslator = pathTranslator; this.pathTranslator = pathTranslator;
this.rootLocator = rootLocator;
} }
/** /**
@ -79,6 +84,18 @@ public Object getValue(String expression) {
return null; 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.getProjectProperties()));
interpolator.addValueSource(new MapBasedValueSource(context.getUserProperties())); interpolator.addValueSource(new MapBasedValueSource(context.getUserProperties()));

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1 @@
org.apache.maven.model.root.DefaultRootLocator

View File

@ -41,7 +41,7 @@ Maven Model Builder
** profile activation: see {{{./apidocs/org/apache/maven/model/profile/activation/package-summary.html}available activators}}. ** 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 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}}), ** file model validation: <<<ModelValidator>>> ({{{./apidocs/org/apache/maven/model/validation/ModelValidator.html}javadoc}}),
with its <<<DefaultModelValidator>>> implementation with its <<<DefaultModelValidator>>> implementation
@ -51,7 +51,7 @@ Maven Model Builder
* phase 2, with optional plugin processing * 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) *** Resolve version of versionless parents based on relativePath (including ci-friendly versions)
@ -156,13 +156,13 @@ Maven Model Builder
* Model Interpolation * Model Interpolation
Model Interpolation consists in replacing <<<$\{...\}>>> with calculated value. It is done in <<<StringSearchModelInterpolator>>> Model Interpolation consists in replacing <<<$\{...\}>>> with calculated value. It is done in <<<StringVisitorModelInterpolator>>>
({{{./apidocs/org/apache/maven/model/interpolation/StringSearchModelInterpolator.html}javadoc}}, ({{{./apidocs/org/apache/maven/model/interpolation/StringVisitorModelInterpolator.html}javadoc}},
{{{./xref/org/apache/maven/model/interpolation/StringSearchModelInterpolator.html}source}}). {{{./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 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: Values are evaluated in sequence from different syntaxes:
@ -183,6 +183,8 @@ Maven Model Builder
| <<<project.baseUri>>>\ | <<<project.baseUri>>>\
<<<pom.baseUri>>> (<deprecated>) | the directory containing the <<<pom.xml>>> file as URI | <<<$\{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>>>\ | <<<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\}>>> | <<<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\}>>> |
*----+------+------+ *----+------+------+

View File

@ -19,6 +19,8 @@
package org.apache.maven.model.interpolation; package org.apache.maven.model.interpolation;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
@ -41,11 +43,13 @@
import org.apache.maven.model.building.DefaultModelBuildingRequest; import org.apache.maven.model.building.DefaultModelBuildingRequest;
import org.apache.maven.model.building.ModelBuildingRequest; import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.SimpleProblemCollector; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; 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()); 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 @Test
public void testEnvars() throws Exception { public void testEnvars() throws Exception {
Properties context = new Properties();
context.put("env.HOME", "/path/to/home"); context.put("env.HOME", "/path/to/home");
Map<String, String> modelProperties = new HashMap<>(); Map<String, String> modelProperties = new HashMap<>();

View File

@ -20,6 +20,6 @@
public class StringVisitorModelInterpolatorTest extends AbstractModelInterpolatorTest { public class StringVisitorModelInterpolatorTest extends AbstractModelInterpolatorTest {
protected ModelInterpolator createInterpolator() { protected ModelInterpolator createInterpolator() {
return new StringVisitorModelInterpolator(null, null); return new StringVisitorModelInterpolator(null, null, bd -> true);
} }
} }

View File

@ -28,11 +28,13 @@
import org.apache.maven.model.path.DefaultPathTranslator; import org.apache.maven.model.path.DefaultPathTranslator;
import org.apache.maven.model.path.ProfileActivationFilePathInterpolator; import org.apache.maven.model.path.ProfileActivationFilePathInterpolator;
import org.apache.maven.model.profile.DefaultProfileActivationContext; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/** /**
* Tests {@link FileProfileActivator}. * Tests {@link FileProfileActivator}.
@ -49,9 +51,10 @@ class FileProfileActivatorTest extends AbstractProfileActivatorTest<FileProfileA
@BeforeEach @BeforeEach
@Override @Override
void setUp() throws Exception { 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()); File file = new File(tempDir.resolve("file.txt").toString());
if (!file.createNewFile()) { 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 @Test
void testIsActiveNoFile() { void testIsActiveNoFile() {
assertActivation(false, newExistsProfile(null), context); assertActivation(false, newExistsProfile(null), context);

View File

@ -40,6 +40,8 @@ public final XmlPullParser get(XmlPullParser orgParser, Path projectPath) {
parser = buildPomXMLFilterFactory.get(parser, projectPath); parser = buildPomXMLFilterFactory.get(parser, projectPath);
// Remove root model attribute
parser = new RootXMLFilter(parser);
// Strip modules // Strip modules
parser = new ModulesXMLFilter(parser); parser = new ModulesXMLFilter(parser);
// Adjust relativePath // Adjust relativePath

View File

@ -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;
}
}

View File

@ -395,6 +395,16 @@ protected Event bufferEvent() throws XmlPullParserException {
event.prefix = pp.getPrefix(); event.prefix = pp.getPrefix();
event.empty = pp.isEmptyElementTag(); event.empty = pp.isEmptyElementTag();
event.text = pp.getText(); 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; break;
case END_TAG: case END_TAG:
event.name = pp.getName(); event.name = pp.getName();

View File

@ -207,7 +207,7 @@ public class ${className}
#if ( $field.type == "String" ) #if ( $field.type == "String" )
${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) ); ${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) );
#elseif ( $field.type == "boolean" || $field.type == "Boolean" ) #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 #else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end #end

View File

@ -236,7 +236,7 @@ public class ${className}
#if ( $field.type == "String" ) #if ( $field.type == "String" )
${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) ); ${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) );
#elseif ( $field.type == "boolean" || $field.type == "Boolean" ) #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 #else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end #end

View File

@ -175,7 +175,7 @@ public class ${className}
private boolean getBooleanValue( String s, String attribute, XmlPullParser parser ) private boolean getBooleanValue( String s, String attribute, XmlPullParser parser )
throws XmlPullParserException throws XmlPullParserException
{ {
return getBooleanValue( s, attribute, parser, null ); return getBooleanValue( s, attribute, parser, false );
} //-- boolean getBooleanValue( String, String, XmlPullParser ) } //-- boolean getBooleanValue( String, String, XmlPullParser )
/** /**
@ -189,18 +189,14 @@ public class ${className}
* any. * any.
* @return boolean * @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 throws XmlPullParserException
{ {
if ( s != null && s.length() != 0 ) if ( s != null && s.length() != 0 )
{ {
return Boolean.valueOf( s ).booleanValue(); return Boolean.valueOf( s ).booleanValue();
} }
if ( defaultValue != null ) return defaultValue;
{
return Boolean.valueOf( defaultValue ).booleanValue();
}
return false;
} //-- boolean getBooleanValue( String, String, XmlPullParser, String ) } //-- boolean getBooleanValue( String, String, XmlPullParser, String )
/** /**
@ -709,7 +705,7 @@ public class ${className}
#if ( $field.type == "String" ) #if ( $field.type == "String" )
${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) ); ${classLcapName}.${field.name}( interpolatedTrimmed( value, "$fieldTagName" ) );
#elseif ( $field.type == "boolean" || $field.type == "Boolean" ) #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 #else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end #end
@ -749,7 +745,7 @@ public class ${className}
${classLcapName}.${field.name}( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ) ); ${classLcapName}.${field.name}( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ) );
break; break;
#elseif ( $field.type == "boolean" || $field.type == "Boolean" ) #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; break;
#elseif ( $field.type == "int" ) #elseif ( $field.type == "int" )
${classLcapName}.${field.name}( getIntegerValue( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ), "${fieldTagName}", parser, strict, ${field.defaultValue} ) ); ${classLcapName}.${field.name}( getIntegerValue( interpolatedTrimmed( parser.nextText(), "${fieldTagName}" ), "${fieldTagName}", parser, strict, ${field.defaultValue} ) );

View File

@ -210,8 +210,15 @@ public class ${className}
#set ( $fieldCapName = $Helper.capitalise( $field.name ) ) #set ( $fieldCapName = $Helper.capitalise( $field.name ) )
#if ( $field.type == "String" ) #if ( $field.type == "String" )
writeAttr( "$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer ); 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 #else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end #end
#end #end
#end #end

View File

@ -192,8 +192,15 @@ public class ${className}
#set ( $fieldCapName = $Helper.capitalise( $field.name ) ) #set ( $fieldCapName = $Helper.capitalise( $field.name ) )
#if ( $field.type == "String" ) #if ( $field.type == "String" )
writeAttr( "$fieldTagName", ${classLcapName}.get${fieldCapName}(), serializer ); 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 #else
// TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity} // TODO: type=${field.type} to=${field.to} multiplicity=${field.multiplicity}
#end #end
#end #end
#end #end