Ant: the defacto standard
All of these issues with make irritated a Java programmer named James Duncan Davidson enough to cause him to create Ant as an open-source tool that migrated to the Apache project at https://jakarta.apache.org/ant. This site contains the full download including the Ant executable and documentation. Ant has grown and improved until it is now generally accepted as the defacto standard build tool for Java projects.
To make Ant cross-platform, the format for the project description files is XML (covered in Thinking in Enterprise Java). Instead of a makefile, you create a buildfile, which is named by default build.xml (this allows you to just say ‘ant’ on the command line. If you name your buildfile something else, you have to specify that name with a command-line flag).
The only rigid requirement for your buildfile is that it be a valid XML file. Ant compensates for platform-specific issues like end-of-line characters and directory path separators. You can use tabs or spaces in the buildfile as you prefer. In addition, the syntax and tag names used in buildfiles result in readable, understandable (and thus, maintainable) code.
On top of all this, Ant is designed to be extensible, with a standard interface that allows you to write your own tasks if the ones that come with Ant aren’t enough (however, they usually are, and the arsenal is regularly expanding).
Unlike make, the learning curve for Ant is reasonably gentle. You don’t need to know much in order to create a buildfile that compiles Java code in a directory. Here’s a very basic build.xml file, for example, from Chapter 2 of this book:
<?xml version="1.0"?>
<project name="Thinking in Java (c02)"
default="c02.run" basedir=".">
<!-- build all classes in this directory -->
<target name="c02.build">
<javac
srcdir="${basedir}"
classpath="${basedir}/.."
source="1.4"
/>
</target>
<!-- run all classes in this directory -->
<target name="c02.run" depends="c02.build">
<antcall target="HelloDate.run"/>
</target>
<target name="HelloDate.run">
<java
taskname="HelloDate"
classname="HelloDate"
classpath="${basedir};${basedir}/.."
fork="true"
failonerror="true"
/>
</target>
<!-- delete all class files -->
<target name="clean">
<delete>
<fileset dir="${basedir}" includes="**/*.class"/>
<fileset dir="${basedir}" includes="**/*Output.txt"/>
</delete>
<echo message="clean successful"/>
</target>
</project>
The first line states that this file conforms to version 1.0 of XML. XML looks a lot like HTML (notice the comment syntax is identical), except that you can make up your own tag names and the format must strictly conform to XML rules. For example, an opening tag like <project must either end within the tag at its closing angle brace with a slash (/>) or have a matching closing tag like you see at the end of the file (</project>). Within a tag you can have attributes, but the attribute values must be surrounded in quotes. XML allows free formatting, but indentation like you see here is typical.
Each buildfile can manage a single project described by its <project> tag. The project has an optional name attribute that is used when displaying information about the build. The default attribute is required and refers to the target that is built when you just type ant at the command line without giving a specific target name. The directory reference basedir can be used in other places in the buildfile.
A target has dependencies and tasks. The dependencies say “which other targets must be built before this target can be built?” You’ll notice that the default target to build is c02.run, and the c02.run target says that it in turn depends on c02.build. Thus, the c02.build target must be executed before c02.run can be executed. Partitioning the buildfile this way not only makes it easier to understand, but it also allows you to choose what you want to do via the Ant command line; if you say ‘ant c02.build,’ then it will only compile the code, but if you say ‘ant co2.run’ (or, because of the default target, just ‘ant’), then it will first make sure things have been built, and then run the examples.
So, for the project to be successful, targets c02.build and c02.run must first succeed, in that order. The c02.build target contains a single task, which is a command that actually does the work of bringing things up-to-date. This task runs the javac compiler on all the Java files in this current base directory; notice the ${} syntax used to produce the value of a previously-defined variable, and that the orientation of slashes in directory paths is not important, since Ant compensates depending on the operating system you run it on. The classpath attribute gives a directory list to add to Ant’s classpath, and source specifies the compiler to use (this is actually only noticed by JDK 1.4 and beyond). Note that the Java compiler is responsible for sorting out the dependencies between the classes themselves, so you don’t have to explicitly state inter-file dependencies like you must with make and C/C++ (this saves a lot of effort).
To run the programs in the directory (which, in this case, is only the single program HelloDate), this buildfile uses a task named antcall. This task does a recursive invocation of Ant on another target, which in this case just uses java to execute the program. Note that the java task has a taskname attribute; this attribute is actually available for all tasks, and is used when Ant outputs logging information.
As you might expect, the java tag also has options to establish the class name to be executed, and the classpath. In addition, the
fork="true"
failonerror="true"
attributes tell Ant to fork off a new process to run this program, and to fail the Ant build if the program fails. You can look up all the different tasks and their attributes in the documentation that comes with the Ant download.
The last target is one that’s typically found in every buildfile; it allows you to say ant clean and delete all the files that have been created in order to perform this build. Whenever you create a buildfile, you should be careful to include a clean target, because you’re the person who typically knows the most about what can be deleted and what should be preserved.
The clean target introduces some new syntax. You can delete single items with the one-line version of this task, like this:
<delete file="${basedir}/HelloDate.class"/>
The multiline version of the task allows you to specify a fileset, which is a more complex description of a set of files and may specify files to include and exclude by using wildcards. In this example, the filesets to delete include all files in this directory and all subdirectories that have a .class extension, and all files in the current subdirectory that end with Output.txt.
The buildfile shown here is fairly simple; within this book’s source code tree (which is downloadable from www.BruceEckel.com) you’ll find more complex buildfiles. Also, Ant is capable of doing much more that what we use for this book. For the full details of its capabilities, see the documentation that comes with the Ant installation.
Ant extensions
Ant comes with an extension API so that you can create your own tasks by writing them in Java. You can find full details in the official Ant documentation and in the published books on Ant.
As an alternative, you can simply write a Java program and call it from Ant; this way, you don’t have to learn the extension API. For example, to compile the code in this book, we need to verify that the version of Java that the user is running is JDK 1.4 or greater, so we created the following program:
//: com:bruceeckel:tools:CheckVersion.java
// {RunByHand}
package com.bruceeckel.tools;
public class CheckVersion {
public static void main(String[] args) {
String version = System.getProperty("java.version");
char minor = version.charAt(2);
char point = version.charAt(4);
if(minor < '4' || point < '1')
throw new RuntimeException("JDK 1.4.1 or higher " +
"is required to run the examples in this book.");
System.out.println("JDK version "+ version + " found");
}
} ///:~
This simply uses System.getProperty( ) to discover the Java version, and throws an exception if it isn’t at least 1.4. When Ant sees the exception, it will halt. Now you can include the following in any buildfile where you want to check the version number:
<java
taskname="CheckVersion"
classname="com.bruceeckel.tools.CheckVersion"
classpath="${basedir}"
fork="true"
failonerror="true"
/>
If you use this approach to adding tools, you can write them and test them quickly, and if it’s justified, you can invest the extra effort and write an Ant extension.