Automated Releases with Maven and Google App Engine

Google App Engine LogoVersature has been building our applications using Java and deploying them on Google App Engine for some time. It has been a very effective solution for us.

Recently, I wanted to improve our release process by making it more formal and automated. After the usual battle with Maven, I managed to win. :)

Our current build automates the following:

  • Remove the trailing -SNAPSHOT from the version number in the POM
  • Tag the release in Subversion
  • Filter the appengine-web.xml to contain the version number
  • Build and deploy to GAE
  • Increment version number in the POM and add the trailing -SNAPSHOT

All of the above is achieved with two Maven commands: release:prepare and release:perform.

Here is the POM with most of the irrelevant parts removed:

<?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>
    <version>0-9-0-SNAPSHOT</version>
    <packaging>war</packaging>
 
    <name>Versature Dashboard</name>
 
    <scm>
        <connection>scm:svn:https://REMOVED/trunk</connection>
    </scm>
 
    <repositories>
        <repository>
            <id>google-maven-repo</id>
            <name>Maven Google App Engine Repository</name>
            <url>http://maven-gae-plugin.googlecode.com/svn/repository/</url>
        </repository>
        <repository>
            <id>codehaus</id>
            <name>codehaus</name>
            <url>http://repository.codehaus.org/org/codehaus/groovy/</url>
        </repository>
    </repositories>
 
    <pluginRepositories>
        <pluginRepository>
            <id>maven-gae-plugin-repo</id>
            <name>Maven Google App Engine Repository</name>
            <url>http://maven-gae-plugin.googlecode.com/svn/repository/</url>
        </pluginRepository>
    </pluginRepositories>
 
    <dependencies>
        REMOVED
    </dependencies>
 
    <build>
        <testOutputDirectory>target/test-classes</testOutputDirectory>
        <outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>
        <testSourceDirectory>src/test/java</testSourceDirectory>
 
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1.1</version>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>exploded</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <webResources>
                      <resource>
                        <directory>src/main/webapp</directory>
                        <filtering>true</filtering>
                        <includes>
                          <include>**/appengine-web.xml</include>
                        </includes>
                      </resource>
                    </webResources>
                    <webappDirectory>${webappDirectory}</webappDirectory>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>gwt-maven-plugin</artifactId>
                <version>${gwt-plugin.version}</version>
                <configuration>
                    <logLevel>INFO</logLevel>
                    <port>${gae.port}</port>
                    <server>com.google.appengine.tools.development.gwt.AppEngineLauncher</server>
                    <runTarget>/REMOVED</runTarget>
                    <style>OBF</style>
                </configuration>
                <executions>
                    <execution>
                        <configuration>
                            <extraJvmArgs>-Xmx1024m -Xms512m</extraJvmArgs>
                            <module>com.versature.dashboard.Dashboard</module>
                        </configuration>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
 
            <plugin>
                <groupId>net.kindleit</groupId>
                <artifactId>maven-gae-plugin</artifactId>
                <version>0.9.2</version>
                <configuration>
                    <unpackVersion>${gae.version}</unpackVersion>
                    <serverId>appengine.google.com</serverId>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>net.kindleit</groupId>
                        <artifactId>gae-runtime</artifactId>
                        <version>${gae.version}</version>
                        <type>pom</type>
                    </dependency>
                </dependencies>
            </plugin>
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>2.2.2</version>
                <configuration>
                    <scmCommentPrefix>[RELEASE] </scmCommentPrefix>
                    <goals>gae:deploy</goals>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.codehaus.gmaven</groupId>
                <artifactId>gmaven-plugin</artifactId>
                <version>${gmaven.version}</version>
                <executions>
                    <execution>
                        <id>sources</id>
                        <goals>
                            <goal>generateStubs</goal>
                            <goal>compile</goal>
                            <goal>generateTestStubs</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>properties</id>
                        <phase>validate</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                        <configuration>
                            <source>
                                project.properties["gae-application.version"] = project.version.toLowerCase()
                            </source>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
    <!-- Specify hard-coded project properties here -->
    <properties>
        <gmaven.version>1.3</gmaven.version>
        <gae.version>1.6.2.1</gae.version>
        <gae.port>9090</gae.port>
 
        <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>
    </properties>
 
    <profiles>
        <profile>
            <id>release-build</id>
            <activation>
                <property>
                    <name>performRelease</name>
                    <value>true</value>
                </property>
            </activation>
 
            <properties>
                <gwt.style>OBF</gwt.style>
            </properties>
        </profile>
    </profiles>
    <groupId>com.versature.dashboard</groupId>
    <artifactId>dashboard</artifactId>
</project>

If you run into an error similar to this one:

com.google.appengine.tools.admin.AdminException: Unable to update app: Error posting to URL: https://appengine.google.com/api/appversion/create?app_id=versature-dashboardhr&version=0-8-1-SNAPSHOT&
400 Bad Request
Error when loading application configuration:
Unable to assign value '0-8-1-SNAPSHOT' to attribute 'version':
Value '0-8-1-SNAPSHOT' for version does not match expression '^(?!-)[a-z\d-]{1,100}$'

It is because GAE only accepts lowercase letters, digits and hyphens in version names. (Because those version names become URLs.) I solved this by using a snippet of Groovy (Using the GMaven plugin.) code to automatically create a property with all lowercase letters before filtering it into the appengine-web.xml. So version 0-8-0-SNAPSHOT becomes 0-8-0-snapshot:

1
project.properties["gae-application.version"] = project.version.toLowerCase()

And that’s it, it Just Works™. Gotta love Maven!

Google App Engine’s Python Exception Syntax

Remember, Python 2.5 has this exception syntax:

try:
    raise FooException("Darn!")
except FooException, e
    print("Error: %s" % e)
The newer syntax is:
try:
    raise FooException("Darn!")
except FooException as e
    print("Error: %s" % e)
I ran into this when uploading an application to Google App Engine, which then produced this cryptic error:
<type 'exceptions.SyntaxError'>: invalid syntax (main.py, line 64)
Where line 64 is the line containing the not-yet-implemented as keyword.