Guide to Configuring Default Mojo Executions

In most cases where you need to configure a plugin, there are two options that work well: plugin-level configuration and execution-level configuration. Plugin-level configuration is the most common method for configuring plugins that will be used from the command line, are defined as part of the default lifecycle, or that use a common configuration across all invocations. In fact, for direct invocation from the command line, plugin-level configuration has been the only option historically.

On the other hand, in cases where a more advanced build process requires the execution of mojos - sometimes the same mojos, sometimes different ones - from a single plugin that use different configurations, the execution-level configuration is most commonly used. These cases normally involve plugins that are introduced as part of the standard build process, but which aren't present in the default lifecycle mapping for that particular packaging. In these cases, common settings shared between executions are still normally specified in the plugin-level configuration.

However, these two options leave out a few important configuration use cases:

  • Mojos run from the command line and during the build, when the CLI-driven invocation requires its own configuration.
  • Mojo executions that are bound to the lifecycle as part of the default mapping for a particular packaging, especially in cases where the same mojos need to be added to a second execution with different configuration.
  • Groups of mojos from the same plugin that are bound to the lifecycle as part of the default mapping for a particular packaging, but require separate configurations.

Default executionIds for Implied Executions

When you consider the fact that the aforementioned configuration use cases are for mojos that are not explicitly mentioned in the POM, it's reasonable to refer to them as implied executions. But if they're implied, how can Maven allow users to provide configuration for them? The solution we've implemented is rather simple and low-tech, but should be more than adequate to handle even advanced use cases. Each mojo invoked directly from the command line will have an execution Id of default-cli assigned to it, which will allow the configuration of that execution from the POM by using this default execution Id. Likewise, each mojo bound to the build lifecycle via the default lifecycle mapping for the specified POM packaging will have an execution Id of default-<goalName> assigned to it, to allow configuration of each default mojo execution independently.

Example: Command-line variant invocation of the assembly plugin

Consider the case where the user wants to execute the assembly:assembly mojo directly on the command line, but already has a configuration for the assembly:single mojo that runs during the main build lifecycle. Since these configurations require different options, the user cannot use the plugin-level configuration section to specify common elements.

In this case, the assembly-plugin configuration might look like this:

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <tarLongFileMode>gnu</tarLongFileMode>    
  </configuration>
  <executions>
    <execution>
      <id>build-distros</id>
      <phase>package</phase>
      <goals>
        <goal>single</goal>
      </goals>
      <configuration>
        <descriptors>
          <descriptor>src/main/assembly/bin.xml</descriptor>
          <descriptor>src/main/assembly/src.xml</descriptor>
        </descriptors>
      </configuration>
    </execution>
    <execution>
      <id>default-cli</id>
      <configuration>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
          <descriptorRef>project</descriptorRef>
        </descriptorRefs>
      </configuration>
    </execution>
  </executions>
</plugin>

In the above example, you can see several interesting things. First, the main build process will invoke the assembly:single mojo during the package phase of the build, and produce both binary and source distribution artifacts using custom assembly descriptors included with the project. Second, all invocations of the assembly plugin should use a tarLongFileMode strategy of gnu. Finally, when the assembly plugin is invoked from the command line, it will build the standard jar-with-dependencies and project artifacts for the project, and ignore the custom assembly descriptors in src/main/assembly.

Now, notice the difference in the way the two execution blocks reference assembly descriptors. One uses custom descriptors via the descriptors section, and the other uses standard descriptors via the descriptorRefs section. These two sections cannot override one another, so it's impossible to setup one section - say, descriptorRefs - in the plugin-level configuration block (to provide CLI access to it, as historical versions of Maven would require), then have the build-distros invocation override it with the custom descriptors specified in the descriptors section.

Example: Configuring compile to run twice

In this scenario, the user wants to run the compiler:compile mojo twice for his jar packaging project. The main reason for this is to provide an entry point into the application that will warn the user if he's using a Java version older than 1.5, then exit gracefully. Without such an entry point, the user would be confronted with a stacktrace in the event he tried to run this application with a 1.4 or older JRE.

Therefore, the user needs to compile the bulk of his application to target the 1.5 Java specification, then compile the rest (the entry point) to target an older specification...say, 1.3. The first execution will specify the source and target values at 1.5, and add an excludes section to avoid compiling the entry point for the application. The second pass will then re-specify source and target to 1.3, and basically invert the original excludes section to be an includes section, so as to compile only the entry point class.

The resulting configuration might look something like this:

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <source>1.5</source>
    <target>1.5</target>
  </configuration>
  <executions>
    <execution>
      <id>default-compile</id>
      <configuration>
        <excludes>
          <exclude>**/cli/*</exclude>
        </excludes>
      </configuration>
    </execution>
    <execution>
      <id>build-java14-cli</id>
      <phase>compile</phase>
      <goals>
        <goal>compile</goal>
      </goals>
      <configuration>
        <source>1.3</source>
        <target>1.3</target>
        <includes>
          <include>**/cli/*</include>
        </includes>
      </configuration>
    </execution>
  </executions>
</plugin>

There are three important things to notice in the above compiler-plugin configuration:

  • The default source and target compatibility levels are for Java 1.5. This means that the compiler will generate binaries for Java 1.5 from both the main codebase and the test codebase, unless otherwise overridden.
  • The default pass of the compile goal will exclude the **/cli/* path pattern, but will compile everything else in src/main/java to run under Java 1.5.
  • The second pass of the compile mojo - in the execution called build-java14-cli - resets the source and target versions to 1.3, and inverts the exclude rule from the first pass. This means the second time around, the compiler will produce 1.4-targeted binaries for the classes matching the **/cli/* path pattern.

Example: Configuring compile and testCompile mojos separately

Finally, building on our use of the compiler plugin to tease out these different use cases, consider the case where a user wants to target version 1.4 of the Java specification as his runtime platform. However, he still wants the convenience and other advantages to be found in a unit-testing framework like TestNG. This forces the user to configure the compile mojo with one set of source and target values - specifically, 1.4 - and the testCompile mojo with another (1.5).

The resulting compiler-plugin configuration might look something like the following:

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <executions>
    <execution>
      <id>default-compile</id>
      <configuration>
        <source>1.3</source>
        <target>1.3</target>
      </configuration>
    </execution>
    <execution>
      <id>default-testCompile</id>
      <configuration>
        <source>1.5</source>
        <target>1.5</target>
      </configuration>
    </execution>
  </executions>
</plugin>

This example is fairly simple and straightforward. First, the default-compile execution sets the source and target values to 1.3 to allow older Java versions to run the project. Then, the default-testCompile execution resets the source and target values to 1.5, which enables the project to use tools like TestNG that use annotations.

Incidentally, it's perhaps useful to point out that the example above is a little bit contrived; the compiler plugin targets Java 1.3 by default, so the only configuration that's really required is the default-testCompile execution. The default-compile execution respecifies plugin defaults. The only time this might be useful is when a parent POM defines a plugin-level configuration for source and target that needs to be changed for the purposes of these different compiler executions. This example is meant to be illustrative of the potential for separate configuration of default lifecycle mojos.