2010-04-13

GWT Part II: From Ant to Maven

In the last entry I showed how to get started with GWT and get a nicer build file with Ant. In this entry I'll explain the process of moving from Ant to Maven.

My goal here is to take advantage of all of Maven's features while still maintaining a workable project configuration that I can use from a command line and from Eclipse. GWT's tools generate an Eclipse project but don't do other things like, for instance, adding dependencies to the Eclipse project when you add them to the build file. Maybe you can do this using Apache's Ivy. I haven't tried it, maybe I will someday if I end up hitting a wall using Maven...

First thing to do is to rearrange our source code. Maven expects things to be on different paths and we will have to do that. I started by moving all java source code to a newly created "src/main/java". I'm using git so I just did:
$ mkdir -p src/main/java
$ git mv src/org src/main/java/org
I had to do a similar step for the unit tests:
$ mkdir -p src/test/java
$ git mv test/org src/test/java
$ rmdir test
Finally I had to move the war file to the folder maven expects the WAR resources to be int. It is also a simple move operation:
$ mkdir -p src/main/webapp
$ git mv war/* src/main/webapp/
$ rmdir war
$ git rm -f src/main/webapp/WEB-INF/lib/gwt-servlet.jar
I also removed the gwt-servlet.jar file that was added by the GWT tools as maven will take care of it for me.

Maven supports resources and resource filtering but GWT has special needs. This means that GWT's XML files for the source code need to reside on the same code as the java files, something that is not usual in Maven projects. But I had to do this for the test resources, that is, for the GWT xml file used in the tests. That was relatively simple:
$ mkdir -p src/test/resources/org/nuno/backoffice
$ git mv src/test/java/org/nuno/backoffice/BackOfficeJUnit.gwt.xml src/test/resources/org/nuno/backoffice
But this is not sufficient. The naming convention for unit tests are different. We want to support plain old unit tests and GWT Test Cases. For Maven the convention is to have your tests named "GwtTest*" for GWT based unit tests and ending with either "Test" or "TestCase" for plain old Unit Tests. Luckely this is a simple change. I renamed the "BackOfficeTest.java" file to "GwtBackOffice.java" with the following command:
$ git mv src/test/java/org/nuno/backoffice/client/BackOfficeTest.java src/test/java/org/nuno/backoffice/client/GwtTestBackOffice.java
Next I edited the file to change the name of the class. I will try to explain how to have 70% of your unit tests being plain old JUnit tests (without GWT) overhead in a future entry.

Since I'm want to use Eclipse's Maven plug-in I simply deleted the eclipse projects with "git rm .classpath .project". Now comes the hard part: the Project Object Model (POM).

Making a POM for a GWT project was a collection of trials and errors with many searches on Google, forums and others. I finally got a simple POM that works for all that I needed so far, that is, to build the project, run simple unit tests and GWT based tests, run in development mode, make unit and coverage reports and that can be used directly from Eclipse.

The GWT for Maven plug-in has support to generate the ASYNC interfaces automatically. I decided to use this support I went ahead and removed the Async version of the interface as follows:
$ git rm src/main/java/org/nuno/backoffice/client/GreetingServiceAsync.java
With all these steps and a lot of debugging, trial and error I ended up with this POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.nuno.backoffice</groupId>
<artifactId>BackOffice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Back Office</name>

<properties>
<gwt.version>2.0.3</gwt.version>
</properties>

<dependencies>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>${gwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwt.version}</version>
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>

<repositories>
<repository>
<id>gwt-maven</id>
<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo/</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>gwt-maven</id>
<url>http://gwt-maven.googlecode.com/svn/trunk/mavenrepo</url>
</pluginRepository>
</pluginRepositories>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.2</version>
<configuration>
<runTarget>org.nuno.backoffice.Backoffice/Backoffice.html</runTarget>
<hostedWebapp>${project.build.directory}/gwt-run-war</hostedWebapp>
<tomcat>${project.build.directory}/tomcat</tomcat>
</configuration>
<executions>
<execution>
<goals>
<goal>generateAsync</goal>
<goal>compile</goal>
<goal>resources</goal>
<goal>clean</goal>
</goals>
</execution>
<!-- GWT Test Cases are run in the test phase instead o the integation-test phase -->
<execution>
<id>gwt-tests</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>

<!-- Work around the fact that gwt:test creates a tomcat folder -->

<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<directory>tomcat</directory>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>

<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>emma-maven-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>


With this POM we can simple do:
  • "mvn package" to build the WAR file (running all unit tests);
  • "mvn gwt:run" to run in development mode
  • "mvn clean" to clean all output
  • "mvn site" to generate the site for the project with all the reports.
  • In principle any other Maven task or phase.
Using the M2 plugin for eclipse you can simple import the Maven project but you will have to choose "Maven"->"Update project Configuration" once it is imported so it caches the generated sources folder.

The project will work well in eclipse but you won't be able to use GWT eclipse integration fully: you can edit all source files with all the assistance but you can't run the code using the GWT plugin for eclipse. What you can do is configure an external maven target of "gwt:run" and work as if nothing had happened.

On future entries I have a lot more things to cover like how to run plain old Unit Tests for 70% of your tests (this POM is already prepared for it); Unit Testing with MVP and whatever comes to mind.

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.