Asymmetrical View

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 zipfilesets 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&#39;Reilly Media
http://www.onjava.com/pub/a/onjava/2003/12/17/ant_bestpractices.html

&#39;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 ClassNotFoundExceptions) 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.

Kyle Burton, 28th June 2009 – Wayne PA

Links

Photo Credits
Tags: programming,clojure