Today’s Rug: maven executable jar

this is supposed to be a rug with a jar on it. An active jar.

I like being a polyglot developer, even though it’s painful sometimes. I use lots of languages, and in every one I have to look stuff up. That costs me time and concentration.

Yesterday I wanted to promote my locally-useful project from “I can run it in the IDE” to “I can run it at the command line.” It’s a Scala project built in maven, so I need an executable jar. I’ve looked this up and figured this out at least twice before. There’s a maven plugin you have to add, and then I have to remember how to run an executable jar, and put that in a script. All this feels like busywork.

What’s more satisfying than cut-and-pasting into my pom.xml and writing another script? Automating these! So I wrote a Rug editor. Rug editors are code that changes code. There’s a Pom type in Rug already, with a method for adding a build plugin, so I cut and paste the example from the internet into my Rug. Then I fill in the main class; that’s the only thing that changes from project to project so it’s a parameter to my editor. Then I make a script that calls the jar. (The script isn’t executable. I submitted an issue in Rug to add that function.) The editor prints out little instructions for me, too.

$ rug edit -lC ~/code/scala/org-dep-graph MakeExecutableJar main_class=com.jessitron.jessMakesAPicture.MakeAPicture
...
run `mvn package` to create an executable jar
Find a run script in your project’s bin directory. You’ll have to make it executable yourself, sorry.
...

→ Changes
├── pom.xml updated 2 kb
├── pom.xml updated 2 kb
├── bin/run created 570 bytes
└── .atomist.yml created 702 bytes

Successfully edited project org-dep-graph

It took a few iterations to get it working, probably half an hour more than doing the task manually. It feels better to do something permanently than to do it again. Encoded in this editor is knowledge: * what is that maven plugin that makes an executable jar? [1] * how do I add it to the pom? [2] * what’s the maven command to build it? [3] * how do I get it to name the jar something consistent? [4] * how do I run an executable jar? [5] * how do I find the jar in a relative directory from the script? [6] * how do I get that right even when I call the script from a symlink? [7]

It’s like saving my work, except it’s saving the work instead of the results of the work. This is going to make my brain scale to more languages and build tools.

— — — — — — — — — — — — — — — — — — — — — —

below the fold: the annotated editor. source here, instructions here in case you want to use it -> or better, change it -> or even better, make your own.

@description “teaches a maven project how to make an executablejar” @tag “maven” editor MakeExecutableJar
@displayName “Main Class”
@description “Fully qualified Java classname”
@minLength 1 @maxLength 100
param main_class: @java_package
let pluginContents = “””<plugin><groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<outputFile>target/executable.jar</outputFile>[4]
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation=”org.apache.maven.plugins.shade.resource.ManifestResourceTransformer”>
<mainClass>__I_AM_THE_MAIN__</mainClass>
</transformer> </transformers> </configuration> </execution> </executions>
</plugin>“”” [2]
let runScript = “””#!/bin/bash
# http://stackoverflow.com/questions/59895/getting-the-source-directory-of-a-bash-script-from-within
SOURCE=”${BASH_SOURCE[0]}”
while [ -h “$SOURCE” ]; do
DIR=”$( cd -P “$( dirname “$SOURCE” )” && pwd )”
SOURCE=”$(readlink “$SOURCE”)”
[[ $SOURCE != /* ]] && SOURCE=”$DIR/$SOURCE”
done [7]
DIR=”$( cd -P “$( dirname “$SOURCE” )” && pwd )” [6]
java -jar $DIR/../target/executable.jar “$@” [5]
“””
with Pom p
do addOrReplaceBuildPlugin “org.apache.maven.plugins” “maven-shade-plugin” pluginContents [1]
with File f when path = “pom.xml” begin
do replace “__I_AM_THE_MAIN__” main_class
do eval { print(“run `mvn package` to create an executable jar”)} [3]
end
with Project p begin
do eval { print(“Find a run script in your project’s bin directory. You’ll have to make it executable yourself, sorry”) }
do addFile “bin/run” runScript
end

Originally published at blog.jessitron.com on January 13, 2017.