Article
Apache Ant Demystified - Parts 1 and 2
In Part 1 of this series, we learnt why using Ant to do builds is a good idea, and were introduced to the core Ant concepts of the project, targets and tasks. We also wrote and executed a simple build file.
Now, let's take a look at some of the other ways that Ant can make our lives as Java developers easier.
Target Dependencies
Ant targets can depend on other targets. When you think about it, this is actually really useful. You could have a target for creating a JAR file that depends on a target for compiling the source code. Let's add two more targets to our build.xml file (note that a complete version of build.xml is available for download):
<?xml version="1.0" encoding="UTF-8"?>
<project name="helloworld" default="compile" basedir=".">
<description>
Build file for the Hello World application.
</description>
<target name="compile" description="Compile all sources.">
<mkdir dir="classes" />
<javac srcdir="src" destdir="classes" />
</target>
<target name="clean" description="Clean up output directories.">
<delete dir="classes" />
</target>
<target name="rebuild" depends="clean,compile"
description="Cleanly compiles all sources." />
</project>
I've introduced some new syntax here. Let's take the clean target first. This contains a delete task that simply deletes the classes output directory and its contents. Ant's delete task is a versatile little fellow: it can delete files and directories in various combinations and ways. We also have a rebuild target that doesn't contain any tasks at all! What's going on here?
You'll notice that the rebuild target has a new depends attribute set to clean,compile. This means that the rebuild target should first execute the clean target, before it executes the compile target. So the classes output directory will be deleted, then re-created and the Java code re-compiled. In other words, a full rebuild occurs, which is useful when you want to compile absolutely everything, and make sure that you haven't got any old class files lying about for classes whose source code you've subsequently deleted. Having obsolete classes in your classpath without realising it can cause you to waste hours needlessly debugging.
Important: Ant only executes targets once, even when more than one target depends on it.
Run the rebuild target using ant rebuild and observe the output. We can now perform a clean build, safe in the knowledge that we're using only those classes we think we're using!
DRY Using Properties
When programming, it's important to follow the DRY principle: Don't Repeat Yourself. If you're eagle-eyed, you may have noticed that some duplication is present in our build file as it stands. The classes directory is mentioned by name three times: twice in the compile target, and a third time in the clean target. What we need is a way to introduce some indirection, so that if we suddenly decide we'd like our compiled Java code to end up in a directory named bytecode, instead of classes, we only have to make the edit once. Fortunately, Ant's got it covered with properties.
Let's examine the syntax. This is what our build file looks like once we add a property to store the name of the output directory:
<?xml version="1.0" encoding="UTF-8"?>
<project name="webforum" default="compile" basedir=".">
<description>
Build file for the WebForum application.
</description>
<property name="classes.dir" location="classes" />
<target name="compile" description="Compile all sources.">
<mkdir dir="${classes.dir}" />
<javac srcdir="src" destdir="${classes.dir}" />
</target>
<target name="clean" description="Clean up output directories.">
<delete dir="${classes.dir}" />
</target>
<target name="rebuild" depends="clean,compile"
description="Cleanly compiles all sources." />
</project>
Properties are simply name/value pairs. In this case, the name is classes.dir and the value is classes. We actually use the location attribute to set the latter because we're dealing with a file path that we want evaluated relative to the value set for the basedir attribute. For other types of properties, use the value attribute. When you want to refer to the actual value that the property represents, simply use the ${property name} syntax, for example, ${classes.dir}. If you try to use the build file now, you should find that it works exactly as before; the important point is that it's easier to maintain.
Let's suppose that for some bizarre reason we really did want our compiled classes to go into a directory named bytecode instead of classes. How would we achieve this? We could edit the property declaration and benefit from having only to make the change in one place, but I want to show you another way that's very powerful.
Ant lets you override properties on the command line. You may have come across Java's -D switch, which lets you set a system property. Ant uses the same syntax. Therefore, run ant –Dclasses.dir=bytecode rebuild and observe the results.
Our compiled classes end up in bytecode instead of classes. Were you expecting the classes directory to be deleted, because we told Ant to run the rebuild target, which, in turn, calls the clean target? If you were, be sure to remember that the clean target also refers to the value of ${classes.dir}, which we passed in. For this reason, the old classes directory is left in place. Also, note that there is no space between the -D and the property that's being overridden.
Generally, you'll only override properties this way if it's a change that you want to make for that build only. If we permanently wanted to use bytecode in lieu of classes, we would edit the property declaration in the build file. Remember: a good build process is repeatable.
Hiding Targets
Sometimes, when you're writing build files, you'll want to hide targets so that they can't be executed directly. You may wonder what use a target is if it can't be executed directly. Well, it's useful to be able to create so-called internal targets that do useful work for other targets that are visible. To hide a target, just place a minus sign before its name. Add the highlighted code below to our build file:
<?xml version="1.0" encoding="UTF-8"?>
<project name="helloworld" default="compile" basedir=".">
<description>
Build file for the Hello World application.
</description>
<property name="classes.dir" location="classes" />
<property name="dist.dir" location="dist" />
<property name="dist.jarfile" value="helloworld.jar" />
<target name="compile" depends="-init" description="Compile all sources.">
<mkdir dir="${classes.dir}" />
<javac srcdir="src" destdir="${classes.dir}" />
</target>
<target name="clean" description="Clean up output directories.">
<delete dir="${classes.dir}" />
</target>
<target name="rebuild" depends="clean,compile"
description="Cleanly compiles all sources." />
<target name="-init">
<!-- Create the time stamp. -->
<tstamp>
<format property="TODAY_UK" pattern="dd MMM yyyy HH.mm" locale="en_GB" />
</tstamp>
</target>
<target name="dist" depends="rebuild"
description="Creates the binary distribution.">
<mkdir dir="${dist.dir}/${TODAY_UK}" />
<jar basedir="${classes.dir}"
destfile="${dist.dir}/${TODAY_UK}/${dist.jarfile}" />
</target>
</project>
There are quite a few changes here! The compile target now depends upon the hidden init target. The init target itself uses Ant's tstamp task to assign a date and timestamp to the new property TODAY_UK using a UK date and time format. We'll use this property later in the build file, to create a new directory with a name that reflects the date and time at which the build was run. We hide this target because, on its own, it doesn't do anything very useful, so we don't want to invoke it from the command line.
Moving back to the top of the build file, we've set up a couple of new properties to define a dist output directory, and the file name of an output JAR file: helloworld.jar.
Finally, we've added a new dist target that depends on the rebuild target. Our old friend the mkdir task is used again. It uses both the dist.dir and TODAY_UK properties to create an output directory in which the JAR file will go.
Ant's jar task is then used to create the JAR file. The jar task can accept a lot of different parameters, but here we're setting just two of them. The basedir attribute tells the jar task where to find the compiled Java classes that we want to put into the JAR file, while the destfile attribute specifies the name of the output JAR file itself.
If you now enter ant dist into the Command Prompt, you should see output similar to this:
Buildfile: build.xml
clean:
[delete] Deleting directory C:\SitePoint Tutorials\Ant\classes
-init:
compile:
[mkdir] Created dir: C:\SitePoint Tutorials\Ant\classes
[javac] Compiling 1 source file to C:\SitePoint Tutorials\Ant\classes
rebuild:
dist:
[mkdir] Created dir: C:\SitePoint Tutorials\Ant\dist\26 Feb 2005 12.38
[jar] Building jar: C:\SitePoint Tutorials\Ant\dist\26 Feb 2005 12.38\helloworld.jar
BUILD SUCCESSFUL
Total time: 2 seconds
Notice that Ant traversed the tree of dependencies that we've created. We told it to execute the dist target, which depends on the rebuild target, which in turn depends on the clean target, followed by the compile target. The compile target itself depends on the –init target. Ant automatically unravels all this, and executes the targets in the following order: clean, -init, compile, rebuild and finally dist.
One final point to note is that, if you peek inside the helloworld.jar file that Ant built (you can use WinZip to do this), you'll see that Ant has helpfully automatically added a MANIFEST.MF file, which contains brief details of the build, such as the version of Ant and the Java compiler that were used. On my machine, it looks like this:
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.2
Created-By: 1.5.0-b64 (Sun Microsystems Inc.)
Of course, you can always override this behaviour and get Ant's jar task to use your own manifest file if you need to. The details are included in the Ant manual's documentation for the jar task.
Target Aliasing
A technique that I often use in my build files is target aliasing. This means that I define a target that does nothing but invoke another. This may seem like a waste of time, but consider the scenario of providing a target that displays some simple help text to the user via Ant's echo task:
<target name="help" depends="usage" />
<target name="usage" description="Display usage information.">
<echo message=" Execute 'ant -projecthelp' for build file help." />
<echo message=" Execute 'ant -help' for Ant help." />
</target>
The help target is aliased to the usage target by creating a dependency on the usage target. Now the user of our build file can get online help by typing either ant usage or ant help. Note that the help target doesn't appear in the target listing displayed by Ant's –projecthelp option, because we didn't provide a description for it. For an additional refinement, make either the help or usage targets the default target, so that the user can get help if they just enter ant, with no arguments, on the command line.
Tip: Seriously consider making your build file user-friendly by making its default target one that doesn't do anything potentially time-consuming and/or dangerous! Displaying help is a good candidate for the default.
Awesome Ant
We've really only just scratched the surface of Ant's capabilities, but I hope I've given you a flavour of just how flexible and powerful a tool it is. In fact, you'd be really hard-pressed to think of anything useful that you'd want to do in the realm of Java development that Ant can't handle.
It's worth taking the time to look through the list of core tasks that Ant offers, and experimenting with some of them. If you go to the folder in which you installed Ant, you'll find a docs folder. Within this is an index.html file that will open your very own local copy of the Apache Ant Website. This contains a link to a local copy of the Ant manual, which is generally well-written because Ant is a mature program now. The manual also has lots of links to some great external resources on Ant. Happy building!