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
Resolving dependencies for jessitron:scattered-rugs:0.1.0 ← local completed
Loading jessitron:scattered-rugs:0.1.0 ← local into runtime completed
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.
Running editor MakeExecutableJar of jessitron:scattered-rugs:0.1.0 ← local completed
→ Project
~/code/scala/org-dep-graph/ (8 mb in 252 files)
→ 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: ^.*$
let pluginContents = “””
org.apache.maven.plugins
maven-shade-plugin
2.4.3
target/executable.jar [4]
package
shade
__I_AM_THE_MAIN__
“”” [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 # resolve $SOURCE until the file is no longer a symlink
DIR=”$( cd -P “$( dirname “$SOURCE” )” && pwd )”
SOURCE=”$(readlink “$SOURCE”)”
[[ $SOURCE != /* ]] && SOURCE=”$DIR/$SOURCE” # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
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