I’ve been tweaking quite a few Ant build files at work in the last couple of weeks. Firstly, thank crap for IDEA and its Ant completion mode (Eclipse has a similar mode). Also, being able to comment out a whole XML block with Ctrl-Shift-/ is nice, too.

Secondly, there is a certain amount of noise in an Ant XML file. Now, I know that I recently made noises myself in defence of Ant’s verbosity, but I decided to do a little thought experiment and see what an Ant build file might look like in some alternate syntaxes.

This is my example build file. It is a cut-down version of a real build.xml (but details changed to protect the guilty):

<project name="thinger" default="code" basedir=".">

    <property environment="env"/>
    <property name="jboss.home" location="${env.JBOSS_HOME}"/>

    <property name="jars" location="../../jars"/>

    <property name="log4j.jar" location="${jars}/log4j-1.2.7/log4j.jar"/>
    <property name="junit.jar" location="${binaries.jars}/junit-3.8.1/junit.jar"/>

    <path id="all.sources.path">
        <pathelement location="source/thinger"/>
        <pathelement location="source/tests"/>
    </path>
    <path id="all.classes.path">
        <pathelement location="build/classes/thinger"/>
        <pathelement location="build/classes/tests"/>
    </path>

    <path id="jboss.classpath">
        <fileset dir="${jboss.home}/client" includes="*.jar"/>
    </path>

    <path id="build.classpath">
        <path refid="jboss.classpath"/>
        <pathelement location="${log4j.jar}"/>
    </path>

    <path id="test.classpath">
        <path refid="build.classpath"/>
        <pathelement location="${junit.jar}"/>
    </path>

    <target name="code">
        <!-- depends -->
        <mkdir dir="build/depcache"/>
        <property name="depend.sourcepath.value" refid="all.sources.path"/>
        <property name="depend.destpath.value" refid="all.classes.path"/>
        <depend srcDir="${depend.sourcepath.value}"
            destDir="${depend.destpath.value}"
            cache="build/depcache"
            dump="true"
            />

        <!-- compile thinger -->
        <mkdir dir="build/classes/thinger"/>
        <javac destdir="build/classes/thinger"
            debug="on"
            classpathref="build.classpath">
            <src path="source/thinger"/>
        </javac>

        <!-- compile tests -->
        <mkdir dir="build/classes/tests"/>
        <javac destdir="build/classes/tests"
            debug="on"
            classpathref="test.classpath">
            <src path="source/tests"/>
        </javac>
    </target>

    <target name="jars" depends="code">
        <jar jarfile="build/thinger.jar">
            <fileset dir="build/classes/thinger"/>
        </jar>
    </target>

    <target name="clean">
        <delete dir="build"/>
    </target>
</project>

(I’ve never used any of the following syntaxes before, so I might have made some notational errors.)

SOX

This is what it might look like using SOX. It’s is about the same vertical size as the original build file, but I had to do much less typing (less angle brackets and less double quotes). Also, forced indentation makes it a bit more readable; I found I was able to visually scan the file more easily.

project>
    name=thinger
    default=code
    basedir=.
    
    property>
        environment=env
        
    property>
        name=jboss.home
        location=${env.JBOSS_HOME}
    property>
        name=jars
        location=../../jars
    property>
        name=log4j.jar
        location=${jars}/log4j-1.2.7/log4j.jar
    property>
        name=junit.jar
        location=${binaries.jars}/junit-3.8.1/junit.jar
    path> id="all.sources.path"
        pathelement> location=source/thinger
        pathelement> location=source/tests
    path> id=all.classes.path
        pathelement> location=build/classes/thinger
        pathelement> location=build/classes/tests
    path> id=jboss.classpath
        fileset>
            dir=${jboss.home}/client
            includes=*.jar
    path> id=build.classpath
        path> refid=jboss.classpath
        pathelement> location=${log4j.jar}
    path> id=test.classpath
        path> refid=build.classpath
        pathelement> location=${junit.jar}
        
    target> name=code
        
        # depends 
        mkdir> dir=build/depcache
        property>
            name=depend.sourcepath.value
            refid=all.sources.path
        property>
            name=depend.destpath.value
            refid=all.classes.path
        depend>
            srcDir=${depend.sourcepath.value}
            destDir=${depend.destpath.value}
            cache=build/depcache
            dump=true
            
        # compile thinger 
        mkdir> dir=build/classes/thinger
        javac>
            destdir=build/classes/thinger
            debug=on
            classpathref=build.classpath
            src> path=source/thinger
            
        # compile tests
        mkdir> dir=build/classes/tests
        javac>
            destdir=build/classes/tests
            debug=on
            classpathref=test.classpath
            src> path=source/tests
            
    target> name=jars
        depends=code
        
        jar> jarfile=build/thinger.jar
            fileset> dir=build/classes/thinger
            
    target> name=clean
        delete> dir=build

SLiP

This is what it might look like using SLiP. It’s cool that the brackets match (Emacs complained about the angle brackets not matching in the SOX example). (Also, SLiP doesn’t use angle brackets, so it is easier to cut-and-paste into a HTML document.) I was kind-of sold on how SOX made the double quotes on attributes unnecessary, whereas they are required by SLiP.

This SLiP example is a bit more compact, but maybe not as easy to scan visually.

project(name="thinger", default="code", basedir="."):
    
    property(environment="env"):
    property(name="jboss.home", location="${env.JBOSS_HOME}"):
    property(name="jars", location="../../jars"):
    property(name="log4j.jar", location="${jars}/log4j-1.2.7/log4j.jar"):
    property(name="junit.jar", location="${binaries.jars}/junit-3.8.1/junit.jar"):
    path(id="all.sources.path"):
        pathelement(location="source/thinger"):
        pathelement(location="source/tests"):
    path(id="all.classes.path"):
        pathelement(location="build/classes/thinger"):
        pathelement(location="build/classes/tests"):
    path(id="jboss.classpath"):
        fileset(dir="${jboss.home}/client", includes="*.jar"):
    path(id="build.classpath"):
        path(refid="jboss.classpath"):
        pathelement(location="${log4j.jar}"):
    path(id="test.classpath"):
        path(refid="build.classpath"):
        pathelement(location="${junit.jar}"):
        
    target(name="code"):
        
        # depends 
        mkdir(dir="build/depcache"):
        property(name="depend.sourcepath.value", refid="all.sources.path"):
        property(name="depend.destpath.value", refid="all.classes.path"):

        # NB: I assume you can split arguments into lines, like Python
        depend(srcDir="${depend.sourcepath.value}",
               destDir="${depend.destpath.value}",
               cache="build/depcache",
               dump="true"):
            
        # compile thinger 
        mkdir(dir="build/classes/thinger")
        javac:(destdir="build/classes/thinger",
               debug="on",
               classpathref="build.classpath"):
            src(path="source/thinger"):
            
        # compile tests
        mkdir(dir="build/classes/tests"):
        javac(destdir="build/classes/tests",
              debug="on",
               classpathref="test.classpath")
            src(path="source/tests"):
            
    target(name="jars", depends="code"):
        
        jar(jarfile="build/thinger.jar"):
            fileset(dir="build/classes/thinger"):
            
    target(name="clean"):
        delete(dir="build"):

Others

Other structured syntaxes include YAML, PYX, and SXML. YAML is interesting, but I can’t say I’m a fan of PYX nor SXML for Ant purposes. Further reading:

Ant ain’t too bad

Although I do think the SOX notation improves the readability of an Ant file, I’m not sure if it would be worth the effort trying to change Ant in that way. If you were really keen, you could just convert your SOX to XML before running Ant. Or maybe a “-preprocess=SOX” option could be added to Ant.

In the mean time, thank crap for IDEA.