I admit that I was a bit slow to see the value of Maven. Perhaps I had too many years steeped in Unix and make
, but ant
felt comfortable and Maven did not, at least initially. Suffice it to say that I’m a convert, and now find myself disappointed when I can’t manage a project with Maven. (Android projects are still challenging, for example.)
I’ve been working on a Google Web Toolkit (GWT) project for a while using Maven. There have been some minor challenges, so in this article I thought I’d summarize some of the nits I uncovered during the process.
Dependencies
My core GWT library dependencies look like this:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory> <gwt.version>2.5.1</gwt.version> ... </properties> <dependencies> ... <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-servlet</artifactId> <version>${gwt.version}</version> </dependency> <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-user</artifactId> <version>${gwt.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.gwt</groupId> <artifactId>gwt-dev</artifactId> <version>${gwt.version}</version> <scope>test</scope> </dependency> ... <dependencies>
Note that gwt-user
doesn’t have a compile
scope the way that one usually associates with dependencies, since its contents are actually translated into JavaScript. Similarly, gwt-dev
is only needed for development purposes, and so is scoped test
so that it will not be bundled into the final product, but to be available during the test phase.
Build plugin
The build process for a GWT project differs considerably from that of a conventional WAR project. The gwt-maven-plugin
handles this. The build definition I use looks like this:
<plugins> <!-- GWT Maven Plugin --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>gwt-maven-plugin</artifactId> <version>${gwt.version}</version> <executions> <execution> <goals> <goal>compile</goal> <goal>test</goal> <goal>generateAsync</goal> </goals> </execution> </executions> <configuration> <runTarget>index.html</runTarget> <hostedWebapp>${webappDirectory}</hostedWebapp> <extraJvmArgs>-XX:MaxPermSize=512m -Xmx1024m -Xss10m</extraJvmArgs> <gwtSdkFirstInClasspath>true</gwtSdkFirstInClasspath> </configuration> </plugin> ... <plugins>
In my project, we are not currently using GWT’s I18N facility. (We will probably do so eventually, but right now we’re single-language.) If you’re using them, you’ll want to add an additional
<goal>i18n</goal>
to the execution
section above. The extraJvmArgs
bumps up the available memory for the build process – GWT builds are a bit on the memory-intensive side.
The gwtSdkFirstInClasspath
is a lifesaver. GWT has its own process for compiling Java into JavaScript. Unfortunately, their own implementation conflicts with almost any package that uses the standard jdtcore
packages to do runtime compilation. In our case, for example, we use JasperReports, which has a jdtcore
dependency in order to enable it to compile reports at runtime. The upshot is that you can get java.lang.NoSuchMethodException
s at build time because GWT is looking for things that are in its own version of the classes but which aren’t in the mainline one. Adding the gwtSdkFirstInClasspath
parameter set to true
tells the plugin to put the GWT build jars first in the classpath, which eliminates the compile-time issue. It is possible, of course, that this could also be handled by careful ordering of the dependencies in pom.xml
(since Maven uses this to establish classpath order), but this can be a real nuisance to try to work out.
Building the WAR
When you’re getting ready to deploy your application, you typically want a fully-built WAR file. During development, however, the best way to set things up is to have Maven build an “exploded” WAR that can be used by the version of Jetty that is bundled with GWT. Fortunately, there’s a way to do both. Configuring the maven-war-plugin
as shown:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <executions> <execution> <id>explode-during-compile</id> <phase>compile</phase> <goals> <goal>exploded</goal> </goals> </execution> <execution> <id>war-during-package</id> <phase>package</phase> <goals> <goal>war</goal> </goals> </execution> </executions> <configuration> <webappDirectory>${webappDirectory}</webappDirectory> </configuration> </plugin>
will cause the exploded version to be constructed as part of the compilation process, but the “all in one” version to be constructed as part of the package
phase.
Eclipse
I use Eclipse for my day-to-day development. Because of the customizations of the GWT build process, Eclipse needs to be told how to go about integrating with Maven to make sure everything builds properly.
First, I have the Google Plugin for Eclipse and M2E plugins installed.
Second, my pom.xml
contains the following:
<pluginManagement> <plugins> <!-- Make Eclipse happy --> <plugin> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.codehaus.mojo</groupId> <artifactId>gwt-maven-plugin</artifactId> <versionRange>[2.4.0,)</versionRange> <goals> <goal>resources</goal> <goal>compile</goal> <goal>i18n</goal> <goal>generateAsync</goal> </goals> </pluginExecutionFilter> <action> <execute /> </action> </pluginExecution> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <versionRange>[2.1.1,)</versionRange> <goals> <goal>exploded</goal> </goals> </pluginExecutionFilter> <action> <execute /> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement>
This makes sure that the appropriate goals on the gwt-maven-plugin
and maven-war-plugin
are called when the project is being built by Eclipse instead of via the mvn
command line. The M2E plugin will monitor your pom.xml
file, and will warn you if you make changes to it that require Eclipse to update its own build settings. The latter is generally a matter of using the “Update Project…” feature of the M2E plugin.
Compile Report
The GWT compile report is useful if you’re going to analyze code splitting. This can be includes as follows:
<reporting> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>gwt-maven-plugin</artifactId> <version>{gwt.version}</version> <reportSets> <reportSet> <reports> <report>compile-report</report> </reports> </reportSet> </reportSets> </plugin> </plugins> </reporting>
Issues
GWT is still not, in my opinion, a “first class citizen” when it comes to Maven. In particular, as of GWT 2.5.1, GWT is still bundling “foreign” packages into its jar files instead of declaring transitive dependencies. This, unfortunately, causes problems if you’re trying to use those same dependencies in your project. One of the more painful sets are the servlet
classes – gwt-servlet
includes classes for Servlet 2.5, which pretty well precludes using both gwt-servlet
and Servlet 3.0 in a standard Maven build.
There is an outstanding GWT Issue #4484 to address this – hopefully it will gain some momentum.
I also have a small test class that is useful to hook the copy of Jetty that GWT embeds into JUnit tests. But that, dear reader, is for another post.