What are the Clojure Tools?

This post is about "getting" the Clojure Tools. The reason? They stumped me in the beginning and I felt like if I can make someone's journey just a bit easier that might be a good thing.

My Clojure learning journey started by asking questions like:

  • How do I install Clojure?
  • How do I run a Clojure program?
  • How do I manage Clojure packages (dependencies)?
  • How do I configure a Clojure project?
  • How do I build Clojure for production?

Now, when I started working with Clojure the answer to these questions was: choose either lein or boot. Then Rich Hickey and his ride or die homeboys rolled by and provided their own answer: The Clojure Tools .

Their vision, like the vision of Clojure itself, is a bit offbeat. So, this post is about reviewing the Clojure Tools and figuring out a mental model for them.

At a high level, the Clojure Tools currently consist of:

  • Clojure CLI
  • tools.build

The first is a CLI tool and the second is a Clojure library which provides some helper functions to make it easier to build Clojure artifacts. The rest of this post will dig into each of these tools.

Clojure CLI #

The Clojure CLI is made up of the following subprograms:

And here is what it looks like to use the Clojure CLI and some of the things it can do:

Run a Clojure repl

clj

Run a Clojure program

clj -M -m your-clojure-program

manage Clojure dependencies

clj -Sdeps '{:deps {bidi/bidi {:mvn/version "2.1.6"}}}'

The above is just the tip of the CLojure CLI iceburg. I have omitted more interesting examples so we can focus on the Clojure CLI at a higher level. In honor of said "high level" overview, the following sections will cover each of theClojure CLI's subprograms.

clj/clojure #

As we see above, the Clojure CLI is invoked by calling one of the two shell commands:

  • clj
  • clojure

When you read through the Official Deps and CLI Guide you will see that you can use either clj or clojure. clj is the recommended version, but both are used. Furthermore, when you start to look at open source code, you will see that both are used.

What's the difference between these two commands? clj is mainly used during development. clojure is mainly used in a production or CI environment. The reason for this is because clj is a light wrapper around the clojure command.

The clj command wraps the clojure command in another tool called rlwrap. rlwrap improves the developer experience by making it easier to type in the terminal while you're running your Clojure REPL.

The tradeoff for the convenience provided by clj is that clj introduces dependencies. This is a tradeoff because you may not have access to rlwrap in production. In addition, a tool like rlwrap can make it harder to compose the clj command with other tools. As a result, it's a common practice to use clojure in production/ci environments .

Now that we see they both more or less the same command, what do they do? clj/clojure has one job: run Clojure programs against a classpath. If you dig into the clj/clojure bash script you see that it ultimatley calls a command like this:

java [java-opt*] -cp classpath clojure.main [init-opt*] [main-opt] [arg*]

The above might look like a simple command, but the value of having clj/clojure is that you as a new Clojure developer don't have to manually build the classpath, figure out the exact right Java command to run or work to make this execute on different environments (windows, linux, mac etc).

In summary, clj/clojure is about running Clojure programs in a classpath and orchestrates other tools. For example, in order to run against a classpath, there has to be a classpath. clj/clojure is not responsible for figuring out the classpath though. That's a job for tools.deps.alpha

tools.deps.alpha #

tools.deps.alpha is a Clojure libary responsible for managing your dependencies. What it does is:

  • reads in dependencies from a deps.edn file
  • resolves the dependencies and their transitive dependencies
  • builds a classpath

Note that I said it's a Clojure library. You don't have to be using the Clojure CLI in order to use this tool. You can just use it by itself if you wanted to.

What makes tools.deps.alpha so great is that it's a small and focused library. There isn't much more to say about this other than if you want to learn more about the history, development and goals of the tool from the Clojure team I recommend listening to this episode of Clojure Weekly Podcast which features Alex Miller, the author of tools.deps.alpha.

As noted above, the first thing tools.deps.alpha is going to do is read in your project configuration and deps. This information is stored in deps.edn.

deps.edn #

The deps.edn file is a Clojure map with a specific structure. Thus, when you run clj/clojure one of the first things it does is find a deps.edn file and reads it in.

deps.edn is where you configure your project and specify project dependencies. At it's heart, deps.edn is just an edn file. You can think of it like Clojure's version of package.json.

Here is an example of what a deps.edn file looks like:

{:deps    {...}
 :paths   [...]
 :aliases {...}}

As you can see, we use the keywords :deps, :paths and :aliases and more to start to describe your project and the dependencies it requires.

Tools.Build #

This is the newest Clojure Tool. It's been in the works for a while and might be the simplest to understand conceptually: It's a Clojure library with functions that do things like build a jar, uberjar etc.

One distinction that's important to note is that tools.build is not the same as the Clojure CLI tool's -T switch. I am calling this out now because when tools.build was released the Clojure CLI was also enhanced to provide the -T switch. As one can imagine, this could be seen as confusing because of the similarity of their names.

The best way that I can currently explain the -T switch is by saying that it's meant to be another level of convenience provided by the Clojure CLI.

Regarding usage, it helps to first breakdown the main types of Clojure programs one might build into 3 sub categories:

  • A tool
  • A library
  • An app

You would use -T for Clojure programs that you want to run as a "tool". For example, deps-new is a Clojure library which creates new Clojure projects based on a template you provide. This is a great example of a Clojure project which is built to be a "tool".

I don't want to go into more detail about -T now because that means we would have to dive into other Clojure CLI switches like -X and -M. That's for another post. On to the Installer!

Installer #

The "Clojure CLI Installer" is a fancy way of referring to the brew tap used to install Clojure on mac and linux machines. As of February 2020, Clojure started maintaining their own brew tap. Thus, if you installed the Clojure CLI via

brew install clojure

you will likely want to uninstall clojure and install the following:

brew install clojure/tools/clojure

In all likelihood, you would probably be fine with brew install clojure as it will recieve updates. However, while brew install clojure will still see some love, it won't be as active as the clojure/tools/clojure tap.

clj v lein v boot #

This section will provide a quick comparison of clj, lein and boot.

Firstly, all of the above tools are more or less addressing the same problems in their own way. Your job is to choose the one you like best.

If you're curious which to choose, my answer is the Clojure CLI. The reason I like the Clojure CLI is because the tool is simple. You can read through clj and tools.deps.alpha in an afternoon and understand what they are doing if you had to. The same (subjectively of course) cannot be said for lein or boot. This is not just implementation, but also usage. Yes, lein seems easier to start, but the moment you break away from the beginner examples you are left deeps in the woods without a compass.

Secondly, the Clojure Tools promote libraries over frameworks. This is important when working with a language like Clojure because it really does reward you for breaking down your thinking.

Finally, the Clojure community is really leaning into building tools for Clojure CLI. For example, where lein used to have significantly more functionality, the community has built a ton of incredible tools that will cover many of your essential requirements.

So yes, Clojure Tools for the win.