Maven, Gems, and a JRuby Console for All

July 02, 2013

How great would it be if your Java and Ruby applications had access to equivalent consoles? At Square, we use shaded jars to distribute and run applications. A shaded jar contains all the necessary dependencies and allows any app to be started simply via java -jar path-to-jar.jar. We have also been able to bundle our JRuby integration framework (Minecart) into these shaded jars, allowing any Java app to have access to a console. The console can use all the niceties which have been built into Minecart, such as hash handling of Protocol Buffers. In this post we will show how to build a shaded jar with an embedded JRuby console that has access to any gem dependencies specified in the pom.xml.

Specifying Gems in your Pom

First let's take a quick dive into how gem packaging for our shaded jar works. You can find the complete plugin configuration here.

To accomplish the task we will need to use three maven plugins.

The maven shade and torquebox plugins conflict out of the box. To rectify this we actually start by excluding any gems from the shaded jar.

<artifactSet>
  <excludes>
    <exclude>*:*:gem:*</exclude>
  </excludes>
</artifactSet>

Without this, the maven shade plugin will complain that it doesn't know how to extract the source of the gem to package it. The next step is to use the build helper maven plugin to manually add the files to the jar.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>1.7</version>
  <executions>
    <execution>
      <id>add-resource</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>add-resource</goal>
      </goals>
      <configuration>
        <resources>
          <resource>
            <directory>${project.build.directory}/rubygems</directory>
            <targetPath>rubygems</targetPath>
          </resource>
        </resources>
      </configuration>
    </execution>
  </executions>
</plugin>

And done! The resulting shaded Jar now contains the requested gems. This is definitely another case of some hard-won keystrokes.

JRuby console

Now let's use this gem in a console. First we need to grab the sample project.

git clone https://github.com/daicoden/minecart-console-example.git
cd minecart-console-example
mvn -am package

You will notice the gem packaging configuration described above is included in the base pom.xml allowing anyone to include gems via:

<dependency>
  <groupId>rubygems</groupId>
  <artifactId>awesome_print</artifactId>
  <version>1.1.0</version>
  <type>gem</type>
</dependency>

In the example there are two projects, the pizza project and the minecart-console project. When we ran mvn -am package we built a shaded jar for the pizza project which depends on minecart-console. Let's start by running the pizza project as it's meant to be in production.

java -jar pizza/target/pizza-HEAD-SNAPSHOT-shaded.jar
#=> Making a pepperoni pizza now!
#=> Size: Large, Toppings: Pepperoni, Price: $20.00

The thing to note here is that the app ran without invoking the JRuby interpreter. This means that the production environment's execution is unaffected by including the console. But there's more! We can start a console from the single shaded jar.

# Start the console
java -cp pizza/target/pizza-HEAD-SNAPSHOT-shaded.jar com.squareup.minecart.Console

kitchen = com.pizza.PizzaKitchen.new
pizza = kitchen.makePepperoni
pizza.properties
# => {"Size"=>"Large", "Toppings"=>"Pepperoni", "Price"=>"$20.00"}

Furthermore we get access to the gems specified in pizza's pom.xml.

# Just to be clear awesome_print is coming from the jar
gem uninstall awesome_print
# Start the console
java -cp pizza/target/pizza-HEAD-SNAPSHOT-shaded.jar com.squareup.minecart.Console

require 'awesome_print'

kitchen = com.pizza.PizzaKitchen.new
pizza = kitchen.makePepperoni
ap pizza.properties.to_hash
#=> {
        "Size" => "Large",
    "Toppings" => "Pepperoni",
       "Price" => "$20.00"
    }

Boom! As you peruse the code you will see that minecart-console is actually pretty simple. The only complicated bit comes, pretty much, directly from Warbler, a project which allows you to transform a Ruby project into a standalone executable jar.

There are lots of console solutions out there for Java, but this one is intriguing because it allows everyone to leverage the advancements of any Ruby development. As more Java libraries are consumed and nicer Ruby idiomatic APIs are built, the console improves for everyone. It's surprising how many tools, in both quality and capability, exist in the JRuby community. If there is an integration you want to do, chances are somebody's started building it. It just may need some elbow grease for that last 10% – or should I say 80%.

This post is part of a series, which highlights discoveries and insights found while integrating Ruby with our robust Java stack.

Co-Inventor of Square Wallet, Engineer

Comments

Get support help at squareup.com/support. We'll delete off-topic comments.