Section 4. Building a simple Java project
Building a simple Java project introduction
Now that we have seen the format of Ant build files, and learned how to define properties and dependencies and how to run Ant, we are ready to construct a build environment for a basic Java project. This will involve learning the Ant tasks for compiling source code and assembling JAR files.
Compiling source code
As Ant is primarily aimed at building Java applications, it should come as no surprise that it has excellent built-in support for invoking the javac compiler and other Java-related tasks. Here’s how to write a task that compiles some Java code:
This tag looks for all files ending in .java in the src directory and invokes the javac compiler on them, generating the classfiles in the same directory. Of course, it is usually cleaner to put the classfiles under a separate directory structure; you can have Ant do this by adding a destdir attribute. Other useful attributes:
classpath: Equivalent to the -classpath option on javac.
debug=”true”: Indicates to the compiler that the source files should be compiled with debug information.
An important characteristic of the javac task is that it will only compile those source files that it believes it needs to. If a classfile already exists, and that classfile’s corresponding source file hasn’t been changed since the classfile was generated, then that source file won’t be recompiled. The output of the javac task shows the number of source files that were actually compiled. It is good practice to write a clean target that removes any generated classfiles from that target directory. This can then be used if you want to make sure that all of the source files are compiled. This behavior characterizes many of Ant’s tasks: If the task can determine that the requested operation doesn’t need to be performed, then it will be skipped.
Like Ant, the javac compiler is itself implemented in the Java language. This is used to good advantage by the javac task in Ant, because it usually invokes the compiler classes in the same Java virtual machine (JVM) in which Ant itself is running in. Other build tools would typically need to launch a new javac process each time Java code needed to be compiled, which would in turn require a new JVM instance. But with Ant, only a single JVM instance is required, both to run Ant itself and to perform all of the required compilation tasks (along with other related tasks, such as manipulating JAR files). This is a far more efficient use of resources and can lead to greatly improved build times.
As we saw in the previous section, the default behavior of the Ant javac task is to invoke the standard compiler of whichever JVM is being used to run Ant itself, and to invoke that compiler in-process (that is, without launching a separate JVM). However, there may be times when you want the compiler to be invoked separately — when you wish to specify certain memory options to the compiler, for instance, or when you need to use a different level of the compiler. To achieve this, simply set javac ‘s fork attribute to true, like this:
And if you want to specify a different javac executable, and pass a maximum memory setting to it, you would do something like this:
You can even configure Ant to use a different compiler altogether. Supported compilers include the open source Jikes compiler and the GCJ compiler from the GNU Compiler Collection (GCC). (See Resources for information on these two compilers.) These can be specified in two ways: either by setting the build.compiler property, which will apply to all uses of the javac task, or by setting the compiler attribute in each javac task as required.
Creating a JAR file
After Java source files have been compiled, the resulting classfiles are typically packaged into a JAR file, which is similar to a zip archive. Every JAR file contains a manifest file that can specify properties of the JAR file.
Here’s a simple use of the jar task in Ant:
This creates a new JAR file called package.jar and adds all the files in the classes directory to it (JAR files can contain any type of file, not just classfiles). In this case, no manifest file was specified, so Ant will provide a basic one.
The manifest attribute allows you to specify a file to use as the manifest for the JAR file. The contents of a manifest file could also be specified within the build file using the manifest task. This task can write a manifest file to the filesystem, or can actually be nested inside the jar task so that the manifest file and JAR file are created all in one go. For example:
It is often desirable in a build environment to use the current time and date to mark the output of a build in some way, in order to record when it was built. This might involve editing a file to insert a string to indicate the date and time, or incorporating this information into the filename of the JAR or zip file.
This need is addressed by the simple but very useful tstamp task. This task is typically called at the start of a build, such as in an init target. No attributes are required, so just is sufficient is many cases.
The tstamp task doesn’t produce any output; instead, it sets three Ant properties based on the current system time and date. Here are the properties that tstamp sets, an explanation for each, and examples of what they might be set to:
Property Explanation Example
DSTAMP This is set to the current date, the default format being yyyymmdd 20031217
TSTAMP This is set to the current time, the default format being hhmm 1603
TODAY This is set to a string describing the current date, with the month written in full December 17 2003
For example, in the previous section we created a JAR file as follows:
After having called the tstamp task, we could name the JAR file according to the date, like this:
So, if this task were invoked on December 17, 2003, the JAR file would be named package-20031217.jar.
The tstamp task can also be configured to set different properties, apply an offset before or after the current time, or format the strings differently. All of this is done using a nested format element, as follows:
The listing above sets the OFFSET_TIME property to a time in hours, minutes, and seconds that is 10 minutes after the current time.
The characters used to define the format strings are the same as those defined by the java.text.SimpleDateFormat class.
Putting it all together
The previous sections have given us enough knowledge to build a simple Java project. We will now assemble the code snippets into a complete build file that compiles all the source code under a src directory, puts the resulting classfiles under a build directory, and then packages everything up into a JAR file in a dist directory, ready for distribution. In order to try this out for yourself, all you need is a src directory containing one or more Java source code files — anything from a simple “Hello World” program to a large number of source files from an existing project. If you need to add JAR files or anything else to the Java classpath in order to successfully compile your source code, simply add a classpath attribute for it in the javac task.
The build file looks like this:
A simple Java project
And here is some sample output from a build that was run with that file (yours may vary, depending on the contents of your src directory):
[mkdir] Created dir: E:\tutorial\javaexample\build
[mkdir] Created dir: E:\tutorial\javaexample\dist
[javac] Compiling 10 source files to E:\tutorial\javaexample\build
[jar] Building jar: E:\tutorial\javaexample\dist\package-20031217.jar
[jar] Building jar: E:\tutorial\javaexample\dist\package-src-20031217.jar
Total time: 5 seconds
Notice that the JAR file is named according to the current date, and that a manifest entry is set for the main class of the application, so that it can be launched directly with a command along the lines of java -jar package-20031217.jar. We also create a JAR file consisting of just the source code of the project.