Deps and CLI Guide

Clojure provides command line tools for:

  • Running an interactive REPL (Read-Eval-Print Loop)

  • Running Clojure programs

  • Evaluating Clojure expressions

In all the above scenarios you might want to use other Clojure and Java libraries (dependencies or 'deps'). These may be libraries you are writing locally, projects in git (e.g. on GitHub) or, commonly, libraries available in the Maven ecosystem and hosted by central repositories like Maven Central or Clojars.

In all cases, using a library involves:

  1. specifying which library you want to use, providing its name and other aspects like version

  2. getting it (once) from the git or maven repositories to your local machine

  3. making it available on the JVM classpath so Clojure can find it while your REPL or program is running

Clojure tools specify a syntax and file (deps.edn) for (a), given which they’ll handle (b) and (c) automatically.

See Getting Started for details on how to install the tools. Here we will demonstrate how to get started. See Deps and CLI for a complete reference. See the changelog for version information.

Running a REPL and using libraries

After you download and install the tools, you can start a REPL by running the clj tool:

$ clj
Clojure 1.10.1

Once in the REPL you can type Clojure expressions and press enter to evaluate them. Type Control-D to exit the REPL:

user=> (+ 2 3)   # press the `enter` key after typing the expression to evaluate it
5                # result of expression
user=>           # type Ctrl-D here to exit the REPL (does not print)

There are many Clojure and Java libraries available that provide access to practically any functionality you might need. For example, consider the commonly used Clojure library for working with dates and times.

To work with this library, you need to declare it as a dependency so the tool can ensure it has been downloaded and add it to the classpath. The readme in most projects shows the name and version to use. Create a deps.edn file to declare the dependency:

 { {:mvn/version "0.3.2"}}}

Restart the REPL with the clj tool:

$ clj
Downloading: clojure/java-time/ from clojars
Downloading: clojure/java-time/ from clojars
Clojure 1.10.1
user=> (require '[java-time :as t])
user=> (str (t/instant))

You will see messages about a library being downloaded the first time you use a dependency. Once the file is downloaded, it will be reused in the future. You can use the same process to add other libraries to your deps.edn file and explore Clojure or Java libraries.

Writing a program

Soon you will want to build and save your own code that makes use of these libraries. Create a directory hello-world and change to that directory. Copy the deps.edn file into this directory. By default, the clj tool will look for source files in the src directory, so create the src directory and declare your program at src/hello.clj:

(ns hello
  (:require [java-time :as t]))

(defn time-str
  "Returns a string representation of a datetime in the local time zone."
    (t/with-zone (t/formatter "hh:mm a") (t/zone-id))

(defn -main []
  (println "Hello world, the time is" (time-str (t/instant))))

Using a main

This program has a static entry point named -main that is suitable for external invocation. The clj tool acts as a Clojure program launcher with the -m option, which specifies the namespace to run:

$ clj -m hello
Hello world, the time is 10:53 PM

Using local libraries

You might decide to move part of this application into a library. The clj tool uses local coordinates to support projects that exist only on your local disk. Let’s extract the java -time parts of this application out into a library in a parallel directory time-lib. The final structure will look something like this:

├── time-lib
│   ├── deps.edn
│   └── src
│       └── hello_time.clj
└── hello-world
    ├── deps.edn
    └── src
        └── hello.clj

Under time-lib, use a copy of the deps.edn file you already have, and create a file src/hello_time.clj:

(ns hello-time
  (:require [java-time :as t]))

(defn now
  "Returns the current datetime"

(defn time-str
  "Returns a string representation of a datetime in the local time zone."
    (t/with-zone (t/formatter "hh:mm a") (t/zone-id))

Update the application at hello-world/src/hello.clj to use your library instead:

(ns hello
  (:require [hello-time :as ht]))

(defn -main []
  (println "Hello world, the time is" (ht/time-str (ht/now))))

Modify hello-world/deps.edn to use a local coordinate that refers to the root directory of the time-lib library (make sure to update the path for your machine):

 {time-lib/time-lib {:local/root "/path/to/time-lib"}}}

You can then test everything from the hello-world directory by running the application:

$ clj -m hello
Hello world, the time is 02:07 PM

Using git libraries

It would be great to share that library with others. You can accomplish this by pushing the project to a public or private git repository and letting others use it with a git dependency coordinate.

First, create a git library for the time-lib:

cd time-lib
git init
git add deps.edn src
git commit -m 'init'

Then go to a public git repository host (like GitHub) and follow the instructions for creating and publishing this git repository.

Finally, modify your app to use the git dependency instead. You’ll need to gather the following information:

  • repository url - in GitHub, use the HTTPS url, like

  • sha - indicate which version of the git library you want to use. You can run git rev-parse HEAD to get the sha of the current repo

Update the hello-world/deps.edn to use a git coordinate instead:

  {:git/url "" :sha "04d2744549214b5cba04002b6875bdf59f9d88b6"}}}

Note that we’ve altered the library name. When artifacts are deployed in a Maven repository, it’s a best practice to use a groupId (the first part of the name) that is something you control (usually via DNS or trademark). In the case where you have neither, you can instead combine the name of a site that establishes identities (like GitHub) with your identity on that site, here github-yourname.

Now you can run the app again, making use of the (shared) git repository library. The first time you run it you’ll see extra messages on the console when clj downloads and caches the repository and the commit working tree:

$ clj -m hello
Checking out: at 04d2744
Hello world, the time is 02:10 PM

Now your friends can use time-lib too!

Other examples

As your program gets more involved you might need to create variations on the standard classpath. The Clojure tools supports classpath modifications using aliases, which are parts of the deps file that are only used when the corresponding alias is supplied. Some of the things you can do are:

Include a test source directory

Typically, the project classpath includes only the project source, not its test source by default. You can add extra paths as modifications to the primary classpath in the make-classpath step of the classpath construction. To do so, add an alias :test that includes the extra relative source path "test":

 {org.clojure/core.async {:mvn/version "0.3.465"}}

 {:test {:extra-paths ["test"]}}}

Apply that classpath modification and examine the modified classpath by invoking clj -A:test -Spath:

$ clj -A:test -Spath
... same as before

Note that the test dir is now included in the classpath.

Add an optional dependency

Aliases in the deps.edn file can also be used to add optional dependencies that affect the classpath:

 {:bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}}

Here the :bench alias is used to add an extra dependency, namely the criterium benchmarking library.

You can add this dependency to your classpath by adding the :bench alias to modify the dependency resolution: clj -A:bench.

Override a dependency

You can use multiple aliases in combination. For example this deps.edn file defines two aliases - :old-async to force the use of an older core.async version and :bench to add an extra dependency:

 {org.clojure/core.async {:mvn/version "0.3.465"}}

 {:old-async {:override-deps {org.clojure/core.async {:mvn/version "0.3.426"}}}
  :bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}}

Activate both aliases as follows: clj -A:bench:old-async.

Include a local jar on disk

Occasionally you may need to refer directly to a jar on disk that is not present in a Maven repository, such as a database driver jar.

Specify local jar dependencies with a local coordinate that points directly to a jar file instead of a directory:

 {db/driver {:local/root "/path/to/db/driver.jar"}}}

Ahead-of-time (AOT) compilation

When using gen-class or gen-interface, the Clojure source must be ahead-of-time compiled to generate the java class(es).

This can be done by calling compile. The default destination for compiled class files is classes/, which needs to be created and added to the classpath:

$ mkdir classes

Edit deps.edn to add "classes" to the paths:

{:paths ["src" "classes"]}

Declare a class with gen-class in src/my_class.clj:

(ns my-class)

  :name my_class.MyClass
  :methods [[hello [] String]])

(defn -hello [this]
  "Hello, World!")

Then you can reference the class with :import in another source file src/hello.clj. Notice that the namespace is also added in :require so compilation can automatically find all dependent namespaces and compile them.

(ns hello
  (:require [my-class])
  (:import (my_class MyClass)))

(defn -main [& args]
  (let [inst (MyClass.)]
    (println (.hello inst))))

You can compile in the REPL or run a script to do the compilation:

$ clj -e "(compile 'hello)"

And then run the hello namespace:

$ clj -m hello
Hello, World!

See Compilation and Class Generation for a complete reference.

Original author: Alex Miller