Takes deps.edn and packs an uberjar out of it.
- Fast: Does not unpack intermediate jars on disk.
- Explicit: Prints dependency tree. Realize how much crap you’re packing.
- Standalone: does not depend on current classpath, does not need live Clojure environment.
- Embeddable and configurable: fine-tune your build by combining config options and calling specific steps from your code.
It is important to be aware that build classpath is not the same as your runtime classpath. What you need to build your app is usually not what you need to run it. For example, build classpath can contain uberdeps, tools.deps.alpha, ClojureScript compiler etc. Your production application can happily run without all that! Build classpath does not need your app dependencies. When you just packing files into a jar you don’t not need something monumental like Datomic or nREPL loaded!
Other build systems sometimes do not make a strict distinction here. It’s not uncommon to e.g. see ClojureScript dependency in project.clj in main profile.
Uberdeps is different from other archivers in that it strictly separates the two. It works roughly as follows:
- JVM with a single
uberdepsdependency is started (NOT on the app’s classpath). - It reads your app’s
deps.ednand figures out from it which jars, files and dirs it should package. Your app or your app’s dependencies are not loaded! Just their dependencies are analyzed, usingtools.deps.alphaas a runtime library. - Final archive is created, JVM exits.
Ideally you would setup a separate deps.edn for packaging:
project
├ deps.edn
├ src
├ ...
└ uberdeps
├ deps.edn
└ package.sh
with following content:
uberdeps/deps.edn:
{:deps {uberdeps {:mvn/version "0.1.8"}}}uberdeps/package.sh:
#!/bin/bash -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
clojure -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jarTo be clear:
/uberdeps/deps.ednis used only to start uberdeps. Files, paths, profiles from it won’t affect resulting archive in any way./deps.edn(referred as--deps-file ../deps.ednfrom/uberdeps/packages.h) is what’s analyzed during packaging. Its content determines what goes into the final archive.
In an ideal world, I’d prefer to have /uberdeps.edn next to /deps.edn in a top-level dir instead of /uberdeps/deps.edn. Unfortunately, clj / clojure bash scripts do not allow overriding deps.edn file path with anything else, that’s why extra uberdeps directory is needed.
You CAN use aliases to control what goes into resulting archive, just as you would with normal deps.edn. Just remember to tell uberdeps about it with --aliases option:
deps.edn:
{ :paths ["src"]
...
:aliases {
:package {
:extra-paths ["resources" "target/cljs/"]
}
:nrepl {
:extra-deps {nrepl {:mvn/version "0.6.0"}}
}
}
}uberdeps/package.sh:
#!/bin/bash -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
clojure -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar --aliases package:nrepl:...Sometimes it’s just too much setup to have an extra script and extra deps.edn file just to run simple archiver. In that case you can add uberdeps in your main deps.edn under an alias. This will mean your app’s classpath will load during packaging, which is extra work but should make no harm.
project
├ deps.edn
├ src
└ ...
deps.edn:
{ :paths ["src"]
...
:aliases {
:uberdeps {
:deps {uberdeps {:mvn/version "0.1.8"}}
:main-opts ["-m" "uberdeps.uberjar"]
}
}
}and invoke it like this:
clj -A:uberdepsIn that case execution will happen like this:
- JVM will start with
:uberdepsalias which will ADDuberdepsdependency to your app’s classpath. uberdeps.uberjarnamespace will be invoked as main namespace.- Uberdeps process will read
deps.ednAGAIN, this time figuring out what should go into archive. Note again, it doesn’t matter what’s on classpath of Uberdeps process. What matters is what it reads fromdeps.ednitself. Archive will not inherit any profiles enabled during execution, or any classpath resources, meaning, for example, that uberdeps won’t package its own classes to archive. - Final archive is created, JVM exits.
You can invoke Uberdeps from command line at any time without any prior setup.
Add to your bash aliases:
clj -Sdeps '{:deps {uberdeps {:mvn/version "0.1.8"}}}' -m uberdeps.uberjarOr add to your ~/.clojure/deps.edn:
:aliases {
:uberjar {:deps {uberdeps {:mvn/version "0.1.8"}}
:main-opts ["-m" "uberdeps.uberjar"]}
}Both of these method will merge uberdeps with whatever is in your deps.edn, so at runtime it is an exact equivalent of “Quick and dirty” setup.
If your project has a -main function, you can run it from within the generated uberjar:
java -cp target/<your-project>.jar clojure.main -m <your-namespace-with-main>Given your project has a -main function like below:
(ns app.core
(:gen-class))
(defn -main [& args]
(println "Hello world"))You can create an executable jar with these steps:
# 1. Ensure dir exists
mkdir classes
# 2. Add `classes` dir to the classpath in `deps.edn`:
{:paths [... "classes"]}
# 3. Aot compile
clj -e "(compile 'app.core)"
# 4. Uberjar with --main-class option
clojure -A:uberjar --main-class app.coreThis will create a manifest in the jar under META-INF/MANIFEST.MF, which then allows you to run your jar directly:
java -jar target/<your-project>.jarFor more information on AOT compiling in tools.deps, have a look at the official guide.
Supported command-line options are:
--deps-file <file> Which deps.edn file to use to build classpath. Defaults to 'deps.edn'
--aliases <alias:alias:...> Colon-separated list of alias names to include from deps file. Defaults to nothing
--target <file> Jar file to ouput to. Defaults to 'target/<directory-name>.jar'
--main-class <ns> Main class, if it exists (e.g. app.core)
--multi-release (true|false) Add a multi-release flag to the manifest. Defaults to false.
--level (debug|info|warn|error) Verbose level. Defaults to debug
(require '[uberdeps.api :as uberdeps])
(let [exclusions (into uberdeps/exclusions [#"\.DS_Store" #".*\.cljs" #"cljsjs/.*"])
deps (clojure.edn/read-string (slurp "deps.edn"))]
(binding [uberdeps/exclusions exclusions
uberdeps/level :warn]
(uberdeps/package deps "target/uber.jar" {:aliases #{:uberjar}})))- Resolve
:pathsrelative todeps.ednlocation
- --main-class / :main-class option added #13 #18 thx @gnarroway
- tools.deps updated to 0.8.599
- Replace
\with/when building on Windows #16 #17 thx @gnarroway
- Ignore non-jar dependencies #14 #15
- Package paths before jars so that local files take priority over deps #9
- Fixed NPE when target is located in current dir #7
- Make target dirs if don’t exist #4
- Normalize dependencies without namespaces #3
- Initial version
Copyright © 2019 Nikita Prokopov
Licensed under MIT (see LICENSE).