Developing your First Mojo

For the purposes of this chapter, you will look at the development effort surrounding a sample project, called Guinea Pig. This development effort will have the task of maintaining information about builds that are deployed to the development repository, for the purposes of debugging. This information should capture relevant details about the environment used to build the Guinea Pig artifacts, which will be deployed to the Maven repository system. Capturing this information is key, since it can have a critical effect on the build process and the composition of the resulting Guinea Pig artifacts. In addition to simply capturing build-time information, you will need to disseminate the build to the rest of the development team, eventually publishing it alongside the project’s artifact in the repository for future reference (refer to Chapter 7 for more details on how teams use Maven).

BuildInfo Example: Capturing Information with a Java Mojo

To begin, consider a case where the POM contains a profile, which will be triggered by the value of a given system property – say, if the system property os.name is set to the value Linux (for more information on profiles, refer to Chapter 3). When triggered, this profile adds a new dependency on a Linux-specific library, which allows the build to succeed in that environment. When this profile is not triggered, a default profile injects a dependency on a windows-specific library. For simplicity, this dependency is used only during testing, and has no impact on transitive dependencies for users of this project.
Here, the values of system properties used in the build are clearly very important. If you have a test dependency which contains a defect, and this dependency is injected by one of the aforementioned profiles, then the value of the triggering system property – and the profile it triggers – could reasonably determine whether the build succeeds or fails. Therefore, it makes sense to publish the value of this particular system property in a build information file so that others can see the aspects of the environment that affected this build.

Prerequisite: Building the buildinfo generator project

Before writing the buildinfo plugin, you must first install the buildinfo generator library into your Maven local repository. The buildinfo plugin is a simple wrapper around this generator, providing a thin adapter layer that allows the generator to be run from a Maven build. As a side note, this approach encapsulates an important best practice; by separating the generator from the Maven binding code, you are free to write any sort of adapter or front-end code you wish, and take advantage of a single, reusable utility in many different scenarios.
To build the buildinfo generator library, perform the following steps:

cd buildinfo
mvn install

Using the archetype plugin to generate a stub plugin project

Now that the buildinfo generator library has been installed, it’s helpful to jump-start the plugin-writing process by using Maven’s archetype plugin to create a simple stub project from a standard plugin-project template. Once you have the plugin’s project structure in place, writing your custom mojo is simple. To generate a stub plugin project for the buildinfo plugin, simply execute the following from the top level directory of the chapter 5 sample code:

mvn archetype:create -DgroupId=com.exist.mvnbook.plugins \
-DartifactId=maven-buildinfo-plugin \
-DarchetypeArtifactId=maven-archetype-mojo
When you run this command, you’re likely to see a warning message saying “${project.build.directory} is not a valid reference”. This is a result of the Velocity template, used to generate the plugin source code, interacting with Maven’s own plugin parameter annotations. This message does not indicate a problem.

This will create a project with the standard layout under a new subdirectory called maven-buildinfo-plugin within the current working directory. Inside, you’ll find a basic POM and a sample mojo. For the purposes of this plugin, you will need to modify the POM as follows:

  • Change the name element to Maven BuildInfo Plugin.
  • Remove the url element, since this plugin doesn’t currently have an associated Web site.

You will modify the POM again later, as you know more about your mojos’ dependencies. However, this simple version will suffice for now.
Finally, since you will be creating your own mojo from scratch, you should remove the sample mojo. It can be found in the plugin’s project directory, under the following path:

src\main\java\com\exist\mvnbook\plugins\MyMojo.java.

The mojo

You can handle this scenario using the following, fairly simple Java-based mojo:

[...]
/**
 * Write the environment information for the current build execution
 * to an XML file.
 * @goal extract
 * @phase package
 * @requiresDependencyResolution test
 *
 */
public class WriteBuildInfoMojo extends AbstractMojo {
    /**
     * Determines which system properties are added to the buildinfo file.
     * @parameter
     */
    private String systemProperties;

    /**
     * The location to write the buildinfo file. Used to attach the buildinfo
     * to the project jar for installation and deployment.
     * @parameter expression="${buildinfo.outputFile}" default-    \             value="${project.build.directory}/${project.artifactId}-    \    ${project.version}-buildinfo.xml"
     * @required
     */
    private File outputFile;

    public void execute() throws MojoExecutionException {
        BuildInfo buildInfo = new BuildInfo();
        addSystemProperties( buildInfo );
        try {
            BuildInfoUtils.writeXml( buildInfo, outputFile );
        } catch ( IOException e ) {
            throw new MojoExecutionException( "Error writing buildinfo    \        XML file. Reason: " + e.getMessage(), e );
        }
    }

    private void addSystemProperties( BuildInfo buildInfo ) {
        Properties sysprops = System.getProperties();
        if ( systemProperties != null ) {
            String[] keys = systemProperties.split( "," );
            for ( int i = 0; i < keys.length; i++ ) {
                String key = keys[i].trim();
                String value = sysprops.getProperty( key,    \                BuildInfoConstants.MISSING_INFO_PLACEHOLDER );
                buildInfo.addSystemProperty( key, value );
            }
        }
    }
}

While the code for this mojo is fairly straightforward, it’s worthwhile to take a closer look at the javadoc annotations. In the class-level javadoc comment, there are two special annotations:

/**
 * @goal extract
 * @phase package
 */

The first annotation, @goal, tells the plugin tools to treat this class as a mojo named extract. When you invoke this mojo, you will use this name. The second annotation tells Maven where in the build life cycle this mojo should be executed. In this case, you’re collecting information from the environment with the intent of distributing it alongside the main project artifact in the repository. Therefore, it makes sense to execute this mojo in the package phase, so it will be ready to attach to the project artifact. In general, attaching to the package phase also gives you the best chance of capturing all of the modifications made to the build state before the jar is produced.
Aside from the class-level comment, you have several field-level javadoc comments, which are used to specify the mojo’s parameters. Each offers a slightly different insight into parameter specification, so they will be considered separately. First, consider the parameter for the systemProperties variable:

/**
 * @parameter expression="${buildinfo.systemProperties}"
 */

This is one of the simplest possible parameter specifications. Using the @parameter annotation by itself, with no attributes, will allow this mojo field to be configured using the plugin configuration specified in the POM. However, you may want to allow a user to specify which system properties to include in the build information file. This is where the expression attribute comes into play. Using the expression attribute, you can specify the name of this parameter when it’s referenced from the command line. In this case, the expression attribute allows you to specify a list of system properties on-the-fly, as follows:

localhost $ mvn buildinfo:extract \
-Dbuildinfo.systemProperties=java.version,user.dir
The module where the command is executed should be bound to a plugin with a buildinfo goal prefix. In this case, the guinea-pig module is bound to the maven-buildinfo-plugin having the buildinfo goal prefix so run the above command from the guinea-pig directory.

Finally, the outputFile parameter presents a slightly more complex example of parameter annotation. However, since you have more specific requirements for this parameter, the complexity is justified. Take another look:

/**
    * The location to write the buildinfo file. Used to attach the buildinfo
    * for installation and deployment.
    *
    * @parameter expression="${buildinfo.outputFile}" default-	\
      value="${project.build.directory}/${project.artifactId}-	\
      ${project.version}-buildinfo.xml"
    *
    * @required
*/

In this case, the mojo cannot function unless it knows where to write the build information file, as execution without an output file would be pointless. To ensure that this parameter has a value, the mojo uses the @required annotation. If this parameter has no value when the mojo is configured, the build will fail with an error. In addition, you want the mojo to use a certain value – calculated from the project’s information – as a default value for this parameter. In this example, you can see why the normal Java field initialization is not used. The default output path is constructed directly inside the annotation, using several expressions to extract project information on-demand.

The Plugin POM

Once the mojo has been written, you can construct an equally simple POM which will allow you to build the plugin, as follows:

 <project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.exist.mvnbook.plugins</groupId>
  <artifactId>maven-buildinfo-plugin</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>

  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>2.0</version>
    </dependency>
    <dependency>
      <groupId>com.exist.mvnbook.shared</groupId>
      <artifactId>buildinfo</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    [...]
  </dependencies>
</project>

This POM declares the project’s identity and its two dependencies.
Note the dependency on the buildinfo project, which provides the parsing and formatting utilities for the build information file. Also, note the packaging – specified as maven-plugin – which means that this plugin build will follow the maven-plugin life-cycle mapping. This mapping is a slightly modified version of the one used for the jar packaging, which simply adds plugin descriptor extraction and generation to the build process.

Binding to the life cycle

Now that you have a method of capturing build-time environmental information, you need to ensure that every build captures this information. The easiest way to guarantee this is to bind the extract mojo to the life cycle, so that every build triggers it. This involves modification of the standard jar life-cycle, which you can do by adding the configuration of the new plugin to the Guinea Pig POM, as follows:

 <build>
  [...]
  <plugins>
    <plugin>
      <groupId>com.exist.mvnbook.plugins</groupId>
      <artifactId>maven-buildinfo-plugin</artifactId>
      <executions>
        <execution>
          <id>extract</id>
          <configuration>
            <systemProperties>os.name,java.version</systemProperties>
          </configuration>
          <goals>
            <goal>extract</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
    [...]
  </plugins>
  [...]
</build>

The above binding will execute the extract mojo from your new maven-buildinfo-plugin during the package phase of the life cycle, and capture the os.name system property.

The output

Now that you have a mojo and a POM, you can build the plugin and try it out! First, build the buildinfo plugin with the following commands:

cd C:\book-projects\maven-buildinfo-plugin
mvn clean install

Next, test the plugin by building Guinea Pig with the buildinfo plugin bound to its life cycle as follows:

cd C:\book-projects\guinea-pig
mvn package

When the Guinea Pig build executes, you should see output similar to the following:

[...]

[INFO] [buildinfo:extract {execution: extract}]
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Guinea Pig Sample Application ......................... SUCCESS [6.468s]
[INFO] Guinea Pig API ........................................ SUCCESS [2.359s]
[INFO] Guinea Pig Core ....................................... SUCCESS [0.469s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

[...]

Under the target directory, there should be a file named:

guinea-pig-1.0-SNAPSHOT-buildinfo.xml

In the file, you will find information similar to the following:

 <?xml version="1.0" encoding="UTF-8"?><buildinfo>
  <systemProperties>
    <java.version>1.5.0_06</java.version>
    <os.name>Windows XP</os.name>
  </systemProperties>
  <sourceRoots>
    <sourceRoot>src\main\java</sourceRoot>
  </sourceRoots>
  <resourceRoots>
    <resourceRoot>src\main\resources</resourceRoot>
  </resourceRoots>
</buildinfo>

While the name of the OS and the java version may differ, the output of of the generated build information is clear enough. Your mojo has captured the name of operating system being used to execute the build and the version of the jvm, and both of these properties can have profound effects on binary compatibility.

BuildInfo Example: Notifying Other Developers with an Ant Mojo

Now that some important information has been captured, you need to share it with others in your team when the resulting project artifact is deployed. It’s important to remember that in the Maven world, “deployment” is defined as injecting the project artifact into the Maven repository system. For now, it might be enough to send a notification e-mail to the project development mailing list, so that other team members have access to it.
Of course, such a task could be handled using a Java-based mojo and the JavaMail API from Sun. However, given the amount of setup and code required, and the dozens of well-tested, mature tasks available for build script use (including one specifically for sending e-mails), it’s simpler to use Ant.
After writing the Ant target to send the notification e-mail, you just need to write a mojo definition to wire the new target into Maven’s build process.

The Ant target

To leverage the output of the mojo from the previous example – the build information file – you can use that content as the body of the e-mail. From here, it’s a simple matter of specifying where the e-mail should be sent, and how. Your new mojo will be in a file called notify.build.xml, and should look similar to the following:

 <project>
  <target name="notify-target">
    <mail from="maven@localhost" replyto="${listAddr}"
          subject="Build Info for Deployment of ${project.name}"
          mailhost="${mailHost}" mailport="${mailPort}"
          messagefile="${buildinfo.outputFile}">

      <to>${listAddr}</to>

    </mail>
  </target>
</project>

If you’re familiar with Ant, you’ll notice that this mojo expects several project properties. Information like the to: address will have to be dynamic; therefore, it should be extracted directly from the POM for the project we’re building. To ensure these project properties are in place within the Ant Project instance, simply declare mojo parameters for them.

The Mojo Metadata file

Unlike the prior Java examples, metadata for an Ant mojo is stored in a separate file, which is associated to the build script using a naming convention. In this example, the build script was called notify.build.xml. The corresponding metadata file will be called notify.mojos.xml and should appear as follows:

 <pluginMetadata>
  <mojos>
    <mojo>
      <call>notify-target</call>
      <goal>notify</goal>
      <phase>deploy</phase>
      <description><![CDATA[
        Email environment information from the current build to the
        development mailing list when the artifact is deployed.
      ]]></description>
      <parameters>
        <parameter>
          <name>buildinfo.outputFile</name>
          <defaultValue>
              ${project.build.directory}/${project.artifactId}-    \    ${project.version}-buildinfo.xml
            </defaultValue>
          <required>true</required>
          <readonly>false</readonly>
        </parameter>
        <parameter>
          <name>listAddr</name>
          <required>true</required>
        </parameter>
        <parameter>
          <name>project.name</name>
          <defaultValue>${project.name}</defaultValue>
          <required>true</required>
          <readonly>true</readonly>
        </parameter>
        <parameter>
          <name>mailHost</name>
          <expression>${mailHost}</expression>
          <defaultValue>localhost</defaultValue>
          <required>false</required>
        </parameter>
        <parameter>
          <name>mailPort</name>
          <expression>${mailPort}</expression>
          <defaultValue>25</defaultValue>
          <required>false</required>
        </parameter>
      </parameters>
    </mojo>
  </mojos>
</pluginMetadata>

At first glance, the contents of this file may appear different than the metadata used in the Java mojo; however, upon closer examination, you will see many similarities.
First of all, since you now have a good concept of the types of metadata used to describe a mojo, the overall structure of this file should be familiar. As with the Java example, mojo-level metadata describes details such as phase binding and mojo name.
Also, metadata specify a list of parameters for the mojo, each with its own information like name, expression, default value, and more. The expression syntax used to extract information from the build state is exactly the same, and parameter flags such as required are still present, but expressed in XML.
When this mojo is executed, Maven still must resolve and inject each of these parameters into the mojo; the difference here is the mechanism used for this injection. In Java, parameter injection takes place either through direct field assignment, or through JavaBeans-style setXXX() methods. In an Ant-based mojo however, parameters are injected as properties and references into the Ant Project instance.

The rule for parameter injection in Ant is as follows: if the parameter’s type is java.lang.String (the default), then its value is injected as a property; otherwise, its value is injected as a project reference. In this example, all of the mojo’s parameter types are java.lang.String. If one of the parameters were some other object type, you’d have to add aelement alongside theelement, in order to capture the parameter’s type in the specification.

Finally, notice that this mojo is bound to the deploy phase of the life cycle. This is an important point in the case of this mojo, because you’re going to be sending e-mails to the development mailing list. Any build that runs must be deployed for it to affect other development team members, so it’s pointless to spam the mailing list with notification e-mails every time a jar is created for the project. Instead, by binding the mojo to the deploy phase of life cycle, the notification e-mails will be sent only when a new artifact becomes available in the remote repository.
As with the Java example, a more in-depth discussion of the metadata file for Ant mojos is available in Appendix A.

Modifying the Plugin POM for Ant Mojos

Since Maven 2.0 shipped without support for Ant-based mojos (support for Ant was added later in version 2.0.2), some special configuration is required to allow the maven-plugin-plugin to recognize Ant mojos. Fortunately, Maven allows POM-specific injection of plugin-level dependencies in order to accommodate plugins that take a framework approach to providing their functionality.
The maven-plugin-plugin is a perfect example, with its use of the MojoDescriptorExtractor interface from the maven-plugin-tools-api library. This library defines a set of interfaces for parsing mojo descriptors from their native format and generating various output from those descriptors – including plugin descriptor files. The maven-plugin-plugin ships with the Java and Beanshell provider libraries which implement the above interface.
This allows developers to generate descriptors for Java- or Beanshell-based mojos with no additional configuration. However, to develop an Ant-based mojo, you will have to add support for Ant mojo extraction to the maven-plugin-plugin.
To accomplish this, you will need to add a dependency on the maven-plugin-tools-ant library to the maven-plugin-plugin using POM configuration as follows:

 <project>
  [...]
  <build>
    <plugins>
      <plugin>
        <groupId>com.exist.mvnbook.plugins</groupId>
      <artifactId>maven-plugin-plugin</artifactId>
        <dependencies>
          <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-tools-ant</artifactId>
            <version>2.0.2</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
  [...]
</project>

Additionally, since the plugin now contains an Ant-based mojo, it requires a couple of new dependencies, the specifications of which should appear as follows:

 <dependencies>
  [...]
  <dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-script-ant</artifactId>
    <version>2.0.2</version>
  </dependency>
  <dependency>
    <groupId>ant</groupId>
    <artifactId>ant</artifactId>
    <version>1.6.5</version>
  </dependency>
  [...]
</dependencies>

The first of these new dependencies is the mojo API wrapper for Ant build scripts, and it is always necessary for embedding Ant scripts as mojos in the Maven build process. The second new dependency is, quite simply, a dependency on the core Ant library (whose necessity should be obvious). If you don’t have Ant in the plugin classpath, it will be quite difficult to execute an Ant-based plugin.

Binding the Notify Mojo to the life cycle

Once the plugin descriptor is generated for the Ant mojo, it behaves like any other type of mojo to Maven. Even its configuration is the same. Adding a life-cycle binding for the new Ant mojo in the Guinea Pig POM should appear as follows:

 <build>
  [...]
  <plugins>
    <plugin>
      <groupId>com.exist.mvnbook.plugins</groupId>
    <artifactId>maven-buildinfo-plugin</artifactId>
      <executions>
        <execution>
          <id>extract</id>
          [...]
        </execution>
        <execution>
          <id>notify</id>
          <goals>
            <goal>notify</goal>
          </goals>
          <configuration>
            <listAddr>dev@guineapig.codehaus.org</listAddr>
          </configuration>
        </execution>
      </executions>
    </plugin>
    [...]
  </plugins>
</build>
The existing section – the one that binds the extract mojo to the build – is not modified. Instead, a new section for the notify mojo is created. This is because an execution section can address only one phase of the build life cycle, and these two mojos should not execute in the same phase (as mentioned previously).

In order to tell the notify mojo where to send this e-mail, you should add a configuration section to the new execution section, which supplies the listAddr parameter value.
Now, execute the following command:

mvn deploy

The build process executes the steps required to build and deploy a jar – except in this case, it will also extract the relevant environmental details during the package phase, and send them to the Guinea Pig development mailing list in the deploy phase. Again, notification happens in the deploy phase only, because non-deployed builds will have no effect on other team members.

Note: You have to configure distributionManagement and scm to successfully execute mvn deploy. See section on Deploying your Application of chapter 3.

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.