Catalysoft   Turtle
home products articles about us contact us

Recent Articles

What's Good About Clojure?

Clojure is a relatively new language to appear on the Java Virtual Machine (JVM), although it draws on very mature roots in the form of the LISP langu ...

Should You Care About Requirements Engineering?

Recently, I (Adil) was invited to participate in a one day seminar on the subject of Requirements Engineering. Whilst I have no direct experience of t ...

Tips for Setting Up Your First Business Website

To attract all potential customers to your business you need a presence on the web. The problem is that if you haven't set up a website before, you p ...

What's Good about LISP?

LISP is a general-purpose programming language and is the second-oldest programming language still in use, but how much do you know about it? Did you ...

Open Source Tools for Developers: Why They Matter

From a developer's point of view use of open-source tools has advantages beyond the obvious economic ones. With the open-source database MySQL in mind ...

Top Java Tips: Apache Ant

Discuss this article >>>

Ant Books

cover cover cover

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>

Discuss this article >>>


Simon White