Using Ant for build scripts

Ant is an excellent tool for creating build scripts.

Ant has these characteristics:

Every Java programmer should at least be familiar with Ant.

Using Ant, a build file is made up of tasks, which perform operations commonly needed for builds. The list of tasks is extensive:

Example Build File

Here's an example of an Ant build file:
<project name="test-ant-builds" default='all' basedir="." >
  
   <description>
     Demonstrate the use of the Ant build tool with a simple Java project.
   </description>

   <!-- 
    First define properties, datatypes, and default tasks; then define targets.
    Any Ant tasks placed outside of any target are always executed first.
   -->  
  
  <!-- Override default property values with an external properties file, if present. -->
  <property file='build.properties'/>

  <!-- Default property values, if not overridden elsewhere: -->
  <property name='build' location='build' />
  <property name='app.version' value='1.0'/>
  <property name='app.name' value='Example App'/>
  <property name='distro-name' value='example-app-${app.version}'/>
  <tstamp><format property='build.time' pattern='yyyy-MM-dd HH:mm:ss'/></tstamp>

  <path id='compile.classpath'>
    <fileset dir='lib'>
       <include name='*.jar'/>
    </fileset>
  </path>

  <!-- Simply extends the compile.classpath with your own compiled classes. -->
  <path id='run.classpath'>
    <path refid='compile.classpath'/>
    <path location='src'/>
  </path>
  
  <fileset id='class.files' dir='src'>
     <include name='**/*.class'/>
  </fileset>

  <fileset id='files.for.jar' dir='src'>
    <exclude name='**/*.java'/>
    <exclude name='**/doc-files/'/>
  </fileset>
  
  <fileset id='test.classes' dir='src'>
    <include name='**/TEST*.java'/>
  </fileset>

  <!-- Text files using Ant's '@' syntax are here called template files. -->
  <fileset id='template.files' dir='.'>
    <include name='**/*_template.*'/>
  </fileset>

  <!-- Inspect the environment, to see if a deployment host is currently running. -->
  <condition property='deployment.server.running' value='true' else='false'> 
     <socket port='8081' server='127.0.0.1' />
  </condition>
  
  <!-- A connection to this URL is used when building javadoc. -->
  <condition property='jdk.javadoc.visible' value='true' else='false'> 
    <http url='http://java.sun.com/javase/6/docs/api/' />
  </condition>
  
  <echo>
    Application: ${app.name} ${app.version}
    Build File : ${ant.file} 
    Run Date   : ${build.time}
    Run by     : ${user.name}
    Build Dir  : ${build}
    Base Dir   : ${basedir}
    Java Home  : ${java.home}
    Deployment host running: ${deployment.server.running}
    Connected to the web   : ${jdk.javadoc.visible}
  </echo>
    
  <echo message='Create build directory, and its subdirectories.'/>
  <mkdir dir="${build}/javadoc"/>
  <mkdir dir="${build}/dist"/>
  <mkdir dir="${build}/templates"/>

  <!-- Now define the targets, which use the properties and datatypes defined above. -->

   <target name='clean' description="Delete all build artifacts." >
     <delete dir='${build}'/>
     <delete>
      <fileset refid='class.files'/>
     </delete>
     <mkdir dir="${build}/javadoc"/>
     <mkdir dir="${build}/dist"/>
     <mkdir dir="${build}/templates"/>
   </target>
  
   <target name='compile' description='Compile source files and place beside source.'>
     <javac srcdir="src">   
       <classpath refid='compile.classpath'/>
     </javac>
     <!-- Here's a simple way of debugging a path, fileset, or patternset, using its refid: -->
     <echo>Classpath: ${toString:compile.classpath}</echo>
   </target> 
  
   <target name='test' description='Run all JUnit tests.' depends='compile'>
     <junit haltonfailure='false'> 
       <classpath>
         <pathelement location="src"/>
       </classpath>
       <batchtest>
         <fileset refid='test.classes'/>
       </batchtest>
       <formatter type='brief' usefile='no'/>
     </junit>
   </target>

   <target name='launch' description='Build and run the program.' depends='compile, test'>
     <java classname='hirondelle.ante.Launcher' classpathref='run.classpath' failonerror='true'>
       <arg value="Solar System"/>
     </java>
   </target>
  
   <target name='jar' description='Create a jar file for distribution.' depends='compile'>
    <jar destfile='${build}/dist/${distro-name}.jar' manifest='MANIFEST.MF' duplicate='preserve'>
      <fileset refid='files.for.jar'/>
      <!-- The static manifest.mf file is merged with additional dynamic items, specified here : -->
      <manifest>
        <attribute name='Specification-Version' value='${app.version}'/> 
        <attribute name='Specification-Title' value='${app.name}' />
        <attribute name='Implementation-Version' value='${app.version}'/> 
        <attribute name='Implementation-Title' value='${app.name}' />
      </manifest>
    </jar>
   </target>
  
  <target name='javadoc' description='Generate javadoc.' >
    <javadoc  
      use='true' author='true'  version='true'
      overview='overview.html'  
      access='package' 
      sourcepath='src'
      packagenames='*.*' 
      destdir='${build}/javadoc'
      windowtitle='${app.name} ${app.version}'
      noqualifier='java.*:javax.*:com.sun.*'   
      linksource='true'
    >
      <classpath refid='compile.classpath'/>
      <link href='http://java.sun.com/javase/6/docs/api/'/>      
      <header><![CDATA[<h1>${app.name} ${app.version}</h1>]]></header>
    </javadoc>
  </target>
  
  <target name='text-templates' description='Process template files, and assign values to @ variables.'>
    <copy overwrite='true' todir='${build}/templates'>
      <fileset refid='template.files'/>       
      <!-- New files have 'template' removed from their name : -->      
      <globmapper from='*_template.txt' to='*.txt'/>
      <filterset>
        <filter token='app.name' value='${app.name}'/>
        <filter token='app.version' value='${app.version}'/>
        <filter token='build.time' value='${build.time}'/>
      </filterset>
    </copy>
  </target>
  
  <target name='distro-binary' description='Create zip file with executable jar, docs.' depends='jar, javadoc, text-templates'>
    <zip destfile='${build}/dist/${distro-name}-binary.zip' duplicate='preserve'>
      <zipfileset dir='${build}/dist/' includes='${distro-name}.jar'/>
      <zipfileset dir='${build}/javadoc' prefix='javadoc' />
      <zipfileset dir='${build}/templates' includes='README.txt'/>
    </zip>
  </target>
  
  <target name='distro-source' description='Create zip file with project source code.'>
    <zip destfile='${build}/dist/${distro-name}-src.zip' duplicate='preserve' >
      <!-- exclude items specific to the author's IDE setup: -->
      <zipfileset dir='.' excludes='.classpath, .project'/>
    </zip>
  </target>
  
  <!-- Add mail.jar and activation.jar to your ANT_HOME/lib! -->
  <!-- Please edit, using values appropriate to your environment. -->
  <target name='email' >
    <mail mailhost='smtp.blah.com' mailport='25' user='blah@blah.com'  
       password='blah'  messageMimeType='text/html'
       tolist='blah@whatever.com' from='blah@blah.com'  subject='Build completed.'
     >
      <message>
        <![CDATA[ Test email. <P><a href='http://www.google.com'>link</a> ]]>
      </message>
    </mail>
  </target>
  
  <target name='all' description='Create all build artifacts.' 
             depends='clean, compile, test, jar, javadoc, distro-binary, distro-source'>
    <echo>Finished creating all build artifacts.</echo>
  </target>
  
</project>
Example of running the above build script:
C:\\johanley\\Projects\\ant-test>ant -file build.xml all
Buildfile: build.xml
     [echo]
     [echo]     Application: Example Appl  1.1
     [echo]     Build File : C:\\johanley\\Projects\\ant-test\\build.xml
     [echo]     Run Date   : 2010-07-21 19:14:11
     [echo]     Run by     : John
     [echo]     Build Dir  : C:\\@build
     [echo]     Base Dir   : C:\\johanley\\Projects\\ant-test
     [echo]     Java Home  : C:\\jdk1.5.0\\jre
     [echo]     Deployment host running: true
     [echo]     Connected to the web   : true
     [echo]
     [echo] Create build directory, and its subdirectories.
    [mkdir] Created dir: C:\\@build\\javadoc
    [mkdir] Created dir: C:\\@build\\dist
    [mkdir] Created dir: C:\\@build\\templates

clean:
   [delete] Deleting directory C:\\@build
   [delete] Deleting 3 files from C:\\johanley\\Projects\\ant-test\\src
    [mkdir] Created dir: C:\\@build\\javadoc
    [mkdir] Created dir: C:\\@build\\dist
    [mkdir] Created dir: C:\\@build\\templates

compile:
    [javac] Compiling 5 source files
     [echo] Classpath: C:\\johanley\\Projects\\ant-test\\lib\\junit.jar

test:
    [junit] Testsuite: hirondelle.ante.TESTLauncher
    [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec


jar:
      [jar] Building jar: C:\\@build\\dist\\example-app-1.1.jar

javadoc:
  [javadoc] Generating Javadoc
  [javadoc] Javadoc execution
  [javadoc] Loading source files for package hirondelle.ante...
  [javadoc] Loading source files for package hirondelle.ante.deluvian...
  [javadoc] Constructing Javadoc information...
  [javadoc] Standard Doclet version 1.5.0_07
  [javadoc] Building tree for all the packages and classes...
  [javadoc] Generating C:\\@build\\javadoc\\hirondelle/ante/\\package-summary.html...
  [javadoc] Copying file C:\\johanley\\Projects\\ant-test\\src\\hirondelle\\ante\\doc-files\\VersionHistory.html 
            to directory C:\\@build\\javadoc\\hirondelle\\ante\\doc-files...
  [javadoc] Building index for all the packages and classes...
  [javadoc] Building index for all classes...

text-templates:
     [copy] Copying 1 file to C:\\@build\\templates

distro-binary:
      [zip] Building zip: C:\\@build\\dist\\example-app-1.1-binary.zip

distro-source:
      [zip] Building zip: C:\\@build\\dist\\example-app-1.1-src.zip

all:
     [echo] Finished creating all build artifacts.

BUILD SUCCESSFUL
Total time: 6 seconds

For those already familiar with Ant, here are some reminders regarding its use.

General

Datatypes

Paths

Filesets

Patternset

Patternsets use 4 special char's:

Other datatypes

Properties

Properties are immutable once set.

Sources of Properties

Built-in Properties

Properties Files

Example
# example properties file
app.version = 1.0

# This style also works, and it's not sensitive to order 
# of appearance in this file
# Thus, the contents of this file is manipulated somewhat 
# after being loaded.
# This does NOT work when using 'ant -propertyfile'
app.name = My Wonderful App ${app.version}

# you need to escape backslashes
build.dir = C:\\\\@build

Reminders and Practices

See Also :
Use a testing framework (JUnit)