Clojure - Deps and CLI Guide (original) (raw)
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
- Use a test runner to run all tests
- Add an optional dependency
- Add a dependency from the command line
- Prep source dependency libs
- Override a dependency version
- Use a local jar on disk
- Ahead-of-time (AOT) compilation
- Run a socket server remote repl
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"
:
{:deps
{org.clojure/core.async {:mvn/version "1.3.610"}}
:aliases
{:test {:extra-paths ["test"]}}}
Apply that classpath modification and examine the modified classpath by invoking clj -A:test -Spath
:
$ clj -A:test -Spath
test:
src:
/Users/me/.m2/repository/org/clojure/clojure/1.12.0/clojure-1.12.0.jar:
... same as before (split here for readability)
Note that the test dir is now included in the classpath.
Use a test runner to run all tests
You can extend the :test
alias in the previous section to include the cognitect-labs test-runner for running all clojure.test tests:
Extend the :test
alias:
{:deps
{org.clojure/core.async {:mvn/version "1.3.610"}}
:aliases
{:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}}}
And then execute the test runner using the default config (run all tests in -test namespaces under the test/ dir):
Aliases in the deps.edn
file can also be used to add optional dependencies that affect the classpath:
{:aliases
{: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
.
Add a dependency from the command line
It can be helpful to experiment with a library without adding it to an existing deps.edn
file or creating one.
$ clojure -Sdeps '{:deps {org.clojure/core.async {:mvn/version "1.5.648"}}}'
Clojure 1.12.0
user=> (require '[clojure.core.async :as a])
nil
Note that due to escaping rules, it’s best to wrap the config data in single quotes.
Preparing source dependency libs
Some dependencies will require a preparation step before they can be used on the classpath. These libs should state this need in their deps.edn
:
{:paths ["src" "target/classes"]
:deps/prep-lib {:alias :build
:fn compile
:ensure "target/classes"}}
Including the top-level key :deps/prep-lib
tells the tools.deps classpath construction that something extra is needed to prepare this lib and that can be performed by invoking the compile
function in the :build
alias. Once the prepare step has been done, it should create the path "target/classes"
and that can be checked for completion.
You depend on this library like any other source-based library (could be git or local):
{:deps {my/lib {:local/root "../needs-prep"}}}
If you then try to include that library on your classpath you’ll see an error:
$ clj
Error building classpath. The following libs must be prepared before use: [my/lib]
You can then tell the CLI to prep using this command (this is a 1-time action for a particular lib version):
$ clj -X:deps prep
Prepping io.github.puredanger/cool-lib in /Users/me/demo/needs-prep
$ clj
Clojure 1.12.0
user=>
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:
{:deps
{org.clojure/core.async {:mvn/version "0.3.465"}}
:aliases
{: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:
{:deps
{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:
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)
(gen-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 -M -e "(compile 'hello)"
And then run the hello namespace:
$ clj -M -m hello
Hello, World!
Run a socket server remote repl
Clojure provides built-in support for running socket servers, and in particular using them to host remote REPLs.
To configure a socket server repl, add the following base configuration to your deps.edn
:
{:aliases
{:repl-server
{:exec-fn clojure.core.server/start-server
:exec-args {:name "repl-server"
:port 5555
:accept clojure.core.server/repl
:server-daemon false}}}}
And then start the server by invoking with the alias:
If you like, you can also override the default parameters (or add additional options) on the command line:
clojure -X:repl-server :port 51234
You can use netcat to connect from another terminal:
nc localhost 51234
user=> (+ 1 1)
2
Use Ctrl-D to exit the repl and Ctrl-C to exit the server.
List all dependencies
There are several helpful tools in the built-in :deps
alias to explore the full set of transitive deps used by your project (and their licenses).
To list the full set of all the deps included on your classpath, use clj -X:deps list
. For example in the hello-world
application at the top of this guide, you would see something like this:
% clj -X:deps list
clojure.java-time/clojure.java-time 1.1.0 (MIT)
org.clojure/clojure 1.12.0 (EPL-1.0)
org.clojure/core.specs.alpha 0.2.62 (EPL-1.0)
org.clojure/spec.alpha 0.3.218 (EPL-1.0)
time-lib/time-lib ../cli-getting-started/time-lib
The full set of transitive dependencies used by your application is listed in alphabetical order with version and license. See the api docs for additional printing options.
If you want to understand the tree structure of your dependencies and how version selection choices were made, use clj -X:deps tree
:
% clj -X:deps tree
org.clojure/clojure 1.12.0
. org.clojure/spec.alpha 0.3.218
. org.clojure/core.specs.alpha 0.2.62
time-lib/time-lib /Users/alex.miller/tmp/cli-getting-started/time-lib
. clojure.java-time/clojure.java-time 1.1.0
There were no version selections made here, but see the docs for more on how choices are explained in the tree if needed.
Both of these helper functions take an optional :aliases
argument if you wish to examine the dependency list or tree with one or more aliases applied, such as clj -X:deps list :aliases '[:alias1 :alias2]'
.