Creating Executable Jars For Your Clojure Application
It is possible to create stand alone executable Jar files for your Clojure programs. In this post I walk you through the issues you need to keep in mind and the steps you need to take to create the jar. You can download the example code this post walks through in its entirety from my GitHub account (under examples/exec-jar).
I used ant to build the jar, other Java development tools can also do the task.
Jar Files
Jar files are Zip files with a few conventions for what goes where and of their contents. Jar files support a Manifest File file which tells Java what the archive contains. The manifest file has a simple format of key/value pairs, very similar to an HTTP header. This is manifest for the Clojure Jar:
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.7.1 Created-By: 11.2-b01 (Sun Microsystems Inc.) Main-Class: clojure.main Class-Path: .
The important bit there is ‘Main-Class’. The Main-Class specifies the Java class that will be executed by default when run as java -jar clojure.jar
. In clojure.jar
the main class is clojure.main
.
Clojure Application
The example application performs a Google search, printing each link and its text:
(ns com.github.kyleburton.app (:gen-class) (:use [com.github.kyleburton.sandbox.web :as kweb] [com.github.kyleburton.sandbox.landmark-parser :as lp] [clojure.contrib.str-utils :as str-utils])) (defn fetch-page [terms] (let [page (kweb/get->string (format "http://www.google.com/search?q=%s" (str-utils/str-join "+" terms)))] (doseq [link (filter #(.contains % "class=l") (lp/html->anchors page))] (let [href (lp/anchor->href link) text (kweb/strip-html (lp/anchor->body link))] (println text) (println href) (println "\n"))))) (defn show-help [] (pritnln "app term [term2 [term3 ...]] Performs a Google Search for the given terms ")) (defn -main [& terms] (cond (empty? terms) (show-help) true (fetch-page terms)))
Compilation
Java will only run Java byte code so we need to compile the application. I’ve set up the build.xml
to do so:
<target name="compile" description="Run the Clojure Compiler"> <property name="cpath" refid="classpath" /> <echo message="cpath=${cpath}" /> <mkdir dir="${classes.dir}"/> <java classname="clojure.lang.Compile" fork="true"> <sysproperty key="clojure.compile.path" value="${classes.dir}" /> <classpath refid="classpath" /> <arg value="com.github.kyleburton.app" /> </java> </target>
The jar-with-manifest
then uses the compiled classes, generates a manifest file and uses Ant’s jar
task with a series of zipfileset
s to combine the clojure, clojure-contrib and other dependency’s jars into a single, executable, jar:
<target name="jar-with-manifest" depends="compile" description="Build the JAR"> <manifest file="${target.dir}/MANIFEST.MF"> <attribute name="Built-By" value="${user.name}" /> <attribute name="Main-Class" value="com.github.kyleburton.app" /> </manifest> <jar jarfile="${target.dir}/${jar.file.name}" manifest="${target.dir}/MANIFEST.MF"> <fileset dir="${classes.dir}" includes="**/*.class"/> <zipfileset src="${clojure.jar}" /> <zipfileset src="${clojure-contrib.jar}" /> <zipfileset src="${krb-utils.jar.repo}/commons-httpclient-3.1.jar" /> <zipfileset src="${krb-utils.jar.repo}/commons-codec-1.3.jar" /> <zipfileset src="${krb-utils.jar.repo}//commons-logging-1.1.1.jar" /> <zipfileset src="${krb-utils.jar.repo}//commons-logging-1.1.1-sources.jar" /> <zipfileset src="${krb-utils.jar.repo}//commons-logging-adapters-1.1.1.jar" /> <zipfileset src="${krb-utils.jar.repo}//commons-logging-api-1.1.1.jar" /> </jar> </target>
Running The Application
Our application can now be run all by itself with java -jar ...
:
kyleburton@indigo64 exec-jar[master*]$ java -jar target/exec-jar-0.1.jar ant zipfileset ZipFileSet Type http://ant.apache.org/manual/CoreTypes/zipfileset.html Zip Task http://ant.apache.org/manual/CoreTasks/zip.html Ant Best Practices: Use ZipFileSet | The Build Doctor http://www.build-doctor.com/2008/07/13/ant-best-practices-use-zipfileset Top 15 Ant Best Practices - O'Reilly Media http://www.onjava.com/pub/a/onjava/2003/12/17/ant_bestpractices.html 'cvs commit: ant/src/main/org/apache/tools/ant/types ZipFileSet ... http://marc.info/?l=ant-dev&m=112197754500691&w=2 [picocontainer-scm] [CVS java] improved maven scri: msg#00056 java ... http://osdir.com/ml/java.picocontainer.cvs/2004-07/msg00056.html ZipFileSet Apache Ant 1.6.5 API Documentation and Javadoc http://www.jdocs.com/link/org/apache/tools/ant/types/zipfileset.html svn commit: r719578 - /ant/core/trunk/src/tests/antunit/types ... http://mail-archives.apache.org/mod_mbox/ant-notifications/200811.mbox/%3C20081121133918.572D2238889E@eris.apache.org%3E ZipFileSet (Apache Ant API) http://lia.deis.unibo.it/Courses/TecnologieWeb0607/materiale/laboratorio/ant/api/org/apache/tools/ant/types/ZipFileSet.html [news.eclipse.platform] Re: Problem with ANT zipfileset ... Re ... http://dev.eclipse.org/newslists/news.eclipse.platform/msg07778.html kyleburton@indigo64 exec-jar[master]$
Conclusion
Bundling your application into a single Jar can simplify the deployment, and distribution of your application. What tends to be less desirable about this technique though is that you have to track your dependencies carefully (you risk ClassNotFoundException
s) and the jars you create are often quite large. You can not add additional jars or paths to the classpath when running java -jar
, which means that you won’t be able to re-use the libraries across other applications.
This technique allows you to create a single file that your users can download to run your clojure based applications.