Ant build process mini how-to

An automated build process for anything but the most trivial set of source files is absolutely critical to the typical software project. This was a prime consideration for early Unix developers as operating system and application projects grew larger, involved more developers, and encompassed more life cycles. From this, the ubiquitous make utility was born. For years, make has reigned supreme as the de facto, standard, automated build facility. For quite some time, make has held steadfast in this role, even for use in Java-centric development efforts. Despite a continued synergy between tried-and-true make and Java projects, a disparity has emerged. Make, although almost universally available on countless varieties of Unix platforms, is not necessarily available on every Java-compatible platform. It is exactly this quandary from which Ant, a Java- and XML-based build tool from Apache's Jakarta project, has emerged.

What follows is a discussion regarding automated build philosophy in general and Ant specifically. I will provide some background from my personal experience in terms of automated build schemes, as well as a simple, step-by-step example of a typical Ant build file. At the conclusion of this article, readers should have a general understanding of Ant and (hopefully) a curiosity to evaluate this utility on their own. My intent is to neither vilify any particular build mechanism nor to sanctify Ant. I do, however, want to emphasize the need to evaluate a particular tool in terms of the problem it is being asked to solve.

The motivation to use a robust, reliable build environment is easily seen in any non-trivial IT project. With an overly complicated or error-prone compilation and assembly process, software projects suffer relentlessly. How often do we download software and attempt execution on the build script only to be assaulted with compilation and system errors galore? And how do you then start to unwind the mess? For me, it's usually a trip to the "delete" key to trash the freshly downloaded software. IT projects with multiple developers suffer a similar fate, but without the luxury of the "delete key" option. Oftentimes, project builds are folklore between developers, handmade scripts of unending variety, or proprietary IDEs utilizing their own "special sauce" to accomplish the task at hand.

I once heard the nightly build of an application described as "The heartbeat of the project." This concept is important because a regular, reliable, and consistent build of the entire application is the first step in quality assurance. If it doesn't build, it certainly won't execute. A comprehensive, script-based mechanism validates the software bits-and-pieces, and can reliably be pulled out of version control, assembled, and even unit tested on a regular basis. Some organizations even go to the extreme of having an Ant-based system build, unit test, deploy, and transmit results via e-mail and/or a Web browser as files are inserted into the version-control environment. Ant can provide an extremely flexible mechanism in which this sophistication can thrive.

A Brief Comparison of Automated Build Tools
As mentioned, Unix developers have known for years the importance of a mechanism that supports repeatable, target-oriented builds such that consistency and ubiquity are achieved in the build process. Ant fulfills these goals with several notable improvements to its Unix cousin (make).

Ant's reliance on the Extensible Markup Language (XML) provides for parsing and document validation of the build script. Ant's XML heritage provides for concise, clear, diagnostic information. Generally, the error diagnostics received from an ill-fated Ant build are clearer and more easily understood than a corresponding make error. In particular, a spurious space or a misplaced tab in a makefile is disastrous and hard to diagnose. In contrast, Ant can easily catch malformed commands and statements and provide clear line number/problem diagnostics, owed in part to the nature of XML and document validation.

Ant is not tied to a particular platform. Nor do you have to rewrite build scripts for the various environments and operating systems you wish to build on. It can be argued that this is a virtue of Java and not Ant specifically, which is true to a certain degree. Java's ability to universally compile (and execute) on multiple platforms only addresses trivial compilation scenarios. It is of little effort to produce OS-specific scripts to compile a basic HelloWorld.java application; however, when version control, EJB compilation, dependency checking, and deployment come into play, the need for an OS-independent build mechanism becomes clear.

IDEs to the Rescue?
Today's marketplace is chock full of very advanced, sophisticated development environments that pack countless handy features. Among source control integration, visual development tools, and other assorted goodies lurk deceptively simple mechanisms for a developer to target and build directories full of Java code. With a click of a dialog box, a developer can turn debugging on and off, optimize compiles, shoot out .jar files, and even step through code and examine variables. All these features are without question indispensable lifesavers to the corporate developer.

With most IDEs hiding the complexities of the build process, it is often simply a matter of specifying what directories a developer wishes to include in a particular build. The IDE takes over the chore of building the target, often having the intuition to know an EJB compiler needs to be invoked and even going so far as to actually deploy the EJB or servlet as the case may be. This scenario seems to be the most promising until one considers that not all developers use the same IDE. In fact, there are those among us that choose to use no IDE at all and those that alternatively choose a simple text editor (i.e., vi, emacs, TextPad, etc.). Although the decision of whether to use an IDE or not can certainly be remedied through corporate policy, it is often difficult (if not impossible) for an IDE to create a comprehensive multi-tiered, multi-component build of an application that spans multiple operating systems whose sources may reside on different machines in various archives.

The problem is that IDEs have a tendency to be very developer-centric and not particularly friendly outside their confined haven. Proprietary build schemes do little to alleviate the situation, and often lend to its complexities. IDEs providing build mechanisms that can only be executed within the context of the IDE restrict flexibility in the build process, and often make the introduction of automated procedures, extensions, or customizations to the build process impossible.

Having Your Cake and Eating It, Too
The most recent version of Ant tries to address IDEs, specifically those IDE vendors who open up their environment through the use of plug-ins. Currently, Ant has subprojects that allow integration with some popular IDEs—JBuilder, Forté, VisualAge—and there is an effort afoot to produce a visual front end to Ant dubbed "Antidote." The important lesson here is that IDEs, while indispensable, are only part of the solution. We still must have a build mechanism that lives outside the IDE, but there is no reason why you can't have a scripted build process that is also integrated within the IDE.

Let's look at the pros and cons of Ant vs. alternate build schemes. In many respects, Ant has some clearcut advantages over other build schemes. For example:

  • Ant is available on any platform that provides Java and XML parser support.
  • Ant's XML heritage allows for a wide variety of tools to create build scripts from simple editors (i.e., notepad, vi, emacs) as well as from more XML-centric tools (XML-notepad, XML spy, etc.).
  • Ant allows for project-wide builds of many different types of components, across many OS types.
  • Debugging build scripts is fairly easy.
  • Ant has an excellent track record from some fairly complex open-source projects (Apache projects, JBoss application server).
  • Good results are achieved when used with multitiered projects that have multiple deployable components.
  • Ant integrates directly with JUnit and CVS for unit testing and version control.
  • Ant is highly configurable, easily automated, and extendible.
In the interest of fairness, however, Ant also has some notable detractions:
  • It lacks support for incorporating OS-specific environment variables in the build, as this construct is not portable and not supported directly by Java.
  • The Ant-specific GUI/front end is still in its development phase, and there is only limited support in leading IDEs.
  • Ant is very Java-centric, so it would not be my first choice for C/C++, Smalltalk, or the like. Although it is certainly possible to use Ant to build non-Java projects, there is a notable bias toward Java.
As the Ant documentation is fond of saying: "Ant is a Java-based tool. In theory, it is kind of like make without the wrinkles."

Ant Basics: Installation and Running
Now that you've (hopefully) been convinced that scripted builds are a good thing and that Ant should be considered for your next Java project, let's examine how Ant is installed on one popular operating system. In general, the Ant package comes as a binary release or source. Unless you are curious or want to noodle around, it is best to get the binary image. Unzipping the zip file yields all the required jars, docs, and binary files. For my installation on a WinTel-2K box, I chose the following as my installation directory:


C:\JavaPkgs
This yields a fully qualified install path of:


C:\JavaPkgs\Jakarta-ant-1.3
The environment should be configured in the following manner:


MyComputer->Properties
Go to the "Advanced" tab
Click "Environment Variables"
Add the following to your environment:
ANT_HOME=c:\JavaPkgs\Jakarta-ant-1.3
JAVA_HOME=c:\jdk1.3
PATH=%PATH%;%ANT_HOME%\bin
I chose to modify my environment properties, but I could have created a small .cmd file to accomplish setting environmental variables in the context of a single command shell. However, I use Ant to a sufficient degree that the latter is more effective for my situation. Note: It is important to set the JAVA_HOME variable, as Ant needs resolution of this in order to call tools such as javac, java, etc.

Test the installation by typing:


C:\>ant
This should yield the following:


Searching for build.xml ...
Could not locate a build file!
C:\>
If all is not well at this point, please review the path settings and verifying that the path provided points to the location where the Ant package resides.

You should now have a working version of Ant installed on your computer. Next, let's examine the anatomy of a simple build.xml file.

Project Directory Structure, Naming, and Convention
Ant looks for a file named build.xml in the current working directory. Typing "Ant" on the command line executes Ant. In the absence of a build.xml file, a different file can be specified with the -d option. A comprehensive list of command line options can be obtained by executing "Ant" with a -help command line switch.

Before we create our first build script, some thought should be given to the directory structure that comprises the project. Ant is best used with relative directory paths as opposed to fully qualified paths. The reason for this is twofold. First, it assures a more portable solution. You don't want to commit to DOS path names such as C:\MyProject\MySource only to have to convert to a Unix structure such as /usr/home/Peterb/MyProject/MySource. Ant abstracts path constructs in the same manner as Java, so it is prudent not to undo this benefit by inadvertently constructing an OS-dependent path. Second, you can gain a bit of latitude in where project components can live. OS-specific scripts can point to the base location of a project and the Ant facility can operate relative to the specified base location. This will be explained in a moment.

Figure 1 shows a snippet of a screen that depicts a hypothetical directory structure for an EJB component written for the JBoss container implementation.

Figure 1
Figure 1. Typical Ant source tree.

Some of these directories are actually generated by the build process ("dist" and "build," which are not shown), while the others are considered part of the source of the project. The Ant build script lives in antDemo/src/build. The base directory in this example would be described as C:\JavaPkgs\antDemo. The Ant build script will operate on components relative to this location.

We now have a background and a basis to actually build a project. As it stands, our project is constructing an EJB to be deployed in a JBoss EJB container. The project source is under /src/main. It is important to note that source files underneath the /src/main/app directory follow the package names associated with the different source files. For example, the interface com.utopiansoft.SomeBean.interfaces. SomeBean lives in the directory /src/main/app/com/utopiansoft/SomeBean/interfaces/SomeBean.java. This is significant because the dependency checking only works when source is stored in a manner that mirrors the package structure. In other words, if the Java package structure is not followed, compilation occurs every time the build is invoked. Consider yourself warned.

Anatomy of the build.xml File
We've now explored all the ground required up to the actual implementation of the build script. The biggest concept to grasp in Ant is the notion of targets. Targets can be specified when Ant is executed on the command line or internally in the build.xml file. Targets can also have dependencies. For example, you may want to ensure that source compilations are up-to-date prior to creating a .jar file. You do this through dependencies, which we'll explore in a minute. Right now, let's dissect the build.xml file that will build our "SimpleBean" EJB.

Consider the header of the build file:





We have a standard XML header followed by some preliminary project attributes. Most directives in Ant follow a name-value combination. Here, we declare the project name, "SimpleBean," and the default target. In this case, the default target is "dist." This tells Ant that, in the absence of any specific target being declared on the command line, it should execute the directives associated with the "dist" target. By design, all dependencies of "dist" will also be executed. Finally, we declare the base directory of the project, which is, as suggested, a relative path to the directory in which Ant was executed.

Now, let's declare more items we'll use in the build file:


   
   
   
   

   
   
   
   
To declare a property other than a built-in property, use the syntax. Here, we are declaring some properties related to directories that will be used during the build process. We are declaring a "name" property to name our .jar file-"build," "dist," "dist.lib," and "basedir.src.resources" all relate to directories for the different pieces of our project. The syntax ${SomeProperty} inserts the value of the specified property in place of the token. Unix or C/C++ folks can relate this to a macro expansion.

Moving through the build.xml file:


  
  

  
  
  
  
We can now see the class path being defined. Notice there is no reference to any system defined class path. It is recommended that you not pick up a class path from the environment for use in building an application, but rather opt for explicit declaration of the class path within the context of the build. You may, however, want to pass in some properties to the script at runtime. To accomplish this, use the -D option when invoking Ant. Hence, the directive:


C:\JavaPkgs\antDemo\src\build>ant
        -DSomeImportantProp="aValue"
build.xml
would make the property SomeImportantProp available inside the Ant environment. Essentially, it is equivalent to inside a build.xml file. Very handy.

It would now be prudent to declare some targets that are at the heart of the Ant build mechanism. As with make, there are some targets that have emerged as "best practices." These include targets such as "prepare," "dist," "compile," and "clean." All of these have been developed in this example, so let's look at them individually.

The "prepare" target is declared as follows:


   
   
   
   
      
      
      
      
      
      
   
In our case, the "prepare" target does some groundwork prior to launching into any sophisticated application assemblage. The target name is declared as "prepare" followed by the tasks that comprise this target. The "tstamp" directive sets the timestamp properties for the current project. Following the timestamp initialization are tasks that create the various directories needed in the build process. Notice the directories that are built are based on the nomenclature developed in previous sections of the build file. Once this target executes, we will be guaranteed the infrastructure required by the other tasks. By declaring this task a dependency of subsequent tasks, we provide an insurance policy that we have in place a proper directory structure on which to operate.

The Ant documentation includes a long list of native tasks. Some other examples of tasks include file copying, .jar file creation, directory deletion, class compilation, and the like. We'll see more examples of tasks in a moment. It is also important to note that this framework is extendable, as it allows programmers to develop their own customized tasks. This discussion is beyond this tutorial and I'll defer to the Ant documentation for a full explanation.

Here is the "compile" target:


   
   
   
   
      
      
   
This target does the actual work of compilation; notice a new construct in the "target name" element. It declares a dependency named "prepare," which should ring a bell. This tells the Ant environment it needs to invoke the target "prepare" prior to "compile." To illustrate this example, try the following:


C:\JavaPkgs\antDemo\src\build>ant compile
This should yield:


Buildfile: build.xml

prepare:
    [mkdir] Created dir: C:\JavaPkgs\antDemo\build
    [mkdir] Created dir: C:\JavaPkgs\antDemo\dist
    [mkdir] Created dir: C:\JavaPkgs\antDemo\dist\lib

compile:
      [javac] Compiling 4 source files to C:\JavaPkgs\ antDemo\build

BUILD SUCCESSFUL

Total time: 7 seconds
The thing to note here is that despite the "compile" target being specified, its dependency "prepare" was first executed followed by "compile." Thus, the dependency mechanism is a useful one for ensuring elements of the build process are executed in the proper sequence without a cumbersome syntax.

So far the build has executed flawlessly. We've had our directory structure built for us. We've invoked the java compiler and (hopefully) received no errors from our source file compilation. If you were diligent, you noticed the class files were dumped in the "build" directory off the base directory. This shouldn't be confused with the /src/build directory that contains the build.xml file. The aforementioned "build" directory was created in our "prepare" target, and the "compile" target then thoughtfully deposited the .class files in this directory structure for us. This is desirable and intentional, as any generated files should live outside of the source structure. This makes .jar file generation and version control activities much easier.

Now it is time for the "dist" target (see Listing 1 in our Code Section). Keep in mind this is our default target in the absence of specifying one during the invocation of Ant.

Things should once again be very familiar at this point. We do a lot of housekeeping in this target. Essentially, we are readying ourselves for the jar utility invocation by setting up the directories for the deployment descriptors, and copying those files out of the resources directory into the newly created META-INF directory. It is now time to call the .jar file utility, which is a native task of Ant. Notice the rich syntax available to describe which files are to be included in the .jar file. Anyone who has done this in a scripting language immediately recognizes the luxury of this terse syntax.

Finally, it is time for the final target in our build.xml file:


  
   
   
   
      
      
   
No mystery here. This is a hold back to my many crafted makefiles. It provides a nice mechanism for cleaning up the project and getting rid of all the generated files. After the execution of this target, you should have no intermediate files left in the project directories.

Notice that this target doesn't have any dependencies associated with it. It stands by itself, and the reason should be clear. There are no dependencies required to make this target execute properly. It also doesn't make much sense to have this be a dependency of any other target in our build script. So, for invocation of this target that will clean up all the generated output, specify the "clean" target on the command line:


C:\JavaPkgs\antDemo\src\build>ant clean
Buildfile: build.xml

clean:
   [delete] Deleting directory C:\JavaPkgs\antDemo\build
   [delete] Deleting directory C:\JavaPkgs\antDemo\dist

BUILD SUCCESSFUL

Total time: 0 seconds
I could continue with more targets, many of which would make this example a comprehensive showcase of all of Ant's capabilities, but I'm sure you'd become groggy. To fuel the imagination, I would like to seed the discussion with some "next steps." You could, for example, declare a target that deploys this bean into an EJB environment. In JBoss it is as easy as copying the .jar file into the "deploy" directory. Other application servers require the execution of an EJB compiler ("ejbc"). After this deployment, you could execute the JUnit test fixtures and report any execution problems that may be present. With the conclusion of a successful unit test, the component could be FTP'd to the integration machine with an e-mail sent to any subscribed developers that may be interested in this component. Finally, you could have Ant generate a detailed report of the entire build, test, and deployment evolution, and then e-mail it to your boss declaring yet another deadline having been met.

I have barely scratched the surface regarding Ant's capabilities. There are huge bodies of built-in tasks that are available to build administrators. Ant's advanced power lies in its ability to act as a very sophisticated deployment tool. It has the capability to move files around using a very rich set of include/exclude directives that allow for an infinite variety of deployment scenarios. Ant also has the capability to move files via FTP, which should bring a smile to the face of anyone who has had the daunting responsibility of deploying different components to various groups (development, test, QA, production) on numerous machines. Ant unifies this process and makes it almost fun.

URLs
Jakarta Ant Download
http://jakarta.apache.org/ant/

The Apache Software Foundation
http://www.apache.org

JBoss Application Server
http://www.jboss.org