CLI tools make Clojure very accessible and simple to install as they are a essentially a wrapper for running Clojure code using the
java command and use additional libraries to manage dependencies, class paths, create projects and build java archive (jar) files.
Its quite common to use the
java command to run your code in production, usually defined in a shell script. Leiningen can be used to run your application in production too, however, because Leiningen creates 2 JVM instances (one for itself and one for the application), its more efficient to just use the
Leiningen does provides a very rich set of templates that speed up development with Clojure and has a multitude of plugins. Plugins provide a rich source of features but they are not very composable, especially compared to the Clojure language itself.
Clojure CLI tools provide a minimal but elegant layer on top of the
java command and enables libraries, configuration and code to compose together just like Clojure functions. So we will continuing the exploration of Clojure CLI tools and dig a little deeper under the covers to understand how they work and how to configure projects to be very flexible, especially the different sources of code you can use .
This article follows on from getting started with Clojure CLI tools
Using the command
lein new app classic creates a simple project called
classic containing some source code and test code. We can use
lein repl to give instant feedback on the evaluation of the code in our project.
This command also compiles our code to Java bytecode, so it can run on the JVM just like compiled Java or Scala code.
lein jar and more commonly
lein uberjar is used to package up our code into a single file. These commands compile the Clojure code into classes when Ahead Of Time compilation is used. Any namespaces with
(:gen-class) directive included in compiled into a JVM bytecode class is
lein uberjar creates a single file that contains our application and the Clojure library, so we can use with the java command line
java -cp target/myproject-standalone.jar
If I had created a library project, with
lein new classic, then I would need to specify clojure.main and the main class for the
java command to work correctly.
java -cp target/myproject-standalone.jar clojure.main -m classic.core
It is also possible to run the compiled source code, however, we will also need to add Clojure as a dependency. There is a copy of the Clojure library in my maven cache from previous projects I have worked on.
java -cp target/uberjar/classes/:/home/jr0cket/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar classic.core
If I just wanted to run a repl, I can call clojure.main as my namespace
java -cp /home/jr0cket/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar clojure.main
Already there are a few things to remember. As your project gets bigger then the command you use will get bigger and harder to manage safely, its often put into scripts but then there is no real validation that you got the script right, without some manual testing
java $JVM_OPTS -cp target/todo-list.jar clojure.main -m todo-list.core $PORT
It is very easy to create a project for CLI tools, using the
clojure command or the
clj wrapper for that command, which will use a readline to improve the command line experience.
CLI tools project only requires a
deps.edn file, a default file comes with the CLI tools install.
~/.clojure/deps.edn is created the first time you run the
/usr/local/lib/clojure/deps.edn contains a few basic options that are applied to all projects.
src is set as the relative path to the source code
The dependencies include
1.10.1 version of the Clojure library.
Aliases define additional libraries that will only be included during development, in this case
org.clojure/tools.deps.alpha which is used to find and download project dependencies and build a classpath for the project.
Finally maven central and clojars are the repositories where dependencies are downloaded from.
There is some duplication of the configurations
The cognitect-labs/test-runner is a recent project so we are including this directly from its GitHub repository. We use the latest commit
Using the Git commit removes the need to create a Jar file from the source code.
Create a new file in the
test directory called
core_test.clj that contains a test with two assertions.
clojure.test namespace is included in the
org.clojure/clojure dependency, so we do not have to add anything to the
We run the failing tests with the following command
You can see that the first time we are using the test-runner the CLI tools download the source code from the Git repository.
NOTE: Using a Git commit is relatively stable dependencies. The only risk is if you are using a shared repository and a force commit is made that replaces the commit you have as dependency.
Everything is working correctly and the tests are failing because we have not written the code that the test is using. So write the application code and make the test pass and execute the test runner again.
Use different versions of dependencies in your project that is set globally. One example is if you are activly building a project, you may want to include the latest commit on a feature branch. Or you may be using a third party library and want to test out a new beta version. Or perhaps you are releasing a library and want to test it with earlier versions of Clojure, for example.
Passing options to the Java Virtual Machine can be very important to shape the performance dynamics of your Clojure application. For example, not enough memory allocation can really grind your application to a halt. I experienced this with a third party Java project, they only had 512Mb as the memory allocation size and after a number of uses we working with it then it would steadily grind to a halt. Doubling the JVM memory allocation made the application fly for hundreds of concurrent users.
We saw that Leiningen created a single file that we can use to deploy our application and call from the
java command line.
depstar is a CLJ tools based uberjar tool
Create an uberjar file using
To run the generated jar file
java -cp simple.jar clojure.main -m simple.core
depstar does not do any ahead of time compilation (AOT) so your application may start up more slowly as the code first needs to be compiled into Java byte code.
This work is licensed under a Creative Commons Attribution 4.0 ShareAlike License, including custom images & stylesheets. Permissions beyond the scope of this license may be available at @jr0cket