tools.build
With tools.deps
we can now build libraries and publish them to our favourite Git hosting provider and have others consume
them as dependencies without the need to package and publish them as JARs. As we’ve seen previously, we can do this using
the tools provided by Clojure and the JVM. However, these do not integrate easily with tools.deps
and there are various
common tasks we’d like to automate. Clojure provides the tools.build library for
defining build tasks.
After showing your CTO the Clojure greet application, she’s very impressed but informs you the company has recently invested heavily in spare {
and }
keys,
so all library development must be done in Java. The application itself can still be written in Clojure.
You restructure your project to have separate source directories for Java and Clojure:
greeter/
deps.edn
build.clj
java/
src/
clojure/
src/
Then define the Java library:
java/src/com/picosoft/greet/Greeter.java
package com.picosoft.greet;
public class Greeter {
public void greet(String who, boolean excite) {
String message = excite ?
String.format("Hello %s!", who) :
String.format("Hello %s", who);
System.out.println(message);
}
}
The main Clojure namespace imports this class for the greet implementation
clojure/src/greet/main.clj
(ns greet.main
(:gen-class)
(:require [clojure.tools.cli :as cli])
(:import [com.picosoft.greet Greeter]))
(def cli-options [["-x" "--excite" "Show some excitement"
:default false]])
(defn- parse-args [args]
(let [{:keys [options arguments errors]} (cli/parse-opts args cli-options)]
(if (seq errors)
(binding [*out* *err*]
(doseq [err errors]
(println err))
(System/exit 1))
{:who (or (first arguments) "world")
:excite (:excite options)})))
(defn -main [& args]
(let [{:keys [who excite]} (parse-args args)
g (Greeter.)]
(.greet g who excite)))
The Clojure source location needs to be specified in the deps.edn
file along with the tools.cli
dependency. There is also
a build
alias used by our build definition.
deps.edn
{:paths ["clojure/src"]
:deps {org.clojure/tools.cli {:mvn/version "1.0.219"}}
:aliases {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.9.5" :git/sha "24f2894"}}
:ns-default build}}
}
Finally our tools.build
script defines an uber
task which compiles the Java and Clojure code and packages it into an uberjar.
build.clj
(ns build
(:require [clojure.tools.build.api :as b]))
(defn uber [_]
(let [basis (b/create-basis)
output-dir "target/uber"]
(b/javac {:basis basis
:src-dirs ["java/src"]
:class-dir output-dir})
(b/compile-clj {:basis basis
:class-dir output-dir
:src-dirs ["clojure/src"]})
(b/uber {:uber-file "target/greeter-standalone.jar"
:class-dir output-dir
:basis basis
:main 'greet.main})))
(defn clean [_]
(b/delete {:path "target"}))
This task can be invoked with
> clojure -T:build uber
this builds the uberjar at target/greeter-standalone.jar
which can be run with
> java -jar target/greeter-standalone.jar --excite everyone
Hello everyone!