How to make a plugin that runs once during a build


May 22, 2009 By Brian Fox

With it’s default behavior, Maven runs a plugin invocation for each project in a multi-module build. For plugins that operate on a single project at a time, this is what the author wants.

Some plugins are what we call “aggregators” which means they actually do want all the information about the full multi-module build before execution. These plugins, when run on a tree of projects cause Maven to resolve all the children before calling the plugin’s execute() method. In this mode a plugin executes just once, but effectively on the whole tree at once. (as a side note, you never want to bind an aggregator goal in your pom as this would cause the plugin to run an n! recursive build since the lifecycle would step into each child and execute the aggregator…which would cause Maven to reresolve all the children, etc)

Sometimes neither of those behaviors are what you want. If I want a plugin to run only once in my build, I can do that by only binding it once in a pom. But what if you want it to run once regardless of if I start my build at the root, or somewhere down in a module? An example of where this is handy is the maven-enforcer-plugin… if you’re checking environmental constraints like jdk, os, maven version, it’s pretty much guaranteed that this can’t change between the root and when the next module is built.

This has most recently cropped up as a use case in the maven-assembly-plugin. We want to configure the plugin to produce a source archive during releases of the entire tree. Since the root project would contain the code below that point, we don’t need to also archive the source for each sub-module separately. We want to be able to configure this at our “Maven” or “Apache” pom (the moral equivalent of a “Corporate” pom) and have it simply work regardless of where a build is launched from.

It turns out that it is fairly easy for a plugin to programatically detect when it is building the project at the execution root (that is where mvn was launched). The code looks like this:

57
        boolean result = mavenSession.getExecutionRootDirectory().equalsIgnoreCase(basedir.toString());

A full mojo to implement this would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* @goal skipper
* @execute phase="validate"
**/
public class SkipperMojo extends AbstractMojo
{
    /**
     * Base directory of the project.
     *
     * @parameter default-value="${basedir}"
     * @required
     * @readonly
     */
    private File basedir;
 
    /**
     * This will cause the assembly to run only at the top of a given module tree. That is, run in the project
     * contained in the same folder where the mvn execution was launched.
     * @parameter expression="${runOnlyAtExecutionRoot}" default-value="false"
     * @since 2.2-beta-4
     */
    private boolean runOnlyAtExecutionRoot;
 
    public void execute()
        throws MojoExecutionException, MojoFailureException
    {
 
        //run only at the execution root.
        if (runOnlyAtExecutionRoot && !isThisTheExecutionRoot())
        {
            getLog().info( "Skipping the assembly in this project because it's not the Execution Root" );
        }
        else
        {
            getLog().info("Doing something usefull");
        }
   }
    /**
     * Returns true if the current project is located at the Execution Root Directory (where mvn was launched)
     * @return
     */
    protected boolean isThisTheExecutionRoot()
    {
        Log log = this.getLog();
        log.debug("Root Folder:" + mavenSession.getExecutionRootDirectory());
        log.debug("Current Folder:"+ basedir );
        boolean result = mavenSession.getExecutionRootDirectory().equalsIgnoreCase(basedir.toString());
        if (result)
        {
            log.debug( "This is the execution root." );
        }
        else
        {
            log.debug( "This is NOT the execution root." );
        }
        return result;
    }
}