Let's talk about tools.
I've written previously on this blog about my love for Clojure. When working in Clojure, I use Leiningen as my build tool and general Swiss army knife, and Vim as my editor. I use a healthy number of plugins for both. This post will focus on Leiningen, with a second post to follow that will focus on Vim.
For the record, I consider the state of my current toolchain to be good, but not great. That having been said, the focus of this post will be descriptive rather than normative.
This was written for people who either haven't used Clojure and are curious what a Clojure developer's toolchain looks like, or for people who currently use Clojure and are interested in learning about how other developers work within the language's development ecosystem.
Leiningen
Leiningen is, by far, the most popular Clojure build tool. However, it does a lot more than just serve as a build tool - so much so that instead of trying to enumerate it's features I'm going to cheat and just quote the authors directly:
Leiningen is for automating Clojure projects without setting your hair on fire
It's obvious what they mean by that, right? Of course it is. Don't mind my avoiding eye contact with you, that's just my way of saying we agree.
Why Use Leiningen?
Having to access a language via a Java jar is not ideal. Nobody wants to have to type this on a regular basis:
java -cp clojure-${VERSION}.jar clojure.main
So, what are our other options? Clojure code is (or at some point will be) Java code, so we could rely on traditional Java build tools like Ant or Maven. Speaking personally, however, I've yet to meet anybody who was particularly enthusiastic about either of those.
Fortunately for us, Leingingen actually is a decent multi-tool - it has a good build system, convenient hooks into core project aspects like testing and application initialization, is highly extensible via hooks, middleware and plugins, and has strong ecosystem buy-in. It's not going away any time soon.
At the moment, I'm only aware of one really compelling reason not to use Leiningen, which is if your team is primarily a Java shop. If you're only dipping your toes in the Clojure ecosystem, chances are that you'll get a lot more mileage out of continuing to use your existing tools.
Extra Credit, Part I: The name "Leiningen" comes from the famous short story Leiningen Versus the Ants. The observant reader can hopefully put two and two together here.
Extra Credit, Part II: Why not Boot? The very short answer is just that I haven't tried it, but it's still fairly young and I don't have a compelling reason to leave the land of Leiningen. I'd be eager to hear from developers who've had experience working with it.
How You Use It
Leiningen handles all of its configuration through a set of core config files, with different files sitting in different locations, according to their corresponding scope. For the current project, there's a project.clj
file that sits in the project root. For user-level configuration, you can also store a profiles.clj
file in the project root (that's usually left out of version control). Cross-project user-level configuration goes in ~/.lein/profiles.clj
.
As mentioned earlier, Leiningen has quite a few core features, some of which I use on a daily basis. Here's the hall of fame for me (which, unsurprisingly, has a 1:1 mapping to the Basic Usage section of the Leiningen README):
lein new [TEMPLATE] $NAME
Creates a new Clojure project. If you don't provide a template, creates a library-style proejct, with /doc
, /resources/
, /src
, and /test
directories, as well as project.clj
, README
and LICENSE
files.
...this obviously doesn't get used on a daily basis, but is still extremely handy for when you just want to bootstrap a handy skeleton. The optional [TEMPLATE] parameter is fantastic for when you want to make something a little more complicated that has to plug into someone else's ecosystem (e.g. making a Leiningen plugin with lein new plugin $NAME
), or for when you're starting out with an application (lein new app $NAME
).
lein test [TESTS]
Runs (clojure.test/run-all-tests)
.
It's actually a little cleverer than that. Leiningen lets you set metadata on tests so that you can filter for particular test sets in addition to configuring which directories contain test code. Don't feel like waiting for all of your integration tests to complete every time? Flag those puppies with a ^:integration
metadata flag and configure test selectors to only run when you do lein test :integration
(or better, lein test :all
)
The optional [TESTS] parameter is useful for when you're not sure what the state of your application is, and just want to poke at a single namespace, or even a single test, e.g.:
lein test :only api.test.controllers.ach
or
lein test :only api.test.controllers.ach/valid-ach?-works
lein repl
Launches an nREPL (networked REPL)! Probably my favorite, because - in case I haven't said this before - I love REPLs. Clojure nREPLs, unlike most REPLs I am familiar with, launch not only an interactive client but also a running server with an exposed port that other nREPL clients can connect to.
One server, many nREPLs - with state shared between them. Dun dun dunnn...
This is handy for many reasons, but I get the most mileage out it by using an editor with a built-in nREPL client. You'll have to read my toolchain post on Vim to learn more. Seriously, it's so cool. You're going to be so excited. I almost want to tell you right now, but I won't ruin the surprise.
lein run -m my.namespace
Initializes and runs your application from the target namespace. Typically, most applications will have specified the main namespace in their project.clj
, and so will only need to pass lein run
.
An aside on nREPLs and mains: When I'm developing, I almost always want both the application and an nREPL server running. Why not do both at once? I've gone back and forth on whether it's better to develop by writing code within your application that starts an nREPL server or to launch your application by invoking the main method from an nREPL session.
I have a soft preference for the former, but I've been trying out the latter and it's growing on me. If you do go the latter route, it helps to have log output going to a file, so your nREPL output and application logs don't go through the same terminal window.
lein uberjar
Compiles a Java jar with all of its dependencies - perfect for when you want to deploy in a Docker container.
An aside on the subject of jar compilation - compiling a Java jar forces you to pick a main namespace to be compiled into a Java class, which will be loaded by the Java class loader. The differences between the Clojure and Java class loaders aren't trivial, and can result in some unexpected (and probably undesired) changes to an application's initialization process.
All of which is to say that if you've been running your application in production using Leiningen, switching to using an uberjar is definitely the sort of thing one should test rigorously before trying to get it going in some sort of container. Trust me on this one, I'm an expert.
lein deploy clojars
Okay, this is really my favorite, because it's the moment when you take all of your code and wrap it up in a pretty box and send it out into the world to play with all of the other little libraries. Don't they just grow up so fast? *sniff*
Word at the soda fountain is that public key cryptography is a thing these days. Leiningen lets you configure your deployment step to automatically sign your jars with your public key, which is nice if you care about someone not maliciously injecting code into the projects of all those poor saps who trusted you as a dependency.
Leiningen Plugins
The maintained list of Leiningen plugins is long, and there are even more plugins out there in the wild that aren't on the list. Personally, I only use a few plugins, and they largely relate to maintaining code quality. At work, we use Phabricator, and I've written hooks for some of them to run automatically when submitting a diff for review.
Cljfmt
At long last, Clojure finally has its own gofmt-like tool, thanks to the ever-industrious @weavejester. Cljfmt both checks (lein cljfmt check
) and formats (lein cljfmt fix
) your code nicely in adherence to the generally accepted Clojure style rules, and has options for configuration in case you have your own feelings on the subject.
Eastwood
Eastwood is a Clojure linter, invoked with lein eastwood
. As a general request, please use a linter. Some of my favorite moments in the last year have come from people trying to merge code with lint errors and insisting that the linter was wrong. It's like an open admittance that they've made a mistake but are unwilling to do the work to understand what it is.
I make no claims to perfection, myself - Eastwood regularly catches minor mistakes like misaligned parenthesis in tests or out-of-place docstrings - but Eastwood at least prevents me from quietly sneaking such errors into my code.
Kibit and lein-bikeshed
Both kibit and lein-bikeshed are tools that hunt for, essentially, "bad code smell". Kibit (lein kibit
) is a static code analyzer that specializes in finding code blocks that replicate existing functions (for instance, when you've typed (when (not ...))
instead of (when-not ...)
), and lein-bikeshed (unsurprisingly, lein bikeshed
) looks for things like long lines, missing docstrings, and extra whitespace.
I'm not particularly strict in my enforcement of their recommendations, but they're great tools to have available.
Ultra
Is it shameless to hype your own plugin? Probably, but I'm going to do it anyways.
Ultra is a plugin for a superior development environment (especially when your workflow is oriented around the terminal). It adds pretty-printed diffs to your test output, syntax highlighting in your REPL, more legible stacktraces, and functions to make interactive Java usage more friendly.
If you're curious, I've got a more extensive writeup on Ultra that you can read here.
Conclusion
As of the State of Clojure 2014, 98% of the respondents were using Leiningen as their build system - the ecosystem buy-in is almost total, so if you're writing in Clojure chances are pretty good you're using Leiningen and are probably familiar with it.
Even so, hopefully the above has been helpful for those of you who aren't familiar with the Clojure ecosystem, or who are still sitting mostly in Java-land and are wondering what things look like on the other side.
As I noted at the beginning, I'll have a follow-up post to this coming up shortly on how I use Vim as part of my Clojure toolchain, which will also touch on how I get my different tools to play nicely with each other.
Stay tuned :)
Discuss this post on Hacker News or on Twitter (@venantius)
Thanks to Keith Ballinger (@keithba) and Bill Cauchois (@wcauchois) for reading drafts of this post.