This post is over 6 months old. Some details, especially technical, may have changed.

Building Command Line Apps with Clojure

I recently read an article by Ryan Neufeld around parsing command line arguments with Clojure. It's a good article and you should read it if the idea of building command line utilities in Clojure floats your boat.

To complement that article I've decided to take the building of the command line app one step further and support running your creation as a standalone command. Of course its possible, as demonstrated in the article, to run the app using Leiningen directly but you typically want to be bundling your app into a self conatined package (minus any necessary runtimes - in our case the JVM) for deployment to other environments.

So lets start were Ryans article left off, a simple command line app that can be run via Leiningen. The core.clj looks like this,

(ns runs.core
  (:require [clojure.tools.cli :refer [cli]])
  (:gen-class))


(defn -main [& args]
  (let [[opts args banner] (cli args
                                ["-h" "--help" "Print this help"
                                 :default false :flag true])]
    (when (:help opts)
      (println banner))))

And the project.clj looks like this

(defproject runs "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/tools.cli "0.2.4"]]
  :main runs.core)

As Ryans article mentions we can run this via lein run and pass our arguments in.

lein run -- -h

Uberjar

First thing we want to do is take Leiningen out of the equation and bundle the app as a standalone JAR. Unlike other JVM build tools that need to be extended with plugins such as shade, onejar and assembly Leingingen comes with uberjar prebundled. To create a basic uberjar, a JAR containting your application code and all the necessary dependencies, we can call lein uberjar. There are also a bunch of configuration options that go along with uberjar but what we have specified is sufficient to build our jar. The JAR will be output to target dir appended with -standalone. We can run this JAR using via the usual Java way

java -jar target/app-0.1.0-SNAPSHOT-standalone.jar -h

lein-bin

I don't know about you but I'm lazy and I like my commands names to represent what they do (roughly) so the thought of running java -jar blahblah.jar -h every time I want to run the command is just "meh". We could write a shell script to run the more verbose command but thats less than ideal (it's messy and on top of my laziness I'm a tad OCD about carting around wrapper scripts).

Enter lein-bin a Leiningen plugin that utilises the fact ZIP files and therefore JARs can have stuff prepended to the front of it. What it does is prepend shell commands to your JAR making it executable in and of itself.

So to use lein-bin we need to add the plugin to our projects project.clj or the global Leiningen profile (~/.lein/profiles.clj)

{ :plugins [[lein-bin "0.3.4"]] }

We can also add some lein-bin related configuration like changing the output name (there are some other options as well, all noted in the lein-bin README)

:bin { :name "runs" })

So running lein bin now leaves us with another file in target called runs that is executable.

target/runs -h

Now we've got a command line util called runs (yeah a terrible name) rather than some verbose Java command or being force to use a build tool on all environments. It's the little things.

Published in Clojure on March 02, 2014