Video version of this article
Jars and uberjars
The most common way to prepare your Clojure project for distribution is to pack it into a
JAR format came from the Java world and it stands for “Java ARchive”. It is a zip archive with a
*.jar extension that contains Java class files, resources, and metadata.
To distribute Clojure library you can create a jar with library’s source files or with compiled code (Java *.class files with bytecode). Optionally you can put both - source files and compiled files. Compiled files will be preferred over a source files unless source files are newer.
To distribute a Clojure application you have to create a jar that contains compiled Clojure code of the app along with all its dependencies. Such self-contained archive called uberjar.
In addition to Clojure sources and Java bytecode files, jars can contain resources and metadata files.
When preparing the jar you can put there any resource files that your library or app needs (images, text, etc). From the Clojure code, those resources can be accessed via
Manifest is a special file that contains information about jar content. Here can be specified the entry point of an application but you won’t need to do that manually.
tools.build will generate a default manifest for every jar automatically. If you will need to extend manifest with custom fields, you can use
:manifest option in
(uber) functions of
More info about manifest can be found in Java documentation.
In the Java world, there is a super-popular buiid framework called
Maven. it uses
pom.xml files to store project information and configuration. This framework also has its own type of repository for built projects called “maven repository”.
In Clojure, we don’t have to use Maven to build projects but we extensively use maven repositories to store build artifacts (jars). Dependencies of our project could be downloaded from maven repos and the resulting artifact of our project (its jar) can be uploaded to maven repo to be accessible by others.
The most popular maven repo for Clojure artifacts is Clojars.
If you are planning to upload your project’s jar to some maven repository, that jar should contain a
pom.xml with artifact info.
More info about POM can be found in maven documentation.
Not so long ago you had to use 3rd-party tools to create jars for your own Clojure projects. But now the official
tools.build library can be used.
The main idea behind
tools.build is that project’s build is also a program and it can be written in Clojure code. And
tools.build is a library that provides functions commonly needed for builds.
tools.build to a project
Let’s imagine that we have a simple project structured like this:
To start using
tools.build we need to:
- create a Clojure namespace to keep build functions
- add a new alias to
deps.ednto call build functions with its help
- invoke build function via Clojure CLI
Let’s add a
build.clj to the root of the project. This is where we are going to keep all the build tasks. For now, there will be only one function -
(clean) . It removes
target directory using
target, we will keep all our build artifacts, so it should be cleaned between builds.
We don’t use any arguments inside
(clean) but it should be defined as a function with one argument because, when invoked from Clojure CLI, it will be called with one - map of the arguments passed via the command line (
nil if you didn’t pass any).
To make build functions accessible via Clojure CLI, let’s create a new alias in
deps.edn. In that alias there should be
:deps key with
tools.build dependency and
:ns-default key which tells CLI in what namespace it should look for the function mentioned in the command line.
And finally, to run
(clean) from the command line using the new alias, we need to call Clojure CLI with
-T option. That option ignores the rest of
deps.edn content and uses the dependencies only from the current alias. In this way, we do not interfere with the other project dependencies and source files.
creating a jar for the library
Now let’s review an example of creating a jar for the library with the following folder structure
mymathlibary ├── build.clj ├── deps.edn ├── resources │ └── lib_resource.txt └── src └── mymath └── sum.clj
sum.clj there is a
sum function that adds two values and prints text from the resource file (please promise me to not do like this in real-world libs).
hello from mymath lib
Let’s say we want our jar to contain only source code, without compiled bytecode. To achieve this, in
build.clj we need to add a new function named
(jar) that does the following:
targetdirectory from leftovers
- copies sources and resources to
target. They should go into the result jar.
pom.xmlfile. You will need it if you are going to put the library into maven repositories.
- creates the jar file
Here is the code of our new
In the code of
(jar) we use three functions from
tools.build to achieve steps mentioned before:
(b/jar). Their names and arguments are pretty self-explanatory except one -
The basis is a big structure that contains a superset of all
deps.edn files, project classpath, and description of all dependencies. In the official Clojure documentation, it is mentioned here.
tools.build uses basis in a few functions and gives a function to create it -
(b/write-pom) function uses basis to correctly reflect the library dependencies in
Now to create the jar we can run
(jar) function using Clojure CLI (assuming your
deps.edn already contains
Now it can be uploaded to a maven repo and used as a dependency in other projects.
You can examine jar content using
jar -tf command:
creating a runnable ubjerjar
Now we can take a look at how to create a runnable uberjar for a Clojure application.
For example, we have a project with the following structure:
simpleapp ├── build.clj ├── deps.edn ├── resources │ └── app_resource.txt └── src └── dev └── core.clj
(-main) function that will work as an entry point when the application invoked. The
(sum) function from the library created in a previous section and prints text from the local resource file.
Most important thing here is a
:gen-class directive to
(ns) function. It tells Clojure compiler to generate an additional bytecode file with java class corresponding to this namespace. That class will serve an entry point for our uberjar.
build.clj we can create an
(uber) function that builds uberjar. Here is what it should do:
targetdirectory from leftovers
- copy resources to
target. They should go into result uber.
- compile Clojure code
- create the uberjar with
Full code of
The code is quite similar to the one where we were creating jar and probably doesn’t require many explanations. New functions here are
By the way you could have used
(b/compile-clj) in library example to create a library jar with compiled code.
Now let’s bulid our uberjar:
And run it using
java -jar command:
As we can see, resource file content was printed from the app and the lib. That means uberjar was created successfully and contains all the necessary files.
The output of
jar -tf target/myapp-0.0.1-standalone.jar will be too long to post it here but I encourage you to investigate the content of created uberjar to see how dependencies are included there.
build.clj is not restricted to creating jars and uberjars. There are more functions in
tools.build that give you freedom to build scripts of an arbitrary complexity:
(process) - runs an arbitrary command with arguments
(git-process) - runs git command
(unzip) - work with archives
(install) - installs jar to a local maven repository
More details can be found in the
tools.build official api.