Monday, June 25, 2007

ANT as a automation tool

When it come to build automations, ANT is one of the most powerful tool that can be used. There might be other tools too, but ANT is most widely available and usable. If you are new to ANT then visit http://ant.apache.org. If you want to explore the power of ANT then visit http://ant.apache.org/manual/index.html.
To write or use ANT first you will have to install it. To find out from where to download and how to isntall ANT please visit http://ant.apache.org/manual/install.html.

Here I will take an process as an example and we'll see how we can automate the process using ANT and make our life easier.

Consider the following process:
You have some bunch of programmers working on a project and you are using a central repository; CVS or VSS. At the end of the day when all the programmers are done with their devlopment/bug fixing task for the day a monotonous process is required to be followed. All the source code from the repository needs to be taken and sent to your team at other end (Onsite) through ftp. This process can be broken down in to following steps:
1. Get the latest source code from the repository.
2. Label the source code appropriately to indicate today's work.
3. Filter the files that need to be send.
4. Archive your source code to zip or gzip.
5. Upload the zip file to ftp.
6. And finally send a mail to all the stakeholders stating the upload path and a release note.

Now if we perform this process manually, it will take considerable time and might also cause problems due to human errors.
But if we automate this process, believe me it will not take more than 30 secs to release a project with around 500 source files. What we need to do is identify the atomic tasks to be performed and automate them.

As you can see, the steps mentioned above are atomic tasks that needs to be performed. ANT provides a huge set of core as well as optional tasks [].
I hope that you have basic knowledge about targets, tasks, taskdef etc.

We will define each step or set of steps as a target. Though the complete script can be writting in one target, I prefer it former way for obvious reasons.

The paramters specified in the code snippets are explained in the end.
Considering the above process;

1. Get the latest source code from the repository.
If you are using CVS, ANT provides core task to perform CVS operations.
This can be written in ANT as:

<cvspass cvsRoot="${cvs.root}" password="${cvs.pass}" passfile=".cvs-pass"/>
<cvs command="login" cvsRoot="${cvs.root}" passfile=".cvs-pass"/>
<cvs dest="${base.dir}" cvsRoot="${cvs.root}" command="update -A -dPC" passfile=".cvspass"/>

If you are using VSS, ANT provides optional task for the same.
<vssget serverpath="${vss.server}" vsspath="${vss.path}/${app.name}" ssdir="${vss.ssdir}" localpath="${base.dir}" login="${vss.username},${vss.pass}" recursive="true" />
The piece of code above will get the latest version of code from repository into the specified destination folder.

2. Label the source code appropriately to indicate released work.

<echo message="Tag ${vss.path}/${app.name} with ${app.name}-${timestamp}"/>
<cvs cvsRoot="${cvs.root}" command="tag ${app.name}-${timestamp}" package="${app.name}" passfile=".cvspass"/>

If you are using VSS, ANT provides optional task for the same.
<echo message="Label ${vss.path}/${app.name} with ${app.name}-${timestamp}"/>
<vsslabel serverpath="${vss.server}" vsspath="${vss.path}/${app.name}" ssdir="${vss.ssdir}" login="${vss.username},${vss.pass}" label="${app.name}-${timestamp}" />

The above two tasks can be specified in one target for modularity and hence the reaulting target will be:

<!-- CVS task Starts-->
<target name="cvs" description="get the latest source code from CVS" >
<cvspass cvsRoot="${cvs.root}" password="${cvs.pass}" passfile=".cvs-pass"/>
<cvs command="login" cvsRoot="${cvs.root}" passfile=".cvs-pass"/>
<cvs dest="${base.dir}" cvsRoot="${cvs.root}" command="update -A -dPC" passfile=".cvspass"/>
<cvs cvsRoot="${cvs.root}" command="tag ${app.name}-${timestamp}" package="${app.name}" passfile=".cvspass"/>
</target>
<!-- CVS task ends -->

<!-- VSS task starts -->
<target name="vss" description="get the latest source code from VSS" >
<vssget serverpath="${vss.server}" vsspath="${vss.path}/${app.name}" ssdir="${vss.ssdir}" localpath="${base.dir}" login="${vss.username},${vss.pass}" recursive="true" />
<echo message="Label ${vss.path}/${app.name} with ${app.name}-${timestamp}"/>
<vsslabel serverpath="${vss.server}" vsspath="${vss.path}/${app.name}" ssdir="${vss.ssdir}" login="${vss.username},${vss.pass}" label="${app.name}-${timestamp}" />
</target>
<!-- VSS task ends -->

Now moving to next steps:
3. Filter the files that need to be send.
4. Archive your source code to zip or gzip.
These two steps include filtering out those files which need to be sent and bundle those files.
The target to accomplish this task would look something like this:

<target name="package" description="Package the source code and Contextroot" >
<property name="target.filename" value="${app.name}-${timestamp}.zip"/>
<zip zipfile="${zip.dir}/${app.name}/${target.filename}" >
<fileset dir="${base.dir}">
<include name="src/**"/>
<include name="ContextRoot/jsp/**"/>
<include name="ContextRoot/WEB-INF/**"/>
<include name="src/META_INF/ejb-jar.xml"/>
<exclude name="ContextRoot/WEB-INF/classes/**"/>
<exclude name="ContextRoot/WEB-INF/lib/**"/>
<exclude name="ContextRoot/jsp/**/*.gif"/>
<exclude name="ContextRoot/jsp/image/**"/>
<exclude name="src/META_INF/**"/>
</fileset>
</zip>
</target>

We have used here ANT task to zip a set of files. The set of files can be specified
Here you need to specify the parameters like the target path and name of the zip file, the source files to be zipped, etc.

5. Upload the zip file to ftp server.
ANT provides a task that allows you to upload files to ftp server. The ANT target for the same is:

<target name="upload" description="uploads to FTP">
<echo message="Uploading file to ftp : ${target.filename}"/>
<ftp server="${ftp.url}" userid="${ftp.username}" password="${ftp.password}" binary="yes" remotedir="${ftp.remotedir}">
<fileset dir="${zip.dir}/${app.name}">
<include name="${target.filename}"/>
</fileset>
</ftp>
<echo message="Release uploaded successfully "/>
</target>

As you can see, the parameters that needs to be provided are connection details for ftp server, transfer mode, files upload path and source file.

The next and final step:
6. And finally send a mail to all the stakeholders stating the upload path and a release note.
ANT provides a optional task for sending MIME mail. But this task highly depends on what contents you require in your mail.
For this task, I have written a class that generates a release note for me which I attach to my mail. Though this highly depends on your requirement but here I have used a very important feature of ANT that is developing custom tasks.
To know how to develop a custoom task, please visit ant.apache.org/manual/develop.html
Here taskdef is used to declare a new Task to ANT and which class should be loaded to implement this task.
The below code declares a new task which is named as releasenote and then this task is to create the release note.

<taskdef name="releasenote" classname="com.note.ReleaseNoteCreator"/>
<releasenote appName="${app.name}" noteName="${note.name}" releasedFile="ftp://ftp.zensar.com/${ftp.remotedir}/${target.filename}"/>

Once the release note is created, the following task will release a mail for you according to the paramters provided.

<mail from="${mail.from}" tolist="${mail.to}" cclist="${mail.cc}" files="${note.name}" user="${mail.user}" password="${mail.pass}" ssl="no" replyto="${mail.reply}" subject="${mail.subject}" mailhost="${mail.host}" mailport="${mail.port}" messagefile="${mail.message}"/> <!--message="${mail.message}"/-->

The complete target will look something like this:

<target name="mail" description="Send release mail" depends="init">
<echo message="Generating Release Note"/>
<property name="note.name" value="${zip.dir}/${app.name}/ReleaseNote-${timestamp}.txt"/>
<mail from="${mail.from}" tolist="${mail.to}" cclist="${mail.cc}" files="${note.name}" user="${mail.user}" password="${mail.pass}" ssl="no" replyto="${mail.reply}" subject="${mail.subject}" mailhost="${mail.host}" mailport="${mail.port}" messagefile="${mail.message}"/> <!--message="${mail.message}"/-->
</target>

As you can see each target depends on a "init" target. This "init" target is used to perform some chores before starting the reelase process. Here it defines a timestamp variable which is used to generate distinct filesnames. The init target will look something like this
Here the record task is used for ANT logging.

<target name="init" description="Create password file and try Login">
<tstamp/>
<property name="timestamp" value="${DSTAMP}-${TSTAMP}"/>
<record name="${log.file}-${timestamp}.log" action="start" append="yes" loglevel="debug"/>
<echo message="Update, Tag, Package, Upload, and release latest source code from ${rep.type} for ${app.name}"/>
</target>

And finally you can either define a target which will call other targets or set the target execution sequence while executing ANT build file.

My final ANT build file for this process comes out to be:

<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="RELEASE_BUILD" default="getsource" basedir=".">

<property file="${arg-app}-build.properties"/>
<property name="rep.type" value="${arg-rep}"/>
<property name="base.dir" value="${ws.dir}/${app.name}"/>

<taskdef name="releasenote" classname="com.note.CreateReleaseNote"/>
<target name="init" description="Create password file and try Login">
<tstamp/>
<property name="timestamp" value="${DSTAMP}-${TSTAMP}"/>
<record name="${log.file}-${timestamp}.log" action="start" append="yes" loglevel="debug"/>
<echo message="Update, Tag, Package, Upload, and release latest source code from ${rep.type} for ${app.name}"/>
</target>

<!-- CVS Starts-->
<target name="cvs" description="get the latest source code from CVS" >
<cvspass cvsRoot="${cvs.root}" password="${cvs.pass}" passfile=".cvs-pass"/>
<cvs command="login" cvsRoot="${cvs.root}" passfile=".cvs-pass"/>
<cvs dest="${base.dir}" cvsRoot="${cvs.root}" command="update -A -dPC" passfile=".cvspass"/>
<cvs cvsRoot="${cvs.root}" command="tag ${app.name}-${timestamp}" package="${app.name}" passfile=".cvspass"/>
</target>
<!-- CVS ends -->

<!-- VSS starts -->
<target name="vss" description="get the latest source code from VSS" >
<vssget serverpath="${vss.server}" vsspath="${vss.path}/${app.name}" ssdir="${vss.ssdir}" localpath="${base.dir}" login="${vss.username},${vss.pass}" recursive="true" />
<echo message="Label ${vss.path}/${app.name} with ${app.name}-${timestamp}"/>
<vsslabel serverpath="${vss.server}" vsspath="${vss.path}/${app.name}" ssdir="${vss.ssdir}" login="${vss.username},${vss.pass}" label="${app.name}-${timestamp}" />
</target>
<!-- VSS ends -->

<!-- Common Packaging, uploading and mail starts -->
<taskdef name="releasenote" classname="com.note.CreateReleaseNote"/>

<target name="package" description="Package the source code and Contextroot" >
<property name="target.filename" value="${app.name}-${timestamp}.zip"/>
<zip zipfile="${zip.dir}/${app.name}/${target.filename}" >
<fileset dir="${base.dir}">
<include name="src/**"/>
<include name="ContextRoot/jsp/**"/>
<include name="ContextRoot/WEB-INF/**"/>
<include name="src/META_INF/ejb-jar.xml"/>
<exclude name="ContextRoot/WEB-INF/classes/**"/>
<exclude name="ContextRoot/WEB-INF/lib/**"/>
<exclude name="ContextRoot/jsp/**/*.gif"/>
<exclude name="ContextRoot/jsp/image/**"/>
<exclude name="src/META_INF/**"/>
</fileset>
</zip>
</target>

<target name="upload" description="uploads to FTP">
<echo message="Uploading file to ftp : ${target.filename}"/>
<ftp server="${ftp.url}" userid="${ftp.username}" password="${ftp.password}" binary="yes" remotedir="${ftp.remotedir}">
<fileset dir="${zip.dir}/${app.name}">
<include name="${target.filename}"/>
</fileset>
</ftp>
<echo message=" Release uploaded successfully "/>
</target>

<target name="mail" description="Send release mail" depends="init">
<echo message="Generating Release Note"/>
<property name="note.name" value="${zip.dir}/${app.name}/ReleaseNote-${timestamp}.txt"/>
<releasenote appName="${app.name}" noteName="${note.name}" releasedFile="ftp://ftp.zensar.com/${ftp.remotedir}/${target.filename}"/>
<mail from="${mail.from}" tolist="${mail.to}" cclist="${mail.cc}" files="${note.name}" user="${mail.user}" password="${mail.pass}" ssl="no" replyto="${mail.reply}" subject="${mail.subject}" mailhost="${mail.host}" mailport="${mail.port}" messagefile="${mail.message}"/> <!--message="${mail.message}"/-->
</target>

<!-- Common Packaging, uploading and mail ends -->
<target name="getsource" description="Update, Tag, Package, Upload, and release latest source code from repository" depends="init">
<antcall target="${rep.type}" />
<echo message="Update Latest Source code completed"/>
<antcall target="release" />
</target>

<target name="release" description="Package, Upload, and release Mail" depends="package,upload,mail">
<echo message="**Release of ${app.name} latest source code completed successfully**"/>
</target>

</project>

And the properties file that specifies all the paramters required is...

# Application Name as used in APWORKS.
app=MyProject
app.name=MyProject


###### CVS Path (Optional) ########
cvs.root=:pserver:husain@192.168.1.155:/app/cvs/cvsroot
#CVS Password
cvs.pass=

# The cvs module path of the application required for checkout operation.
cvs.module=DEVELOPMENT/MyProject
######## VSS Details #########
# As specified in VSS server path
vss.server=\\\\servername\\repository\\
vss.path=$/VSS/DEVELOPMENT
vss.ssdir=D:/VSS Client
vss.username=vssuser
vss.pass=vssuserpass

####### Workspace Details ##########
#Workspace location. The code from CVS is updated at ws.dir/app.name
ws.dir=D:/ide/workspace
#Target local as well as backup Directory to store zip files to be uploaded to ftp server. Used as parent dir to app.name dir.
zip.dir=D:/BackUp


######## ftp Details ########
ftp.url=ftp.ind.zensar.com
#Target ftp directory
ftp.remotedir=SOURCE/MyProject
ftp.username=user
#ftp password.
ftp.password=password

##############Mail Settings. Used to mail the release note.
mail.message=mailBody.txt
mail.host=mail.mailserver.com
mail.port=25
# list of mail ids
mail.to=target1@domain.com,cm@domain.com
mail.cc=target3@domain.com,cm2@domain.com

#Senders mail-id
mail.user=myself@domain.com
mail.from=myreplymail@domain.com

#Reply mail-id
mail.reply=myreplymail@domain.com
#Senders mail-id password.
mail.pass=mypassword
mail.subject=Source Code Release [Auto-Generated]

#Logger
log.file=D:/BackUp/buildlog


Resources:
Ant Home Page: http://ant.apache.org
Ant installation Manual: http://ant.apache.org/manual/install.html
Ant Manual: http://ant.apache.org/manual/index.html
A good article on ANT Automation: http://www.onjava.com/pub/a/onjava/2002/07/24/antauto.html