Ruby and Maven
Chapter 5
5.1. 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: https://www.jruby.org/.
5.2. 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
<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
# @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:
[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:
...
[INFO] [firstruby:echo]
[INFO] Hello Maven World
...
5.3 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:
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:
# @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
# @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:
...
[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.4 Logging from a Ruby Mojo
To log from a Ruby Mojo, call the info(), debug(), and error() methods with a message.
# @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.5 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
# @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:
...
INFO] [firstruby:error]
[INFO] Hello Maven World
[ERROR] This Mojo Raised a MojoError
5.6 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
# @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.