Compiling Clojure
We saw previously how Clojure source code is located on the classpath, read and evaluated. It also supports defining classes which can be compiled
into .class
files with gen-class. The following example defines
two classes with gen-class
:
src/greet.clj
(ns greet)
(gen-class
:name "greet.Greeter"
:prefix "greeter-"
:state "message"
:init "init"
:constructors {[String] []}
:methods [[greet [] void]])
(defn- greeter-init [message]
[[] message])
(defn- greeter-greet [this]
(println (str "Hello " (.message this) "!")))
(gen-class
:name "greet.main"
:prefix "-"
:main true)
(defn -main [& args]
(let [g (greet.Greeter. (or (first args) "world"))]
(.greet g)))
This can be compiled with the compile function. As described in the documentation,
the .class
files are output to a directory defined by *compile-path*
which must be on the classpath. The value of *compile-path*
is classes
by default.
Create this directory and compile the greet
namespace with compile
:
mkdir classes
java -cp classes:src:clojure-1.11.1.jar:core.specs.alpha-0.2.62.jar:spec.alpha-0.3.218.jar clojure.main -e "(compile 'greet)"
This defines two classes using gen-class
- greeter.Greet
and greeter.main
. You can see how these are compiled into Java classes
with javap
:
greet.Greeter
public class greet.Greeter {
public final java.lang.Object message;
public static {};
public greet.Greeter(java.lang.String);
public boolean equals(java.lang.Object);
public java.lang.String toString();
public int hashCode();
public java.lang.Object clone();
public void greet();
}
greet.main
public class greet.main {
public static {};
public greet.main();
public boolean equals(java.lang.Object);
public java.lang.String toString();
public int hashCode();
public java.lang.Object clone();
public static void main(java.lang.String[]);
}
The generated class methods simply delegate to Clojure functions with the appropriately-prefixed names. For example the greet.Greeter
class defines a function prefix
of greeter-
, so its greet
method delegates to the function greeter-greet
within the implementation namespace (by default the current namespace). Similarly, the main
method of the greet.main
class should be defined by a -main
function.
The main
class can now be invoked as with another other main Java class:
> java -cp classes:clojure-1.11.1.jar:core.specs.alpha-0.2.62.jar:spec.alpha-0.3.218.jar greet.main everyone
Hello everyone!
Namespace compilation
The ns
function used to define namespaces supports an optional :gen-class
option to compile a class for the namespace.
It supports all the options of gen-class
and by default uses the namespace name as the class name, sets :main
to true
, and sets the prefix to β-β. This means
a -main
function is expected to exist which corresponds to the main
method of the generated class.
This allows us to write straightforward Clojure code which is compiled into a Java entrypoint without having to invoke it via
clojure.main
or use gen-class
directly.
src/greet/core.clj
(ns greet.core)
(defn greet [who]
(printf "Hello %s!%n" who)
(flush))
src/greet/main.clj
(ns greet.main
(:require [greet.core :as g])
(:gen-class))
(defn -main [& args]
(g/greet (or (first args) "world")))
This can be compiled and run as before:
> mkdir classes
> java -cp clojure-1.11.1.jar:core.specs.alpha-0.2.62.jar:spec.alpha-0.3.218.jar:classes:src clojure.main -e "(compile 'greet.main)"
> java -cp clojure-1.11.1.jar:core.specs.alpha-0.2.62.jar:spec.alpha-0.3.218.jar:classes:src greet.main everyone
Hello everyone!
Clojure uberjars
Now we can create Java entrypoints for our Clojure applications, we can create βfatβ JARs by including the Clojure JARs along with the application source files and any compiled classes. As before, we need to define a manifest file which specifies the main class
manifest.mf
Main-Class: greet.main
The fat JAR can then be built with
> mkdir classes uber
> unzip -d uber clojure-1.11.1.jar
> unzip -d -o -d uber/ core.specs.alpha-0.2.62.jar
> unzip -d -o -d uber/ spec.alpha-0.3.218.jar
> java -cp clojure-1.11.1.jar:core.specs.alpha-0.2.62.jar:spec.alpha-0.3.218.jar:classes:src clojure.main -e "(compile 'greet.main)"
> cp -r classes/* uber/
> cp -r src/* uber/
> jar --create --file greet-standalone.jar --manifest=manifest.mf -C uber .
This JAR can now be invoked with:
> java -jar greet-standalone.jar everyone
Hello everyone!
Note the caveats for correctly handling path conflicts discussed previously.