The Basics
Ant is very similar to the standard Unix tool "make" that just about
every experienced C programmer is familiar with. It does its work based on a
build file, typically called build.xml, that tells Ant how and what to
build. The contents of the build file are marked up in XML, making it rather
self-explanatory. Different actions are triggered by aptly named XML tags
with attributes and subtags detailing the work to be done.
Listing 1 contains an extremely simple build file. First I'll show how
to tell Ant to use this file from the command line and what the output will
be, then I'll dissect the file, explaining the different parts. To get us
moving a "task" is a basic unit of work and a "target" is a grouping of
tasks that does something useful. Your build file will consist of several
targets, each containing one or more tasks. Targets, in turn, are contained
in a single "project." I'll explain these concepts shortly, but now let's
run this thing and see what happens! (The source code for this article can be downloaded from below.)
Running Ant
Running Ant is simply a matter of invoking the shell script or batch
file that came with the Ant distribution. When you execute this file with no
command-line arguments, Ant generally looks in the current directory for a
file called build.xml. If it finds this file, it will execute the default
target specified in it. Figure 1 shows what happens when we execute Ant.bat
with no command-line parameters.
Figure 1
Notice that Ant tells you which targets are being executed (the
left-aligned text followed by a colon) and which task within a target it's
working on (the word inside the brackets). This gives you a "progress meter"
of the build. Also notice that even though we didn't tell Ant which target
to run, it executed our JAR target, which is specified as the default target
inside the build file.
You can also tell Ant which targets you want it to execute and the
order. Most build files will contain a target called clean that removes any
artifacts from previous builds and "resets" the project. It's not uncommon
to run this clean task just before rebuilding the project. For our example
this can be accomplished by specifying the command line:
ant clean jar
Figure 2 shows the output from executing this command line. Pay special
attention to the order of the targets that are executed.
Figure 2
Ant also supports a command-line switch that tells it where to find a
build file to use. This switch has the intuitive name of buildfile and
takes a pathname to the file that will serve as the build file. Using this
switch, you're no longer required to name your build file build.xml, and it
obviously doesn't have to be in the current directory. As you'll see, this
is where the basedir attribute of the <project> tag comes in handy. If you
set it to "." or didn't specify it, all the relative path information will
be handled properly, since the dot means "relative to the build file." This
is generally what you want.
If your build file is living in a directory that has a name similar to
"build" and the source and compile directories are siblings of the "build"
directory, then you would need to set basedir equal to the parent directory
or "..".
The Build File
The Ant build file is an XML document that tells Ant how to do the work
to build a project. The first line in Listing 1 is the standard XML
processing instruction that tells the XML parser which version of XML this
document complies with (since there's currently only one version of XML,
this line will always be set to version 1.0).
The <project> Tag
Line 3 is where the interesting content begins. The <project> tag is the
only top-level tag that is allowed in a build file. In this example the
<project> tag defines a logical name for this project (the name attribute)
and specifies a default target (the default attribute) and a base directory
for relative path specifications (the basedir attribute).
The name is not really used anywhere with an out-of-the-box Ant
installation, but integrated development environments that understand and
integrate with Ant will use the name to differentiate between different
projects. Examples of Ant-aware IDEs include Eclipse, jEdit, and IntelliJ
IDEA (URLs are provided at the end of the article).
The default attribute tells Ant which target to execute if you don't
specify one on the command line. In general, the default target should be
set to the one that's most useful or the one you execute the most; this will
save some typing later on. In our case we want everything to execute, so
we'll set the JAR target as the default.
The basedir attribute is used when building up relative pathnames. There
are many properties set in a typical build file that specify directories or
files. It's much easier and portable to specify these in a relative rather
than absolute fashion. In other words, using the basedir attribute will
allow you to pick up and move your entire build directory structure to a
different disk or machine, without radically changing your build file. This
is a big win, especially in a multideveloper scenario where different
developers may not have the project directory in the same place on their
machines.
Setting Properties
Just as with make, it's useful and indeed encouraged to set constants
for often-used information to avoid retyping and copying. This is
accomplished with the <property> tag that can appear either by itself
directly inside the <project> tag or inside a <target> tag. The <property>
tag has several options but we'll consider only one here: file. Supplying a
file name (which will be found relative to the basedir attribute if no path
is given) will create properties for each line in the file. The following is
the properties file that we're using for this project.
- projectname=example1
- src.dir=src
- jar.name=${projectname}.jar
- build.dir=build
This is standard Java properties syntax. We are defining four
properties, one of which is based on one other; the jar.name property on
line 3 is based on the projectname property on line 1. The ${} syntax is
Ant's way of dereferencing a property, substituting the property's value.
You'll notice that on lines 9, 13, 14, 18, 19, 24, and 25 of the build file
we use this same syntax the same dereferencing syntax that works in the
properties file works in the build file.
Note that we could have specified each of these properties directly in
the build file, but it is much cleaner to move them out to a properties
file. This makes the build file less cluttered and the properties a little
easier to read.
Defining Targets
The <target> tag is the real workhorse of an Ant build file. Define a
target for each major area of functionality that you need; this generally
consists of some common setup, a compilation setup, a step to create a JAR
file, and perhaps steps to create a WAR or EAR file.
In our example, we have five targets defined: init, prepare, compile,
jar, and clean. The init target in our example merely loads up the
properties file, as explained in the previous section. There's nothing
stopping you from performing other tasks in this target; one common task
that you will find in init is a call to <timestamp>, which sets properties
containing the current time and date. You'll see an init target in most
build files that you examine.
The prepare target is another common target that provides functionality
that can be shared by multiple targets. Here the prepare target is simply
creating a directory using the built-in task <mkdir> that we can compile our
Java source code in. Notice that the directory we ask it to create is the
result of using a property. The <prepare> target could do quite a bit more,
such as create entire directory hierarchies, but for our purposes, simply
creating this one directory is sufficient.
Notice that at the end of line 8 there's an attribute that says
depends="init". This attribute sets up a dependency between the current
target and the target(s) specified. When Ant gets ready to run our prepare
target, it will see that we have a dependency and will then go off to run
the depended-upon target(s) and then come back and run ours. This is a
powerful mechanism for avoiding redundancy. As build files get larger,
you'll end up with many targets that need some common functionality; moving
this commonality into a single target and then setting up a dependency
allows this to happen. Here we specify that prepare depends on init, which
makes sense because prepare uses a property that's set inside init. If init
were not called, we'd end up with a directory called ${build.name} being
created. (Yes, we could have put the <mkdir> task directly into the compile
target or inside init, but then you wouldn't have seen the dependency
mechanism in action.)
Next, our compile target does what you might expect: it compiles our
code! As you can see from Listing 1, there's a built-in task called <javac>,
a wrapper for the standard javac compiler. It has many attributes, most of
which are optional. Here we tell it where to find the source code to
compile, srcdir="${src.dir}", and where to put the compiled class files,
destdir="${build.dir}". Using the task this way will compile all Java source
files that it finds in the ${src.dir} directory (and all of its
subdirectories) and put the binary class files in the directory specified by
the ${build.dir} attribute. If the source files declared a package, then the
package structure will be represented in the destination directory.
Notice that the compile target depends on prepare. Since we need the
build directory to be created before we try to write class files to it, we
need to call prepare. But we also need the properties that get created
inside init. Because compile depends on prepare and prepare depends on init,
everything will be executed in the proper order and compile will work just
fine.
Moving on, our jar target will take the output of our compile step and
build a Java archive out of it. This example is almost overkill because of
the simplicity of the project, but I wanted to show you a few of the
interesting built-in tasks that Ant provides. You can see here that the jar
task will build the archive specified by destfile and will include in it any
class files (and directory structure) that are returned by the enclosed
<fileset>. In this case, it will return only a single class file, but if
there were others located anywhere under ${build.dir}, it would also return
those. And notice again that it has dependencies. It wouldn't be terribly
useful to create a JAR file without first compiling our source files. Thus
setting depends="compile" will cause everything to be compiled (and prepared
and inited) before trying to create a JAR. Usually if a target fails, the
build aborts. There are a few exceptions, but generally this is the rule.
Finally we have a target called clean. This target can be executed to
remove all artifacts of previous builds to ensure a clean build. Here we're
deleting the compilation directory and the JAR file created from a previous
run. Both these deletes will fail silently if the specified file/directory
doesn't exist, which is fine. You'll almost certainly want a clean target in
most of your build files, but care must be exercised when using the delete
task, the same care you exercise any time you're deleting files and
directories. It's quite painful if you delete the current directory and its
contents instead of a subdirectory. Notice that our clean target depends on
init. The reason for this is that if init is not run, then the two
properties that are used by the <delete>s would never be defined and nothing
would get deleted.
That's About It
That wraps up our discussion of the simple build file (the Hello, World
of the Ant world, if you will). You should now have an understanding of what
a build file looks like, how the different pieces relate to each other, how
to declare that a task is dependent on another, and what a couple of the
built-in tasks are.
When Things Go Awry...
Inevitably you're going to write a build file that you think is perfect,
but when you try to run it your screen will fill up with stack traces and
other error messages. One of the most common errors in a build file is not
properly formatting the XML code. Remember that XML, unlike HTML, requires
all attributes to be quoted and all tags must be closed! The build file in Listing 2
will not parse correctly because there are missing quotes all over the place
and at least one target isn't closed. Even leaving off one quote will cause
your file not to parse properly. An editor that does color syntax
highlighting or checks for well-formed XML will help you spot these errors.
Figure 3 shows the output from running Ant with the bad.xml file in
Listing 2 .
Figure 3
The parser encounters the first error and indicates it as being on line
5 (the number after the colon next to the file name). Actually it's on line
4, which is where we forgot to add the closing quote to the name attribute
of the init target. Since Ant couldn't complete parsing the file, it will
abort the run after the first error. Once you add the missing quote and run
again, you'll see the next error, an unclosed <property> tag.
Remember that the error messages may not always explicitly help you and,
in fact, may be caused by something other than your build file, such as a
failed execution of an external program. Get comfortable with Ant's error
messages; this is certainly not the last time you'll see one.
Summary
In this article I provided a quick introduction to an extremely useful
tool. You should be able to take what you've learned here and immediately
start applying it to your current business problem. There are a number of
books on Ant available (one of which I coauthored) that would be helpful.
Don't forget that the entire Ant manual is included with the Ant
distribution and is located in ANT_HOME/docs/manual. It's also available on
the Ant home page.
Links
Jakarta Web site: http://jakarta.apache.org
Ant pages: http://jakarta.apache.org/ant
Ant Manual: http://jakarta.apache.org/ant/manual
Eclipse: www.eclipse.org
jEdit: www.jedit.org
IntelliJ IDEA: www.intellij.com/idea
BravePoint: www.bravepoint.com
Author Bio
Joey Gibson is a senior consultant and instructor for BravePoint, a
consulting company in Atlanta, GA. He is the coauthor of Ant Developers
Handbook published by SAMS.
joey@joeygibson.com
Listing 1: build.xml
1 <?xml version="1.0"?>
2
3 <project name="Example1" default="jar" basedir=".">
4 <target name="init">
5 <property file="build.properties"/>
6 </target>
7
8 <target name="prepare" depends="init">
9 <mkdir dir="${build.dir}"/>
10 </target>
11
12 <target name="compile" depends="prepare">
13 <javac srcdir="${src.dir}"
14 destdir="${build.dir}"/>
15 </target>
17 <target name="jar" depends="compile">
18 <jar destfile="${jar.name}">
19 <fileset dir="${build.dir}"
includes="**/*.class"/>
20 </jar>
21 </target>
22
23 <target name="clean" depends="init">
24 <delete dir="${build.dir}"/>
25 <delete file="${jar.name}"/>
26 </target>
27 </project>
Listing 2: bad.xml, A Build File with Problems
1 <?xml version="1.0"?>
2
3 <project name="Example1" default="jar" basedir="." >
4 <target name="init>
5 <property file="build.properties">
6 </target>
7
8 <target name="prepare" depends="init">
9 <mkdir dir="${build.dir}"/>
10 </target>
11
12 <target name="compile" depends=prepare>
13 <javac srcdir="${src.dir}"
14 destdir="${build.dir}>
15 </project>