Top Java Tips: Apache Ant
Apache Ant is probably the most popular build tool for Java.
Its a bit like the old make command popularised under UNIX, but has a rich set
of predefined tasks and is expressed in an XML syntax. The purpose of this document is not to
explain the basics of how to use ant, but to give some tips which you
cannot easily find in the online documentation.
So here are some dos and don'ts for using ant. Let us know if you have some more!
DO make sure you get your target dependencies right
It's obvious, isn't it? But ant scripts grow to become complex beasts, and too often I've seen basic errors like missing dependencies or even dependencies running in the wrong direction. If you get the dependencies right at the beginning of a project, and maintain their correctness throughout, you will grow to trust your build and
there will be fewer unwelcome surprises. On the other hand, if you continue to develop using an ant script that
still needs work, then you are storing an inevitable problem for later.
In particular, don't be satisfied that your ant script is correct because it appeared to work correctly on one occasion. Your ant
script is critical to your project, so take good care of it - refactor and nurse it at least as well as you would your source code.
DON'T replicate information several times in the same script.
In many respects, the same basic principles apply to an ant script as to a piece of program code.
One of those basic principles is to avoid duplicating information. The problem is that duplicated
information becomes very difficult to maintain. If the same information appears in multiple places,
then a single change to that information requires multiple changes to the code. And if an inconsistency
creeps in, and you look at the code some time later, you may no longer be sure which version of the information
is correct, or even if they should be the same at all.
One way that this problem arises in ant build scripts is through the use of parameters supplied to
ant targets. For example, we might have two separate compilation targets in the same script, and for the
moment we prefer not to see warnings generated at compile time. We therefore write the targets in the following
form:
<target name=component1>
<javac nowarn=on srcdir=".">
</target>
<target name=component2>
<javac nowarn=on srcdir=".">
</target>
The script will work, but if we would like to see compilation warnings, or change the
location of the source code, we would need to make multiple changes to the build script.
The script is better written using properties:
<property name="src" value="."/>
<property name="nowarn" value="on"/>
<target name=component1>
<javac nowarn="${nowarn}" srcdir="${src}"/>
</target>
<target name=component2>
<javac nowarn="${nowarn}" srcdir="${src}"/>
</target>
The properties act like variables in a program. With this approach, we can change the values of the
properties and those changes are automatically propagated to the targets.
DON'T use absolute pathnames in your build script.
Sooner or later, someone else will want to use your script to build and run the code in their
environment. If you build in absolute paths, it will be more effort to edit the script to make it
work for them. If you use relative paths, the same script should work in different
environments without needing to be changed, provided your code keeps the same structure.
DO switch on debugging in the javac compilation target
For example:
<javac debug="on" srcdir="${src}" />
Otherwise, you don't get to see the source code line numbers at which problems
occurred in a stack trace, if an exception is thrown. Obviously, these line numbers are invaluable when you are
debugging. Of course you may wish to switch the debug option off if you have code which has been
tested and debugged, but this is not the case for most runs of a build script.
And you will probably want to use a property to set the value of your debug parameter, for those
seldom occasions when line numbers should be hidden.
DO use the default target
The default target is the target that is executed when you just type ant
at the command line. If you don't explicitly define a default target, then an error occurs. If
you define a meaningful, commonly used target as the default, it can save a lot of typing in
the long run.
My personal favourite is to set a target called run , which depends on a compile
target, and executes the main application (or perhaps a test harness). The edit-compile-test loop then
simply involves editing the code and typing ant at the command line, as the run
target will automatically compile the source first if necessary.
The shape of such a build script would be as follows:
<project name="myproject" default="run">
<property name="main.class" value="com.catalysoft.myclass"/>
<target name="compile">
<javac src="whatever"/>
</target>
<target name="run" depends="compile">
<java classname="${main.class}" fork="yes"/>
</target>
</project>
DO use the -projecthelp option
When you define a target which is supposed to be used from the command line, write a
description of what the target does, and put it as the value of the description parameter
to the target.
For example,
<target name="deploy"
description="compiles and deploys to JBoss on local machine">
<!-- deployment target details -->
</target>
Then if you can't remember the names of your targets (or what they do), you can type
ant -projecthelp at the command line and ant will list the main targets and their
descriptions.
DO set the fork option, when using a java target.
If you don't run Java code in a separate JVM to the ant script, you can get some pretty strange
errors that are difficult to diagnose. For example, I got a NoClassDefFoundError when looking for Sun's
SerializationConstructorAccessorImpl (!):
run:
[java] In main
[java] java.lang.NoClassDefFoundError:
sun/reflect/SerializationConstructorAccessorImpl
The problem was fixed by setting fork=true in the java target.
DO supply a source parameter to java targets
Well, certainly if you want to use assertions. You will get a compilation error if you use
assertions in your code and do not supply source="1.4" as a parameter to the
java target.
DO print messages to the screen to help you debug your build script
This is probably something you do as a matter of habit in Java programs, but you should also consider
doing it in a build script.
For example, to print out a classpath that is being used by a java or javac
target, you can use the following pattern:
<!-- Sets up the classpath -->
<path id="main.classpath">
<pathelement location="${src}"/>
</path>
<!-- Convert the classpath to a property, then print it out using echo -->
<target name="run" depends="compile">
<property name="myclasspath" refid="main.classpath"/>
<echo message="classpath= ${myclasspath}"/>
<java classname="${main.class}" fork="yes" classpathref="main.classpath"/>
</target>
|