• Stars
    star
    299
  • Rank 134,146 (Top 3 %)
  • Language
    Clojure
  • License
    MIT License
  • Created about 5 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Uberjar builder for deps.edn

Uberjar builder for deps.edn projects

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.

Rationale

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:

  1. JVM with a single uberdeps dependency is started (NOT on the app’s classpath).
  2. 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.
  3. Final archive is created, JVM exits.

Project setup

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/uberdeps {:mvn/version "1.3.0"}}}

uberdeps/package.sh:

#!/bin/bash -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
clojure -M -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar

To be clear:

  • /uberdeps/deps.edn is 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.edn from /uberdeps/package.sh) 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.

Project setup — extra aliases

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/nrepl {:mvn/version "0.6.0"}}
    }
  }
}

uberdeps/package.sh:

#!/bin/bash -e
cd "$( dirname "${BASH_SOURCE[0]}" )"
clojure -M -m uberdeps.uberjar --deps-file ../deps.edn --target ../target/project.jar --aliases package:nrepl:...

Project setup — quick and dirty

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 {
      :replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}}
      :replace-paths []
      :main-opts ["-m" "uberdeps.uberjar"]
    }
  }
}

and invoke it like this (requires clj >= 1.10.1.672):

clj -M:uberdeps

In that case execution will happen like this:

  1. JVM will start with :uberdeps alias which will REPLACE all your normal dependencies on your app’s classpath with uberdeps dependency.
  2. uberdeps.uberjar namespace will be invoked as main namespace.
  3. 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.
  4. Final archive is created, JVM exits.

No-config setup

You can invoke Uberdeps from command line at any time without any prior setup.

Add to your bash aliases:

clj -Sdeps '{:aliases {:uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}} :replace-paths []}}}' -M:uberjar -m uberdeps.uberjar

Or add to your ~/.clojure/deps.edn:

:aliases {
  :uberjar {:replace-deps {uberdeps/uberdeps {:mvn/version "1.3.0"}}
            :replace-paths []
            :main-opts ["-m" "uberdeps.uberjar"]}
}

Both 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.

Using the generated uberjar

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>

Creating an executable jar

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"]}

Make sure you have Clojure as a dependency—uberdeps won’t automatically add it for you:

{:deps {org.clojure/clojure {:mvn/version "1.10.0"}, ...}}

# 3. Aot compile
clj -M -e "(compile 'app.core)"

# 4. Uberjar with --main-class option
clojure -M:uberjar --main-class app.core

This 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>.jar

For more information on AOT compiling in tools.deps, have a look at the official guide.

Command-line options

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'
--exclude <regexp>                Exclude files that match one or more of the Regular Expression given. Can be used multiple times
--main-class <ns>                 Main class, if it exists (e.g. app.core)
--multi-release                   Add Multi-Release: true to the manifest. Off by default.
--level (debug|info|warn|error)   Verbose level. Defaults to debug

Programmatic API

(require '[uberdeps.api :as uberdeps])

(let [exclusions [#"\.DS_Store" #".*\.cljs" #"cljsjs/.*"]
      deps       (clojure.edn/read-string (slurp "deps.edn"))]
  (binding [uberdeps/level      :warn]
    (uberdeps/package deps "target/uber.jar" {:aliases #{:uberjar}
                                              :exclusions exclusions})))

Merging

Sometimes 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:

META-INF/plexus/components.xml uberdeps.api/components-merger
#"META-INF/services/.*"        uberdeps.api/services-merger   
#"data_readers.clj[cs]?"       uberdeps.api/clojure-maps-merger

You can provide your own merger by passing a merge function to uberdeps.api/package:

(def readme-merger
  {:collect
   (fn [acc content]
     (conj (or acc []) (str/upper-case content)))
   :combine
   (fn [acc]
     (str/join "\n" acc))})

Merger 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:

((:collect readme-merger) acc content) -> acc'

File 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.

Combine 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:

((:combine readme-merger) acc) -> content'

Custom mergers can be passed to uberdeps.api/package in :mergers option along with file path regexp:

(uberdeps.api/package
  deps
  "target/project.jar"
  {:mergers {#"(?i)README(\.md|\.txt)?" readme-merger}})

Passing custom mergers does not remove the default ones, but you can override them.

License

Copyright © 2019 Nikita Prokopov.

Licensed under MIT (see LICENSE).

More Repositories

1

FiraCode

Free monospaced font with programming ligatures
Clojure
74,361
star
2

AnyBar

OS X menubar status indicator
Objective-C
5,856
star
3

datascript

Immutable database and Datalog query engine for Clojure, ClojureScript and JS
Clojure
5,353
star
4

rum

Simple, decomplected, isomorphic HTML UI library for Clojure and ClojureScript
HTML
1,767
star
5

vscode-theme-alabaster

A light theme for Visual Studio Code
Clojure
386
star
6

Clojure-Sublimed

Clojure support for Sublime Text 4
Clojure
349
star
7

tongue

Do-it-yourself i18n library for Clojure/Script
Clojure
305
star
8

Universal-Layout

Пакет из английской и русской раскладок, спроектированных для удобного совместного использования
Shell
291
star
9

font-writer

Monospaced font for long-form writing
242
star
10

sublime-scheme-alabaster

Minimalist color scheme for Sublime Text 3
234
star
11

datascript-chat

Sample SPA using DataScript and core.async
Clojure
160
star
12

grumpy

Minimalistic blog engine
Clojure
141
star
13

compact-uuids

Compact 26-char URL-safe representation of UUIDs
Clojure
126
star
14

net.async

Network commucations with clojure.core.async interface
Clojure
123
star
15

sublime-scheme-writer

A color scheme for focused long-form writing
119
star
16

clojure-future-spec

A backport of clojure.spec for Clojure 1.8
Clojure
115
star
17

intellij-alabaster

Alabaster color scheme for IntelliJ IDEA
102
star
18

datascript-transit

Transit handlers for DataScript database and datoms
Clojure
100
star
19

sublime-profiles

Profile Switcher for Sublime Text
Python
81
star
20

datascript-todo

DataScript ToDo Sample Application
Clojure
78
star
21

persistent-sorted-set

Fast B-tree based persistent sorted set for Clojure/Script
Clojure
78
star
22

tonsky.github.io

HTML
65
star
23

clojure-warrior

Visual Studio Code extension for Clojure development
TypeScript
57
star
24

cljs-drag-n-drop

Sane wrapper around Drag-n-Drop DOM API
Clojure
55
star
25

vec

React.js + Immutable.js vector editor
JavaScript
51
star
26

clojure.unicode

Unicode symbols for Clojure
Clojure
48
star
27

clj-simple-router

Simple order-independent Ring router
Clojure
48
star
28

41-socks

Simple match game in cljs+om+react
Clojure
37
star
29

remote-require

Require any Clojure snippet from anywhere in the Internet
Clojure
33
star
30

Sublime-Executor

Run any executable from your working dir in Sublime Text
Python
32
star
31

cljs-skeleton

Skeleton CLJS client/server app with WS, Transit, Rum
Clojure
30
star
32

Heroes

A turn-based tactical game in ClojureScript, DataScript and Rum
Clojure
30
star
33

icfpc2019-rust

Re-implementaion of https://github.com/tonsky/icfpc2019 in Rust to compare performance
Rust
28
star
34

alabaster-lighttable-skin

Light skin & theme for LightTable
CSS
27
star
35

clj-reload

Clojure
27
star
36

openshift-clojure

Clojure/lein openshift cartridge template
Shell
26
star
37

datascript-storage-sql

SQL Storage implementation for DataScript
Clojure
23
star
38

sublime-scheme-commander

Retro color scheme for Sublime Text
23
star
39

sublime-clojure-repl

Basic Clojure REPL for Sublime Text
Python
22
star
40

Levinson-Layout

Keymap & EN/RU layouts for Levinson 40% split ortholinear keyboard
C
21
star
41

boot-anybar

A boot task reporting build status to AnyBar
Clojure
18
star
42

extend-clj

Easily extend clojure.core built-in protocols
Clojure
17
star
43

down-the-rabbit-hole

Entry to Ludum Dare 48
Clojure
17
star
44

bloknote

Fast online notepad
Clojure
16
star
45

sublime-color-schemes

Fun and simple color schemes for Sublime Text
Rust
16
star
46

katybot

Campfire bot written in Clojure
Clojure
15
star
47

toml-clj

Fast TOML parser for Clojure
Java
14
star
48

java-graphics-benchmark

Java Graphics benchmark
Java
13
star
49

Helix-Layout

C
13
star
50

sane-math

Clojure/Script library for infix (normal) math expressions
Clojure
12
star
51

datascript-menu

JavaScript
11
star
52

DarkModeToggle

Statusbar app to quickly toggle between light and dark modes
Swift
11
star
53

icfpc2021

Clojure
11
star
54

humble-ants

Clojure
10
star
55

advent-of-code

https://adventofcode.com/
Clojure
9
star
56

icfpc2019

Clojure
7
star
57

tonsky.me

Clojure
7
star
58

tgadmin

Clojure
7
star
59

jwm

Objective-C++
6
star
60

homm

Clojure
5
star
61

GMTKJam2022

GDScript
5
star
62

advent2018

Solutions to https://adventofcode.com/2018 in Clojure
Clojure
5
star
63

spectre

Fantom
3
star
64

imdbparse

Parser for IMDb text database
Clojure
3
star
65

icfpc2022

Clojure
3
star
66

clojure-bits

Clojure
3
star
67

tonsky

2
star
68

2017-10-Reactive

JavaScript
2
star
69

2017-05-RigaDevDays

JavaScript
2
star
70

clojure-bits-server

Clojure
2
star
71

lein-figwheel-immutant

[tonsky/figwheel-sidecar-immutant "0.5.9"]
Clojure
2
star
72

2018-05-UWDC

http://tonsky.me/2018-05-UWDC/slides/
JavaScript
2
star
73

codingame-fall-2022

Coding Games Fall Challenge 2022
Clojure
2
star
74

datascript-perf

Datasets for DataScript perf testing
Clojure
2
star
75

grumpy_video

1
star
76

datascript_compiler_race

Clojure
1
star
77

roam-calculator

Shell
1
star
78

icfpc2023

Clojure
1
star
79

ldjam53

GDScript
1
star
80

glutin_resize_issue

Rust
1
star