How to build and package a GWT app to a WAR file using Ant

This article is intended as a step by step guide for automating the construction of a WAR file from your GWT application using an Ant script. It covers the following steps
  • Establishing the project structure
  • Compiling the source - Java & Javascript
  • Packaging and accessing static resources - Images, CSS, property files
  • Packaging the deployment archives - JAR & WAR files
Establishing the project structure Create a new GWT Web Application project and setup the directory structure as per the following screenshot BuildTutorial com.rubiconred.buildtutorial: This is where the .gwt.xml module file lives and we have added a public directory. The public directory is where we will place all of our static resources (e.g. Image files) that are accessed directly from within our GWT source code (see further below). com.rubiconred.buildtutorial.client: This is where we place our GWT source code that is not referenced anywhere in our server code. This code will be compiled to Javascript but not to .class files. Note that you should not be putting RemoteService classes in here as these interfaces are implemented by server-side RemoteServiceServlet classes. com.rubiconred.buildtutorial.shared: This is where we place our GWT source code that is referenced by both client and server code. This code will be compiled to both Javascript and to .class files. This is the place to put RemoteService classes. com.rubiconred.buildtutorial.server: This is where we have access to the full Java APIs and will be the location for our RemoteServiceServlet classes. This code will not be compiled to Javascript but only to .class files. Note: To ensure the client & shared packages will both be compiled to Javascript the following must be present in your .gwt.xml module file (generated by default if using Eclipse plugin) [code] <!-- Specify the paths for translatable code --> <source path='client'/> <source path='shared'/> [/code] war/client_resources: We will use this directory to store our static resources that are accessed directly from the BuildTutorial.html or BuildTutorial.cssfiles. Remember static resources accessed from within the GWT code should be placed in the public directory as described above. war/server_resources: We will use this directory to store static resources used by our server code, such as properties or XML files. build.xml: Create a new file build.xml and place it on the project root directory. Following is the skeleton of this build script which we will flesh out over the next few steps to automate the build and packaging process for us. [code] <?xml version="1.0"?> <project name="BuildTutorial" basedir="." default="war"> <target name="prepare"/> <target name="clean"/> <!-- Compile the java source code using javac --> <target name="compile" depends="prepare/"> <!-- Invoke the GWT compiler to create the Javascript for us --> <target name="gwt-compile" depends="compile"/> <!-- Package the compiled Java source into a JAR file --> <target name="jar" depends="compile"/> <!-- Copy the static server resources into the required directory ready for packaging --> <target name="copy-resources"/> <!-- Package the JAR file, Javascript, static resources and external libraries into a WAR file --> <target name="war" depends="gwt-compile, jar, copy-resources"/> <!-- Deploy the WAR file (optional) --> <target name="deploy" depends="war"/> </project> [/code] Compiling the project source  We will begin by establishing the Ant tasks in our build script that will compile our source code. For a GWT app this includes the additional task of invoking the GWT compiler to build our client and shared code to Javascript. First we should establish the properties that we will reference throughout the build script. Add the following to the top of build.xml and update the property values to match your own project and domain names. [code] <property name="gwt.module.name" value="com.rubiconred.buildtutorial.BuildTutorial"/> <property name="server.resources.name" value="server_resources"/> <property name="jar.name" value="buildtutorial.jar"/> <property name="war.name" value="buildtutorial.war"/> <property name="src.dir" location="src"/> <property name="server.resources.dir" location="war/${server.resources.name}"/> <property name="build.dir" location="build"/> <property name="build.server.resources.dir" location="war/WEB-INF/classes/server_resources"/> <property name="lib.dir" location="war/WEB-INF/lib"/> <property name="gwt.client.dir" location="com/rubiconred/soauiext/client"/> [/code] Next add the following to establish the classpath that we will reference when compiling. Any third party JARs you are using in your project should be available in the war/WEB-INF/lib directory. [code] <path id="project.classpath"> <fileset dir="${lib.dir}"> <include name="**/*.jar"/> </fileset> </path> [/code] Setup Ant tasks to create and delete the build directory. The build directory is used as a temporary staging area for the compiled source code during the build process. [code] <target name="prepare"> <mkdir dir="${build.dir}"/> </target> <target name="clean"> <delete dir="${build.dir}"/> </target> [/code] Next is the Ant task to invoke the Java compiler. Note that while we are compiling everything in the src dir (client, shared, server) later when we package this into the JAR we will exclude the client classes. The client classes are only included here as they are required by the GWT compiler in the next task.
[code]<target name="compile" depends="prepare"> <javac srcdir="${src.dir}" destdir="${build.dir}"> <classpath refid="project.classpath"/> </javac> </target>[/code]
Now we are ready to invoke the GWT compiler. In order for the GWT compiler to work the following JAR files must be on the classpath
  • gwt-dev.jar
  • gwt-user.jar
Add these files to the war/WEB-INF/lib directory. You should find them in your Eclipse installation directory (GWT 2.2) ECLIPSE_HOMEpluginscom.google.gwt.eclipse.sdkbundle.2.2.0_2.2.0.v201102111811gwt-2.2.0 [code] <target name="gwt-compile" depends="compile"> <java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler"> <classpath> <!-- src dir is added to ensure the module.xml file(s) are on the classpath --> <pathelement location="${src.dir}"/> <pathelement location="${build.dir}"/> <path refid="project.classpath"/> </classpath> <jvmarg value="-Xmx256M"/> <arg value="${gwt.module.name}"/> </java> </target> [/code] Packaging and accessing static resources One of the trickiest parts of packaging and deploying a GWT app is figuring out where to place the various types of static resource files and how to access them in your code so that they can be located after you have deployed to an application server just as they are in your development environment. The following list shows a strategy that I have found effective Resource Type Client resources accessed directly within your GWT code e.g. Image files used by an Image widget Public_Images Access Method [code]Image image = new Image(GWT.getModuleBaseURL() + "/images/myImage.png");[/code] Resource Type Client resources accessed from the HTML or CSS files client_resources Access Method background-image: url(client_resources/images/light_blue_vert.png); Resource Type Server resources accessed from server side code e.g. Properties file server_resources Access Method [code] InputStream in = this.getClass().getClassLoader().getResourceAsStream("server_resources/serverProps.properties"); props = new Properties(); props.load(in); [/code] As part of the build process we need to copy the server_resources into the WEB-INF/classes directory so that they will be properly packaged into the WAR file and can be accessed when we deploy. The following Ant task will take care of this. It may also be necessary to invoke this target during development to keep the resource directories in sync so that they can be accessed when running in development mode too. [code] <target name="copy-resources"> <copy todir="${build.server.resources.dir}" preservelastmodified="true"> <fileset dir="${server.resources.dir}"/> </copy> </target> [/code] Packaging the deployment archives The first step here is to package the compiled Java source into a JAR file that will then be packaged into the WAR file in the WEB-INF/lib directory as part of the next step. Because we don't want to deploy any unnecessary code to the server we exclude the client classes from the package. [code] <target name="jar" depends="compile"> <jar jarfile="${lib.dir}/${jar.name}" basedir="${build.dir}/"> <!-- Don't wrap any of the client only code into the JAR --> <exclude name="${gwt.client.dir}/**/*.class"/> </jar> </target> [/code] Now we are ready to build the WAR file. The following task packages the JAR, Javascript, static resources and third party JAR files (excluding gwt-dev.jar & gwt-user.jar) that we established during the previous build targets into the deployable WAR file that we require. [code] <target name="war" depends="gwt-compile, jar, copy-resources"> <war basedir="war" destfile="${war.name}" webxml="war/WEB-INF/web.xml"> <exclude name="WEB-INF/**" /> <exclude name="${server.resources.name}/**"/> <webinf dir="war/WEB-INF/"> <include name="classes/${server.resources.name}/**" /> <include name="**/*.jar" /> <exclude name="**/gwt-dev.jar" /> <exclude name="**/gwt-user.jar" /> </webinf> </war> </target> [/code] Final Steps Run the default task by right clicking on the build.xml file and selecting Run As > Ant Build. If you refresh the project directory in Eclipse you will notice a new build directory containing your compiled source has been added. You can remove this using the Ant target clean if you wish. You will also findbuildtutorial.war has been added to the root directory of your project. If you copy this WAR file over to your server and deploy it onto the application server you will be able to run your GWT app in the deployed environment. It is also possible to automate the deployment step with Ant but that is beyond the scope of this article. And here is the final build.xml contents [code] <?xml version="1.0"?> <project name="BuildTutorial" basedir="." default="war"> <property name="gwt.module.name" value="com.rubiconred.buildtutorial.BuildTutorial"/> <property name="server.resources.name" value="server_resources"/> <property name="jar.name" value="buildtutorial.jar"/> <property name="war.name" value="buildtutorial.war"/> <property name="src.dir" location="src"/> <property name="server.resources.dir" location="war/${server.resources.name}"/> <property name="build.dir" location="build"/> <property name="build.server.resources.dir" location="war/WEB-INF/classes/server_resources"/> <property name="lib.dir" location="war/WEB-INF/lib"/> <property name="gwt.client.dir" location="com/rubiconred/soauiext/client"/> <path id="project.classpath"> <fileset dir="${lib.dir}"> <include name="**/*.jar"/> </fileset> </path> <target name="prepare"> <mkdir dir="${build.dir}"/> </target> <target name="clean"> <delete dir="${build.dir}"/> </target> <!-- Compile the java source code using javac --> <target name="compile" depends="prepare"> <javac srcdir="${src.dir}" destdir="${build.dir}"> <classpath refid="project.classpath"/> </javac> </target> <!-- Invoke the GWT compiler to create the Javascript for us --> <target name="gwt-compile" depends="compile"> <java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler"> <classpath> <!-- src dir is added to ensure the module.xml file(s) are on the classpath --> <pathelement location="${src.dir}"/> <pathelement location="${build.dir}"/> <path refid="project.classpath"/> </classpath> <jvmarg value="-Xmx256M"/> <arg value="${gwt.module.name}"/> </java> </target> <!-- Package the compiled Java source into a JAR file --> <target name="jar" depends="compile"> <jar jarfile="${lib.dir}/${jar.name}" basedir="${build.dir}/"> <!-- Don't wrap any of the client only code into the JAR --> <exclude name="${gwt.client.dir}/**/*.class"/> </jar> </target> <!-- Copy the static server resources into the required directory ready for packaging --> <target name="copy-resources"> <copy todir="${build.server.resources.dir}" preservelastmodified="true"> <fileset dir="${server.resources.dir}"/> </copy> </target> <!-- Package the JAR file, Javascript, static resources and external libraries into a WAR file --> <target name="war" depends="gwt-compile, jar, copy-resources"> <war basedir="war" destfile="${war.name}" webxml="war/WEB-INF/web.xml"> <exclude name="WEB-INF/**" /> <exclude name="${server.resources.name}/**"/> <webinf dir="war/WEB-INF/"> <include name="classes/${server.resources.name}/**" /> <include name="**/*.jar" /> <exclude name="**/gwt-dev.jar" /> <exclude name="**/gwt-user.jar" /> </webinf> </war> </target> </project> [/code]