Building Clojure Projects with Leiningen

clojure-iconEverybody who once used Java, struggled with Java’s classpath at some point during their career. You have to put all the right paths in there, the right .jar files and so on, both when compiling and running your Java project. To make this somewhat simpler you typically end up doing it either in an IDE, or using a tool like Ant or Maven. These are pretty heavy weight tools, and the latter too involve writing XML, which hardly anybody does for fun anymore.

Leiningen is a simple build tool for Clojure, based on Maven (I’m pretty sure). It offers a simple, Clojuresque way of constructing build files for your Clojure projects (which run on the JVM).

To install Leiningen you only have to download one file and put it in some directory that’s on your PATH:

cd ~/bin
wget http://github.com/technomancy/leiningen/raw/stable/bin/lein
chmod +x lein

You then do a self-install:

lein self-install

This will dowload a number of jar files, including Clojure itself, so you do not even have to have Clojure installed at this point.

To make a new project, create a directory for it, e.g. helloworld:

mkdir helloworld
mkdir helloworld/src

In the source directory you put your source files, for instance a helloworld/src/helloworld.clj:

(ns helloworld
  (:gen-class))
 
(defn -main [& args]
  (println "Hello world!"))

Then, in the helloworld/ directory, create a project.clj file:

(defproject helloworld "0.1"
    :dependencies [[org.clojure/clojure
                      "1.1.0-master-SNAPSHOT"]
                   [org.clojure/clojure-contrib
                      "1.0-SNAPSHOT"]]
    :main helloworld)

The :main there defines namespace containing your -main function (analogous to the typical public static void main(...)), if any. Then, from the helloworld directory you run Leiningen:

$ lein compile

     [copy] Copying 2 files to /.../helloworld/lib

Compiling helloworld

And subsequently we can build a .jar for it, or even an uberjar, which will create a big jar file for easy distribution, also containing all of its dependencies (including Clojure itself): 

$ lein uberjar
Unpacking clojure-1.1.0-alpha-20091113.120145-2.jar
Unpacking clojure-contrib-1.0-20091114.050149-13.jar
Compiling helloworld
      [jar] Building jar: helloworld.jar
$ java -jar helloworld.jar 
Hello world!

Leiningen has some other tasks as well:

  • lein deps, installs dependencies in lib/
  • lein test [PRED], runs the project’s tests, optionally filtered on PRED
  • lein compile, ahead-of-time compiles into classes/
  • lein repl, launches a REPL with the project classpath configured
  • lein clean, removes all build artifacts
  • lein jar, creates a jar of the project
  • lein uberjar, creates a standalone jar that contains all dependencies
  • lein pom, outputs a pom.xml file for interop with Maven
  • lein install, installs in local repo (currently requires mvn)
  • lein help [TASK], shows a list of tasks or help for a given TASK

Enjoy!

 

Tags:

  • Wow, you beat me to blogging about it; awesome!

    You're right; Leiningen is based on a subset of Maven as well as Ant via Lancet, the project from Stuart's Programming Clojure book.

    I hope to have a "new" task that will create a project skeleton for you in the future so those mkdirs etc aren't necessary. Hope it works well for you!
  • Thanks! I just downloaded and installed leiningen over the weekend. This is a good description of how to use it.

    I think Phil (@technomancy) has done a fabulous job to help newbies like me work with Clojure easy.
  • patricklogan
    I'm using (pretty sure) the latest lein. The uberjar is called <project>-standalone.jar and the command for that also makes the <project>.jar at the same time.
  • clojuretest
    Hi
    I just downloaded Leiningen (lein) as was attempting to initiate commands which results in the following errors:

    Am I missing anything in terms of installation:
    I have installed clojure, maven, ant, java

    c01@c01:~/opt/incanter-helloworld$ lein compile
    Exception in thread "main" java.lang.Exception: Unable to resolve symbol: -main in this context (NO_SOURCE_FILE:1)
    at clojure.lang.Compiler.analyze(Compiler.java:4420)
    at clojure.lang.Compiler.analyze(Compiler.java:4366)
    at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:2828)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:4594)
    at clojure.lang.Compiler.analyze(Compiler.java:4405)
    at clojure.lang.Compiler.analyze(Compiler.java:4366)
    at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:3942)
    at clojure.lang.Compiler$FnMethod.parse(Compiler.java:3777)
    at clojure.lang.Compiler$FnMethod.access$1100(Compiler.java:3654)
    at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3024)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:4590)
    at clojure.lang.Compiler.analyze(Compiler.java:4405)
    at clojure.lang.Compiler.eval(Compiler.java:4640)
    at clojure.core$eval__5238.invoke(core.clj:2018)
    at clojure.main$eval_opt__7365.invoke(main.clj:225)
    at clojure.main$initialize__7372.invoke(main.clj:244)
    at clojure.main$null_opt__7400.invoke(main.clj:269)
    at clojure.main$main__7420.doInvoke(main.clj:338)
    at clojure.lang.RestFn.invoke(RestFn.java:426)
    at clojure.lang.Var.invoke(Var.java:363)
    at clojure.lang.AFn.applyToHelper(AFn.java:175)
    at clojure.lang.Var.applyTo(Var.java:476)
    at clojure.main.main(main.java:37)
    Caused by: java.lang.Exception: Unable to resolve symbol: -main in this context
    at clojure.lang.Compiler.resolveIn(Compiler.java:4797)
    at clojure.lang.Compiler.resolve(Compiler.java:4743)
    at clojure.lang.Compiler.analyzeSymbol(Compiler.java:4720)
    at clojure.lang.Compiler.analyze(Compiler.java:4387)
    ... 22 more
  • and you use the example program and buildfile as provided?
  • So recently we changed it so that self-install is only recommended for the stable branch. Zef: could you update the bin link at the top to the stable branch instead? http://github.com/technomancy/leiningen/raw/sta...

    Thanks!
  • Done. Thanks for letting me know.
  • 1.1.0-alpha is no more - it's 1.1.0-master
  • Fixed that. Thanks.
  • feldt
    Fresh install of leiningen stable, then changed project.clj to depend on 1.1.0-master-SNAPSHOT, everything compiles but when running helloworld.jar I get:

    Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/IFn
    Caused by: java.lang.ClassNotFoundException: clojure.lang.IFn
    at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:315)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:330)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:250)
    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:398)
  • Did you do lein jar, or lein uberjar? Because if you did lein jar, you still need all your dependencies to be on your classpath.
  • Tim Dysinger
  • David Cabana
    Same situation as feldt reports: fresh lein-stable install, same exception when trying to run helloworld.jar. On the other hand, running the helloworld-stanalone.jar works just fine.
  • vs
    Perhaps someone can answer this because maybe I am not understanding it correctly. I randomly did a 'lein clean' today and it purged the clojure, contrib and swank jars in my /lib folder. Is this expected behavior or did I fail to configure my project? I did not handle the project.clj in anyway after the 'lein new' and 'lein deps'.

    Thanks,
    vss
  • This is the intent, lein clean cleans all generated files and dependencies so that you end up with a clean source tree.
  • vss
    Thanks Zef - sounds reasonable. I just sort of expected it to clean the class files etc that were used to compile my project.

    Appreciate it, and of course the wicked fast response!

    vss
  • Actually, I would also expect it to clean the class files. Not sure why it doesn't.
blog comments powered by Disqus