Chapter 18. Writing Plugins in Alternative Languages

You can write a Mojo in Java, or you can write a Mojo in an alternative language. Maven has support for a number of implementation languages, and this chapter is going show you how to create plugins in three languages: Groovy, Ant, and Ruby plugins.

18.1. Writing Plugins in Ant

Favicon

Ant isn't a language as much as it is a build tool which allows you to describe a build as a set of tasks grouped into build targets. Ant then allows you to declare dependencies between build targets; for example, in Ant you are essentially creating your own lifecycle. An Ant build.xml might have an install target which depends on a test target which depends on a compile target. Ant is something of a ancestor to Maven, it was the ubiquitous procedural build tool that almost every project used before Maven introduced the concept of wide-scale reusability of common build plugins and the concept of a universal lifecycle.

While Maven is an improvement on Ant, Ant can still be useful when describing parts of the build process. Ant provides a set of tasks which can come in handy when you need to perform file operations or XSLT transformations or any other operation you could think of. There is a large library of available Ant tasks for everything from running JUnit tests to transforming XML to copying files to a remote server using SCP. An overview of available Ant tasks can be found online in the Apache Ant Manual. You can use these tasks as a low-level build customization language, and you can also write a Maven plugin where, instead of a Mojo written in Java, you can pass parameters to a Mojo which is an Ant build target.

18.2. Creating an Ant Plugin

Favicon

To create a Maven plugin using Ant, you will need to have a pom.xml and a single Mojo implemented in Ant. To get started, create a project directory named firstant-maven-plugin. Place the following pom.xml in this directory.

Example 18.1. POM for an Ant Maven Plugin

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>firstant-maven-plugin</artifactId>
  <name>Example Ant Mojo - firstant-maven-plugin</name>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-script-ant</artifactId>
      <version>2.0.9</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.4</version>
        <dependencies>
          <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-tools-ant</artifactId>
            <version>2.4</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>

Next, you will need to create your Ant Mojo. An Ant mojo consists of two parts: the Ant tasks in an XML file, and a file which supplies Mojo descriptor information. The Ant plugin tools are going to look for both of these files in ${basedir}/src/main/scripts. One file will be named echo.build.xml and it will contain the Ant XML.

Example 18.2. Echo Ant Mojo

<project>
  <target name="echotarget">
    <echo>${message}</echo>
  </target>
</project>

The other file will describe the Echo Ant Mojo and will be in the echo.mojos.xml file also in ${basedir}/src/main/scripts.

Example 18.3. Echo Ant Mojo Descriptor

<pluginMetadata>
  <mojos>
    <mojo>
      <goal>echo</goal>
      <call>echotarget</call>
      <description>Echos a Message</description>
      <parameters>
        <parameter>
          <name>message</name>
          <property>message</property>
          <required>false</required>
          <expression>${message}</expression>
          <type>java.lang.Object</type>
          <defaultValue>Hello Maven World</defaultValue>
          <description>Prints a message</description>
        </parameter>
      </parameters>
    </mojo>
  </mojos>
</pluginMetadata>

This echo.mojos.xml file configures the Mojo descriptor for this plugin. It supplies the goal name "echo", and it tells Maven what Ant task to call in the call element. In addition to configuring the description, this XML file configures the message parameter to use the expression ${message} and to have a default value of "Hello Maven World."

If you've configured your plugin groups in ~/.m2/settings.xml to include org.sonatype.mavenbook.plugins, you can install this Ant plugin by executing the following command at the command-line:

$ mvn install
[INFO] ------------------------------------------------------------------------
[INFO] Building Example Ant Mojo - firstant-maven-plugin
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
[INFO] [plugin:descriptor]
[INFO] Using 3 extractors.
[INFO] Applying extractor for language: java
[INFO] Extractor for language: java found 0 mojo descriptors.
[INFO] Applying extractor for language: bsh
[INFO] Extractor for language: bsh found 0 mojo descriptors.
[INFO] Applying extractor for language: ant
[INFO] Extractor for language: ant found 1 mojo descriptors.
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Note that the plugin:descriptor goal found a single Ant mojo descriptor. To run this goal, you would execute the following command-line:

$ mvn firstant:echo
...
[INFO] [firstant:echo]

echotarget:
     [echo] Hello Maven World
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

The echo goal executed and printed out the default value of the message parameter. If you are used to Apache Ant build scripts, you will notice that Ant prints out the name of the target executed and then adds a logging prefix to the output of the echo Ant task.

18.3. Writing Plugins in JRuby

Favicon

Ruby is an object-oriented scripting language which provides a rich set of facilities for meta-programming and reflection. Ruby's reliance on closures and blocks make for a programming style that is both compact and powerful. Although Ruby has been around since 1993, most people came to know Ruby after it was made popular by a Ruby-based web framework known as Ruby on Rails. JRuby is a Ruby interpreter written in Java. For more information about the Ruby language, see: http://www.ruby-lang.org/, and for more information about JRuby, see: http://jruby.codehaus.org/.

18.3.1. Creating a JRuby Plugin

Favicon

To create a Maven plugin using JRuby, you will need to have a pom.xml and a single Mojo implemented in Ruby. To get started, create a project directory named firstruby-maven-plugin. Place the following pom.xml in this directory.

Example 18.4. POM for a JRuby Maven Plugin

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>firstruby-maven-plugin</artifactId>
  <name>Example Ruby Mojo - firstruby-maven-plugin</name>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>jruby-maven-plugin</artifactId>
      <version>1.0-beta-4</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.4</version>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jruby-maven-plugin</artifactId>
            <version>1.0-beta-4</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>

Next, you will need to create a Mojo implemented in Ruby. Maven is going to look for a Ruby Mojo in ${basedir}/src/main/scripts. Put the following Ruby class in ${basedir}/src/main/scripts/echo.rb.

Example 18.5. The Echo Ruby Mojo

# Prints a message
# @goal "echo"
# @phase "validate"
class Echo < Mojo

  # @parameter type="java.lang.String" default-value="Hello Maven World" \
    expression="${message}"
  def message
  end

  def execute
    info $message
  end

end

run_mojo Echo

The Echo class must extend Mojo, and it must override the execute() method. At the end of the echo.rb file, you will need to run the mojo with "run_mojo Echo". To install this plugin, run mvn install:

$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Example Ruby Mojo - firstruby-maven-plugin
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
...
[INFO] [plugin:descriptor]
...
[INFO] Applying extractor for language: jruby
[INFO] Ruby Mojo File: /echo.rb
[INFO] Extractor for language: jruby found 1 mojo descriptors.
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

During the build, you should see that the Maven Plugin Plugin's descriptor goal applies the JRuby extractor to create a plugin.xml which captures the annotations in the Echo class. If you've configured your default plugin groups to include org.sonatype.mavenbook.plugins, you should be able to run this echo goal with the following command-line:

$ mvn firstruby:echo
...
[INFO] [firstruby:echo]
[INFO] Hello Maven World
...

18.3.2. Ruby Mojo Implementations

Favicon

Ruby Mojos are annotated using comments in Ruby source files. A single annotation like @parameter takes a number of attributes, and each of these attributes must be specified on the same line. There can be no line-breaks between an annotations attribute in the Ruby source. Both classes and parameters are annotated. Parameters are annotated with four annotations: @parameter, @required, @readonly, and @deprecated. The @parameter attribute takes the following attributes:

alias

An alias for the parameter. An alternate name which can be used to populate the same parameter.

default-value

Provides a default value to the parameter if the supplied value or the parameter expression produces a null result. In echo.rb, we specify the default as "Hello Maven World".

expression

Contains an expression which can resolve to a Maven property or a System property.

type

The fully qualified Java type of the parameter. If the type is not specified it will default to java.lang.String.

In addition to the @parameter annotation, a parameter can take the following annotations:

@required "<true|false>"

Marks the parameter as being required. The default value is false.

@readonly "<true|false>"

Marks the parameter as read-only. If this is true, you may not override the default value or the value from the expression from the command line. The default value is false.

@deprecated "<true|false>"

Marks the parameter as deprecated. The default value is false.

Putting this altogether, a fully annotated message parameter from echo.rb would look like the following code:

# @parameter type="java.lang.String" default-value="Hello Maven World" \
  expression="${message}"
# @readonly true
# @required false
# @deprecated false
def message
end

Ruby Mojo classes are annotated with the following attributes:

@goal

Specifies the name of the goal.

@phase

The default phase to bind this goal to.

@requiresDependencyResolution

True if the Mojo requires that dependencies be resolved before execution.

@aggregator

Marks this mojo as an aggregator.

@execute

Provides the opportunity to execute a goal or lifecycle phase before executing this Mojo. The @execute annotation takes the following attributes:

goal

Name of the goal to execute

phase

Name of the lifecycle phase to execute

lifecycle

Name of the lifecycle (if other than default)

For an example of an annotated Mojo class, consider the following code example:

# Completes some build task
# @goal custom-goal
# @phase install
# @requiresDependencyResolution false
# @execute phase=compile
class CustomMojo < Mojo
   ...
end

Mojo parameters can reference Java classes and Maven properties. The following example shows you how to get access to the Maven Project object from a Ruby Mojo.

Example 18.6. Referencing a Maven Project from a Ruby Mojo

# This is a mojo description
# @goal test
# @phase validate
class Test < Mojo
  # @parameter type="java.lang.String" default-value="nothing" alias="a_string"
  def prop
  end  

  # @parameter type="org.apache.maven.project.MavenProject" \
    expression="${project}"
  # @required true  
  def project
  end  

  def execute    
    info "The following String was passed to prop: '#{$prop}'"    
    info "My project artifact is: #{$project.artifactId}"  
  end
end

run_mojo Test

In the previous example, we can access properties on the Project class using standard Ruby syntax. If you put test.rb in firstruby-maven-plugin's src/main/scripts directory, install the plugin, and then run it, you will see the following output:

$ mvn install
...
[INFO] [plugin:descriptor]
[INFO] Using 3 extractors.
[INFO] Applying extractor for language: java
...
[INFO] Applying extractor for language: jruby
[INFO] Ruby Mojo File: /echo.rb
[INFO] Ruby Mojo File: /test.rb
[INFO] Extractor for language: jruby found 2 mojo descriptors.
...
$ mvn firstruby:test
...
[INFO] [firstruby:test]
[INFO] The following String was passed to prop: 'nothing'
[INFO] My project artifact is: firstruby-maven-plugin

18.3.3. Logging from a Ruby Mojo

Favicon

To log from a Ruby Mojo, call the info(), debug(), and error() methods with a message.

# Tests Logging
# @goal logtest
# @phase validate
class LogTest < Mojo

  def execute
    info "Prints an INFO message"
    error "Prints an ERROR message"
    debug "Prints to the Console"
  end

end

run_mojo LogTest

18.3.4. Raising a MojoError

Favicon

If there is an unrecoverable error in a Ruby Mojo, you will need to raise a MojoError. Example 18.7, “Raising a MojoError from a Ruby Mojo” shows you how to raise a MojoError. This example mojo prints out a message and then raises a MojoError.

Example 18.7. Raising a MojoError from a Ruby Mojo

# Prints a Message
# @goal error
# @phase validate
class Error < Mojo

  # @parameter type="java.lang.String" default-value="Hello Maven World" \
    expression="${message}"
  # @required true
  # @readonly false
  # @deprecated false
  def message
  end

  def execute
    info $message
    raise MojoError.new( "This Mojo Raised a MojoError" )
  end

end

run_mojo Error

Running this Mojo, produces the following output:

$ mvn firstruby:error
...
INFO] [firstruby:error]
[INFO] Hello Maven World
[ERROR] This Mojo Raised a MojoError

18.3.5. Referencing Plexus Components from JRuby

Favicon

A Ruby Mojo can depend on a Plexus component. To do this, you would use the expression attribute of the @parameter annotation to specify a role and a hint for Plexus. The following example Ruby Mojo, depends upon an Archiver component which Maven will retrieve from Plexus.

Example 18.8. Depending on a Plexus Component from a Ruby Mojo

# This mojo tests plexus integration
# @goal testplexus
# @phase validate
class TestPlexus < Mojo

  # @parameter type="org.codehaus.plexus.archiver.Archiver" \
expression="${component.org.codehaus.plexus.archiver.Archiver#zip}"
  def archiver
  end

  def execute
    info $archiver
  end
end

run_mojo TestPlexus

Please note that the attributes for an annotation in a Ruby Mojo cannot span multiple lines. If you were to run this goal, you would see Maven attempt to retrieve a component from Plexus with a role of org.codehaus.plexus.arhiver.Archiver and a hint of zip.

18.4. Writing Plugins in Groovy

Favicon

Groovy is a dynamic language based on the Java Virtual Machine which compiles to Java bytecode. Groovy is a project in the Codehaus community. If you are fluent in Java, Groovy will seem like a natural choice for a scripting language. Groovy takes the features of Java, pares down the syntax a bit, and adds features like closures, duck-typing, and regular expressions. For more information about Groovy, please see the Groovy web site at http://groovy.codehaus.org.

18.4.1. Creating a Groovy Plugin

Favicon

To create a Maven Plugin using Groovy, you only need two files: a pom.xml and a single Mojo implemented in Groovy. To get started, create a project directory named firstgroovy-maven-plugin. Place the following pom.xml in this directory.

Example 18.9. POM for a Groovy Maven Plugin

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.plugins</groupId>
  <artifactId>firstgroovy-maven-plugin</artifactId>
  <name>Example Groovy Mojo - firstgroovy-maven-plugin</name>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.mojo.groovy</groupId>
      <artifactId>groovy-mojo-support</artifactId>
      <version>1.0-beta-3</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>2.4</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo.groovy</groupId>
        <artifactId>groovy-maven-plugin</artifactId>
        <version>1.0-beta-3</version>
        <extensions>true</extensions>
        <executions>
          <execution>
            <goals>
              <goal>generateStubs</goal>
              <goal>compile</goal>
              <goal>generateTestStubs</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

What's going on in this POM? First, notice that the packaging of the POM is maven-plugin because we are creating a project that will package a Maven plugin. Next, note that the project depends on the groovy-mojo-support artifact in the org.codehaus.mojo.groovy group.

Then under src/main/groovy in a directory org/sonatype/mavenbook/plugins, create a file named EchoMojo.groovy which contains the EchoMojo class.

Example 18.10. 

package org.sonatype.mavenbook.plugins

import org.codehaus.mojo.groovy.GroovyMojo

/**
 * Example goal which echos a message
 *
 * @goal echo
 */
class EchoMojo extends GroovyMojo {

    /**
     * Message to print
     *
     * @parameter expression="${echo.message}"
     *            default-value="Hello Maven World"
     */
    String message

    void execute() {
      log.info( message )
    }
}


Creative Commons License
Maven: The Definitive Guide by Sonatype, Inc. is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License.
Based on a work at www.sonatype.com.

Favicon Report Typos, Errors, Ask Questions, Discuss, Share Your Ideas with us at Get Satisfaction