I've worked on dozens of projects that used Ant build scripts, with team sizes ranging from just me to tens of developers. Big projects tend to have big Ant scripts, and they can be a maintenance nightmare. I've developed a structural style for scripts that I find easier to maintain and explain on larger projects.
Take the following Ant script as a typical example.
<project name="foo" basedir="." default="test">
<target name="init" description="Prepare directories for build">
...
</target>
<target name="compile" depends="init" description="Compiles code">
...
</target>
<target name="test" depends="compile" description="Tests code">
...
</target>
</project>
The script has only three targets in it. All targets have a description, and two of them have a dependancy on another target. Those descriptions and dependancies are scattered throughout the file. It is possible to discover the descriptions of targets publicly available by running ant -projecthelp, and the dependancies will be seen when the script runs, but neither are easily viewable at a glance. This is especially true when your Ant script has grown to hundreds of lines and tens of targets.
Here's the same script modified to follow my different convention
<project name="foo" basedir="." default="test">
<target name="-init">
...
</target>
<target name="-compile">
...
</target>
<target name="-test">
...
</target>
<!-- Private targets used only for dependancies -->
<target name="--init" depends="-init"/>
<target name="--compile" depends="-init,-compile"/>
<target name="--test" depends="--compile,-test">
<!-- Public targets with description -->
<target name="init" depends="--init" description="Prepare directories for build">
<target name="compile" depends="--compile" description="Compiles code">
<target name="test" depends="--test" description="Tests code">
</project>
Most of the targets in this script are now "private" targets. Basically - anything that starts with a hyphen can't be run on the command line by Ant, as it thinks you are passing a parameter to the Ant script rather than specifying a target. None of these private targets have any dependancies or descriptions.
All dependancies between targets can be seen at a glance in one section. They are again all listed on private targets, but these ones do no work other than describing dependancies.
All descriptions can be seen at a glance in one section. They are listed on public targets, and delegate to a single other private target which handles the dependancies for it.
Thats all there is to it really. The purist in me doesn't like it, because the test target really does depend on the compile target, so thats where it should be listed. My pragmatic side almost always wins though, and I've seen enough confusion in Ant scripts to adopt this pattern for all my new builds.
Comments
Where a number of projects are being done for the one customer, and you can get some reuse benefits, having a common build file do most of the work really helps. I've put a lot of this in place at Suncorp over the last couple of months, and it's made adding new tools to the build process (like Clover and Simian) a breeze.
For a better way of viewing Ant scripts, I got a lot of benefit from writing an XSLT stylesheet; targets down the left hand side (grouped according to visibility), dependencies expressed as hyperlinks, and source code hidden away but visible as needed.
Posted by: Robert Watkins | July 8, 2004 05:59 PM
What? No ant 1.6 imports and taskdefing?
Posted by: Glen Stampoultzis | July 8, 2004 08:21 PM
I agree with the common build script idea Robert. I've used that extensively too.
Dunno about the stylesheet thing being "better" though. Sounds a bit like a code smell to me. If you have to document the code in some way other than the source, perhaps your source needs to be refactored first.
Posted by: Marty Andrews | July 9, 2004 09:28 AM
The stylesheet simply takes the source and transforms it; I'll send you a copy so you can see what I mean.
It's like Javadoc for Ant.
Posted by: Robert Watkins | July 9, 2004 09:50 AM
Once we started using the Ant 1.6 features things become very modularized with most of our build scripts; maintainability is way up.
Posted by: scot mcphee | July 9, 2004 04:42 PM
Yep - I'm an Ant 1.6 user too. It helps a lot, but any bit extra I can get is good.
Posted by: Marty Andrews | July 9, 2004 05:00 PM
First, I didn't realize - in front makes it private. I thought a private target was always one that didn't depend on anything else. Yet you definitely could call it from the command line, so that is kewl to know. Thanks for the tip.
Second, with Ant 1.6, I noticed someone making a top script that imports compile, package, init, clean and other sub scripts, so that each main functional area of the script is in its own script. Is this the way to go if you can use 1.6? I was thinking of doing this so that it is easier to find specific things. The problem is, we have "plugins" that we build, and I have added my own "loop" taskdef to the ant process to loop through a comma separated list (via a property) that specifies the paths to the extensions. In each plugin extension directory, I always know there is a classes and src folder, and I use the copy task with failonerrror="false" to try to copy files from a few dirs that MAY be there. In some plugins, they are there, in some they are not. Thus, as we add plugins, we simply add to this property the paths to the plugins and the rest is done, including compiling, packaging, init, clean, etc. Now, in some cases we actually have to add a little extra for specific plugins. For example, one plugin uses castor to generate source. I didn't want the generated source to cluter up the hand-coded source, so I put the generated source in a generated-src dir. It makes it easy to find and doesn't bother the code that is in the source control system. PRoblem is, only one plugin has this, so I have to add an extra javac task for this one purpose. Now that I think about it I can probably rework this a bit more and make that go away, but there is no way to make sure all plugins have only those couple dirs.
Anyway, point is, where as we used to ahve a build script for every plugin and a few others that totlaed over 3000 lines, I have consolidated it into one build script with about 600 lines.
Word to the wise, trying to keep everything relative is VERY GOOD. However, I found out the hard way that if you specify a taskdef and a target that uses it, but DONT actually use that target at all for a given build, if the taskdef location of the library is NOT found, the build will not work, period! You may think "why wouldn't it be found". Well, we use Install4J and it has an ant task so that we can automate the build process. We build linux, windows, and mac osx installers. Problem is, if the build is on Windows, it refers to the c:\\xxx path of the library as it has to be found in the install dir of Install4J. On linux, this is obviously not going to work. Hence, my ant script will only build on windows. The way I got around this is to make a second script that only builds the installers, then if the script is available I antcall it from my main script, using the available and if= stuff. I'll fix this to use a property and set the property based on the env.OS setting I think, so maybe it can work. If anyone has suggestions, I am all ears on this.
Thanks.
Posted by: Kevin | July 9, 2004 05:14 PM
It's not that the - in front makes it private, it just means that the command line interface can't interpret it (it thinks it's an unrecognised option). Other interfaces, such as an IDE interface, may or may not show it up depending on what they want.
Posted by: Robert Watkins | July 10, 2004 10:22 AM
Kevin - yep, I suspect this is what Glen was describing to some extent. Scripts for common tasks can be useful to split out. Macros are helpful too (I have a common compile macro and a test macro for example). Its the custom stuff that usually becomes harder to manage. I often write custom scripts for deploying to test environments (which for me usually differ on a per-project basis).
Posted by: Marty Andrews | July 12, 2004 08:46 AM
Kevin, take a look at the os condition of the condition task!
Posted by: Ilja | December 21, 2004 01:15 AM