Wednesday, November 16, 2011

How to mavenize your GWT eclipse projects

Maven and gwt eclipse projects haven`t been playing nicely with each other for quite some time now. There are always some little gotchas that might get you. For me I found a way that works nicely and so I decided to share. This is based on Google Plugin for Eclipse 3.6 Version 2.4.2.relr36v201110112027 and maven 3.0.3.

Update 1:
Users running this on windows reported problems with the linked war file. Changing the path for the linked file using an eclipse variable solves this. I updated the post to reflect this.

Update 2:
As Thorsten pointed out running mvn war:exploded is much faster than running mvn war:war . Updated the post.

The first thing I like to have is one single point of configuration and not a plit up configuration in many different places. To me this is what the pom.xml is for. So I wanted to have a way to be able to set up everything from the pom and let maven write the files that I need in eclipse. (Like .project and .classpath)  First thing for me was to declare the gwt version as a property like this:
<properties>
   <gwtversion>2.4.0</gwtversion>
</properties>
Next up you simply declare gwt as a dependency:
<dependency>
  <groupId>com.google.gwt</groupId>
  <artifactId>gwt-user</artifactId>
  <version>${gwtversion}</version>
</dependency>
<dependency>
  <groupId>com.google.gwt</groupId>
  <artifactId>gwt-servlet</artifactId>
  <version>${gwtversion}</version>
</dependency>
<dependency>
  <groupId>javax.validation</groupId>
  <artifactId>validation-api</artifactId>
  <version>1.0.0.GA</version>
  </dependency>
<dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>1.0.0.GA</version>
   <classifier>sources</classifier>
</dependency>
So far nothing fancy, but right now maven would not be able to compile your gwt sources to javascript. For that we are using the gwt-maven-plugin:
<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>gwt-maven-plugin</artifactId>
   <version>${gwtversion}</version>
   <executions>
      <execution>
         <goals>
            <goal>compile</goal>
            <goal>test</goal>
         </goals>
      </execution>
   </executions>
</plugin>
Note: If for some reason you want to use a different version of the gwt-maven-plugin (using a newer version of the plugin with gwt trunk), you can set this version manually. Because the gwt-maven-plugin takes care of adding gwt-dev to the classpath for compilation only, we avoid having nasty dependency conflicts. So up to know you can simply run mvn package on the console and you will get a nice artifat compiled with gwt, but there is no integration with eclipse so far. For that we are going to use the maven-eclipse-plugin with some nice tricks. First the whole plugin configuration:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-eclipse-plugin</artifactId>
   <version>2.8</version>

   <configuration>
      <downloadSources>true</downloadSources>
      <downloadJavadocs>true</downloadJavadocs>
      <buildOutputDirectory>war/WEB-INF/classes</buildOutputDirectory>
      <projectnatures>
         <projectnature>org.eclipse.jdt.core.javanature</projectnature
         <projectnature>com.google.gdt.eclipse.core.webAppNature</projectnature>
         <nature>com.google.gwt.eclipse.core.gwtNature</nature>
      </projectnatures>
     
      <buildcommands>
         <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
         <buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator</buildcommand>
         <buildcommand>com.google.appengine.eclipse.core.projectValidator</buildcommand>
         <buildcommand>com.google.gwt.eclipse.core.gwtProjectValidator</buildcommand>
      </buildcommands>

      <classpathContainers>
         <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
         <classpathContainer>com.google.gwt.eclipse.core.GWT_CONTAINER</classpathContainer>
      </classpathContainers>
     
      <excludes>
         <exclude>com.google.gwt:gwt-servlet</exclude>
         <exclude>com.google.gwt:gwt-user</exclude>
         <exclude>com.google.gwt:gwt-dev</exclude>
         <exclude>javax.validation:validation-api</exclude>
      </excludes>

      <linkedResources>
         <linkedResource>
            <name>war
            <type>2
            <location>PROJECT_LOC/target/${project.artifactId}-${project.version}</location>
  </linkedResource>
      </linkedResources>

   </configuration>
</plugin>


Lets discuss the different parts in detail:
<linkedResources>
   <linkedResource>
   <name>war
   <type>2
   <location>PROJECT_LOC/target/${project.artifactId}-${project.version}</location>
   </linkedResource>
</linkedResources>
This adds a linked directory to your eclipse project which actually points to the compile directory of maven. So your build from eclipse just use the same output directory. This is quite useful because the google eclipse plugin won't complain about the compile target of the project, but still will deploy to a temporary directory (and not src/main/webapp)

With this part you tell eclipse to complile into the linked directory:
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
<buildOutputDirectory>war/WEB-INF/classes</buildOutputDirectory>


We also have to set the right project natures so that our project is regonzied as java, gwt and a wepp app project:
<projectnatures>
   <projectnature>org.eclipse.jdt.core.javanature</projectnature
   <projectnature>com.google.gdt.eclipse.core.webAppNature</projectnature>
   <nature>com.google.gwt.eclipse.core.gwtNature</nature>
</projectnatures>


And we have to configure the right builders for our project:
<buildcommands>
   <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
   <buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator</buildcommand>
   <buildcommand>com.google.appengine.eclipse.core.projectValidator</buildcommand>
   <buildcommand>com.google.gwt.eclipse.core.gwtProjectValidator</buildcommand>
</buildcommands>


Also we want to have the GWT SDK and the Java SDK on our buildpath:
<classpathContainers>
   <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
   <classpathContainer>com.google.gwt.eclipse.core.GWT_CONTAINER</classpathContainer>
</classpathContainers>


And we have to make sure that gwt maven dependencies are not on the classpath, because the google eclipse plugin needs the local SDK jars to work properly. So for eclipse project we exclude them (google eclipse plugin will provide them).
<excludes>
   <exclude>com.google.gwt:gwt-servlet</exclude>
   <exclude>com.google.gwt:gwt-user</exclude>
   <exclude>com.google.gwt:gwt-dev</exclude>
   <exclude>javax.validation:validation-api</exclude>
</excludes>


If you set up your project to include this in your pom, you can now simply to:

mvn eclipse:eclipse 
mvn war:exploded 

 and import the project into eclipse:

File -> Import -> General -> Existing Projects into Workspace


The one thing I was not able to figure out was how to tell the google eclipse plugin that gwt-servlet and gwt-user are already inside war/WEB-INF/lib, so you still get a warning about that. Something like : 

"The GWT SDK JAR gwt-servlet.jar is missing in the WEB-INF/lib directory"

But you can simply use the quick fix:

> Synchronize /WEB-INF/lib with SDK libaries. 


The google eclipse plugin will copy the gwt-servlet.jar into your WEB-INF/lib folder and you are good to go. I will try to talk about this last issue with some developers from google at the google developer weekend to get this working all the way. For me this is the best way right now to work with maven and gwt in eclipse.


If you did something to your project which needs to rewrite the eclipse project files you can do:


mvn eclipse:clean
mvn eclipse:eclipse


If you are making changes to src/main/webapp that need to be reflected in your war directory, simply run:


mvn war:exploded


For those interested here is a complete pom example from the mgwt showcase:
<?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>com.googlecode.mgwt</groupId>
 <artifactId>mgwt-showcase</artifactId>
 <version>1.0.0-SNAPSHOT</version>
 <packaging>war</packaging>
 <name>Mobile GWT Showcase</name>

 <properties>
  <gwtversion>2.4.0</gwtversion>
 </properties>

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

   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>gwt-maven-plugin</artifactId>
    <version>2.4.0</version>
    <executions>
     <execution>
      <goals>
       <goal>compile</goal>
       <goal>test</goal>
      </goals>
     </execution>
    </executions>
   </plugin>

   

   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-eclipse-plugin</artifactId>
    <version>2.8</version>

    <configuration>
     <downloadSources>true</downloadSources>
     <downloadJavadocs>false</downloadJavadocs>
     <buildOutputDirectory>war/WEB-INF/classes</buildOutputDirectory>
     <projectnatures>
      <projectnature>org.eclipse.jdt.core.javanature</projectnature>
      <projectnature>com.google.gdt.eclipse.core.webAppNature</projectnature>

      <nature>com.google.gwt.eclipse.core.gwtNature</nature>
     </projectnatures>
     <buildcommands>
      <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
      <buildcommand>com.google.gdt.eclipse.core.webAppProjectValidator</buildcommand>

      <buildcommand>com.google.appengine.eclipse.core.projectValidator</buildcommand>
      <buildcommand>com.google.gwt.eclipse.core.gwtProjectValidator</buildcommand>
     </buildcommands>
     <classpathContainers>
      <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>

      <classpathContainer>com.google.gwt.eclipse.core.GWT_CONTAINER</classpathContainer>
     </classpathContainers>
     <excludes>
      <exclude>com.google.gwt:gwt-servlet</exclude>
      <exclude>com.google.gwt:gwt-user</exclude>
      <exclude>com.google.gwt:gwt-dev</exclude>
      <exclude>javax.validation:validation-api</exclude>
     </excludes>
     <linkedResources>
      <linkedResource>
       <name>war</name>
       <type>2</type>
       <location>PROJECT_LOC/target/${project.artifactId}-${project.version}</location>
      </linkedResource>
     </linkedResources>

    </configuration>
   </plugin>
   <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <executions>
     <execution>
      <id>assemble</id>
      <phase>package</phase>
      <goals>
       <goal>single</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <descriptors>
      <descriptor>src/main/assembly/clientcode.xml</descriptor>
     </descriptors>
    </configuration>
   </plugin>
  </plugins>
  <resources>
   <resource>
    <directory>src/main/java</directory>
    <includes>
     <include>**/client/**</include>
     <include>**/*.gwt.xml</include>
    </includes>
   </resource>
  </resources>
 </build>

 <dependencies>
  <dependency>
   <groupId>com.googlecode.mgwt</groupId>
   <artifactId>mgwt</artifactId>
   <version>1.0.1-SNAPSHOT</version>

  </dependency>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.7</version>
   <type>jar</type>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>com.google.gwt</groupId>
   <artifactId>gwt-user</artifactId>
   <version>${gwtversion}</version>
  </dependency>
  <dependency>
   <groupId>com.google.gwt</groupId>
   <artifactId>gwt-servlet</artifactId>
   <version>${gwtversion}</version>
  </dependency>
  <dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>1.0.0.GA</version>
  </dependency>
  <dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>1.0.0.GA</version>
   <classifier>sources</classifier>
  </dependency>
 </dependencies>



</project>

6 comments:

  1. I did pretty much the same thing yesterday, ending up with almost the same layout. Except for i am trying to also stuff app engine support into this.

    A couple of points:
    - that linkedResource-stuff is only really necessary with old versions of GPE, since newer versions handle different paths just fine.
    Of course this means youll have to point your buildOutputDirectory to ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
    - while developing you dont need to run war:war, since you dont actually need a .war file. war:exploded is just fine. Might save you a little time if your webapp is large or your machine is slow :)
    -

    ReplyDelete
  2. Hi Thorsten,

    thanks for your feedback. I know that the build output folder does not need to be the war directory, but after every mvn eclipse:eclipse it complains again so thats just anoying. So I go with the linked directory.

    Good tip about the war:exploded goal. I will make some more tests in windows (some people complained about problems there) and update the blog post.

    Thanks for the help

    ReplyDelete
  3. Nice Article, however I got into issue with the PROJECT_LOC. Have to use a POM project property instead: ${project.build.directory} instead of PROJECT_LOC/target

    ReplyDelete
  4. Thanks for your sharing. It's nice post.

    ReplyDelete
  5. Oh Man this is amazing thanks so much

    ReplyDelete