{"id":13568588,"url":"https://github.com/tonsky/uberdeps","last_synced_at":"2025-12-29T23:52:14.932Z","repository":{"id":46153547,"uuid":"184122318","full_name":"tonsky/uberdeps","owner":"tonsky","description":"Uberjar builder for deps.edn","archived":false,"fork":false,"pushed_at":"2024-08-06T13:24:57.000Z","size":71,"stargazers_count":312,"open_issues_count":0,"forks_count":26,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-06T12:23:14.879Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonsky.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["tonsky"],"patreon":"tonsky"}},"created_at":"2019-04-29T18:25:03.000Z","updated_at":"2025-01-03T19:38:11.000Z","dependencies_parsed_at":"2024-01-14T03:47:10.138Z","dependency_job_id":"56d2fbc7-7c49-4988-ad84-eb77c90d6f9f","html_url":"https://github.com/tonsky/uberdeps","commit_stats":{"total_commits":53,"total_committers":20,"mean_commits":2.65,"dds":0.5094339622641509,"last_synced_commit":"dcf6705718addec59f9b82977d865592259f248b"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fuberdeps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fuberdeps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fuberdeps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonsky%2Fuberdeps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonsky","download_url":"https://codeload.github.com/tonsky/uberdeps/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247123072,"owners_count":20887259,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T14:00:28.702Z","updated_at":"2025-12-29T23:52:14.900Z","avatar_url":"https://github.com/tonsky.png","language":"Clojure","funding_links":["https://github.com/sponsors/tonsky","https://patreon.com/tonsky"],"categories":["Clojure"],"sub_categories":[],"readme":"# Uberjar builder for deps.edn projects\n\nTakes deps.edn and packs an uberjar out of it.\n\n- Fast: Does not unpack intermediate jars on disk.\n- Explicit: Prints dependency tree. Realize how much crap you’re packing.\n- Standalone: does not depend on current classpath, does not need live Clojure environment.\n- Embeddable and configurable: fine-tune your build by combining config options and calling specific steps from your code.\n\n## Rationale\n\nIt 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!\n\nOther 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.\n\nUberdeps is different from other archivers in that it strictly separates the two. It works roughly as follows:\n\n1. JVM with a single `uberdeps` dependency is started (NOT on the app’s classpath).\n2. It reads your app’s `deps.edn` and 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, using `tools.deps.alpha` as a runtime library. \n3. Final archive is created, JVM exits.\n\n## Project setup\n\nIdeally you would setup a separate `deps.edn` for packaging:\n\n```\nproject\n├ deps.edn\n├ src\n├ ...\n└ uberdeps\n  ├ deps.edn\n  └ package.sh\n```\n\nwith following content:\n\nuberdeps/deps.edn:\n\n```clojure\n{:deps {uberdeps/uberdeps {:mvn/version \"1.4.0\"}}}\n```\n\nuberdeps/package.sh:\n\n```sh\n#!/bin/bash -e\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )\"\nclojure -M -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar\n```\n\nTo be clear:\n\n- `/uberdeps/deps.edn` is used only to start uberdeps. Files, paths, profiles from it won’t affect resulting archive in any way.\n- `/deps.edn` (referred as `--deps-file ../deps.edn` from `/uberdeps/package.sh`) is what’s analyzed during packaging. Its content determines what goes into the final archive. \n\nIn 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.\n\n## Project setup — extra aliases\n\nYou 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:\n\ndeps.edn:\n\n```clojure\n{ :paths [\"src\"]\n  ...\n  :aliases {\n    :package {\n      :extra-paths [\"resources\" \"target/cljs/\"]\n    }\n    :nrepl {\n      :extra-deps {nrepl/nrepl {:mvn/version \"0.6.0\"}}\n    }\n  }\n}\n```\n\nuberdeps/package.sh:\n\n```sh\n#!/bin/bash -e\ncd \"$( dirname \"${BASH_SOURCE[0]}\" )\"\nclojure -M -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar --aliases package:nrepl:...\n```\n\n## Project setup — quick and dirty\n\nSometimes 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.\n\n```\nproject\n├ deps.edn\n├ src\n└ ...\n```\n\ndeps.edn:\n\n```clojure\n{ :paths [\"src\"]\n  ...\n  :aliases {\n    :uberdeps {\n      :replace-deps {uberdeps/uberdeps {:mvn/version \"1.4.0\"}}\n      :replace-paths []\n      :main-opts [\"-m\" \"uberdeps.uberjar\"]\n    }\n  }\n}\n```\n\nand invoke it like this (requires clj \u003e= 1.10.1.672):\n\n```sh\nclj -M:uberdeps\n```\n\nIn that case execution will happen like this:\n\n1. JVM will start with `:uberdeps` alias which will REPLACE all your normal dependencies on your app’s classpath with `uberdeps` dependency.\n2. `uberdeps.uberjar` namespace will be invoked as main namespace.\n3. Uberdeps process will read `deps.edn` AGAIN, 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 from `deps.edn` itself. 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.\n4. Final archive is created, JVM exits.\n\n## No-config setup\n\nYou can invoke Uberdeps from command line at any time without any prior setup.\n\nAdd to your bash aliases:\n\n```sh\nclj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version \"1.4.0\"}} :replace-paths []}}}' -M:uberjar -m uberdeps.uberjar\n```\n\nOr add to your `~/.clojure/deps.edn`:\n\n```clojure\n:aliases {\n  :uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version \"1.4.0\"}}\n            :replace-paths []\n            :main-opts [\"-m\" \"uberdeps.uberjar\"]}\n}\n```\n\nBoth of these method will replace whatever is in your `deps.edn` with `uberdeps`, so at runtime it is an exact equivalent of “Quick and dirty” setup.\n\n## Using the generated uberjar\n\nIf your project has a `-main` function, you can run it from within the generated uberjar:\n\n```sh\njava -cp target/\u003cyour-project\u003e.jar clojure.main -m \u003cyour-namespace-with-main\u003e\n```\n\n## Creating an executable jar\n\nGiven your project has a `-main` function like below:\n\n```clojure\n(ns app.core\n  (:gen-class))\n\n(defn -main [\u0026 args]\n  (println \"Hello world\"))\n```\n\nYou can create an executable jar with these steps:\n\n```sh\n# 1. Ensure dir exists\nmkdir classes\n\n# 2. Add `classes` dir to the classpath in `deps.edn`:\n{:paths [... \"classes\"]}\n\nMake sure you have Clojure as a dependency—uberdeps won’t automatically add it for you:\n\n{:deps {org.clojure/clojure {:mvn/version \"1.10.0\"}, ...}}\n\n# 3. Aot compile\nclj -M -e \"(compile 'app.core)\"\n\n# 4. Uberjar with --main-class option\nclojure -M:uberjar --main-class app.core\n```\n\nThis will create a manifest in the jar under META-INF/MANIFEST.MF, which then allows you to run your jar directly:\n\n```sh\njava -jar target/\u003cyour-project\u003e.jar\n```\n\nFor more information on AOT compiling in tools.deps, have a look [at the official guide](https://clojure.org/guides/deps_and_cli#aot_compilation).\n\n## Command-line options\n\nSupported command-line options are:\n\n```\n--deps-file \u003cfile\u003e                Which deps.edn file to use to build classpath. Defaults to 'deps.edn'\n--aliases \u003calias:alias:...\u003e       Colon-separated list of alias names to include from deps file. Defaults to nothing\n--target \u003cfile\u003e                   Jar file to ouput to. Defaults to 'target/\u003cdirectory-name\u003e.jar'\n--exclude \u003cregexp\u003e                Exclude files that match one or more of the Regular Expression given. Can be used multiple times\n--main-class \u003cns\u003e                 Main class, if it exists (e.g. app.core)\n--multi-release                   Add Multi-Release: true to the manifest. Off by default.\n--level (debug|info|warn|error)   Verbose level. Defaults to debug\n```\n\n## Programmatic API\n\n```clojure\n(require '[uberdeps.api :as uberdeps])\n\n(let [exclusions [#\"\\.DS_Store\" #\".*\\.cljs\" #\"cljsjs/.*\"]\n      deps       (clojure.edn/read-string (slurp \"deps.edn\"))]\n  (binding [uberdeps/level      :warn]\n    (uberdeps/package deps \"target/uber.jar\" {:aliases #{:uberjar}\n                                              :exclusions exclusions})))\n```\n\n## Merging\n\nSometimes assembling uberjar requires combining multiple files with the same name (coming from different libraries, for example) into a single file. Uberdeps does that automatically for these:\n\n```clojure\nMETA-INF/plexus/components.xml uberdeps.api/components-merger\n#\"META-INF/services/.*\"        uberdeps.api/services-merger   \n#\"data_readers.clj[cs]?\"       uberdeps.api/clojure-maps-merger\n```\n\nYou can provide your own merger by passing a merge function to `uberdeps.api/package`:\n\n```clojure\n(def readme-merger\n  {:collect\n   (fn [acc content]\n     (conj (or acc []) (str/upper-case content)))\n   :combine\n   (fn [acc]\n     (str/join \"\\n\" acc))})\n```\n\nMerger is a map with two keys: `:collect` and `:combine`. Collect accumulates values as they come. It takes an accumulator and a next file content, and must return the new accumulator:\n\n```\n((:collect readme-merger) acc content) -\u003e acc'\n```\n\nFile content is always a string. Accumulator can be any data structure you find useful for storing merged files. In `readme-merger` accumulator is a string, in `clojure-maps-merger` it is a clojure map, etc. On a first  call to your merger accumulator will be `nil`.\n\nCombine is called when all files with the same name have been processed and it is time to write the resulting single merged file to the jar. It will be called with your accumulator and must return a string with file content:\n\n```\n((:combine readme-merger) acc) -\u003e content'\n```\n\nCustom mergers can be passed to `uberdeps.api/package` in `:mergers` option along with file path regexp:\n\n```\n(uberdeps.api/package\n  deps\n  \"target/project.jar\"\n  {:mergers {#\"(?i)README(\\.md|\\.txt)?\" readme-merger}})\n```\n\nPassing custom mergers does not remove the default ones, but you can override them.\n\n## License\n\nCopyright © 2019 Nikita Prokopov.\n\nLicensed under MIT (see [LICENSE](LICENSE)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonsky%2Fuberdeps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonsky%2Fuberdeps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonsky%2Fuberdeps/lists"}