homeresumeabout
Compiling Clojure Applications and Libraries - Round 2
09.10.22
The previous posting explained one way of compiling Clojure projects using Ant. Since then I've expanded to a more stable re-usable solution that I'll be sharing in this post.
Folders
I'm assuming that you have some folder where your project sits. I'll call this folder trunk. In the trunk folder are two other folders and one file. The two folders are called src and dst; the file is called build.xml.
This results in the following structure to start with:
trunk/
  |-- src/
  |-- dst/
  |-- build.xml

The src folder
Inside the src folder you'll have your package structure. This package structure could look something like: src/com/company/project. At the very end of this package structure you'll want to place the Clojure files that will be compiled. You can have files that are compiled into classes to be used as applications, as well as files that are compiled into functions to be reused as a library.
The location of the files is not what determines ther end-use. Declarations of (:gen-class) inside a file's namespace declaration denotes a class that might be used as an application or in a non-Clojure project. Files that do not have the (:gen-class) declaration would be candidates for use as a Clojure library.
The current folder structure:
trunk/
  |-- src/
  |     |-- com/
  |           |-- company/
  |                 |-- project/
  |-- dst/
  |-- build.xml

The dst folder
The only thing that you want to start with in the dst folder is one other folder called, lib. For projects that become Clojure applications, we'll eventually have Ant place the clojure.jar and clojure-contrib.jar files in this lib folder as well as the JAR file that results from compiling the Clojure files in the src folder. For projects that become Clojure libraries you could have the resulting JAR file placed directly under the dst folder.
The current folder structure:
trunk/
  |-- src/
  |     |-- com/
  |           |-- company/
  |                 |-- project/
  |-- dst/
  |     |-- lib/
  |
  |-- build.xml

Files
The only file that we currently have is the trunk build.xml. We'll be adding an additional build.xml inside src that is copied to dst during the build process. We don't have any Java files, but we will be adding Clojure files inside the project folder under src.
The root build.xml file
For now let's make the content of the root build.xml file be:
<project name="default" default="default">
    <property name="clojure_home" value="/home/user/opt/clojure" />
    <property name="clojure_jar_file" value="clojure.jar" />
    <property name="contrib_jar_file" value="clojure-contrib.jar" />
    <property name="clojure_jar" value="${clojure_home}/${clojure_jar_file}" />
    <property name="contrib_jar" value="${clojure_home}/${contrib_jar_file}" />
    <property name="src_dir" value="src" />
    <property name="dst_dir" value="dst" />
    <property name="dst_lib" value="${dst_dir}/lib" />
    <property name="app_file" value="com/company/project/main.clj" />
    <property name="app" value="com.company.project.main" />
    <property name="app_jar" value="project.jar" />
    <property name="vendor" value="Company" />
    <property name="title" value="Project" />
    <property name="version" value="0.1" />
    <target name="default" depends="compile, run" />
    <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="${src_dir}/${app_file}" />
                </not>
            </condition>
        </fail>
    </target>
    <target name="compile" depends="verifyCompileProperties">
        <mkdir dir="${src_dir}/classes" />
        <java classname="clojure.lang.Compile" fork="true" failonerror="true">
            <classpath>
                <pathelement location="${src_dir}" />
                <pathelement location="${src_dir}/classes" />
                <pathelement location="${clojure_jar}" />
                <pathelement location="${contrib_jar}" />
            </classpath>
            <sysproperty key="clojure.compile.path" value="${src_dir}/classes" />
            <arg value="${app}" />
        </java>
        <delete file="${dst_lib}/${app_jar}" />
        <jar destfile="${dst_lib}/${app_jar}" basedir="${src_dir}/classes" index="true">
            <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="${src_dir}/classes" />
    </target>

    <target name="run" depends="verifyRunProperties">
        <copy file="${clojure_jar}" todir="${dst_lib}" />
        <copy file="${contrib_jar}" todir="${dst_lib}" />
        <copy file="${src_dir}/build.xml" todir="${dst_dir}" />
        <exec dir="${dst_dir}" executable="ant" />
    </target>
</project>

This Ant build file will facilitate creating an Application. The only changes that you'd need to make to create a library would be to remove the run goal as well as the Main-Class attribute inside the manifest element. Either way, app or library, you will want to make sure that the clojure_home property points to a location where both the clojure.jar and the clojure-contrib.jar are found.
The src build.xml file
The build.xml file that starts in the src folder will be copied into the dst folder and used to run the resulting application. For library projects you could easily do without this build.xml file. This build file runs the command java on your application and adds all JAR files in the lib folder to the classpath.
Here is the content of the build.xml file in the src folder.
<project name="default" default="default">
    <target name="default">
        <java classname="com.company.project.main" fork="true" failonerror="true">
            <classpath><fileset dir="lib" includes="**/*.jar"/></classpath>
        </java>
    </target>
</project>

Adding that build.xml file inside the src, we have the current folder structure as follows:
trunk/
  |-- src/
  |     |-- com/
  |     |     |-- company/
  |     |           |-- project/
  |     |
  |     |-- build.xml
  |
  |-- dst/
  |     |-- lib/
  |
  |-- build.xml

Clojure files under the package folders in src
Finally we can add the most basic Clojure file inside src. The name for the file that we add needs to have the same name as the properties in the trunk src build.xml file. You can choose any name that you'd like as long as you update both build.xml files to reflect the new name. We'll be naming our applications primary Clojure file main.clj.
Here is the content of the main.clj Clojure file:
(ns com.company.project.main
    (:gen-class))

(defn -main [& args]
    (println "The setup works!"))

Adding our first Clojure file brings us to the last step, and the resulting folder structure:
trunk/
  |-- src/
  |     |-- com/
  |     |     |-- company/
  |     |           |-- project/
  |     |                 |-- main.clj
  |     |-- build.xml
  |
  |-- dst/
  |     |-- lib/
  |
  |-- build.xml

Building the project
Just run the command ant in the trunk folder. This command will also run the project.
Running the project
Just run the command ant in the dst folder (assuming that you've already built the project). If the project successfully builds and runs you should see the following (with some possible variation in the 'Total time' reported):
[exec] default:
[exec]      [java] The setup works!
[exec] 
[exec] BUILD SUCCESSFUL
[exec] Total time: 0 seconds