Automate your build process using Java and Ant

Introducing the powerful XML-based scripting tool, Ant

A defined process is one of the most necessary but often least-used tools in software development. It is by nature an overhead task that accompanies a development effort. A defined build process ensures that the software in your development project is built in the exact same manner each time a build is executed. As the build process becomes more complex -- for example, with EJB builds or additional tasks -- it becomes more necessary to achieve such standardization. You should establish, document, and automate the exact series of steps as much as possible.

Why do I need a defined build process?

A defined build process is an essential part of any development cycle because it helps close the gap between the development, integration, test, and production environments. A build process alone will speed the migration of software from one environment to another. It also removes many issues related to compilation, classpath, or properties that cost many projects time and money.

What is Ant?

Ant is a platform-independent scripting tool that lets you construct your build scripts in much the same fashion as the "make" tool in C or C++. You can use a large number of built-in tasks in Ant without any customization. Some of the most important tasks are shown in the following table but explained in more detail in the example that follows.

Here are some useful commands that are built in the Ant distribution.

CommandDescription
AntUsed to execute another ant process from within the current one.
CopydirUsed to copy an entire directory.
CopyfileUsed to copy a single file.
CvsHandles packages/modules retrieved from a CVS repository.
DeleteDeletes either a single file or all files in a specified directory and its sub-directories.
DeltreeDeletes a directory with all its files and subdirectories.
ExecExecutes a system command. When the os attribute is specified, then the command is only executed when Ant is run on one of the specified operating systems.
GetGets a file from an URL.
JarJars a set of files.
JavaExecutes a Java class within the running (Ant) VM or forks another VM if specified.
JavacCompiles a source tree within the running (Ant) VM.
Javadoc/Javadoc2Generates code documentation using the javadoc tool.
MkdirMakes a directory.
PropertySets a property (by name and value), or set of properties (from file or resource) in the project.
RmicRuns the rmic compiler for a certain class.
TstampSets the DSTAMP, TSTAMP, and TODAY properties in the current project.
StyleProcesses a set of documents via XSLT.

While other tools are available for doing software builds, Ant is easy to use and can be mastered within minutes. In addition, Ant lets you create expanded functionality by extending some of its classes. I will show this expansion in a following example.

What do I need to use Ant?

You must install three components on your machine to run Ant: JDK, XML parser, and Ant (see Resources for links).

In many cases, the XML parser is part of the lib files distributed with the Servlet runner or the Web Server. If not, the free XML parser from java.sun.com is sufficient.

Ant installation consists of downloading the files, adding the class libraries to the classpath, and adding the Ant binaries to the path.

Example scenario

This example scenario should help show you the value of Ant and provide insight into its benefits and how you can use it.

Because a large amount of the current Java development is focused on server-side Java, I have chosen a server-side application for the example. Developers working on server-side Java applications are typically interested in the compilation of servlets, deployment of JSP files, and deployment of HTML files, configuration files, or images.

A common scheme for doing this build would involve the development of small scripts in platform-specific languages based on the server's operating system. For example, a developer working on an NT machine could create a batch file that performs the compilation tasks and then runs the deployment. However, if the production environment had Unix or Linux, the developer would have to rewrite the script, ensuring that the scripts were in sync.

OK, show me how this works

So, I've hopefully convinced you of the need to use Ant and shown how simple it is to install. Now I'll show you how simple Ant is to use by stepping through an example that performs simple compilation and deployment.

Simple build process with Ant (simple.xml)

<project name="simpleCompile" default="deploy" basedir=".">
   <target name="init">
      <property name="sourceDir" value="src"/ >
      <property name="outputDir" value="classes" />
      <property name="deployJSP" value="/web/deploy/jsp" />
      <property name="deployProperties" value="/web/deploy/conf" />
   </target>
   <target name="clean" depends="init">
      <deltree dir="${outputDir}" />
   </target>
   <target name="prepare" depends="clean">
      <mkdir dir="${outputDir}" />
   </target>
   <target name="compile" depends="prepare">
     <javac srcdir="${sourceDir}" destdir="${outputDir}" />
   </target>
   <target name="deploy" depends="compile,init">
     <copydir src="${jsp}" dest="${deployJSP}"/>
     <copyfile src="server.properties" dest="${deployProperties}"/>
   </target>
</project>

There's a lot to explain in the above example. First, you should understand the structure of the simple.xml file. It is a well-formatted XML file containing a project entity that is comprised of several target entities.

The first line contains information about the overall project that is to be built.

<project name="simpleCompile" default="deploy" basedir=".">

The most important elements of the project line are the default and the basedir.

The default attribute references the default target that is to be executed. Because Ant is a command-line build tool, it is possible to execute only a subset of the target steps in the Ant file. For example, I could perform the following command:

% ant -buildfile simple.xml init

That will execute the ant command and run through the simple.xml file until the init target is reached. So, in this example, the default is deploy. The Ant process invoked in the following line will run through the simple.xml file until the deploy command is reached:

% ant -buildfile simple.xml

The basedir attribute is fairly self-explanatory as it is the base directory from which the relative references contained in the build file are retrieved. Each project can have only one basedir attribute so you can choose to either include the fully qualified directory location or break the large project file into smaller project files with different basedir attributes.

The next line of interest is the target line. Two different versions are shown here:

   <target name="init">
   <target name="clean" depends="init">

The target element contains four attributes: name, if, unless, and depends. Ant requires the name attribute, but the other three attributes are optional.

Using depends, you can stack the Ant tasks so that a dependent task is not initiated until the task that it depends on is completed. In the above example, the clean task will not start until the init task has completed. The depends attribute may also contain a list of comma-separated values indicating several tasks that the task in discussion depends on.

The if and unless commands let you specify commands that are to be performed either if a certain property is set or unless that property is set. The if will execute when the property value is set, and the unless will execute if the value is not set. You can use the available command to set those properties as shown in a following example, or you can set them via the command line.

The init target from the simple example contains four lines of property commands as shown here:

      <property name="sourceDir" value="src" />

These property lines let you specify commonly used directories or files. A property is a simple name value pair that allows you to refer to the directory or file as a logical entity rather than a physical one.

If you wanted to reference the sourceDir variable later in the Ant file, you could simply use the following syntax to alert Ant to obtain the value for this tag: ${sourceDir}.

Two other commands present in the above buildfile are:

      <deltree dir="${ outputDir }" />
      <mkdir dir="${ outputDir }" />

These commands are used to ensure that there are no extraneous files in the outputDir (or classes directory when dereferenced as mentioned above). The first command removes the entire tree contained under the outputDir. The second command creates the directory again.

The last line of major interest to the developer is the following compilation line:

     <javac srcdir="${sourceDir}" destdir="${outputDir}" />

The javac command requires a source directory (the input location of the .java files) and a destination directory (the output location of the .classes file). It is important to note that all directories must either exist prior to the running of the ant command or be created using the mkdir command. Ant does not create directories based upon intuition, so you must create the outputDir, using the mkdir command prior to the compilation step above.

After the compile task has completed, the deploy task will perform the copy operation to move all JSP files from the source directory to a deployment directory. By using the copydir command, you copy the entire JSP directory from one location to another. I used the copyfile command to copy a single properties file as part of the build.

While it took several lines to explain the example, it should be evident that Ant is an easy-to-use tool. Using this buildfile as a starting point, you should be able to incorporate Ant into your development effort. The ant commands shown in the above example have further functionality, some of which will be discussed in this article, the remainder is left to you along with references to the documentation.

Important tasks

It is left to you to read through the built-in tasks included in the Ant distribution. See the user guide in Resources for information about each command. I have chosen two commonly used commands as examples of additional options available to the build manager without any customization.

Compiling code (including EJBs)

In the simple example discussed earlier, you saw a simple form of the javac command. Now, if you examine it in more detail, you see that you can specify the compilation flags such as deprecation, debug, or optimize as well as the files that will or will not be included in the compilation.

    <javac srcdir="${src.dir}"
           destdir="${build.classes}"
           classpath="${classpath}"
           debug="on"
           deprecation="off"
           optimize="on" >
      <include name="**/*.java"/>
      <exclude name="**/Script.java" unless="bsf.present" />
      <exclude name="**/version.txt" />
    </javac>

You can use the include/exclude entities inside the javac task to include/exclude files matching the pattern in the name attribute from the compilation. From the above example, you want to include files contained in any directory ending in .java but, at the same time, you want to exclude files named Script.java unless a property bsf.present is set to true.

You set the bsf.present property using the following task that searches the classpath for the classname specified and sets bsf.present according to the search results:

    <available property="bsf.present" classname="com.ibm.bsf.BSFManager" />

The javac command will not include files called version.txt from the compilation based upon the exclude command above.

Generating javadoc

Another task that Ant can help automate is the generation of javadoc. You can use the following command to generate the javadoc:

    <javadoc packagenames="${packages}"
             sourcepath="${basedir}/${src.dir}"
             destdir="${build.javadocs}"
             author="true"
             version="true"
             windowtitle="${Name} API"
             doctitle="${Name}"
             bottom="Copyright © 2000 GroupServe. All Rights Reserved."
    />

The packages specify the overall packages that the javadoc will include. The sourcepath attribute points towards the location of the source files. The javadoc command also provides attributes allowing you to specify the title of the window and the document. You can also include a copyright notice at the bottom of each javadoc page, using the bottom attribute.

Can Ant do XYZ?

At this point, you have seen some of the possible tasks in your build process that Ant can automate. Those tasks are included out of the box in Ant. You might want to customize Ant to help you perform some more difficult tasks such as building EJBs and performing remote configuration management. Some of you may want to increase Ant's reporting capabilities or construct a user interface that can run the Ant process.

The simple answer to the question "Can Ant do XYZ?" is "Yes, but you may have to customize it."

Extending Ant

Two Ant extensions are interesting to discuss at this point. They are increased reporting and the ability to distribute code remotely using Ant.

Reporting enhancements

If you wanted to extend Ant's functionality to provide notification when certain steps in the build process are completed or are in progress, you can create a class to listen to the Ant process as shown in the following example.

You can create a class that implements the BuildListener interface. Using this class, you can catch each event that is part of the Listener:

    public void buildStarted(BuildEvent event);
    public void buildFinished(BuildEvent event);
    public void targetStarted(BuildEvent event);
    public void targetFinished(BuildEvent event);
    public void taskStarted(BuildEvent event);
    public void taskFinished(BuildEvent event);
    public void messageLogged(BuildEvent event);

The BuildEvent event object contains the following methods by which you can obtain information about the current status of the build:

    public Project getProject() ;
    public Target getTarget() ;
    public Task getTask();
    public String getMessage();
    public int getPriority();
    public Throwable getException();

So if you wanted to write a reporting tool, you need only create a class that implements the BuildListener interface and process the BuildEvents as needed by your design. Because Ant initiates the Java classloader, you must specify the listener as part of your command line arguments. For example:

ant -listener org.apache.tools.ant.XmlLogger

This listener is included with the Ant distribution and outputs an XML representation of the build process to a file called "log.xml".

Multiple machines

The above example shows how to extend the functionality of build reporting. It is of more interest to show how to extend the functionality of the build itself. For that, I've chosen to work through an example in which two files are copied from one machine to another machine, which then performs the Ant operation.

To do that, you can extend Ant by creating custom Task objects for the remote copy and remote ant commands. Here's an explanation of the remote copy task definition.

The RemoteTask object extends the Task object. The RemoteTask object performs all of the functionality necessary for the maintenance of the connection between the local and remote machines. In this case, the connection is a socket-level connection.

The RemoteTask object contains the following method declaration that is necessary for any object that is to extend the Task object:

      public void execute() throws BuildException

The Ant processor calls this method after all of the attributes have been set. Any custom Task object must override this method.

The RemoteCopyTask performs the steps required to execute the remote copy operation. The copy operation is on the local machine and transfers files from the local machine to the remote machine. Some key things to notice in the RemoteCopyTask code are the three accessor methods that allow the creator of the Ant buildfile to set the name, directory, and file type of the file to be transferred.

The RemoteCopyTask, which is run on the local machine, creates a Command object. The Command object loads the file into a byte array to prepare for the transfer to the server. On execute, this command object is serialized and passed to the remote machine.

The RemoteAntHandler object receives the Command object from the ObjectInputStream. The RemoteAntHandler then deserializes the object and determines what command to execute. At this point, I have simplified the example and included both commands in the same handler as different branches of the if statement. Ideally, another framework would let the server process those commands more efficiently.

Because the received command is a copy command, the handler will write the file to disk with the filename and directory as specified in the Command object.

In addition to the remote copy command, I have included a remote ant command. In this case, the local machine can execute the ant command on the remote machine.

I use the RemoteAntTask, which again extends the RemoteTask object. The RemoteAntTask simply sets the command to Ant. Future expansions of this task include the addition of a buildfile specification and additional functionality contained in the original ant command itself.

When the RemoteAntHandler object receives and then deserializes the Command object, it will determine that it should invoke the ant command. That is handled by having the server spawn another process calling ant. Why spawn another process? Due to the architecture of the Ant code, it's not currently possible to call ant and maintain the JVM. So I've spawned another process, using the RunProcess object. Please note that the script that invokes the RemoteServer specifies some command line arguments such as deployment directory. That was done to limit the potential harm that an errant buildfile could cause on the remote machine.

The last step in extending Ant to perform the remote commands is to incorporate these commands in the buildfile. The following buildfile includes the new commands:

<project name="foo" default="ant" basedir=".">
  <target name="init" >
      <taskdef name="remoteCopy" classname="local.RemoteCopyTask"/>
      <taskdef name="remoteAnt" classname="local.RemoteAntTask"/>
  </target>
  <target name="deploy" depends="init2">
      <remoteCopy machine="machinename.groupserve.com" port="9090"
directory="e:\deve\ant\article\deploy" filetype="text"
filename="build.xml" />
      <remoteCopy machine="machinename.groupserve.com" port="9090"
directory="e:\deve\ant\article\deploy" filetype="binary"
filename="app.jar" />
  </target>
  <target name="ant" depends="deploy">
      <remoteAnt machine="machinename.groupserve.com" port="9090" />
  </target>
</project>

Here's the first line of interest:

      <taskdef name="remoteCopy" classname="local.RemoteCopyTask" />

This line creates a taskdef task that associates the name remoteCopy with the class contained in the file local.RemoteCopyTask. From this point forward, I can call the task remoteCopy, and my new code will be executed. For example:

      <remoteCopy machine="machinename.groupserve.com" port="9090"
directory="e:\deve\ant\article\deploy" filetype="text"
filename="build.xml"/>

The remoteCopy task will execute the RemoteCopyTask object and in doing so, connect to the machine/port specified and copy the file over. That task includes properties identifying the server/port of the RemoteAntServer. Also, please note that the three file-related properties correspond with the accessor methods contained in the RemoteCopyTask object. Those values are converted into calls to those methods.

The remoteAnt task has been defined by using a similar taskdef object:

      <taskdef name="remoteAnt" classname="local.RemoteAntTask"/>

The following task will execute the ant command on the remote machine:

      <remoteAnt machine="machinename.groupserve.com" port="9090" />

Again, note that several command line tags are given when the server starts to set up the Ant task.

To run the example, please perform the following steps:

  1. Use the Ant buildfile build.xml jar to build the article code on the local machine
  2. Copy the jar file to the remote machine
  3. Execute the remote.bat (or rework for a Linux/Unix machine on the remote machine)
  4. Edit the build.xml file to use machine names and ports appropriate to your environment
  5. Use the Ant buildfile build.xml to execute the build and deploy functionality on the local machine

If you've performed the above steps, you should notice that the jar file and build file have been transferred to the remote machine and that the Ant task has been performed on the remote machine.

Conclusion

So, you've read through all of this. What have you learned?

The biggest takeaway from this article should be the importance of a build process to construct your environment in an effective and efficient manner. With that understanding, it is less important that you use Ant or some other homegrown scripting mechanism. However, I feel that Ant is an easy-to-learn platform-independent tool that provides expansion as needed. The XML involved in the buildfile is easy to read and understand, and a large number of already supported commands perform the vast majority of your build tasks without expansion. If you find a limitation, you can expand Ant to include your modifications.

Please review the Ant user guide found in Resources below to expand your understanding of the predefined tasks included with Ant. I hope I have given you a starting point for your future investigation of Ant and have inspired you to incorporate this tool into your development process.

Michael Cymerman is director of research and development at GroupServe, a Washington, DC-based telecommunications firm that creates Internet-based applications to facilitate group communication.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more