homeresumeabout
Compiling Clojure applications using Ant
09.03.24
There's an updated article [Compiling-Clojure-Applications-and-Libraries-Round-2.html] that works with the 1.0 releases of the clojure.jar file and the clojure-contrib.jar file.
If you still want to use this process, it's been pointed out in a discussion online that I wrote this before some breaking changes were made in the clojure.jar file. If you are using the latest Clojure JAR files and don't want to use the more recent process that I explain in the post mentioned above, then you'll want to remove the includes attribute from the zipfileset tags.
I finally took the time the other day to sit down and figure out how to compile Clojure applications. Up until then I had been using a quick-n-dirty Ant build file to throw a single Clojure file at clojure.lang.Script. Now that the single non-namespaced approach is wearing thin I realized that I needed a better approach to building/packaging/running my app.
This Ant build script will take an application, compile the Java class files, zip everything up into a JAR, and put a Manifest file in the JAR that allows the JAR to be run directly (java -jar app.jar). You'll need to have a good understanding of Clojure namespaces (the ns macro). You'll also need to have the file referenced by the Ant property 'app' include all other source files that your application depends on.
The Clojure and Clojure Contrib class files are pulled out of their jars and bundled with the resulting app JAR. This means that the resulting file size will be larger than it really needs to be, but you will have all of Clojure and Contrib available to your app and the JAR should be standalone.
The verify* targets aren't really needed, I added them to help first-timers get the environment for their project setup. The bulk of the work is done in the compile goal, with the java and jar tasks being the main subparts. The Main-Class Manifest property is really the only one that you need to run the JAR standalone.
Here's the build.xml file:
<project name="default" default="compile">

	<property file="build.properties" />

	<target name="verifyRunProperties">
		<fail unless="app">
			You need to have a variable 'app' in your build.properties file set to a Clojure namespace (e.g.: com.company.app.main).
			For further explanation, see the simplest :gen-class example on the compilation page of the Clojure website.
		</fail>
		<fail unless="app_jar">You need to have a variable 'app_jar' in your build.properties file set to the name of the resulting jar file.</fail>
	</target>

	<target name="verifyCompileProperties" depends="verifyRunProperties">
		<fail unless="clojure_jar">You need to have a variable 'clojure_jar' in your build.properties file set to the full path to the clojure.jar file.</fail>
		<fail unless="contrib_jar">You need to have a variable 'contrib_jar' in your build.properties file set to the full path to the clojure-contrib.jar file.</fail>
		<fail unless="app_file">You need to have a variable 'app_dir' in your build.properties file set to the relative path to your main file (e.g.: com/company/app/main.clj).</fail>
		<fail unless="vendor">You need to have a variable 'vendor' in your build.properties file set to the name of the application's Author.</fail>
		<fail unless="title">You need to have a variable 'title' in your build.properties file set to the name of the application.</fail>
		<fail unless="version">You need to have a variable 'version' in your build.properties file set to the version of the application.</fail>
		<fail message="You must have your application's main file at the location specified by the variable 'app_file' (${app_file}).">
			<condition>
				<not>
					<available file="${app_file}" />
				</not>
			</condition>
		</fail>
	</target>

	<target name="compile" depends="verifyCompileProperties">
		<echo message="Compiling ${app}" />
		<mkdir dir="classes" />
		<java classname="clojure.lang.Compile" fork="true" failonerror="true">
			<classpath>
				<pathelement location="." />
				<pathelement location="./classes" />
				<pathelement location="${clojure_jar}" />
				<pathelement location="${contrib_jar}" />
			</classpath>
			<sysproperty key="clojure.compile.path" value="./classes" />
			<arg value="${app}" />
		</java>
		<jar destfile="${app_jar}" basedir="classes" index="true">
			<zipfileset src="${clojure_jar}" includes="**/*.class" />
			<zipfileset src="${contrib_jar}" includes="**/*.class" />
			<manifest>
				<attribute name="Implementation-Vendor" value="${vendor}" />
				<attribute name="Implementation-Title" value="${title}" />
				<attribute name="Implementation-Version" value="${version}" />
				<attribute name="Main-Class" value="${app}" />
				<attribute name="Class-Path" value="." />
			</manifest>
		</jar>
		<delete dir="./classes" />
	</target>

	<target name="run" depends="verifyRunProperties">
		<fail message="There is no ${app_jar} JAR file. You must execute the command 'ant' before you execute 'ant run'.">
			<condition>
				<not>
					<available file="${app_jar}" />
				</not>
			</condition>
		</fail>
		<echo message="Running ${app}" />
		<java jar="${app_jar}" fork="true" failonerror="true" />
	</target>

</project>

... and here's a sample build.properties file:
clojure_jar=/home/user/opt/clojure/clojure.jar
contrib_jar=/home/user/opt/clojure/clojure-contrib.jar

app_file=com/company/app/main.clj
app=com.company.app.main
app_jar=app.jar

vendor=Company Name
title=Name of Application
version=0.1

Put both of those in the root of your project, and run the command 'ant'.