I expect the answer to be no, but the fact that deps.edn has the Cognitect seal of approval, plus @seancorfield 's, raises it in my eyes. If Lein is mostly working out for me (except for moments of complexity, particularly re: Jackson), is there any major reason to switch to deps? I assume it plays as nicely with Cider (hopefully without much setup)?
If youâre happy using lein
, you might as well continue to do so.
If youâre using Emacs/CIDER, Iâm pretty sure the latest version knows how to start an nREPL using the CLI/deps.edn
.
As for why you might want to switch:
-
lein
generally starts two JVMs: one for itself and one for your program âclj
/clojure
uses a JVM to compute the classpath but then caches it so subsequent uses ofclj
/clojure
run only one JVM (with the same set of aliases/dependencies) -
lein
relies on the Maven/Aether/Pomegranate resolution algorithm for picking library versions if your dependencies bring in a conflict and that can be the source of weird problems that then drive people intolein deps :tree
and âpedanticâ mode; CLI/deps.edn
uses a simpler algorithm that favors explicit dependencies and more recent versions, which causes fewer surprises and is more easily controlled - CLI/
deps.edn
can easily bring in dependencies from local source installations as well as from anygit
repo (by URL and SHA);lein
can do this too but requires plugins and/or more futzing around in yourproject.clj
file -
lein
always runs with âeverythingâ out of the box; CLI/deps.edn
runs with just the tooling you tell it to use so itâs often faster to start up (even aside from the two vs one JVMs issue above)
We originally switched from lein
to boot
back in 2015 because we wanted a) better programmability and b) easier support of a monorepo with lots of subprojects. boot
served those needs very well but we started to run into performance issues with the fileset abstraction and bugs with the pod refresh stuff so we were happy to switch to the CLI/deps.edn
in 2018 â our custom boot
tasks became simple scripts that could easily be run via clojure
and performance was good.
My main reason to prefer CLI/deps.edn
is âsimple toolsâ. Iâm with Stu Halloway and Eric Normand and others on wanting my dev tooling to be as simple and lightweight as possible: small, composable tools, with no âmagicâ. Thatâs also why I donât use nREPL any more, I donât use CIDER or Compliment, I just use a plain Socket REPL. That means I can fire up any Clojure process (or even a legacy app that includes Clojure) and have it start a Socket REPL with just a JVM option, which means dev, test, and production can all expose the exact same interface: a Socket REPL that is built into Clojure itself. That in turn means that I can use the exact same dev tooling with local processes and with remote processes.
âSwitchâ, I donât know. But deps.edn is poised to make the pie larger. Much larger.
When the culture eventually, inevitably comes around to embrace the notion that itâs natural to run any program straight from CVS, no need to freeze an uberjar, no need to wonder whoâs really in charge over at Maven Central; that will be a momentous moment.
Until then, I like âlein testâ.
I can see a couple of use-cases where deps.edn wins:
-
Learner Drivers
Iâve been working through a couple of books using Lein and Cider, having started from never using emacs before. Though I can now see why itâs a good toolset, itâs not how Iâd want to introduce a complete beginner to the language and I think FP might be the best way to start children programming. I didnât fully understand what Leningen was doing behind the scenes. Iâd rather have started with clj and a simple editor window. I can see what deps.edn is doing. -
Old Hardware
My âLaptopâ is an Eee PC thatâs about 10 years old and I tried using Clojure on a Raspberry Pi 2. The startup times for a lein REPL are far too long and it stopped working on the Pi, after a Java upgrade. CLJ is much quicker on the Eee PC and I hope it might work on a Pi. They are widely used in schools in the UK. I donât like to see people excluded from computing because they donât have the best kit. For environmental reasons, I like to keep old hardware functioning for as long as possible. If companies are letting equipment go after 3 years, Iâd like to see it re-purposed.
Beyond what @seancorfield said. I want to add that itâs not apples to oranges comparing Lein and Clj.
The new Clj tool does two things:
- Download appropriate dependencies.
- Bootstraps a Clojure program with appropriate classpath and main args and what not.
Lein does all the above, but it also:
- Lets you perform a number of common build tasks
Clj doesnât come with any build tasks. The best it can do is create a pom.xml which allows you to use maven for certain build related tasks. But on its own, it means that it canât:
- compile your project
- put it into a jar
- or an uberjar
- it canât run your tests
- lint your code
- clean your compilation target folder
- deploy your library to a remote maven repo
- compile java code
- scaffold new Clojure projects
- search Maven for libraries
- etc.
The only thing Clj can do is download a Clojure program to your machine with all its required dependencies and run it. Or download all of your projectâs dependencies and run it.
So if you want to do anything âbuildâ like, you need to use something else over Clj.
But, because Clj can download and run Clojure programs. It is easy to use it to download other Clojure programs that were designed to perform âbuildâ like tasks and run them.
You can also have it create a pom.xml like I said, and then make use of Maven for certain build tasks. Maven will be good for Java related tasks, and creating Jars, publishing to Mavn repos, etc. Not so much for Clojure specific tasks.
Finally, when you think of Clojure, the truth is, there is very little âbuildingâ required. Since Clj can download and run Clojure programs that are available as source on github, you can go very far with just git and clj.
To be fair â and put your post in context â because Clj supports a user-level deps.edn
file as well as a project-level one, itâs very easy to quickly add aliases that bring in nearly all of that list of features for all your projects with just a one-time setup⌠My dot-clojure file contains options for many of them: JAR files, uberjar files with AOT compilation, test running, Eastwood for linting, scaffolding new Clojure projects (plus a whole bunch of options you didnât list).
Thereâs at least one option out there for deploying to remote repos (I just donât have an alias for it yet in my file).
There are several other options for JAR files (and one of those, I think, deals with Java compilation?)
Leiningenâs Maven search has never worked well for me so Iâve always gone to search.maven.org or clojars.org directly for searching
p.s. Itâs also worth pointing out that Boot doesnât have a test runner or new project scaffolding out of the box either (but Adzerkâs boot-test
and my boot-new
are libraries that add those features â in pretty much exactly the same way the Cognitectâs test-runner
and my clj-new
add them to Clj).
From a user perspective, if youâre someone just looking for a âhow toâ with bullet points, Clj + alias and Lein might end up being an equal user experience. But I wanted to get to the more fundamental underpinning, that none of those additional build tools youâd alias are a plugin to Clj, and neither do they have anything related to it. Each one is just another tool that you need to learn, which might have its own configuration, command line arguments, etc., unrelated to that of Clj. Clj is just one way for you to download the tool and run it. You could assemble them all through Lein as well, or manually through any other means theyâd offer themselves to be downloaded and run.
I like that model, I like it a lot! It is very similar to the Linux command line, you just have programs and you can pipe their input/output together. They donât always play nice, but if you have an assortment of them that have some convention, they could. The big advantage is that none of them are coupled to Clj. The downside is there isnât a lot of enforcement in how they should behave and interact, though hopefully that develops organically through conventions.
The biggest missing part, is there isnât currently a way to orchestrate a build with Clj. So you need to write a script for your OS. Maybe bash, or joker, etc. Say you want to compile -> test -> uberjar. There could be another tool built that you could alias that provides this, but again, all that isnât really related to Clj.
On that note, I think it would be nice if Clj could add the following features:
- A way to list the available main aliases.
- A way to add a doc-string to aliases (which would be displayed along when listing the available main aliases).
Would be cool if it could also maybe support some form of concept on man page, where inside deps.edn you could define a man, and Clj could show you the man for your main aliases.
Are you referring to deps-deploy? Iâve used it recently, and itâs pretty easy to setup.
Yes, @slipsetâs tool for deploying to Clojars. Iâll probably switch to that next time I cut a release of any of my libraries, instead of my mvn
wrapper shell script, and then Iâll add it to my dot-clojure
repo for the benefit of others!
I read somewhere (as I remember it, from an âofficialâ source) that clj/deps is not a build tool. Which sort of have guided my assessment of the question here. But reading this thread the picture is much more nuanced than what I have realized.
Also. I totally agree about the strength of clj dependency management. I recently brought in the lein-tools-deps
plugin into a Leiningen project and think that that is a very nice way to source some of that strength. (Maybe thatâs abvious, but I donât see it mentioned here.)
Yea, agreed. I think a nice future for lein is as a build tool over clj, as I feel having an official package/dependency manager now, that part of lein is the most redundant and likely to just slowly be deprecared or see itself being used less and less. So the use of lein with the lein-tools-deps
plugin I think is pretty great, and Iâve used that in some projects before.
Youâre also correct in that it is much more nuanced. The nuance comes partly with trying to even define what a build
is, and thus what a build tool
looks like.
A build
is really whatever you want. Back in the day, it meant to build the binary for your application. But nowadays, what does it mean to build Clojure? Clojure doesnât need building
like in the old days. You can run a .clj
file as is, your source code is also a program that you can run directly without any additional steps. But there are possibly many steps
youâd want to perform prior to giving your app to others, such as generating documentation, linting, running tests, auto-formatting the source, possibly running AOT, etc.
None of these per-say are required to run your program, thus what youâre going to be doing exactly as part of building
your Clojure program is kind of up to you. You might want to send yourself an email, or play a victory jingle for example. There is no strict set.
Still, you eventually realize that many of the possible tasks youâd want performed as part of your build
from project to project starts to always be the same set of tasks, with only minor modifications to them. And thatâs when a build tool
seems like a good idea. A single program/script where youâll implement the logic for the most common build tasks and expose a certain level of configuration to each so they can adapt to the minor changes between projects.
And with that definition in place, youâll realize that clj/deps is not such a tool. It does not come with a set of common build tasks that you can use with some configuration to adapt them to your project, but lein does!
And yet, clj/deps can look like a build tool, because it is a package manager for Clojure programs. And so let me explain.
As you realize that for each of your Clojure project, you always want to generate documentation, run tests and perform linting. You have two follow up:
- Create a single tool that can do all of these, and has a unified configuration.
- Create a separate tool for each of these, which can be configured.
Lein is the #1 option. A monolithic build tool with a unified configuration, and some support for addition plugins that can be used to add even more tasks to its existing set which all follow its conventions and configuration format.
#2 are some of the new build tools which have been more recently developed to be used with clj/deps, such as clj-new, cljfmt-runner, clj-check, test-runner, kaocha, depstar, etc. A bunch of separate tools each targeting a subset or even a single of the possible tasks youâd want performed at build, using their own configuration and conventions.
Most of these donât need clj/deps, though some of them
I believe do depend on having a deps.edn file. Theyâre all build tools
, like lein, just with a smaller scope.
Previously, getting all these tools installed on your machine and running would have been painful, or youâd have used lein for it (lein is also a package manager), but if youâd have used lein for it, and since lein already comes with most build tasks built in, youâd have probably not tried to get any of those other tool and just used lein or a lein plugin to perform the same task. But now that clj/deps is here, it is super easy to get all these tools downloaded and running in your project. Thatâs where some people are thus saying that clj/deps
can be used as a âbuild toolâ.
Now Iâm not here to fight on word definitions, the fact is indirectly, you can use clj/deps to kick-start build tasks on your project, and that alone could mean we label it a build tool, I donât care. My goal is only to provide a deeper understanding of the differences between lein
and clj/deps
at a fundamental level, for those interested.
Hope I helped.
Regards!
The lein-tools-deps plugin is new to me, and has an excellent readme explaining its justification. Thanks for making me aware!
I like the distinction you make between monolithic solutions and the bespoke/libraries approach. Frankly, #2 seems more in line with general Clojure and maybe Linux culture. Itâs probably my favorite, except that getting started can be rough: it requires a good set of templates to get rolling (something vital for me when I started with Luminus, which was just the template I needed for how an actionable Clojure web app can be laid out, built, and deployed, and with what dependencies).
Iâm trying to make clj-new
as good a starting point as possible so you only need to add one alias to get started:
(! 1144)-> clj -A:new app tory/webdev
Generating a project called webdev based on the 'app' template.
(! 1145)-> cat webdev/deps.edn
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"}}
:aliases
{:test {:extra-paths ["test"]
:extra-deps {org.clojure/test.check {:mvn/version "0.10.0"}}}
:runner
{:extra-deps {com.cognitect/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner"
:sha "f7ef16dc3b8332b0d77bc0274578ad5270fbfedd"}}
:main-opts ["-m" "cognitect.test-runner"
"-d" "test"]}
:uberjar {:extra-deps {seancorfield/depstar {:mvn/version "0.5.1"}}
:main-opts ["-m" "hf.depstar.uberjar" "webdev.jar"
"-C" "-m" "tory.webdev"]}}}
(! 1146)-> ls webdev/
CHANGELOG.md README.md doc resources test
LICENSE deps.edn pom.xml src
At this point, you can easily test the project and build an AOTâd uberjar if you want (this is an app
template). The JAR can be run with java -jar webdev.jar
Iâm planning to update the lib
template to include a :deploy
alias (for Clojars) â it already includes a :jar
alias.
Always happy to have feedback on what can make these templates easier to get started with!
In my experience, deps.edn is worth to switch.
- more controllable at the classpath and having less weird transitive dependencies problems
- start up time is faster
- more flexible project setup, like the project, subproject structure
- donât need to publish to clojars, when I fix some upstream open source libraries for my own project.
- with deps, scripting is much more bearable.
but we still have many problems have to handle.
-
donât have a good âuberjarâ tool ( aot + merging resources + manifest creation + resolving libraries) could be used with deps
-
no easy publishing flow for clojars/standard maven deployment.
-
no way to mix with native java source files.
-
documentation is far from perfect.
-
If you are an advance user, you will need extensive knowledge about how maven is working.
Thank you for that honest review; the pros you mention are compelling, but the problems like uberjars and java interop are nearly show-stoppers. Nearly, because it seems like I have heard people mention work-arounds.
What about Clojurescript? Whatâs the story with CLJS + Deps.EDN?
I donât know about the mixing of java and Clojure source files, but @seancorfieldâs https://github.com/seancorfield/dot-clojure/blob/master/deps.edn has a way of building uberjars. There are other ways of building uberjars as well, such as Cambada, which can just as easily be integrated into an alias. In general, everything leiningen does can be done with deps, but where with leiningen you need to use plugins, with deps you create aliases to scripts or other programs.
With regards to cljs and deps, it works pretty well. I have set up my project with shadow-cljs for cljs builds, and let deps control dependencies for both clj and cljs.
Unless you have a pretty advanced use of leiningen, my guess is that the switch to deps is not that much work.
The latest clj-new
does include :install
and :deploy
aliases in new lib
and template
projects.
depstar
builds uberjars, based on the pom.xml
produced by clojure -Spom
(which, admittedly, needs some manual editing), with AOT and manifest creation, and relies on the CLI/deps.edn
to resolve libraries. With clj-new
, the generated app
projects include a fully-fleshed out pom.xml
so if you use a group-id/artifact-id
style name for the new project (which the docs recommend), then you can go straight to clojure -A:uberjar
(via depstar
) and clojure -A:deploy
(via @slipset's
deps-deploy`). Happy to consider additional features that you feel are missing.
A data point: I tried using tools.deps recently on my reasonably large Clojure+ClojureScript application (my deps.edn was 83 lines). It turned out that my most important task (building an uberjar with AOT) is not obvious (tried depstar, cambada and uberdeps).
I carefully considered why I should invest into migrating, and it turned out that there are two advantages:
- being âsimplerâ: this is true to a certain extent, but complexity rises rapidly as you add software for building uberjars,
- access to GitHub repositories, which is a nice feature, but one I can live without.
Lein startup time (or its two JVMs) have never been a problem for me. My build time is long mostly because of AOT and because of ClojureScript compilation.
My conclusion was that I should wait until tools.deps matures, because I canât afford the cost right now.