Asymmetrical View

Leiningen

Leiningen is a simpler build tool for Clojure. It is easier to get started with than Maven (though you can do it, as more than one post shows), as well as ant.

Leiningen is, as maven is, rooted in convention over configuration to reduce the complexity of its build configuration. Leiningen expects your Clojure source code to be in <project>/src and your tests to be in <project>/test. These can be overridden with :source-path and :test-path respectively.

You create a project.clj in your project’s root directory to control how Leiningen builds your project. Leiningen manages your dependencies in a brief syntax using the same dependency resolution system as maven, and it integrates with Clojars, a jar repository for Clojure projects (which is also a maven repository), both for pulling dependencies as well as allowing you to upload your own open source projects to the remote repository.

There is a full walk through for installing and getting started with Leiningen that is part of the Leiningen documentation, so I am going to focus on what I found useful that isn’t already part of that guide and only show the basics of getting started – you should refer to that page (or the google group) for more information.

Dependency Version Strings

For those of you who have never used Maven, it is worth reading about maven version strings to familiarize yourself with how they work and the effect that something like SNAPSHOT in the version string will have on your build.

:warn-on-reflection

:warn-on-reflection is used to set the Clojure compiler option for warning on reflection. This will point out where adding in Clojure (make this a link) type declarations will help speed up your code.

:dependencies

:dependencies specifies a list of pairs of project and version string of the libraries you depend on.

:dev-dependencies

:dev-dependencies is similar to :dependencies, but the dependencies are not considered to be run-time dependencies, they are only for use by Leiningen during the build. The one I most commonly use is swank-clojure.

Example project.clj

This example is from the clj-bloom library

(defproject com.github.kyleburton/clj-bloom "1.0.1"
  :description "Bloom Filter implementation in Clojure, see also: http://github.com/kyleburton/clj-bloom"
  :warn-on-reflection true
  :dependencies
  [[org.clojure/clojure "1.1.0"]
   [org.clojure/clojure-contrib "1.1.0"]]
  :dev-dependencies
  [[swank-clojure "1.2.1"]])

Common Leiningen Commands

lein deps

Prior to version 1.2 of Leiningen, it did not have a build life cycle. With 1.2 it does, this means that it understands that test depends on compile, similar to how maven’s life-cycle works. If you’re using a version older than 1.2 and you get a message like: "Exception in thread "main" java.lang.NoClassDefFoundError: clojure/main" it is dues to the dependencies not yet having been pulled down.

This most often happened to me right after a lein clean. Thankfully Leiningen uses the same caching strategy that Maven does so the second time you run lein deps it will be much quicker as all it needs to do is pull the dependencies from your ~/.m2/repository directory. Currently it copies the file, in the future my hope is that it will use a symlink, which will be supported by Java7.

lein test

You can run your clojure.test based tests with lein test:

kyle@indigo64 ~/personal/projects/clj-bloom[master]$ lein test
[null] Testing com.github.kyleburton.clj-bloom-test
[null] Ran 5 tests containing 21 assertions.
[null] 0 failures, 0 errors.
[null] --------------------
[null] Total:
[null] Ran 5 tests containing 21 assertions.
[null] 0 failures, 0 errors.
kyle@indigo64 ~/personal/projects/clj-bloom[master]$ echo $?
0
kyle@indigo64 ~/personal/projects/clj-bloom[master]$

As the example shows, a drawback of the pre 1.2 versions of Leiningen is that they do not set the exit code of the process on test or compilation failure to a non-zero value. This makes those versions difficult to use from other software (like deployment or continuous integration tools.

Thankfully this is one of the fixes in the 1.2 branch, among many others. You can run the 1.2 branch, or the HEAD pretty easily by first having a working stable release of Leiningen, building it and then either symlinking or aliasing lein to point to the checked out copy:

kyle@indigo64 ~/personal/projects$ git clone http://github.com/technomancy/leiningen.git
kyle@indigo64 ~/personal/projects$ cd leiningen
kyle@indigo64 ~/personal/projects/leiningen[master]$ lein deps
kyle@indigo64 ~/personal/projects/leiningen[master]$ lein compile
kyle@indigo64 ~/personal/projects/leiningen[master]$ alias lein=~/personal/projects/leiningen/bin/lein
kyle@indigo64 ~/personal/projects/leiningen[master]$

After doing that, going back into the project directory, now lein test sets the exit code:

kyle@indigo64 ~/personal/projects/clj-bloom[master]$ lein test
     [null] Testing com.github.kyleburton.clj-bloom-test
     [null] FAIL in (make-hash-fn-hash-code-test) (clj_bloom_test.clj:9)
     [null] test the hash-fn helper
     [null] expected: (not (= (.hashCode "foo1") ((bf/make-hash-fn-hash-code "1") "foo" 4294967295)))
     [null]   actual: (not (not true))
     [null] Ran 5 tests containing 21 assertions.
     [null] 1 failures, 0 errors.
     [null] --------------------
     [null] Total:
     [null] Ran 5 tests containing 21 assertions.
     [null] 1 failures, 0 errors.
kyle@indigo64 ~/personal/projects/clj-bloom[master]$ echo $?
1
kyle@indigo64 ~/personal/projects/clj-bloom[master]$

Emacs + SLIME Integration

Leiningen eases the integration with Emacs and SLIME. SLIME provides a rich development environment for Clojure. I’ve written previously about SLIME and Clojure (Connecting to a remote REPL). Leiningen provides the ability to run a swank server with the lein-swank plug-in which is part of swank-clojure.

See Setting up Emacs & Clojure with Emacs Starter Kit or Setting up Clojure with Emacs and Leiningen for how to get Emacs configured for Clojure development.

The swank server is the back-end portion of SLIME. It listens to a socket in the JVM where your Clojure code is running and allows Emacs to connect to and interact with it, compiling and executing code, inspecting values and providing services like code completion and documentation look-up.

The example project.clj above shows how to reference swank-clojure as a dev-dependency.

You can start a swank server for your project, with all your project’s dependencies by running:

kyle@indigo64 ~/personal/projects/clj-bloom[master]$ lein swank
     [null] user=> Connection opened on local port  4005
     [null] #<ServerSocket ServerSocket[addr=localhost/127.0.0.1,port=0,localport=4005]>

Then from Emacs, execute M-x slime-connect and accept the defaults (localhost and a port of 4005).

That runs the swank sever on the default port, 4005. If you want to be able to run multiple swank servers on different ports (perhaps you’re working with multiple projects), just pass the additional arguments to the lein-swank plug-in, which are the port then the host (if your machine is known by more than one name or has more than one interface):

kyle@indigo64 ~/personal/projects/clj-bloom[master]$ lein swank 4006
     [null] user=> Connection opened on local port  4006
     [null] #<ServerSocket ServerSocket[addr=localhost/127.0.0.1,port=0,localport=4006]>

I’ve found Leiningen to be a simple, easy to use alternative to using Ant or Maven for building your code.

Kyle Burton, 03 Jun 2010 – Philadelphia PA

Special Thanks

Special thanks to technomancy, for creating Leiningen.


Tags: clojure,leiningen,build-tools