When -DskipTests
is not fast enough: skip Maven mojos properly
-DskipTests
is a well known way to skip the execution of unit tests during a Maven build and thus save time.
This is handy e.g. after pulling the changes from a team git repository which are supposed to have been tested by a CI
already.
This kind of skipping is not specific to maven-surefire-plugin
. Many other plugins have similar options:
Enforcer has -Denforcer.skip
, License Maven plugin has -Dlicense.skip
, etc. The more you want to
build quickly, without running needless mojos, the more you search for these skip parameters.
Some candidates for skipping, like unit tests and integration tests, are pretty obvious. Other may get revealed by
profiling your build either by using a stock Java profiler or by leveraging Maven specific tools like
maven-buildtime-profiler or
Gradle build scans for Maven.
At some point you are skipping every mojo execution that is not essential to your build. You create a dedicated
Maven profile listing all those skip flags, so that you do not need to remember and type them by hand.
$ mvn clean install -DskipTests -Denforcer.skip -Dformatter.skip -Dimpsort.skip -Dquarkus.build.skip -Dgroovy.skip
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:53 min
is not something you are happy about. How can this be made even faster?
Well, common sense (or further profiling with a Java profiler) may unveil that the named skip*
properties
do not prevent the skipped mojos' classes from being loaded, initialized, instantiated, configured and their
execute()
method from being invoked (that’s where the skip*
parameters are actually being evaluated). That
might be quite a lot of superfluous CPU cycles and luckily, Maven offers a way how to avoid them.
The trick is to remove the skippable mojos from the Maven execution plan altogether. How can that be done? - the key
is in playing with the <phase>
to which the given mojo is bound. There are two ways how to reach that some mojo
is bound to no phase:
-
By setting <phase>none</phase>
or shortly <phase/>
-
By moving the given plugin definition from the the default profile-less <build>
section to a profile.
The option 1. needs to be used for mojos that Maven binds to some phase by default. E.g. the test
mojo of
surefire-maven-plugin
is bound to the test
phase by default. The option 2. may be used for all other mojos.
Once we manage it to remove a mojo from the execution plan, its classes will neither be loaded, initialized,
instantiated, configured nor their execute()
methods will be called.
Let’s have a look at an example. Say, that we want all the tests and source tree validation to happen in the default
"profile-less" build (mvn verify
) but we want them all to be skipped when building with the -Dquickly
property.
To implement that, we need a profile. Surprisingly the profile is not going to exclude stuff when -Dquickly
is
present, it will rather include the stuff in the default "profile-less" build. To make -Dquickly
to work the way
we want, the profile will be enabled by default and disabled when -Dquickly
is present. This can be done using
Maven’s "negated" property profile activation: <property><name>!quickly</name></property>
.
This is what it looks like for surefire-maven-plugin
and maven-enforcer-plugin
:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<phase/>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>full</id>
<activation>
<property>
<name>!quickly</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>my-enforcer-rules</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
...
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
After applying this trick to all non-essential mojos in
the source tree with 1200+ modules mentioned above, the quick build time goes down from 03:53 to 2:44:
mvn clean install -Dquickly
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:44 min
Who would expect such a big speedup?
P.S.: You may want to try mvnd
, the Maven Daemon to reduce your build
times even further.