Bootstrapping Into Plugin Development

In addition to understanding Maven’s plugin terminology, you will also need a good understanding of how plugins are structured and how they interact with their environment. As a plugin developer, you must understand the mechanics of life-cycle phase binding and parameter injection. Understanding this framework will enable you to extract the Maven build-state information that each mojo requires, in addition to determining its appropriate phase binding.

The Plugin Framework

Maven provides a rich framework for its plugins, including a well-defined build life cycle, dependency management, and parameter resolution and injection. Using the life cycle, Maven also provides a well-defined procedure for building a project’s sources into a distributable archive, plus much more. Binding to a phase of the Maven life cycle allows a mojo to make assumptions based upon what has happened in the preceding phases. Using Maven’s parameter injection infrastructure, a mojo can pick and choose what elements of the build state it requires in order to execute its task. Together, parameter injection and life-cycle binding form the cornerstone for all mojo development.

Participation in the build life cycle

Most plugins consist entirely of mojos that are bound at various phases in the life cycle according to their function in the build process. As a specific example of how plugins work together through the life cycle, consider a very basic Maven build: a project with source code that should be compiled and archived into a jar file for redistribution. During this build process, Maven will execute a default life cycle for the ‘jar’ packaging. The ‘jar’ packaging definition assigns the following life-cycle phase bindings:

Table 2-1: Life-cycle bindings for jar packaging

Life-cycle Phase Mojo Plugin
process-resources resources maven-resources-plugin
compile compile maven-compiler-plugin
process-test-resources testResources maven-resources-plugin
test-compile testCompile maven-compiler-plugin
test test maven-surefire-plugin
package jar maven-jar-plugin
install install maven-install-plugin
deploy deploy maven-deploy-plugin

When you command Maven to execute the package phase of this life cycle, at least two of the above mojos will be invoked. First, the compile mojo from the maven-compiler-plugin will compile the source code into binary class files in the output directory. Then, the jar mojo from the maven-jar-plugin will harvest these class files and archive them into a jar file.

Only those mojos with tasks to perform are executed during this build. Since our hypothetical project has no “non-code” resources, none of the mojos from the maven-resources-plugin will be executed. Instead, each of the resource-related mojos will discover this lack of non-code resources and simply opt out without modifying the build in any way. This is not a feature of the framework, but a requirement of a well-designed mojo. In good mojo design, determining when not to execute, is often as important as the modifications made during execution itself.

If this basic Maven project also includes source code for unit tests, then two additional mojos will be triggered to handle unit testing. The testCompile mojo from the maven-compiler-plugin will compile the test sources, then the test mojo from the maven-surefire-plugin will execute those compiled tests. These mojos were always present in the life-cycle definition, but until now they had nothing to do and therefore, did not execute.

Depending on the needs of a given project, many more plugins can be used to augment the default life-cycle definition, providing functions as varied as deployment into the repository system, validation of project content, generation of the project’s Web site, and much more. Indeed, Maven’s plugin framework ensures that almost anything can be integrated into the build life cycle. This level of extensibility is part of what makes Maven so powerful.

Accessing build information

In order for mojos to execute effectively, they require information about the state of the current build. This information comes in two categories:

  • Project information – which is derived from the project POM, in addition to any programmatic modifications made by previous mojo executions.
  • Environment information – which is more static, and consists of the user- and machine-level Maven settings, along with any system properties that were provided when Maven was launched.

To gain access to the current build state, Maven allows mojos to specify parameters whose values are extracted from the build state using expressions. At runtime, the expression associated with a parameter is resolved against the current build state, and the resulting value is injected into the mojo, using a language-appropriate mechanism. Using the correct parameter expressions, a mojo can keep its dependency list to a bare minimum, thereby avoiding traversal of the entire build-state object graph.

For example, a mojo that applies patches to the project source code will need to know where to find the project source and patch files. This mojo would retrieve the list of source directories from the current build information using the following expression:

${project.compileSourceRoots}

Then, assuming the patch directory is specified as mojo configuration inside the POM, the expression to retrieve that information might look as follows:

${patchDirectory}

For more information about which mojo expressions are built into Maven, and what methods Maven uses to extract mojo parameters from the build state, see Appendix A.

The plugin descriptor

Though you have learned about binding mojos to life-cycle phases and resolving parameter values using associated expressions, until now you have not seen exactly how a life-cycle binding occurs. That is to say, how do you associate mojo parameters with their expression counterparts, and once resolved, how do you instruct Maven to inject those values into the mojo instance? Further, how do you instruct Maven to instantiate a given mojo in the first place?

The answers to these questions lie in the plugin descriptor. The Maven plugin descriptor is a file that is embedded in the plugin jar archive, under the path /META-INF/maven/plugin.xml. The descriptor is an XML file that informs Maven about the set of mojos that are contained within the plugin. It contains information about the mojo’s implementation class (or its path within the plugin jar), the life-cycle phase to which the mojo should be bound, the set of parameters the mojo declares, and more.

Within this descriptor, each declared mojo parameter includes information about the various expressions used to resolve its value, whether it is editable, whether it is required for the mojo’s execution, and the mechanism for injecting the parameter value into the mojo instance. For the complete plugin descriptor syntax, see Appendix A.

The plugin descriptor is very powerful in its ability to capture the wiring information for a wide variety of mojos. However, this flexibility comes at a price. To accommodate the extensive variability required from the plugin descriptor, it uses a complex syntax. Writing a plugin descriptor by hand demands that plugin developers understand low-level details about the Maven plugin framework – details that the developer will not use, except when configuring the descriptor. This is where Maven’s plugin development tools come into play. By abstracting many of these details away from the plugin developer, Maven’s development tools expose only relevant specifications in a format convenient for a given plugin’s implementation language.

Plugin Development Tools

To simplify the creation of plugin descriptors, Maven provides plugin tools to parse mojo metadata from a variety of formats. This metadata is embedded directly in the mojo’s source code where possible, and its format is specific to the mojo’s implementation language. In short, Maven’s plugin-development tools remove the burden of maintaining mojo metadata by hand. These plugin-development tools are divided into the following two categories:

  • The plugin extractor framework – which knows how to parse the metadata formats for every language supported by Maven. This framework generates both plugin documentation and the coveted plugin descriptor; it consists of a framework library which is complemented by a set of provider libraries (generally, one per supported mojo language).
  • The maven-plugin-plugin – which uses the plugin extractor framework, and orchestrates the process of extracting metadata from mojo implementations, adding any other plugin-level metadata through its own configuration (which can be modified in the plugin’s POM); the maven-plugin-plugin simply augments the standard jar life cycle mentioned previously as a resource-generating step (this means the standard process of turning project sources into a distributable jar archive is modified only slightly, to generate the plugin descriptor).

Of course, the format used to write a mojo’s metadata is dependent upon the language in which the mojo is implemented. Using Java, it’s a simple case of providing special javadoc annotations to identify the properties and parameters of the mojo. For example, the clean mojo in the maven-clean-plugin provides the following class-level javadoc annotation:

/**
 * @goal clean
 */
public class CleanMojo extends AbstractMojo

This annotation tells the plugin-development tools the mojo’s name, so it can be referenced from life-cycle mappings, POM configurations, and direct invocations (as from the command line). The clean mojo also defines the following:

/**
 * Be verbose in the debug log-level?
 *
 * @parameter expression="${clean.verbose}" default-value="false"
 */
private boolean verbose;

Here, the annotation identifies this field as a mojo parameter. This parameter annotation also specifies two attributes, expression and default-value. The first specifies that this parameter’s default value should be set to false. The second specifies that this parameter can also be configured from the command line as follows:

-Dclean.verbose=false

Moreover, it specifies that this parameter can be configured from the POM using:

<configuration>
    <verbose>false</verbose>
</configuration>

You may notice that this configuration name isn’t explicitly specified in the annotation; it’s implicit when using the @parameter annotation.
At first, it might seem counter-intuitive to initialize the default value of a Java field using a javadoc annotation, especially when you could just declare the field as follows:

private boolean verbose = false;

But consider what would happen if the default value you wanted to inject contained a parameter expression. For instance, consider the following field annotation from the resources mojo in the maven-resources-plugin:

/**
 * Directory containing the classes.
 *
 * @parameter default-value="${project.build.outputDirectory}"
 */
private File classesDirectory;

In this case, it’s impossible to initialize the Java field with the value you need, namely the java.io.File instance, which references the output directory for the current project. When the mojo is instantiated, this value is resolved based on the POM and injected into this field. Since the plugin tools can also generate documentation about plugins based on these annotations, it’s a good idea to consistently specify the parameter’s default value in the metadata, rather than in the Java field initialization code.

For a complete list of javadoc annotations available for specifying mojo metadata, see Appendix A.

Remember, these annotations are specific to mojos written in Java. If you choose to write mojos in another language, like Ant, then the mechanism for specifying mojo metadata such as parameter definitions will be different. However, the underlying principles remain the same.

Choose your mojo implementation language

Through its flexible plugin descriptor format and invocation framework, Maven can accommodate mojos written in virtually any language. For example, Maven currently supports mojos written in Java, Ant, and Beanshell. Whatever language you use, Maven lets you select pieces of the build state to inject as mojo parameters. This relieves you of the burden associated with traversing a large object graph in your code, and minimizes the number of dependencies you will have on Maven’s core APIs.
For many mojo developers, Java is the language of choice. Since it provides easy reuse of third-party APIs from within your mojo, and because many Maven-built projects are written in Java, it also provides good alignment of skill sets when developing mojos from scratch. Simple javadoc annotations give the plugin processing plugin (the maven-plugin-plugin) the instructions required to generate a descriptor for your mojo. Plugin parameters can be injected via either field reflection or setter methods. Since Beanshell behaves in a similar way to standard Java, this technique also works well for Beanshell-based mojos.
However, in certain cases you may find it easier to use Ant scripts to perform build tasks. To make Ant scripts reusable, Maven can wrap an Ant build target and use it as if it were a mojo. This is especially important during migration, when translating a project build from Ant to Maven (refer to Chapter 8 for more discussion about migrating from Ant to Maven). During the early phases of such a migration, it is often simpler to wrap existing Ant build targets with Maven mojos and bind them to various phases in the life cycle. Ant-based plugins can consist of multiple mojos mapped to a single build script, individual mojos each mapped to separate scripts, or any combination thereof. In these cases, mojo mappings and parameter definitions are declared via an associated metadata file. This pairing of the build script and accompanying metadata file follows a naming convention that allows the maven-plugin-plugin to correlate the two files and create an appropriate plugin descriptor.
Since Java is currently the easiest language for plugin development, this chapter will focus primarily on plugin development in this language. In addition, due to the migration value of Ant-based mojos when converting a build to Maven, this chapter will also provide an example of basic plugin development using Ant.

A Note on the Examples in this Chapter

When learning how to interact with the different aspects of Maven from within a mojo, it’s important to keep the examples clean and relatively simple. Otherwise, you risk confusing the issue at hand – namely, the particular feature of the mojo framework currently under discussion. Therefore, the examples in this chapter will focus on a relatively simple problem space: gathering and publishing information about a particular build. Such information might include details about the system environment, the specific snapshot versions of dependencies used in the build, and so on.
To facilitate these examples, you will need to work with an external project, called buildinfo, which is used to read and write build information metadata files. This project can be found in the source code that accompanies this book. You can install it using the following simple command:

mvn install

Thank you for requesting a Maestro evaluation! This is our passion, and we want you to be successful. Please let us know how we may help!

Please enter your name, company email address and phone, and we will send you a link to your pre-built hosted evaluation within minutes.






I have read and agree to the Terms and Conditions.