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:
- http://www.scottsweeney.com/static/projects/slip/XMLShorthandComparison.htm
- http://www-106.ibm.com/developerworks/xml/library/x-syntax.html
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.