Monday, April 19, 2010

AppEngine Gradle Plugin

Last Friday I worked on a pet project together with some colleagues. We're building the project with Gradle and we wanted to deploy it to Google AppEngine. Now Gradle has support for plugins, so I decided I'd build a plugin for Google AppEngine....

Creating plugins for Gradle isn't half as hard a creating one for Maven2. You can setup a simple project, I choose groovy, add a gradle build file, and implement a single interface... Let's start with the buildfile:


defaultTasks "clean", "build"

usePlugin 'groovy'

group = 'com.xebia'
version = '0.2'

repositories {
add(new org.apache.ivy.plugins.resolver.URLResolver()) {
name = "GitHub"
addArtifactPattern 'http://github.com/hierynomus/appengine-gradle-plugin/downloads/[organization]-[module]-[revision].[ext]'
}
}

dependencies {
groovy "appengine:tools-api:1.3.2@jar"
groovy fileTree(dir: new File(gradle.gradleHomeDir, 'lib'), includes: ['*.jar'])
}


As you see, the buildfile is nothing special, it looks just like an ordinary Gradle project buildfile. We use the 'groovy' plugin, as our plugin will be written using groovy. Also we use 'fileTree' to add all the gradle jars to the classpath of our plugin.

Next up, the plugin code.


import com.google.appengine.tools.admin.AppCfg

class AppEngine implements Plugin {
void use(Project project, ProjectPluginsContainer pluginContainer) {
// Configure the war plugin to explode the built war file.
configureWarPlugin(project)

project.convention.plugins.appengine = new AppEnginePluginConvention(project)

// The upload task, to upload to AppEngine
project.task('upload') << {
AppCfg.main("update", project.convention.plugins.appengine.exploded.toString())
}

// Make sure that we built the war before uploading
project.upload.dependsOn project.war
}
}


As you can see it is a very concise class that implements the org.gradle.api.Plugin interface. There are onl three other pieces of code to show. First we need to check whether the 'war' plugin was loaded and configure it to explode the WAR archive, as that is how AppEngine likes it.


def configureWarPlugin(Project project) {
if (!project.plugins.hasPlugin('war')) {
throw new InvalidUserDataException("For AppEngine plugin, the war plugin should be enabled!")
}

// Add the exploded-war to the war task
project.war.doLast {
ant.unzip(src: project.war.archivePath, dest: project.convention.plugins.appengine.exploded)
}
}


The other part is the so-called PluginConvention object. In this object we can store settings that are copied from your project build file when you're building the project and using our plugin. In our case we use it to read a properties file that tells us where the Google AppEngine SDK is located.


class AppEnginePluginConvention {
Properties props = new Properties()
Project project
File exploded

def AppEnginePluginConvention(project) {
this.project = project
if (!new File("appengine.properties").exists()) {
throw new InvalidUserDataException("appengine.properties should exist in build root dir")
}
props.load(new FileInputStream("appengine.properties"))
init()
}

def init() {
exploded = new File(project.buildDir, "exploded-war")
System.setProperty("appengine.sdk.root", appEngineSdkRoot)
}

def propertyMissing(String name) { props[name] }
}


In another blog I will describe how you can use this plugin in a project. The code can be found on GitHub. The plugin is still under development to add other AppEngine tasks to it, such as starting the development server.

1 comment:

  1. [...] my previous blog post, I described the Google AppEngine Plugin for Gradle. In this blog I will show you how you can use it to deploy your own applications to Google [...]

    ReplyDelete