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!

 

Got something to say?
  1. Phil says:

    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. Raju says:

    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. patricklogan says:

    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. clojuretest says:

    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. Zef Hemel says:

    and you use the example program and buildfile as provided?

  6. Phil says:

    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. Zef Hemel says:

    Done. Thanks for letting me know.

  8. Tim Dysinger says:

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

  9. feldt says:

    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. David Cabana says:

    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. Zef Hemel says:

    Fixed that. Thanks.

  12. Zef Hemel says:

    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. vs says:

    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. Zef Hemel says:

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

  15. vss says:

    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. Zef Hemel says:

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

  17. gstamp says:

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

  18. Lesya says:

    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. jneira says:

    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. brw314 says:

    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. brw314 says:

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

  22. Andreas says:

    Hi,

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

  23. brw314 says:

    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. giacecco says:

    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. Carson says:

    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 says:

    You need to say:

    java -jar helloworld-standalone.jar

    not

    java -jar helloworld.jar

  27. Thank you! I was having a very difficult time trying to figure out how to get my compiled application to run, turned out I forgot to define :main in project.clj

  28. vikrant behal says:

    Exception in thread “main” java.lang.IllegalAccessError: user-setting exist (NO_SOURCE_FILE:0)         at clojure.lang.Compiler.eval(Compiler.java:5440)         at clojure.lang.Compiler.eval(Compiler.java:5391)         at clojure.core$eval.invoke(core.clj:2382)         at clojure.main$eval_opt.invoke(main.clj:235)         at clojure.main$initialize.invoke(main.clj:254)         at clojure.main$script_opt.invoke(main.clj:270)         at clojure.main$main.doInvoke(main.clj:354)         at clojure.lang.RestFn.invoke(RestFn.java:482)         at clojure.lang.Var.invoke(Var.java:381)         at clojure.lang.AFn.applyToHelper(AFn.java:178)         at clojure.lang.Var.applyTo(Var.java:482)         at clojure.main.main(main.java:37) Caused by: java.lang.IllegalAccessError: user-settings does not exist         at clojure.core$refer.doInvoke(core.clj:3287)         at clojure.lang.RestFn.applyTo(RestFn.java:139)         at clojure.core$apply.invoke(core.clj:542)         at clojure.core$load_lib.doInvoke(core.clj:4781)         at clojure.lang.RestFn.applyTo(RestFn.java:142)         at clojure.core$apply.invoke(core.clj:542)         at clojure.core$load_libs.doInvoke(core.clj:4800)         at clojure.lang.RestFn.applyTo(RestFn.java:137)         at clojure.core$apply.invoke(core.clj:544)         at clojure.core$use.doInvoke(core.clj:4892)         at clojure.lang.RestFn.invoke(RestFn.java:457)         at leiningen.new$loading__4414__auto__.invoke(new.clj:1)         at leiningen.new__init.load(Unknown Source)         at leiningen.new__init.(Unknown Source)         at java.lang.Class.forName0(Native Method)         at java.lang.Class.forName(Class.java:247)         at clojure.lang.RT.loadClassForName(RT.java:1578)         at clojure.lang.RT.load(RT.java:399)         at clojure.lang.RT.load(RT.java:381)         at clojure.core$load$fn__4519.invoke(core.clj:4915)         at clojure.core$load.doInvoke(core.clj:4914)         at clojure.lang.RestFn.invoke(RestFn.java:408)         at clojure.core$load_one.invoke(core.clj:4729)         at clojure.core$load_lib.doInvoke(core.clj:4766)         at clojure.lang.RestFn.applyTo(RestFn.java:142)         at clojure.core$apply.invoke(core.clj:542)         at clojure.core$load_libs.doInvoke(core.clj:4800)         at clojure.lang.RestFn.applyTo(RestFn.java:137)         at clojure.core$apply.invoke(core.clj:542)         at clojure.core$require.doInvoke(core.clj:4881)         at clojure.lang.RestFn.invoke(RestFn.java:408)         at leiningen.core$resolve_task.invoke(core.clj:73)         at leiningen.core$_main.doInvoke(core.clj:110)         at clojure.lang.RestFn.applyTo(RestFn.java:137)         at clojure.core$apply.invoke(core.clj:540)         at leiningen.core$_main.invoke(core.clj:114)         at user$eval46.invoke(NO_SOURCE_FILE:1)         at clojure.lang.Compiler.eval(Compiler.java:5424)         … 11 more

Trackbacks for this post

  1. Building Incanter applications with Leiningen and Clojars.org « Data Analysis and Visualization with Clojure
  2. Clojure – Destillat #4 | duetsch.info - Open Source, Wet-, Web-, Software
  3. Leiningen and Clojure | Smash Company
  4. Using Leiningen to Build Projects « Clojure Companion Cube
  5. Additional Investment To Learn Clojure « Dr. Knucklehead's Blog
  6. [Clojure] building clojure projects with leiningen

Comments are closed now.