As Alex mentioned, the most striking difference is speed from the command line. Cake uses a persistent JVM, so you only encounter the jvm startup overhead when you run a task within your project for the first time. If you are not using emacs + slime + clojure-test-mode, this can be a huge timesaver. For example, a reasonably large set of tests on one of my projects runs in 0.3 seconds in cake, vs 11.2s in lein.

Aside from performance, the core idea behind cake is the dependency task model. Each task is only run once in a given build, taking into account all transitive prerequisites in the dependency graph. Here's an example from Martin Fowler's article on rake in cake syntax, which goes directly in your project.clj.

(deftask code-gen "This task generates code. It has no dependencies." (println "generating code...") ...) (deftask compile #{code-gen} "This task does the compilation. It depends on code-gen." (println "compiling...") ...) (deftask data-load #{code-gen} "This task loads the test data. It depends on code-gen." (println "loading test data...") ...) (deftask test #{compile data-load} "This task runs the tests. It depends on compile and data-load." (println "running tests...") ...)

To do the same in Leiningen, you would first have to create a leiningen directory in your project with 4 files: code_gen.clj, compile.clj, data_load.clj, and my_test.clj.

src/leiningen/code_gen.clj

(ns leiningen.code-gen "This task generates code. It has no dependencies.") (defn code-gen [] (println "generating code..."))

src/leiningen/my_compile.clj

(ns leiningen.my-compile "This task does the compilation. It depends on code-gen." (:use [leiningen.code-gen])) (defn my-compile [] (code-gen) (println "compiling..."))

src/leiningen/data_load.clj

(ns leiningen.data-load "This task loads the test data. It depends on code-gen." (:use [leiningen.code-gen])) (defn data-load [] (code-gen) (println "loading test data..."))

src/leiningen/my_test.clj

(ns leiningen.my-test "This task runs the tests. It depends on compile and data-load." (:use [leiningen.my-compile] [leiningen.data-load])) (defn my-test [] (my-compile) (data-load) (println "running tests..."))

One would expect...

generating code... compiling... loading test data... running tests...

But both data-load and my-compile depend on code-gen, so your actual ouput is...

generating code... compiling... generating code... loading test data... running tests...

You would have to memoize code-gen to prevent it from being run multiple times:

(ns leiningen.code-gen "This task generates code. It has no dependencies.") (def code-gen (memoize (fn [] (println "generating code..."))))

output:

generating code... compiling... loading test data... running tests...

Which is what we want.

Builds are simpler and more efficient if a task is only ran once per build, so we made it the default behavior in cake builds. The philosophy is decades old and shared by a lineage of build tools. You can still use functions, you can still call them repeatedly, and you always have the full power of clojure at your disposal.

Lein just gives you a plain function as a task, but with the added constraint that it must have it's own namespace in src. If a task depends on it, it will be in a separate namespace, and must use/require the other in it's ns macro. Cake builds look much neater and concise in comparison.

Another key difference is how tasks are appended to. Let's say we wanted to add my-test as prerequisite to cake/lein's built in jar task. In cake, you can use the deftask macro to append to a task's forms and dependencies.

(deftask jar #{my-test})

Lein uses Robert Hooke to append to tasks. It's a really cool library, named after everyone's favorite natural philospher, but it would require a macro for the conciseness of deftask .

(add-hook #'leiningen.jar/jar (fn [f & args] (my-test) (apply f args)))

Cake also has the notion of a global project. You can add user specific dev-dependencies, like swank, to ~/.cake/project.clj and have it across all of your projects. The global project is also used to start a repl outside of a project for experimentation. Lein implements similar features by supporting per-user configuration in ~/.lein/init.clj , and global plugins in ~/.lein/plugins . In general, Lein currently has a much richer plugin ecosystem than cake, but cake includes more tasks out of the box (war, deploy, java compilation, native dependencies, clojars, and swank). Cljr may also be worth checking out, it's essentially just a global project with a package manager, but without build capabilities (i have no experience with it however).

The real irreconcible difference is task defintions, as technomancy pointed out. In my (biased) opinion, cake handles tasks much better. The need for a task dependency model became evident when we started using protocol buffers in our project with lein. Protobufs were pre-requisites for all of our tasks, yet compiling them is really slow. We also have alot of inter-dependent tasks, so any build was painful. I also don't like the requirement of a seperate namespace, and therefore an additional src file, for every task I create. Developers should create a lot tasks, lein's approach discourages this by creating too much friction. With cake, you can just use the deftask macro within project.clj.

Cake is still young, and a work in progress, but it's a very active project.