I'm trying to overcome well-known maven issue, described in various SO questions, for example:
before now I was familiar with following workarounds:
mvn install
- that is exactly what I would like to avoid<skip>
/<properties>
/<profiles>
maven-dependency-plugin
into module folder whilst packaging and setup classpath
and all of those workarounds looks very poor from my perspective.
Today I have read about root-reactor aware subfolder builds in maven-4
, however maven-4
is not yet released and I'm interested to get a solution for maven-3
. I have performed some research and have found a couple useful extension points in maven-3
:
if ( workspace != null )
{
File file = workspace.findArtifact( artifact );
if ( file != null )
{
artifact = artifact.setFile( file );
result.setArtifact( artifact );
result.setRepository( workspace.getRepository() );
artifactResolved( session, trace, artifact, result.getRepository(), null );
continue;
}
}
DefaultProjectDependenciesResolver.java
for ( RepositorySessionDecorator decorator : decorators )
{
RepositorySystemSession decorated = decorator.decorate( project, session );
if ( decorated != null )
{
session = decorated;
}
}
and finally I have implemented a very simple maven extension (full source code on github):
@Component(role = RepositorySessionDecorator.class)
public class FakeRepositorySessionDecorator implements RepositorySessionDecorator {
@Requirement
protected ArtifactHandlerManager artifactHandlerManager;
@Override
public RepositorySystemSession decorate(MavenProject project, RepositorySystemSession session) {
String enabled = session.getUserProperties().get("fakerepo");
if (!"true".equalsIgnoreCase(enabled)) {
return null;
}
MavenProject root = project;
while (root != null && !root.isExecutionRoot()) {
root = root.getParent();
}
if (root != null) {
WorkspaceReader workspaceReader = session.getWorkspaceReader();
workspaceReader = new FakeWorkspaceReader(workspaceReader, root, artifactHandlerManager);
return new DefaultRepositorySystemSession(session)
.setWorkspaceReader(workspaceReader);
}
return null;
}
}
The idea is if developer specifies -Dfakeroot
when executing maven plugin goal my extension expands workspace
scope from single module
to the project root
and when requested new expanded workspace tries to find packaged artifact among submodule folders, thus the sequence of commands like:
mvn clean package
mvn exec:exec -pl submodule -Dfakeroot
leads developer to the expected result.
The question is: what I may brake if I remove requirement to specify -Dfakerepo
and enable the behaviour described above by default (i.e. apply new behaviour for all maven goals and lifecycle phases)? From my perspective it is always more reasonable to lookup packaged artifacts among submodule folders rather than in local repository. Or am I missing something?
UPD.
I have found a following hypothetical scenario when my extension may work not like "expected":
A
and B
in multi-module project, and B
depends on A
A
and issues something like mvn -am test -pl B
in that case if A
was packaged previously my extension forces maven to use stale artifact, however default implementation would use A/target/classes
as classpath entry, on the other hand A/target/classes
may contain stale classes (we are not issuing clean
), thus the behaviour of "default implementation" is also far from ideal in that case.
UPD2.
It seems that it is worth to explain why I that issue is bothering me. Actually, there are a couple of "typical" scenarios:
exec
goal in dev
submodule, which performs DB migrations:
<dependencies>
<dependency>
<groupId>tld.project</groupId>
<artifactId>another-submodule</artifactId>
</dependency>
</dependencies>
<execution>
<id>liquibase-update-primary</id>
<phase>install</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<!-- expecting to get module dependencies there -->
<classpath/>
<!-- main class -->
<argument>liquibase.integration.commandline.Main</argument>
<!-- covered by project properties -->
<argument>--changeLogFile=${primary.changeLogFile}</argument>
<!-- covered by profile properties -->
<argument>--url=${jdbc.url}</argument>
<argument>--driver=${jdbc.driver}</argument>
<argument>--username=${jdbc.username}</argument>
<argument>--password=${jdbc.password}</argument>
<argument>--logLevel=info</argument>
<argument>update</argument>
</arguments>
</configuration>
</execution>
and that obviously does not work in maven-3
, because it expects to find tld.project-another-submodule
artifact in local repository, however it is possible to perform the following trick with maven-dependency-plugin
:
<execution>
<id>liquibase-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<!--
now we may tell liquibase to load extra jars
from ${project.build.directory}/liquibase
-->
<groupId>tld.project</groupId>
<artifactId>another-submodule</artifactId>
<type>jar</type>
<destFileName>another-submodule.jar</destFileName>
<outputDirectory>${project.build.directory}/liquibase</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
We would like to run integration tests individually without recompiling/packaging the entire project i.e. issuing something like mvn verify -pl it-submodule
, that is both useful from developer and CI perspective:
package
and verify
phasesverify
multiple times (yep, someone may think about how is it possible to reiterate failed tests in CI pipeline, however our goal is to run verify
phase multiple times in a row to make sure there are no flapping tests)In case of large projects every extra lifecycle step takes a lot of time
Finally I end up with the following:
local repository
and performing mvn install
is now "safe". -Dimh.repository
system property enables this modemaven-4
does, but also paying attention to supplemental artifacts, mvn install
is not required in this case and mvn package
is enough. -Dimh.workspace
system property enables this modeExamples below are based on MNG-7527 project
-Dimh.repository
mode:
% mvn clean install -Dimh.repository| grep Installing
[INFO] Installing MNG-7527/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527/0.0.1-SNAPSHOT/mng7527-0.0.1-SNAPSHOT.pom
[INFO] Installing MNG-7527/mng7527-war/target/mng7527-war-0.0.1-SNAPSHOT.war to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-war/0.0.1-SNAPSHOT/mng7527-war-0.0.1-SNAPSHOT.war
[INFO] Installing MNG-7527/mng7527-war/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-war/0.0.1-SNAPSHOT/mng7527-war-0.0.1-SNAPSHOT.pom
[INFO] Installing MNG-7527/mng7527-war/target/mng7527-war-0.0.1-SNAPSHOT-classes.jar to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-war/0.0.1-SNAPSHOT/mng7527-war-0.0.1-SNAPSHOT-classes.jar
[INFO] Installing MNG-7527/mng7527-dep1/target/mng7527-dep1-0.0.1-SNAPSHOT.jar to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep1/0.0.1-SNAPSHOT/mng7527-dep1-0.0.1-SNAPSHOT.jar
[INFO] Installing MNG-7527/mng7527-dep1/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep1/0.0.1-SNAPSHOT/mng7527-dep1-0.0.1-SNAPSHOT.pom
[INFO] Installing MNG-7527/mng7527-dep2/target/mng7527-dep2-0.0.1-SNAPSHOT.jar to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep2/0.0.1-SNAPSHOT/mng7527-dep2-0.0.1-SNAPSHOT.jar
[INFO] Installing MNG-7527/mng7527-dep2/pom.xml to /Users/apanfilov/.m2/repository/../../work/gh/MNG-7527/target/local-repo/tel/panfilov/maven/mng7527-dep2/0.0.1-SNAPSHOT/mng7527-dep2-0.0.1-SNAPSHOT.pom
% mvn clean package -f mng7527-dep1 -Dimh.repository
[INFO] [IMH] workspace extension disabled
[INFO] [IMH] setting up overlay repository
[INFO] [IMH] using root project target folder as overlay repository: MNG-7527/target/local-repo
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< tel.panfilov.maven:mng7527-dep1 >-------------------
[INFO] Building mng7527-dep1 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mng7527-dep1 ---
[INFO] Deleting MNG-7527/mng7527-dep1/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mng7527-dep1 ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mng7527-dep1 ---
[INFO] Building jar: MNG-7527/mng7527-dep1/target/mng7527-dep1-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
-Dimh.workspace
mode:
% mvn clean package -f mng7527-dep1 -Dimh.workspace
[INFO] [IMH] repository extension disabled
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< tel.panfilov.maven:mng7527-dep1 >-------------------
[INFO] Building mng7527-dep1 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ mng7527-dep1 ---
[INFO] Deleting MNG-7527/mng7527-dep1/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mng7527-dep1 ---
[INFO] skip non existing resourceDirectory MNG-7527/mng7527-dep1/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mng7527-dep1 ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mng7527-dep1 ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mng7527-dep1 ---
[INFO] Building jar: MNG-7527/mng7527-dep1/target/mng7527-dep1-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------