- Notifications
You must be signed in to change notification settings - Fork28
Packager for Clojure based on deps.edn (AKA tools.deps). Supporting jar, uberjar and GraalVM's native-image.
License
luchiniatwork/cambada
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Cambada is a packager for Clojure based ondeps.edn
(AKAtools.deps
). It isheavily inspired by Leiningen's jar and uberjar tasks and also supportsGraalVM's new native-image making it a one-stop shop for any packaging neededfor your Clojure project.
Leiningen has laid the foundations of what many of us have come to accept as thestandard for Clojure projects. Clojure'stools.deps
potentially brings newideas to the Clojure workflow. Cambada brings some of the great features ofLeiningen to thetools.deps
workflow.
Cambada's sole focus is packaging. It doesn't have plugins, templates or Clojarsintegration. It packages yourdeps.edn
progject as one - or all - of:
- jar
- uberjar
- GraalVM native image
On top of Phil Hagelberg's (and so many others') great Leiningen, many thanks toDominic Monroe and his work onpack
as well as Taylor Wood and hisclj.native-image
. These projects offered a lot of inspiration (and, in somecases, donor code too).
- Getting Started
- Easy Aliases
- Packaging as a Jar
- Packaging as an Uberjar
- Packaging as a Native Image
- Caveats
- Performance Comparison
- Bugs
- Help!
Cambada is a simple set of main functions that can be called from adeps.edn
alias. The simplest way to have it available in your project is to add an aliaswithextra-deps
to yourdeps.edn
file:
{:aliases {:cambada {:extra-deps {luchiniatwork/cambada {:mvn/version"1.0.5"}}}}}
Cambada has three main entry points,cambada.jar
,cambada.uberjar
andcambada.native-image
. Let's say you simply want to create an uberjar:
$ clj -R:cambada -m cambada.uberjarCleaning targetCreating target/classes Compiling ...Creating target/project-name-1.0.0-SNAPSHOT.jarUpdating pom.xmlCreating target/project-name-1.0.0-SNAPSHOT-standalone.jar Including ...Done!
Your files will be located attarget/
by default.
All entry points have a few extra configuration options you might be interestedin. For instance:
$ clj -R:cambada -m cambada.uberjar --helpPackage up the project files and all dependencies into a jar file.Usage: clj -m cambada.uberjar [options]Options: -m, --main NS_NAME The namespace with the -main function --app-group-id STRING project-name Application Maven group ID --app-artifact-id STRING project-name Application Maven artifact ID --app-version STRING 1.0.0-SNAPSHOT Application version --[no-]copy-source Copysource files by default -a, --aot NS_NAMES all Namespaces to be AOT-compiled or`all` (default) -d, --deps FILE_PATH deps.edn Location of deps.edn file -o, --out PATH target Output directory -h, --help Shows thishelp
Do try--help
forcambada.jar
andcambada.native-image
if you areinterested or refer to the sections below.
One of the powers-in-simplicity oftools.deps
is the ability to define aliasesondeps.edn
. When we used the aliascambada
on the section above, we simplyspecified it as an dependency to be resolved (therefore the-R
when callingclj
).
You can also be a lot more prescriptive in your aliases by making them do morework for you. For instance, the alias below will create a versioned uberjar:
{:aliases {:uberjar {:extra-deps {luchiniatwork/cambada {:mvn/version"1.0.0"}}:main-opts ["-m""cambada.uberjar""--app-version""0.5.3"]}}}
By having an alias like thisuberjar
one in yourdeps.edn
you can simply runit by using$ clj -A:uberjar
making it very familiar to those used with$ lein uberjar
:
$ clj -A:uberjarCleaning targetCreating target/classes Compiling ...Creating target/project-name-0.5.3.jarUpdating pom.xmlCreating target/project-name-0.5.3-standalone.jar Including ...Done!
Let's start with an empty project folder:
$ mkdir -p myproj/src/myproj/$cd myproj
Create adeps.edn
at the root of your project withcambada.jar
as an alias:
{:aliases {:jar {:extra-deps {luchiniatwork/cambada {:mvn/version"1.0.2"}}:main-opts ["-m""cambada.jar""-m""myproj.core"]}}}
Create a simple hello world on a-main
function atsrc/myproj/core.clj
:
(nsmyproj.core (:gen-class))(defn-main [& args] (println"Hello World!"))
Of course, just for safe measure, let's run this hello world viaclj
:
$ clj -m myproj.coreHello World!
Then just call the alias from the project's root:
$ clj -A:jarCleaning targetCreating target/classes Compiling myproj.coreCreating target/myproj-1.0.0-SNAPSHOT.jarUpdating pom.xmlDone!
Once Cambada is done, you'll have a jar package attarget/
. In order to runit, you'll need to add Clojure and spec to your class path. The paths will varyon your system:
$ java -cp target/myproj-1.0.0-SNAPSHOT.jar myproj.coreHello World!
For a standalone jar file see the uberjar option on the next section.
You can specify the following options forcambada.jar
:
-m, --main NS_NAME The namespace with the -main function --app-group-id STRING project-name Application Maven group ID --app-artifact-id STRING project-name Application Maven artifact ID --app-version STRING 1.0.0-SNAPSHOT Application version --[no-]copy-source Copy source files by default -a, --aot NS_NAMES all Namespaces to be AOT-compiled or `all` (default) -d, --deps FILE_PATH deps.edn Location of deps.edn file -o, --out PATH target Output directory -h, --help Shows this help
These options should be quite self-explanatory and the defaults arehopefully sensible enough for most of the basic cases. By defaulteverything gets AOT-compiled and sources are copied to the resulting jar.
For those used to Leiningen, the application's group ID, artifact IDand version are not extracted fromproject.clj
(since it's assumedyou don't have aproject.clj
in adeps.edn
workflow). Therefore,you must specify these expressively as options.
Let's start with an empty project folder:
$ mkdir -p myproj/src/myproj/$cd myproj
Create adeps.edn
at the root of your project withcambada.jar
as an alias:
{:aliases {:uberjar {:extra-deps {luchiniatwork/cambada {:mvn/version"1.0.0"}}:main-opts ["-m""cambada.uberjar""-m""myproj.core"]}}}
Create a simple hello world on a-main
function atsrc/myproj/core.clj
:
(nsmyproj.core (:gen-class))(defn-main [& args] (println"Hello World!"))
Of course, just for safe measure, let's run this hello world viaclj
:
$ clj -m myproj.coreHello World!
Then just call the alias from the project's root:
$ clj -A:uberjarCleaning targetCreating target/classes Compiling myproj.coreCreating target/myproj-1.0.0-SNAPSHOT.jarUpdating pom.xmlCreating target/myproj-1.0.0-SNAPSHOT-standalone.jar Including myproj-1.0.0-SNAPSHOT.jar Including clojure-1.9.0.jar Including spec.alpha-0.1.143.jar Including core.specs.alpha-0.1.24.jarDone!
Once Cambada is done, you'll have two jar packages attarget/
. One for a basicjar and one standalone with all dependencies in it. In order to run it, simplycall it:
$ java -jar target/myproj-1.0.0-SNAPSHOT-standalone.jarHello World!
cambada.uberjar
has exactly the same options and defaults ascambada.jar
(see above for more details).
If any of your transitive dependencies has a Maven Central dependency,cambada
may fail on you (investigations under way). Therefore, it isrecommended that you explicitly add your repos (Central included) toyourdeps.edn
file i.e.:
{:deps {...}:mvn/repos {"central" {:url"https://repo1.maven.org/maven2/"}"clojars" {:url"https://repo.clojars.org/"}}}
By using GraalVM we now have the option of packaging everything AOTcompiled as a native image.
If you want to use this feature, make sure todownload and installGraalVM.
If you are a MacOS user, GraalVM CE is available as a brew cask:
$ brew cask install graalvm/tap/graalvm-ce
GraalVM'snative-image
is a package that needs to be installedmanually with the following command (attention thatgu
is at$GRAALVM_HOME/bin/
if it is not on yourPATH
):
$ gu install native-image
You will need to set yourGRAALVM_HOME
environment variable to pointto where GraalVM is installed. Alternatevely you can callcambada.native-image
with the argument--graalvm-home
pointing to it.
The entry point for native image packaging iscambada.native-image
. Let's assume yourGRAALVM_HOME
variable isset (if you don't, use--graalvm-home
).
Let's start with an empty project folder:
$ mkdir -p myproj/src/myproj/$cd myproj
Create adeps.edn
at the root of your project withcambada.jar
as an alias:
{:aliases {:native-image {:extra-deps {luchiniatwork/cambada {:mvn/version"1.0.0"}}:main-opts ["-m""cambada.native-image""-m""myproj.core"]}}}
Create a simple hello world on a-main
function atsrc/myproj/core.clj
:
(nsmyproj.core (:gen-class))(defn-main [& args] (println"Hello World!"))
Of course, just for safe measure, let's run this hello world viaclj
:
$ clj -m myproj.native-imageHello World!
Then just call the alias from the project's root:
$ clj -A:native-imageCleaning targetCreating target/classes Compiling myproj.coreCreating target/myproj classlist: 2,810.07 ms (cap): 1,469.31 ms setup: 2,561.28 ms (typeflow): 5,802.45 ms (objects): 2,644.17 ms (features): 40.54 ms analysis: 8,609.18 ms universe: 314.28 ms (parse): 1,834.84 ms (inline): 2,338.45 ms (compile): 16,824.24 ms compile: 21,435.77 ms image: 1,862.44 ms write: 1,276.55 ms [total]: 38,942.48 msDone!
Once Cambada is done, you'll have an executable package attarget/
:
$ ./target/myprojHello World!
Extra options can be sent to GraalVM's packager by using Cambada's--graalvm-opt
option i.e., to includeFILE
as a resource, simplyuse--graalvm-opt H:IncludeResources=FILE
.
A quick comparison of themyproj
hello world as described previously and ranacross different packaging options:
Straight withclj
:
$time clj -m myproj.coreHello World!1.160 secs
As a standalone uberjar:
$time java -jar target/myproj-1.0.0-SNAPSHOT-standalone.jarHello World!0.850 secs
As a native image:
$time ./target/myprojHello World!0.054 secs
Comparing withclj
as a baseline:
Method | Speed in secs | Speed relative toclj |
---|---|---|
clj | 1.160 secs | 1x |
uberjar | 0.850 secs | 1.36x |
native-image | 0.054 secs | 21.48x |
If you find a bug, submit aGithub issue.
This project is looking for team members who can help this project succeed!If you are interested in becoming a team member please open an issue.
Copyright © 2018 Tiago Luchini
Distributed under the MIT License. See LICENSE
About
Packager for Clojure based on deps.edn (AKA tools.deps). Supporting jar, uberjar and GraalVM's native-image.