The Maven Cookbook

5.2. Writing Plugins in JRuby

5.2. Writing Plugins in JRuby

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/.

5.2.1. Creating a JRuby Plugin

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 5.1. 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 5.2. 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
...

5.2.2. Ruby Mojo Implementations

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 5.3. 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

5.2.3. Logging from a Ruby Mojo

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

5.2.4. Raising a MojoError

If there is an unrecoverable error in a Ruby Mojo, you will need to raise a MojoError. Example 5.4, “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 5.4. 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

5.2.5. Referencing Plexus Components from JRuby

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 5.5. 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.