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.

No comments:

Post a Comment