Build Automation with Apache ANT

An introduction to Apache ANT

Apache ANT is a build and deployment tool design specifically for Java applications. It provides a simplistic approach to fetch code from a versioning system, compile, assemble, test and deliver a complete build. It supports writing XML scripts for build and deployment purposes that can be executed from CLI or through some CI tool like Jenkins.

In this article we will discuss how you can write a build script and what its major building blocks are.

Apache ANT installation

You can download Apache ANT binaries from official website. The latest version of ANT requires JDK 8 which you can download from here. Make sure to create system wide environment variable JAVA_HOME and point it to JDK home directory and ANT_HOME directing to ANT home directory. See the examples below, for windows:

Add c:\ant in ANT_HOME
Add c:\jdk1.7.0_51 in JAVA_HOME
Add c:\ant\bin in PATH

For Linux:

Add /usr/local/ant in ANT_HOME
Add /usr/local/jdk1.7.0_51 in JAVA_HOME
Add /usr/local/ant/bin in PATH

Basic Structure of an ANT script

ANT build script is a combination of targets, tasks and properties. A target is a collection of XML line of codes that you write to collectively perform a specific job. For example you can write a target to checkout fresh code from versioning system and a separate target to compile it and so on. Whereas an ANT task represents a preliminary task that you can perform. For example, Apache ANT provides a wide range of default tasks that you can use to copy files, delete files, create directories, compile source, and more. Properties are an important way to customize a build process or to just provide shortcuts for strings that are used repeatedly inside a build file. A separate .properties file can also be maintained for holding the properties.

Basic structure of an ANT script looks like the following

<?xml version="1.0"?>
<project name="HelleWorld-Build" default="target-3" basedir="." xmlns:ac="antlib:net.sf.antcontrib">
       <!-- PROPERTIES-->
<property name=”value”>
<property name=”value”>
       <!-- TARGETS -->
<target name="target-3" depends="target-2">
       <!—list of TASKS-->
</target>
<target name="target-2" depends="target-1">
       <!—list of TASKS -->
</target>
<target name="target-1" >
       <!—list of TASKS -->
</target>
</project>

The project is the main entry point of your build script. The ‘default’ attribute of project is used to specify the name of target that you first want to execute. In this example the ‘default’ attribute points to target-3 which means the first target to run on execution on this script is target-3. As you can see there is an attribute ‘depends’ where we have given name of other target i.e. target-2. This means that target-3 cannot be executed unless target-2 is executed.

If a target does not have the ‘depends’ attribute then it means that execution of that target can start. So, in this shirt example the order of executions of targets will be:

1.       target-1

2.       target-2

3.       target-3

Logical components of an ANT script

Here we provide examples for some of the logical components of a build script.

Initialization

The first target in a build script is usually for initialization purposes. This is the target where we can set properties and create build labels and timestamps etc. Following is an example

<target name="init">

              <echoproperties/>

              <echo message="Creating the timestamp" />

              <property name="label" value="${env.BUILD_NUMBER}"/>                

              <echo message="Timestamp label: ${label}"/>

              <!-- Create the timestamp -->

              <tstamp>

                     <format property="build.date" pattern="ddMMyyyy_HHmm" />

                     <format property="build.timestamp" pattern="yyyy-MM-dd HH:mm:ss" />

                     <format property="build.datetimenumber" pattern="yyMMddHHmm" />

              </tstamp>

              <ac:propertyregex property="BuildNumber"

                     override="true"

            input="${label}"

            regexp="_([0-9]+)$"

            select="\1"

            casesensitive="false"

                     defaultValue="" />

       </target>

Cleaning

It is recommended to clean up the folders so that every file from the last build run gets deleted and we have a new clean build.

<target name="clean" description="Clean up the working directories">

              <echo message="Cleaning up" />

              <delete dir="${build}" />

              <delete dir="${dist}" />

</target>

Re-creating the directories

Re-create the directories that are involved in the build process

<target name="dir" description="Create the working directories">

              <echo message="Creating the working directories" />

              <mkdir dir="${build}" />

              <mkdir dir="${build}/classes" />

              <mkdir dir="${dist}" />

              <mkdir dir="${dist}/jars" />            

</target>

Checkout code from versioning system

<path id="svnant.classpath">

    <fileset dir="${ANT.home.path}/lib">

        <include name="**/*.jar"/>

    </fileset>

</path>

 

<typedef resource="org/tigris/subversion/svnant/svnantlib.xml" classpathref="svnant.classpath" />

 

<svn dateFormatter="yyyy-MM-dd HH:mm">

    <checkout url="${SVN.URL.of.PROJECT1}/"

              destpath="${project1.location}"

<checkout url="${SVN.URL.of.PROJECT2}/"

              destpath="${project2.location}"

 

<checkout url="${SVN.URL.of.WEB.PROJECT}/"

              destpath="${Your-Web-Project-location}"

....

</svn>

Make sure to add svnjavahl.jar, svnClientAdapter.jar and svnant.jar into the lib folder of ANT

Compilation

Firstly, if your code refers to some external jars and you want to add some external jars into the classpath at the time of compilation, then you can do it with something like this:

<path id="external_classpath">

                    

                     <fileset dir="${location.of.external.jars">

                           <include name="**/*.jar" />

                     </fileset>

                    

              </path>

Secondly, if you have multiple projects that are inter dependent then to compile them you will need to place the source code of both projects in a common location and then run compiler.

<copy todir="${src}/AllJava">

                 <fileset dir="${project1.src.path}" erroronmissingdir="false"  >

                      <include name="**"/>

                      

                 </fileset>

                      <fileset dir="${project2.src.path}" erroronmissingdir="false"  >

                      <include name="**/*"/>

                      </fileset>

                                </copy>

And finally the compilation:

<javac destdir="${build}"

                     debug="true"

                     fork="true"

                     memoryinitialsize="256m"

                     memorymaximumsize="2048m"

                     includeAntRuntime="false"

                     failonerror="true"

                     errorProperty="javacError">

                     <src path="${src}/AllJava" />

                     <classpath refid=" external_classpath " />

              </javac>

Packaging a jar file

The compiled code needs to be packaged into a jar file now.

<jar destfile="${dist}/jars/YourProject-${build.datetimenumber}.${label}.jar">

                     <fileset dir="${build}" includes="version.properties" />

                     <fileset dir="${build}" includes="**/*.*"/>

                     <fileset dir="${project1.src.path}/resources" includes="**/*.*" erroronmissingdir="false"  />

                     <fileset dir="${project2.src.path/resources" includes="**/*.*"  erroronmissingdir="false" />

              </jar>

Packaging a web module

If you are building a web application then you can package your WARROOT into an EAR or WAR file for deployment. You will need to have all the dependent code built as jar and added into the lib of your EAR or WAR file.

<copy todir="${Your-Web-Project-root-location}/WebRoot/WEB-INF/lib">

                     <fileset dir="${dist}/jars/" includes="*.jar"/>

              </copy>

<war destfile="${dist}/Your-Web-Project-${build.datetimenumber}.${label}.war"

                           basedir="${Your-Web-Project-location}/WebRoot"

                           webxml="${Your-Web-Project-root-location}/WebRoot/WEB-INF/web.xml"

                           manifest="${Your-Web-Project-root-location}/WebRoot/META-INF/MANIFEST.MF" />

Finally, make a deployable EAR file

<mkdir dir="${dist}/EARRoot/META-INF" />

                    

                     <copy file="${dist}/Your-Web-Project-${build.datetimenumber}.${label}.war" tofile="${dist}/EARRoot/Your-Web-Project-${build.datetimenumber}.${label}.war" />

                     <copy file="${Your-Web-Project-root-location}/application.xml" tofile="${dist}/EARRoot/META-INF/application.xml" overwrite="true"/>

                    

                     <zip destfile="${dist}/EAR/Your-Web-Project-${build.datetimenumber}.${label}.ear" basedir="${distInsurer}/EARRoot" />

                     <delete dir="${dist}/EARRoot" />

 

 

Run your Build Script

Save your script with a proper name. Now, you can directly run the build script from command line 

ant -buildfile your-build-script.xml

Muhammad Ali

Bluestack IT Solutions