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:

View Comments

  1. 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!

  2. 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.

  3. 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.

  4. 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

  5. and you use the example program and buildfile as provided?

  6. 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!

  7. Done. Thanks for letting me know.

  8. 1.1.0-alpha is no more – it's 1.1.0-master

  9. 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)

  10. 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.

  11. Fixed that. Thanks.

  12. 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.

  13. 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

  14. This is the intent, lein clean cleans all generated files and dependencies so that you end up with a clean source tree.

  15. 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

  16. Actually, I would also expect it to clean the class files. Not sure why it doesn't.

  17. I wish it was this easy to work with on windows.

  18. It was interesting to read this article and I hope to read a new article about this subject in your site in the near time.

  19. good article , i get leiningen working on windows but i had some issues,
    first self-install dont works on windows: you have to download jars and set system vars.
    Second i have tried to use last 1.2.o snapshot clojure.jar and didnt works (clojure-1.1.0.jar was the solution) and then “lein compile” raise a “project.clj not found”. I had to change lein.bat to add “.” to classpath.

  20. test2/helloworld rberger$ cat project.clj
    (defproject helloworld “0.1″
    :dependencies [[org.clojure/clojure
    "1.1.0-master-SNAPSHOT"]
    [org.clojure/clojure-contrib
    "1.0-SNAPSHOT"]]
    :main helloworld)
    zephyr:~/desktop/test2/helloworld rberger$
    zephyr:~/desktop/test2/helloworld rberger$ ls src
    helloworld.clj
    zephyr:~/desktop/test2/helloworld rberger$ cat src/helloworld.clj
    (ns helloworld
    (:gen-class))

    (defn -main [& args]
    (println “Hello world!”))

    zephyr:~/desktop/test2/helloworld rberger$ lein clean
    Cleaning up
    zephyr:~/desktop/test2/helloworld rberger$ lein compile
    [copy] Copying 2 files to /Users/rberger/Desktop/test2/helloworld/lib
    Exception in thread “main” java.lang.NullPointerException (NO_SOURCE_FILE:0)
    at clojure.lang.Compiler.eval(Compiler.java:4658)
    at clojure.core$eval__5236.invoke(core.clj:2017)
    at clojure.main$eval_opt__7411.invoke(main.clj:227)
    at clojure.main$initialize__7418.invoke(main.clj:246)
    at clojure.main$null_opt__7446.invoke(main.clj:271)
    at clojure.main$main__7466.doInvoke(main.clj:346)
    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.NullPointerException
    at clojure.core$name__4748.invoke(core.clj:1053)
    at leiningen.compile$find_native_lib_path__775.invoke(compile.clj:80)
    at leiningen.compile$eval_in_project__791.doInvoke(compile.clj:109)
    at clojure.lang.RestFn.invoke(RestFn.java:430)
    at leiningen.compile$compile__806.invoke(compile.clj:154)
    at clojure.lang.Var.invoke(Var.java:359)
    at clojure.lang.AFn.applyToHelper(AFn.java:173)
    at clojure.lang.Var.applyTo(Var.java:476)
    at clojure.core$apply__4370.invoke(core.clj:436)
    at leiningen.core$main_46$fn__49.invoke(core.clj:81)
    at leiningen.core$main_46.doInvoke(core.clj:78)
    at clojure.lang.RestFn.invoke(RestFn.java:413)
    at user$eval__55.invoke(NO_SOURCE_FILE:1)
    at clojure.lang.Compiler.eval(Compiler.java:4642)
    … 10 more

  21. hi, sorry, i forgot to comment above , but I think I followed the directions

  22. Hi,

    I get exactly the same error as above when trying to compile with the same input files. Is something broken?

  23. yes, actually it turns out the problem seems it is Mac OSX 10.4 that leiningen won't compile on, it works fine on the debian system I use, but thanks for a nice tutorial!

  24. Hi Zef,
    I am using leiningen 1.1.0 and 'lein uberjar' creates two jars, helloworld.jar and helloworld-standalone.jar . Only the latter works with 'java -jar helloword-standalone.jar'. You may want to check that your example is consistent with that. Thanks for your tutorial!

  25. I've written a simpler and briefer guide sharing my experience with using Leiningen for building Clojure programs, using the current version Leiningen 1.1.0. Not as detailed as yours here, and definitely based on what I learnt from yours, but thought I'd share what bits I know about the newer version.

    http://blog.carsoncheng.ca/2010/06/building-clo...

    Thanks for writing your guide here. Learnt a lot.

  26. Thirdreplicator

    You need to say:

    java -jar helloworld-standalone.jar

    not

    java -jar helloworld.jar

Leave a comment

blog comments powered by Disqus