JaVa
   

Standard Targets

Steve Loughran wrote an Ant guide called Ant In Anger. This guide explains many pitfalls and recommends ways to use Ant. Two very useful suggestions are a list of names for targets and how to divide buildfiles. The following are some of Steve's recommended names for Ant top-level targets:

The following are some recommended names for Ant internal targets:

We'll discuss some of the thoughts from Ant in Anger in this chapter and the next; however, we strongly suggest that you read this guide, because it contains excellent guidelines for using Ant. The guide is included with the Ant binary distribution under the docs directory. Before we go any further, let's look at a simple example to cement some of the concepts of Ant. The next section presents a straightforward buildfile.

Simple Example

Let's start with a very small, straight-forward example of using Ant. We will begin with the now- infamous “hello world” example. It will create an output directory and then compile a Java source file called HelloWorld.java to the output directory. The Java source file HelloWorld.java is stored in ./src/xptoolkit as follows:

package xptoolkit;
public class HelloWorld{
 public static void main(String []args){
 System.out.println("Hello World!");
 }
}


The following ant buildfile, build.xml, compiles the source file:

<project default="compile">
 <target >
 <mkdir dir="/tmp/classes" />
 </target>
 <target depends="prepare">
 <javac srcdir="./src" destdir="/tmp/classes" />
 </target>
</project>


When you run Ant from the command line, it looks for a buildfile called build.xml in the current working directory. To specify a buildfile with a different name, you must specify the buildfile name using the –buildfile command-line argument (discussed in detail later). Notice that the hello project has targets called compile and prepare. The hello project specifies the compile target as the default target. The compile target has a task called javac, which compiles the location specified by srcdir to the directory specified by the “destdir” attribute. The built-in task javac compiles Java source. Because the default directory location for a project is the current working directory, the javac task will look for a directory called src (srcdir="./src") under the current working directory and compile the contents of the src directory to the /tmp/classes directory. Notice that the compile target's "depends" attribute points to the prepare target (depends="prepare"). As a result, all the tasks associated with the prepare target will be executed before the tasks associated with the compile target. This is a good thing--otherwise, the javac task might try to compile the source code to a directory that did not exist. As you can see, you can use the targets and their dependencies to logically build, deploy, and test a complex system. The next section shows you how to set up your Ant buildfiles.

Setting Up Your Environment

If you are running Unix, install Ant in ~/tools/ant; if you are running Windows, install Ant in c:\tools\ant. You can set up the environment variables in Windows by using the Control Panel. However, for your convenience, we created a Unix shell script (setenv.sh) and a Windows batch file (setenv.bat) to set up the needed environment variables. Your UNIX setenv.sh should look something like this:

#
# Setup build environment variables using Bourne shell
#
export USR_ROOT=~
export JAVA_HOME=${USR_ROOT}/jdk1.5
export ANT_HOME=${USR_ROOT}/tools/ant export PATH=${PATH}:${ANT_HOME}/bin


Your Windows setenv.bat should look something like this:

:
: Setup build environment variables using DOS Batch
:
set USR_ROOT=c:
set JAVA_HOME=%USR_ROOT%\jdk1.5set CLASSPATH=%USR_ROOT%\jdk1.5\lib\tools.jar;%CLASSPATH% set ANT_HOME=%USR_ROOT%\tools\Ant PATH=%PATH%;%ANT_HOME%\bin


Both of these setup files begin by setting JAVA_HOME to specify the location where you installed the JDK. This setting should reflect your local development environment—make adjustments accordingly. Then, the files set up environment variable ANT_HOME, the location where you installed Ant. The examples in this tutorial assume that you have installed Ant in c:\tools\ant on Windows and in ~/tools/ant on Unix; make adjustments if necessary. There are sample setup scripts as well the sample code at this tutorial's Web site (www.wrox.com).

Running Ant for the First Time

To run the sample Ant buildfile, go to the directory that contains the project files. On our computer, they are stored under /CVS/XPToolKit/examples/chap4. The directory structure and files looks like this:

/CVS/XPToolKit/examples/chap4
 setenv.bat
 setenv.sh
 build.xml
 ./src/xptoolkit
 HelloWorld.java


To run Ant, navigate to the examples/chap4 directory and type ant. As stated earlier, Ant will find build.xml, which is the default name for the buildfile. For example, here is the command-line output you should expect:

$ ant Buildfile: build.xml prepare:
 [mkdir] Created dir: /tmp/classes compile:
 [javac] Compiling 1 source file to /tmp/classes BUILD SUCCESSFUL Total time: 3 seconds


Notice that the targets and their associated tasks are displayed. That's it! We wrote our first Ant buildfile. In the next section, we describe how to use Ant properties.

Working with Properties

You'll often find it helpful to define properties. The properties in Ant are similar to the properties in java.lang.System.getProperites(). The properties can be set by the property task; so, the properties can also be set outside Ant. You can use properties for task attributes by placing the property name between "${" and "}", similar to the way environment variables are set in the Bourne shell. For example, if an "outputdir" property is set with the value "/tmp", then the "outputdir" property could accessed in an attribute of a task: ${outputdir}/classes would be a resolved to /tmp/classes. Thus we could change the Ant buildfile to use properties as follows:

<project default="compile">
 <property value="/tmp"/>
 <target >
 <mkdir dir="${outputdir}/classes" />
 </target>
 <target depends="prepare">
 <javac srcdir="./src" destdir="${outputdir}/classes" />
 </target>
</project>


This Ant buildfile defines the "outputdir" property. Then, the buildfile uses the property in the "dir" attribute of the mkdir task of the prepare target and the "destdir" attribute of the javac task of the compile target. The property is used in many attributes; then, if it has to change, you only change it once. For example, if you change the location of the output directory using properties, you only have to make the change once, in one—not two—attribute assignments. Using properties this way can make your buildfiles flexible.

Paths, Filesets, Patternsets, and Selectors

Of course, your Java source files are unlikely to be as simple as the "hello world" example. You may need to use external libraries. For example, you may need to use one or more external libraries (JAR or ZIP files) with Java binaries to compile the source code of your project. Ant can make it simple to set up the classpath for your project. You can use the path element tag, which can contain pathelement tags and filesets. There are two types of pathelements: path and location. A location pathelement sets a single JAR or directory, and a path pathelement sets a colon- or semicolon-separated list of locations (directories and JARs) similar to the CLASSPATH environment variable. The fileset can define a group of files from one directory. This is convenient, for example, when all your library files (JAR files) are in one directory and you don't want to specify them by name. These concepts are much harder to explain than to show; so, look at the next example. The following is a simple example that uses the Apache Log4J library file (log4j.jar) as if the HelloWorld.java source code needed it. The example shows several ways to set up the path:

<project default="compile">
 <property value="../lib"/>
 <property value="/tmp"/>
 <path >
 <pathelement location="." />
 <pathelement location="${lib}/log4j.jar"/>
 </path>
 <path >
 <pathelement path=".;${lib}/log4j.jar"/>
 </path>
 <path >
 <pathelement location="." />
 <fileset dir="${lib}">
 <include name="**/*.jar"/>
 </fileset>
 </path>
 <target >
 <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath ref />
 </javac> <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath ref />
 </javac> <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath ref />
 </javac> 
 <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath >
 <pathelement location="." />
 <pathelement location="${lib}/log4j.jar"/>
 </classpath>
 </javac> </target>
</project>


Notice that the three path tags define almost the same classpath, with the exception that the classpath with the id of 3 includes all JAR files that exist in ${lib}. Here the tags are repeated without the rest of the buildfile for clarity:

 <path >
 <pathelement location="." />
 <pathelement location="${lib}/log4j.jar"/>
 </path>
 <path >
 <pathelement path=".;${lib}/log4j.jar"/>
 </path>
 <path >
 <pathelement location="." />
 <fileset dir="${lib}">
 <include name="**/*.jar"/>
 </fileset>
 </path>


Also notice that to use these three path tags with the javac task, you need only set the reference of the classpath element to the reference id of the paths defined previously. Here they are, referenced respective to the last example:

 <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath ref />
 </javac> <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath ref />
 </javac> <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath ref />
 </javac> 


It's important to note that the javac task with <classpath ref /> would set the classpath to the path set defined by <path >. This is called referring to a classpath by reference. In addition, you can refer to a path in-line using the classpath subtag in the javac task, demonstrated as follows:

 <javac srcdir="./src" destdir="${outputdir}/classes" >
 <classpath >
 <pathelement location="." />
 <pathelement location="${lib}/log4j.jar"/>
 </classpath>
 </javac> 


There will be times when you need to build a fileset that includes some files and excludes others. For example:

<fileset dir="./src" casesensitive="yes">
 <include name="**/*.java"/>
 <exclude name="**/*BAD*"/>
</fileset>


This fileset is designed to pull all of the files within the ./src directory based on a case sensitive search and include all files with a .java extension in them. At the same time, the system needs to exclude any files with the text BAD embedded anywhere in the filename. You will also notice that through these examples, we've hardcoded the srcdir to be "./src". In most cases you will use a property name for the source files as well. For example, we would use:

<fileset dir="${src.dir}">
 <include name="**/*.java"/>
</fileset>


In this code example, we've set the source directory for the fileset to be ${src.dir}. We will of course need to set this property in the build file at some point.

Selectors

Now we've built our filesets using the criteria of the filename—the file has an extension of .java, appears in a directory trees, etc.—but what if we wanted to use other criteria? Ant allows additional criteria for file selection like these core selectors:

For an example of how to use the selectors, consider the following code that will only pull files with a size greater than 5 kb:

<fileset dir="${src.dir}">
 <size value="5" units="Ki" when="more"/>
</fileset>


In this fileset we are pulling all files that are more than 5 kb within the ${src.dir}. The units can be changed to be any of k, M, or G; if the units attribute is not included in the element, then the default is bytes. If just one selector doesn't fit the needs of your <fileset>, you can use a Selector Container. The Selector Containers allow the various selectors to be combined with each other in a Boolean fashion. The current containers are:

To see how the selector containers work, let's consider the <and>. For example:

<fileset dir="${src.dist}" includes="**/*.java">
 <and>
 <contains test="CoreCode" />
 <date datetime="12/31/2003 12:00 AM" when="after"/>
 </and>
</fileset>


In this fileset, we want to match all of the files with a .java extension that contain the text CoreCode and have a current modified date after 12/31/2003. If a file matches both of the selectors, contains and date, then it will match the <and> selector container. Clearly the use of the selectors and the containers provides for a quite complex and comprehensive way to choose files.

Patternsets

If you want to match a set of files but you aren't interested in the granularity of the selector but you still want to be able to reuse the matches, the patternset might be the answer. For example, we might have a set of files that match on the following filename criteria:

 <include name="core/**/*.java"/>
 <include name="extended/**/*.java" if="extended"/>
 <exclude name="**/*BAD*"/>


These criteria will match all java files in the core directory structure as well as the files in the extended directory structure, but only if the extended property is set. In addition, the BAD files will be excluded. If this is a common fileset in your code, you can build a patternset. For example:

<patternset >
 <include name="core/**/*.java"/>
 <include name="extended/**/*.java" if="extended"/>
 <exclude name="**/*BAD*"/>
</patternset>


This specific pattern of include and exclude elements can be referenced in fileset elements or a task that includes an implicit fileset just by using the id. For example:

<fileset>
 <patternset ref />
</fileset>


In addition to the <include> and <exclude> elements, the patternset supports the includes, includesfile, excludes, excludesfile attributes. The patternset id can be used within any Ant construct that allows the <patternset> element. For example, we could have a <patternset> like the following:

<patternset >
 <include name="prodjars/**/*.jar" unless="extended"/>
 <include name="devjars/**/*.jar" if="extended"/>
 <exclude name="**/*BAD*"/>
</patternset>


This patternset will include all jars in one of two paths, prodjars or devjars, based on the value of the extended property. This patternset could be used if you have specific jars used during production that don't include specific optimizations or logging found in the devevelopment jars. When we build a <target> for compiling our project, we will include a <path> element that uses the patternset. For example:

<path id="base.path">
 <pathelement path="${classpath}"/>
 <fileset>
 <patternset ref />
 </fileset>
 <pathelement location="classes"/>
</path>


Finally, we would build a <target> using the path id which uses our patternset id:

<target>
 <javac>
 <classpath refid="base.path">
 </javac>
</target>


Conditional Targets

You don't always have to execute a target. You can write targets that are executed only when a certain property is set or when a certain property is not set. For example, let's say we need to run a buildfile in the Windows XP (no pun intended) development environment and the Solaris production environment. Our development environment does not have the same directory structure as our production environment; thus, we may write a script that looks like this:

<project default="run">
 <target if="production">
 <property value="/usr/home/production/lib"/>
 <property value="/usr/home/production/classes"/>
 </target>

 <target unless="production">
 <property value="c:/hello/lib"/>
 <property value="c:/hello/classes"/>
 </target>
 <target depends="setupProduction,setupDevelopment"/>
 <target depends="setup">
 <echo message="${lib} ${outputdir}" /> </target> </project>


Notice that the setupDevelopment target uses unless="production". This means the target should be executed unless the production property is set. Also notice that the setupProduction target uses if="production". This means to execute this target only if the production property is set. Now we need to set the property (or not set it) to control the behavior of the tasks. To set a property when you execute Ant, you need to pass the property to Ant. This technique is similar to the way you would pass a system property to the Java interpreter. When you execute Ant, you pass the argument –Dproduction=true (it does not have to equal true, it just has to be set). Following is an example of running the buildfile (build2.xml) in production mode:

C:\...\chap2>ant -buildfile build2.xml -Dproduction=true Buildfile: build2.xml setupProduction:
setupDevelopment:
setup:
run:
 [echo] /usr/home/production/lib /usr/home/production/classes BUILD SUCCESSFUL Total time: 0 seconds


From this output, we can see that run was the default target for the project. The run target depended on setup, which depended on our two conditional targets (depends="setupProduction,setupDevelopment"). Thus, because we set the production property on the command line (-Dproduction=true) the setupProduction target was executed rather than setupDevelopment. Running setupProduction sets the "outputdir" and "lib" properties to their Unix environment values. We can see this by looking at the output of the run target's echo task (<echo message="${lib} ${outputdir}" />), which displays the following:

run:
 [echo] /usr/home/production/lib /usr/home/production/classes


What happens when you have deployment descriptor files that differ between the two environments? You use filters, as discussed in the next section.

Using Filters

Filters can be used to replace tokens in a file with their proper values for that particular environment. One scenario that comes to mind follows the example in the previous section. Let's say we have a production database and a development database. When deploying to production or development, we want the values in our deployment descriptor or properties file that refer to the JDBC URL of the needed database to refer to the correct database:

<project default="run">
 <target if="production">
<filter token="jdbc_url" value="jdbc::production"/>
 </target>
 <target unless="production">
<filter token="jdbc_url" value="jdbc::development"/>
 </target>
 <target depends="setupProduction,setupDevelopment"/>
 <target depends="setup">
 <copy todir="/usr/home/production/properties" filtering="true">
 <fileset dir="/cvs/src/properties"/>
 </copy>
 </target> </project>


Again, setupProudction and setupDevelopment are executed conditionally based on the production property. But this time, instead of setting properties, they set filters as follows:

 <target if="production">
<filter token="jdbc_url" value="jdbc::production"/>
 </target>
 <target unless="production">
<filter token="jdbc_url" value="jdbc::development"/>
 </target>


The filter in the setupProduction target sets jdbc_url to jdbc::production. The filter in the setup-Development target sets jdbc_url to jdbc::development. The copy task in the run target turns on filtering, which applies the filter to all in the fileset. Thus, the copy task will copy recursively all the files from the /cvs/src/properties directory into the /usr/home/production/properties directory, replacing all the occurrences of the string "@jdbc_url@" with "jdbc::production" if the "production" property is set or jdbc::development if the "production" property is not set. Of particular warning here is the fact that any file in the given fileset will be searched for the @filter@ string and this includes binary files. There is a good chance a binary file will become corrupt during the 'filter' process therefore, only text files should be in the copied from directory.

Filtersets

As you might expect, we can have more than one filter in our target that needs to be replaced. Building on our previous example, in addition to the jdbc_url, there is also a specific table within the database that we need to access. The code has been written such that the table name is used through the many SQL statements. Thus, we will have another filter like

 <target if="production">
<filter token="jdbc_url" value="jdbc::production"/>
<filter token="table" value="accounts"/>
 </target>
 <target unless="production">
<filter token="jdbc_url" value="jdbc::development"/>
<filter token="table " value="tempacc"/>
 </target>


When the –D command-line option is provided, both of the <filter> elements will be executed by the <copy> command. Of course, this isn't going to work for us because the <copy> element is pulling the properties file and not the source code. So let's look at another option for filtering that uses the concept of a filterset. Filtersets are designed to:

For example, we might build a filterset containing several filters that will be used to change our source code as well as the jdbc_url:

<project default="run">
 <target if="production">
<filter token="jdbc_url" value="jdbc::production"/>

 <filterset begintoken="(*)" endtoken="(*)">
 <filter token="TABLE" value="accounts"/>
 <filter token="USERNAME" value="fullprod"/>
 </filterset>
 </target>
 <target unless="production">
<filter token="jdbc_url" value="jdbc::development"/>
 <filterset begintoken="(*)" endtoken="(*)">
 <filter token="TABLE" value="tempacc"/>
 <filter token="USERNAME" value="readtemp"/>
 </filterset>
 </target>
 <target depends="setupProduction,setupDevelopment">
 <copy toDir="${build.dir}">
 <filterset ref />
 <fileset dir="${cvssrc.dir}"/>
 </copy>
 </target>
 <target depends="setup">
 <copy todir="/usr/home/production/properties" filtering="true">
 <fileset dir="/cvs/src/properties"/>
 </copy>
 </target> </project>


In this example, we've added two filtersets to the production and development targets. There are a few new attributes to discuss within the filtersets. The first is the id used to assign a value to the filterset which can be used with a refid elsewhere throughout the build file. The second is the use of the begintoken and endtoken attributes. Typically, the filtering will check to find the replacement text using the matching text of @value@ but using a filterset allows us to change those values. In our example, we've changed them to (*)value(*).

We've also added another <copy> element to the build script within the setup target. Here we will be pulling the source code from a directory where the source code was pulled from CVS and placing the new files in a build directory. During the copy, the system will filter the code and replace our TABLE and USERNAME values depending on the production property. Notice we've used the refid attribute to specify the filterset to use in our example.

FilterChains and FilterReaders

If you have been a Unix developer, you have probably come to take the redirection abilities of the shell for granted. It is not uncommon to do things like this:

ps –ef | grep myprocess | more


This command sequence says to first obtain a listing of the currently executing processes, store the results in a temporary buffer, pass the buffer to the app called grep which will return any lines in the buffer with the text myprocess in it and finally control the output of the text to the screen using the more command. The | symbol which is called a pipe allows the chaining together of the various Unix shell commands. As you might expect, this capability was one of the common requests of the Ant team and it has been delivered in the form of FilterChains and FilterReaders. A FilterChain is basically a grouping of FilterReaders which are either user defined or built-in generic filters. Let's look at an example before getting into specifics. Here is a filterchain that will pull all of the lines in the source code that contains one of two strings "Today's Date Is" or "date is". Those strings which contain the strings will be further passed to the <replacetokens> filter which will update the @DATE @ strings with today's date. The FilterChain allows for the piping of various FilterReaders.

<copy toDir="${src.dir}" tofile="${dest.file}">
 <filterchain>
 <linecontains>
 <contains value="Today's Date Is">
 <contains value="date is">
 </linecontains>
 <replacetokens>
 <token key="DATE" value="${TODAY}"/>
 </replacetokens>
 </filterchain>
 <fileset dir="${cvssrc.dir}"/>
</copy>


FilterChains can be used in several tasks including concat, copy, loadfile, loadproperties, and move. The built-in FilterReaders are defined here:

Nested Builds

Each project (library module, Web app, set of Enterprise JavaBeans, applet, and so on) should have its own directory with its own buildfile. A large project can depend on lots of other projects. Ant allows one Ant buildfile to call another. Thus, you may have an Ant file that calls a hierarchy of other Ant buildfiles. You can nest buildfiles using this method to build many projects and their dependent projects. The ant task runs a specified buildfile. The ant task can be used to build subprojects and related projects. You have the option of specifying the buildfile name or just the directory (the file build.xml in the directory specified by the "dir" attribute is used). You also have the option of specifying a particular target to execute. If you do not specify a target to execute, the default target is used. Any properties that you set in the called project are available to the nested buildfile. The following are some examples of calling another Ant buildfile from your Ant buildfile. We can call a buildfile from the current buildfile and pass a property as follows:

<ant antfile="./hello/build.xml">
 <property value="true"/>
</ant>
We can call a buildfile from the current buildfile. When you call the ant task, if you don't specify an antfile attribute, then it will use ./hello/build.xml) as follows:<ant dir="./hello"/>


Notice above that we only specified the directory. The default buildfile is build.xml; if you only specify the directory, then build.xml is assumed. We can call a buildfile from the current buildfile and specify that the run target should execute (if the run target was not the default target, it would execute anyway) as follows:

<ant antfile="./hello/build.xml" target="run"/>


Listeners and Loggers

Keeping track of what Ant is doing during a build is important for the Configuration Management team as well as the developers. Ant provides two different mechanisms to handle the tracking including listeners and loggers. A listener is triggered based on seven events:

The Ant supplied loggers are used within the Ant command-line process. For example:

ant –logger <loggerclassname>


The loggers are:

Common Tasks

Ant includes quite a few common tasks already implemented, several of which we've discussed already (the javac and copy elements, for instance). In this section we will explore some of the other common tasks that will be useful in your quest of automating java builds. The following tasks aren't the only ones available, so you should consult the Ant documentation for a complete list.

BuildNumber

If you would like to have Ant keep track of a build number for you, then the BuildNumber task can be used. This task will attempt to read a number from a specific file, increment the number and then write the number back to the file. If a file is not specified, the task will try to open a file called build.number. The format of the task is:

<buildnumber/>
<buildnumber file="version"/>


Chmod

If you are working under a Unix-based file system, you can use the chmod task to change the permissions on a file or set of files. For example:

<chmod perm="w+w">
 <fileset dir="dist">
 <include name="**/deploy/**"/>
 </fileset>
</chmod>


This chmod common task will set all files within the deploy path to world writable.

CVS

If you use the CVS version-control software package, you will want to take advantage of Ant's common task called cvs that enables you to pull code from the repository. Here's an example task:

<cvs cvsRoot=":pserver:node2@localhost:/home/node2/repository"
 package="HelloWorld"
 dest="${cvs.dir}"
 />


This task will log into a CVS server on the local machine, extract the project called HelloWorld and place the resulting files in the cvs.dir directory. There are many options to the cvs common task, so consult the Ant manual. See also of this tutorial for more information about CVS.

Parallel

There may be times when you need to execute numerous Ant tasks at the same time or without waiting for one task to complete before starting another one. A good example is creating a task for starting up a project once it has been built. If you need to start more than one VM, you will need to use the parallel task. For example:

<project>
 <target >
 <parallel>
 <java... VM 1 here>
 <java... VM 2 here>
 </parallel>
 </target>
</project>


In this example, specifying a target of start will cause two Java Virtual Machines to be started without one VM having to execute before the second one starts.

Jar

In most cases the result of an Ant build will or should be a JAR file. The JAR task can be used for this purpose. Here's an example of the task:

<jar destfile="${deploy}/lib/app.jar"
 basedir="${build}/classes"
 />


This task will take all of the class files found in the ${build} directory and place them in a JAR file called app, which it then places in the ${deploy}/lib directory.

Mappers

When we create a target that pulls specific files such as the compile sequence, we know that the .java files will be converted to .class files. For the most part, we only specify the source files but not the target files. It just so happens that Ant handles all of the details for us using a mapper class; the specific mapper is called FileNameMapper. If you need to change the way the mapping is performed from source to target files, a <mapper> element should be used. The basic format of the <mapper> element is:

<mapper
 type="mapper type"
 classname="the classname of the mapper if not built-in"
 classpath="the classpath to find the class specified in classname"
 classpathref="a reference to use for the classpath"
 from="depends on the mapper"
 to="depends on the mapper"
/>


Ant includes a number of built-in mappers:


JaVa
   
Comments