DIY Gradle Plugin Development

Plugging into Gradle Plugins - Part 2

 

The Liquibase Plugin

Liquibase is an open-source database refactoring tool. It provides a means for developers to collaborate on the design of a relational database schema, and to publish a time-ordered stream of schema changes to multiple database instances. It’s very good at keeping multiple developer sandboxes in sync, and for providing an automated mechanism for tracking and deploying database change to shared staging, QA, and production servers. A tool like this is virtually begging to be scripted into a well-constructed build. Liquibase is a Java-based tool with a convenient command-line wrapper built in, but even “convenient” command-line Java is not all that pleasant to run. Liquibase needs a Gradle plugin.

Applying a core plugin is easy: you simply include the statement apply plugin: ‘plugin-id’ in your build script. Because core plugins are a part of the Gradle release, their IDs are reg- istered and their classes are available to the Gradle classload- er. Applying an external plugin is very similar, but we have to take one more step to tell Gradle where to find the plugin.

The buildscript method lets us add jars to the classpath of the build itself, and even declare the repositories Gradle should use to fetch those jars. To include the liquibase plugin in a build, we’d do the following (listing 1).

Listing 1: Applying the Liquibase plugin using the +buildscript+ method.

 

apply plugin: ‚liquibase‘
buildscript { 
        repositories {
                mavenCentral() 
        }
        dependencies {
                classpath ‚com.augusttechgroup:gradle-liquibase-plugin:0.5.1‘
        } 
}

 

Merely applying the liquibase plugin with no other preparation wouldn’t work, since Gradle doesn’t natively know a plugin by that name. To tell Gradle where to find it, we have to declare it as a dependency. The dependencies declaration points to a Maven-style vector which happens to resolve in Maven Central. Note that the dependency is as- signed to the classpath configuration; all dependencies de- clared in the buildscript block should go here. This indicates that they will be made available in the classpath of the build script itself.

The rest of the build file is now free to make use of the Liquibase plugin as it sees fit. This may include using any number of new tasks, task types, project properties, and do- main objects introduced by the plugin. The full details of the

Liquibase plugin are beyond our present scope, but you can follow the project on GitHub for more documentation as it becomes available.

The Plugin API

For all the power Gradle plugins bring us, creating one is sur- prisingly easy. We’ll study the code of the Liquibase plugin as an example. The Liquibase plugin is written as a standalone project of its own, which builds to its own JAR and is deployed under its own artifact ID to Maven Central. This is the preferred way to write and distribute open source plugins, as it makes them very easy to incorporate into new builds, and gives them an independent existence as projects of their own. They can be modified, built, tested, and released just like any other piece of software. Remember, in Gradle’s opinion, the build is code, so it should come as no surprise that we would engineer build extensions just like we would the code the extensions are building.

However, the Liquibase plugin didn’t begin its life this way. It started as code inside an existing build.gradle file in the project whose needs gave rise to the plugin. The examples will show independent source files with a build process of their own, but an amazing feature of Gradle plugins is that they can be written directly inside Gradle build scripts. All of the classes we’re about to see could be – and originally were – coded directly into a Gradle build file and used from there. You should never let a plugin stay mixed together with a build in that manner, but beginning plugin development alongside a build is a perfectly appropriate, low-ceremony path to learning the API and discovering the requirements of your build extension.

Applying Yourself

The minimum subset Gradle plugin is a class which imple- ments the org.gradle.api.Plugin<Project> interface. The Plugin interface defines a single method: void apply(Project project). The Liquibase plugin’s apply() method looks something like in listing 2. Please note that the actual Liquibase plugin code has been modified to be more concise.

Listing 2

 

class LiquibasePlugin implements Plugin<Project> 
        { void apply(Project project) {
                // Create a domain object container for databases
                def databases = project.container(Database)
                // Create a domain object container for changeLogs
                def changelogs = project.container(ChangeLog)
                // Add the Liquibase convention to the project
                project.convention.plugins.liquibase =
                        new LiquibaseDatabaseConvention(databases, changelogs)
                applyTasks(project) 
        }
}

 

Inside this method, we have access to the Project object of the build, which is the best backstage pass a buildmaster could ever hope to lay hands on. Here we can add tasks and domain objects to the build, and also interact with existing domain objects and project properties. If we want our plugin to create an additional SourceSet, this is the place to do it. If we want to add an after-evaluation hook to ensure that all task names contain valid Esperanto phrases in camel case, we can do that here. If we want to ensure that the generated API documentation for a Java project will say “Abandon all hope, ye who enter here,” plugins make it possible.

These examples are lighthearted, but the point is quite useful: plugins can be used as a means to impose control over how individual developer may extend builds. Gradle normally sells itself as a very fluid, extensible build system that puts the build master back in control, but for enterprise environments in which an application architect must maintain strict controls over a large portfolio of applications, imposing control through plugins is a welcome feature.

More commonly, plugins add tasks, task types, and new conventions to a build. Task and task types are already familiar to experienced Gradle users. Conventions are new concept, simple in execution but powerful in implication. They are objects that get “mixed in” to the Gradle build, adding properties and methods to the build according to the needs of the plugin. We’ll look at each of these through the lens of the Liquibase plugin.

 

Pages

Tim Berglund
Tim Berglund

What do you think?

JAX Magazine - 2014 - 03 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions