Wednesday, September 25, 2013

How to compile Groovy scripts and run them on systems with no Groovy installed


/*---[ Problem ]---*/

This week I was faced with the need to write a Groovy script that would run on a Hadoop node at work, but we don't yet have groovy installed on the Hadoop nodes (and I don't have privileges to do that). Since Groovy is our defined scripting language, I had two options in the meantime:

  1. Download the groovy zip package, just unzip it in my user directory on the Hadoop node and run my thing.
  2. Compile the groovy script to bytecode and build an uber-jar with groovy in it and then run it like a Java program (with java -cp myjar.jar blah blah blah).


/*---[ Solution ]---*/

Since the second sounded like more of a challenge and would teach me a few things I hadn't done yet, I picked that. It worked out - here's my cheat sheet for future reference.


/*---[ Quoth the Maven ]---*/

Create a new maven project:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false -DgroupId=net.thornydev -DartifactId=script


/*---[ Two plugins ]---*/

To compile groovy to bytecode use the groovy-eclipse-compiler plugin. Yes, I know that sounds weird, but you don't need to fire up Eclipse. You don't even need to have Eclipse installed.

To build the uberjar having your compiled script and all of groovy, use the maven-shade-plugin. Like most things about maven, I find name "shade-plugin" irritating, but it gets the job done.

Finally include groovy-all.jar as a dependency.

Here's my pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.thornydev</groupId>
  <artifactId>script</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
  <name>script</name>
  <url>http://maven.apache.org</url>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <compilerId>groovy-eclipse-compiler</compilerId>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-eclipse-compiler</artifactId>
            <version>2.7.0-01</version>
          </dependency>
        </dependencies>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>      
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>2.0.7</version>      
    </dependency>
  </dependencies>
</project>


/*---[ Treat groovy like a first class citizen ]---*/

Put your groovy file(s) in the src/main/java directory, not src/main/groovy like that other Groovy compiler tool wants.

The directory structure:

$ tree
.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── net
    │           └── thornydev
    │               └── GroovyApp.groovy

The Groovy code:

package net.thornydev;

class Script {

  def main(args) {
    println "Hello ${args[0]}. I'm groovy."
  }
}

new Script().main(args)


/*---[ Package, push, run ]---*/

Next: mvn clean package

Then scp the script-1.0.jar in the target dir to your desired system and run it:

$ java -cp script-1.0.jar net.thornydev.GroovyApp thornydev
Hello thornydev. I'm groovy.

QED.