Clojure CLI adventures

Recently, I played around with Clojure in terms of porting a set of scripts to a cross platform command line tool.

This was fun, as I had no experience writing actual Clojure applications, just small exercises. I also don’t have a full understanding of the language (complex macros, core.async, other topics that I’ve yet to explore).

The end result of my Clojure experiment was good, reasonably fast and works reasonably well (Not stressed-tested much though). I’m looking forward playing some more with Clojure whenever possible (more POCs or production applications). I encountered couple of oddities but I was able to get through most of them without intense brain activity.

Command line options and arguments parsing

tools.cli is very straightforward and very idiomatic from a Clojure perspective. Its seems fragile, maybe not that popular? I don’t know.

Plumbing custom validation functions didn’t work for me, did I get it wrong?? -> It looks like the support for multiple validation functions is not yet in an official release. http://dev.clojure.org/jira/browse/TCLI-9

Modelling configuration

There are plenty of ways to deal with user configurations:

  • Java properties
  • Some YAML
  • JSON
  • Groovy code using ConfigSlurper for Groovy applications
  • Custom DSL
  • Environment variables
  • etc.

For Clojure I went straight with EDN, code as data, plain hash-maps with few lists and dynamic functions which are evaluated at run-time for object model specific features.

Handling exceptions

In java, I often prefer to create business exceptions. I also like segregating internal errors from external issues (third-party application crash, etc.). This requires couple of classes, sometimes error codes, details about what is happening and if possible how to fix it, etc.

slingshot is a nice library for throwing/catching exceptions in Clojure. No need to create specific dedicated classes that don’t contain much…

(throw+ {:type :external-error :reason "Failed to..." :next-steps "whatever needs to be done" :useless-stacktrace-for-the-user "NullPointerException"})

Handling external processes

I first started with clojure.java.shell, but I noticed quickly that some processes were hanging, plus other annoyances. I then looked at conch, similar problems. Even basic operations seemed to hang once in a while, in addition to known CPU/Memory intensive tasks. CLJ-959 is one nasty bug that I encountered, soooo nasty… Processes seemed to have completed execution and yet my application was was not exiting…

I was left with no other choice than falling back to the Java ProcessBuilder class.

You start an application and print its output as it comes, (i.e. rails server or whatever) then you hit Control-C. What just happened? That server is still running and the process associated to it was not killed?? Instead of using reflection and accessing the process information (UNIXProcess, Win32Process), I used the set-break-handler! function which registers a signal handler for SIGINT. In pure java that would be sun.misc.SignalHandler logic. JVM shutdown hooks just didn’t cut it for destroying sub-processes prior quitting the program.

Handling IO (symbolic links, folder deletion)

While it’s possible to invoke command line tools such as ln or mklink (Windows), a more interesting alternative is to interact with the OS via JNA (Kernel32 for Windows and C library for Unixes).

Also under Windows, all you need from the user is opening a shell with Admin privileges (custom VBS script to open cmd.exe with admin rights), if your application creates symbolic links or requires some specific privileges.

Clojure JNA really makes life simple.

I/O operations using pure Java tend to be slow especially under Windows (Try deleting a huge folder with pure Java, whatever smart graph traversal approach).

Avoiding state

It’s very tempting to introduce static/global variables. You need it, there you have it, you can easily call it from anywhere in the code. Those who have suffered from Singletons know the dangers of introducing some potential architecture/performance limitations.

Not abusing classes/records

As a Java developer, I do have to handle other programming languages once in a while (Groovy, Python, etc.). Perl, humm… no thanks. I tried using Clojure records only when I thought that I needed some kind of grouping.

Packaging

I just went with shell scripts to wrap some fat/uberjar vs. creating a JSmooth or Launch4j executable for Windows and playing with makeself for Unix/Linux. Those wrappers just delegate to jar -jar somejar and pass all your command line arguments to the main class.