Repository with a small sample project illustrating a problem with Maven's MojoExecutor when building multi-threaded, reported in MNG-5960
- Apache Maven 3 (tested with 3.3.9 and 3.4.0-SNAPSHOT)
- Java (duh?)
- Clone this repository (
git clone https://github.com/fvanderveen/maven-mojo-jojo.git
) - Make sure the clone works single-threaded:
mvn clean package
. (This should succeed) - Clean the workspace (
mvn clean
) - Attempt multi-threaded compilation with at least 2 threads (
mvn package -T2
)
Step 4 will fail (most of the time?) on a race condition, causing it to no longer have junit on the compile classpath for the test-compile
execution of the maven-compiler-plugin
.
[BuilderThread 3] [INFO] -------------------------------------------------------------
[BuilderThread 3] [ERROR] COMPILATION ERROR :
[BuilderThread 3] [INFO] -------------------------------------------------------------
[BuilderThread 3] [ERROR] /usr/maven-mojo-jojo/sub-parent-a/module-a2/src/test/java/maven/bug/modulea/ModuleA2Test.java:[3,17] package org.junit does not exist
[BuilderThread 3] [ERROR] /usr/maven-mojo-jojo/sub-parent-a/module-a2/src/test/java/maven/bug/modulea/ModuleA2Test.java:[6,10] cannot find symbol
symbol: class Test
location: class maven.bug.modulea.ModuleA2Test
[BuilderThread 3] [INFO] 2 errors
[BuilderThread 3] [INFO] -------------------------------------------------------------
This breaks if, and only if, the following events happen in the following order:
- Mojo configuration for the
maven-compiler-plugin:test-compile
inmodule-a2
is done. This will configure the resolved artifacts for the project. - Due to the
maven-javadoc-plugin
insub-parent-b
definesaggregator = true
goal andrequiresDependencyResolution = ResolutionScope.COMPILE
, theMojoExecutor
class will start resolving dependencies for all projects in the currentMavenSession
(sincegetProjects()
returns all projects that are included to be built). This will reset the resolved artifacts for the projectmodule-a2
to thecompile
scope dependencies (and thus missing thetest
scoped ones) - The
maven-compiler-plugin
starts, and in its mojo execution queries thegetTestClasspathElements
of themodule-a2
project. Since the resolved artifacts have been reset to thecompile
dependencies, this will result in a classpath withoutjunit:junit:4.12
, causing javac to fail.
Due to this order, the sub-parent sub-parent-b
has a maven-javadoc-plugin
execution bound to the test-compile
phase; increasing the chances of this problem occurring.
If I understand the idea behing MojoExecutor
's ensureDependenciesAreResolved
method correctly, I'm assuming it should call resolveProjectDependencies
only on modules that are housed under the current project. In this sample project, I would expect this method to resolve the dependencies for the modules module-b1
and module-b2
, as these are child-modules of sub-parent-b
.
Currently, this method calls resolveProjectDependencies
for all MavenProject
instances returned by MavenSession#getProjects
. Given the code; this method seems to return all projects included in the build reactor, which without any --projects
argument boils down to all projects housed by the current parent.
I would suggest (recursively) finding projects that are housed under the current project, and only call resolveProjectDependencies
for those projects.
A diff for MojoExecutor
that seems to result in this behaviour would be:
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java b/maven-core/src/main/java/org/apache/maven/life
cycle/internal/MojoExecutor.java
index 8524c5e..9dc3424 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
@@ -257,14 +257,11 @@ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenS
if ( dependencyContext.isResolutionRequiredForAggregatedProjects( scopesToCollect, scopesToResolve ) )
{
- for ( MavenProject aggregatedProject : session.getProjects() )
+ for ( MavenProject aggregatedProject : collectChildModules(project, session.getProjects()) )
{
- if ( aggregatedProject != project )
- {
- lifeCycleDependencyResolver.resolveProjectDependencies( aggregatedProject, scopesToCollect,
- scopesToResolve, session, aggregating,
- Collections.<Artifact>emptySet() );
- }
+ lifeCycleDependencyResolver.resolveProjectDependencies( aggregatedProject, scopesToCollect,
+ scopesToResolve, session, aggregating,
+ Collections.<Artifact>emptySet() );
}
}
}
@@ -279,6 +276,19 @@ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenS
}
}
+ private List<MavenProject> collectChildModules(MavenProject parent, List<MavenProject> projects) {
+ List<MavenProject> children = new ArrayList<>();
+
+ for (MavenProject project : projects) {
+ if (project.getParent() == parent) {
+ children.add(project);
+ children.addAll(collectChildModules(project, projects));
+ }
+ }
+
+ return children;
+ }
+
private ArtifactFilter getArtifactFilter( MojoDescriptor mojoDescriptor )
{
String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired();