2010-04-13

GWT Start-up: improving the Ant Build file

GWT Start-up: improving the Ant Build file

In the past weeks I've been investigation Google's Web Toolkit. I started looking at it because I wanted a good "Web 2.0" application and JSF was cutting it. I tried using JSF and Porlets but Portlets 2.0 isn't ready for prime time yet: the JSF bridges I've tested still don't work, but that is a topic for another post.

Today I want to make a fast track introduction to GWT and to summarise some of those issues and how I worked around them. For now I'll start with the basic project setup.

GWT has first class support for Apache's Ant but I wanted to use Maven. Why Maven? It simply makes sense to me because of all the Project Management support it has. You can improve Ant with Ivy but for me it still feels like shell scripting in XML. If I'm going to pay the price for XML than I want something in return.

There is an archetype for GWT and Maven but I simply don't like it so I decided to start with GWT's support for Ant and change the project to have Maven. But before going to Maven I also decided to improve the Ant build file the application creator generates.

Creating the Skeleton

GWT has a tool that automatically creates the skeleton for your application. I used that tool because I didn't want to wast time reinventing the wheel. It is rather simple just run the following command in the folder you want your project to live in:

$ webAppCreator -junit ~/.m2/repository/junit/junit/4.7/junit-4.7.jar org.nuno.backoffice.BackOffice

I'm using JUnit's jar from by Maven repository. I could have omitted that step but I wanted GWT to generate a Test Case for me so I can use it as a starting point later on. You can check if your project works by simply running "ant devmode".

Optimising the build file

The build file generated by GWT has, in my view, a copy/paste and I believe all copy/paste should be eliminated by refactoring. The part I'm talking about is the test part. The build file has support for running tests in development mode and in production mode. Here is what is generated by GWT:

<target name="test.dev" depends="javac.tests" description="Run development mode tests">
<mkdir dir="reports/htmlunit.dev" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<sysproperty key="gwt.args" value="-standardsMode -logLevel WARN" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
</classpath>
<batchtest todir="reports/htmlunit.dev" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
</target>

<target name="test.prod" depends="javac.tests" description="Run production mode tests">
<mkdir dir="reports/htmlunit.prod" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<sysproperty key="gwt.args" value="-prod -standardsMode -logLevel WARN -standardsMode -out www-test" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
</classpath>
<batchtest todir="reports/htmlunit.prod" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
</target>
These tasks are basically copy/paste of each other and the only change is the output folder for the reports and the GWT parameters. I decided to refactor them to a macro definition to get things a lot simpler a less error prone.

<macrodef name="testgwt">
<attribute name="gwt-args" />
<attribute name="destination" />
<sequential>
<mkdir dir="reports/@{destination}" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<sysproperty key="gwt.args" value="@{gwt-args}" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
</classpath>
<batchtest todir="reports/@{destination}" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
</sequential>
</macrodef>

<target name="test.dev" depends="javac.tests" description="Run development mode tests">
<testgwt gwt-args="-standardsMode -logLevel WARN" destination="htmlunit.dev" />
</target>

<target name="test.prod" depends="javac.tests" description="Run production mode tests">
<testgwt gwt-args="-prod -standardsMode -logLevel WARN -standardsMode -out www-test" destination="htmlunit.prod" />
</target>
Now that the build file has been refactored I decided to make an "ant clean" to find out that it doesn't actually clean all the generated output. Time to fix it.

Fixing the clean task

When I asked ant to clean the project I found out that many artifacts were still laying around. How did I found out? Well, I use git and git pointed them out for me. This is not acceptable: running "ant clean" should clean all output. I immediately fixed the issue.

<target name="clean" description="Cleans this project">
<delete dir="war/WEB-INF/classes" failonerror="false" />
<delete dir="war/backoffice" failonerror="false" />
<delete dir=".gwt-tmp" failonerror="false" />
<delete dir="tomcat" failonerror="false" />
<delete dir="reports" failonerror="false" />
<delete dir="www-test" failonerror="false" />
<delete file="BackOffice.war" />
<delete>
<fileset dir="test" includes="**/*.class" />
</delete>
</target>
I think that GWT should already add code coverage support to the default build file. I mean, why not put an option there since GWT now support EMMA? Well I did just that.

Code Coverage with EMMA

I want to be able to run the tests with and without code coverage, just to be sure that the code coverage would be interfering with the tests somehow. So this is what I did. First I added a configuration area for emma on the top of the build file, right after the GWT configuration:
  <!-- Configure path to GWT SDK -->
<property name="gwt.sdk" location="/opt/NonCriticalStorage/Software/gwt-2.0.3" />

<!-- Configure EMMA -->
<property name="emma.dir" value="/home/nsousa/Software/emma/lib" />

<target name="emma" description="turns on EMMA instrumentation/reporting" >
<property name="emma.enabled" value="true" />
</target>

<path id="emma.lib" >
<pathelement location="${emma.dir}/emma.jar" />
<pathelement location="${emma.dir}/emma_ant.jar" />
</path>

<taskdef resource="emma_ant.properties" classpathref="emma.lib" />

<path id="project.class.path">
The basic idea is that if I specify the "emma" task when calling ant than EMMA will be enabled and I'll have code coverage. If I don't specify the "emma" task then I won't have. In order words, running "ant clean emma test" will run tests with code coverage. Running "ant clean test" will run them without code coverage. I then changed the "javac" task to use EMMA if it is active as follows:
  <target name="javac" depends="libs" description="Compile java source">
<mkdir dir="war/WEB-INF/classes"/>
<javac srcdir="src" includes="**" encoding="utf-8"
destdir="war/WEB-INF/classes"
source="1.5" target="1.5" nowarn="true"
debug="true" debuglevel="lines,vars,source">
<classpath refid="project.class.path"/>
</javac>
<copy todir="war/WEB-INF/classes">
<fileset dir="src" excludes="**/*.java"/>
</copy>
<emma enabled="${emma.enabled}" >
<instr instrpath="war/WEB-INF/classes"
metadatafile="reports/coverage/metadata.emma"
merge="true"
mode="overwrite" />
</emma>
</target>
Then I changed the macro definition for the tests to use EMMA if applicable and to generate EMMA's report as follows (I also added a report for junit):

<macrodef name="testgwt">
<attribute name="gwt-args" />
<attribute name="destination" />
<sequential>
<mkdir dir="reports/@{destination}" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<jvmarg line="-Xmx256m" />
<jvmarg value="-Demma.coverage.out.file=reports/@{destination}/coverage.emma" />
<jvmarg value="-Demma.coverage.out.merge=true" />
<sysproperty key="gwt.args" value="@{gwt-args}" />
<sysproperty key="java.awt.headless" value="true" />
<classpath>
<pathelement location="src" />
<pathelement location="test" />
<path refid="project.class.path" />
<pathelement location="/home/nsousa/.m2/repository/junit/junit/4.7/junit-4.7.jar" />
<path refid="emma.lib" />
</classpath>
<batchtest todir="reports/@{destination}" >
<fileset dir="test" >
<include name="**/*Test.java" />
</fileset>
</batchtest>
<formatter type="plain" />
<formatter type="xml" />
</junit>
<junitreport todir="reports/@{destination}">
<fileset dir="reports/@{destination}">
<include name="TEST-*.xml" />
</fileset>
<report todir="reports/@{destination}" />
</junitreport>
<emma enabled="${emma.enabled}" >
<report sourcepath="src" >
<fileset dir="reports">
<include name="coverage/*.emma" />
<include name="@{destination}/*.emma" />
</fileset>
<txt outfile="reports/@{destination}/coverage.txt" />
<html outfile="reports/@{destination}/coverage.html" />
</report>
</emma>
</sequential>
</macrodef>
And that is it for getting the Ant build file to a decent state. On the next blog entry I'll talk about making a Maven build file for this project and also on rearranging the project's structure to follow Maven's conventions.

No comments:

Post a Comment