Chapter 8. Extending Gradle
This chapter covers
- Gradle’s extension mechanisms by example
- Writing and using script and object plugins
- Testing custom tasks and plugins
- Obtaining configuration from the build script through extension objects
In the previous chapters, we covered a lot of ground discussing how to build a self-contained sample project with Gradle. You added custom logic by declaring simple tasks and custom task classes within your build script. Often, a task becomes so useful that you’ll want to share it among multiple projects. Gradle provides various approaches for reusing code, each with its own unique advantages and drawbacks. Plugins take the concept of reusability and extensibility even further. They enhance your project with new capabilities by introducing conventions and patterns for a specific problem domain.
Earlier in the book, you saw how powerful plugins are. In chapter 3, you used the Java, War, and Jetty plugins to implement a task management web application. Applying these plugins to your project was as simple as adding a single line of code and enhanced your build with new capabilities. The Java plugin adds a standardized way of compiling, testing, and bundling Java code. The War plugin allows for building a WAR file, whereas the Jetty plugin deploys in an embedded Servlet container.
The running example in this book is a web-based To Do application in Java. Through Gradle’s out-of-the-box plugin support, you were able to create a WAR file, deploy it to an embedded Servlet container, and test the application’s functionality in the browser. I bet you’re eager to show off your hard work to end users by deploying it to an internet-accessible web container. The traditional approach to hosting a web application is to manage your own web servers. Though you have full control over the infrastructure, buying and maintaining servers is expensive. Remember the last time you had to ask your infrastructure team to provide you with a server and the compatible runtime environment for your application? Provisioning the hardware and software delayed your time to market, and in the end, you didn’t even have root access to tweak your application’s runtime parameters. A quick and easy way to host an application is to use a platform as a service (PaaS), a combination of a deployment platform and a solution stack that in many cases is free of charge. A PaaS combines traditional application server functionality with support for scalability, load balancing, and high availability. Let’s bring Gradle into the equation.
Plugin development in Gradle isn’t hard. You’ll need to get to know some new concepts while at the same time applying techniques you’ve already learned in previous chapters. Gradle distinguishes two types of plugins: script plugins and object plugins. A script plugin is nothing more than a regular Gradle build script that can be imported into other build scripts. With script plugins, you can do everything you’ve learned so far. Object plugins need to implement the interface org.gradle.api.Plugin. The source code for object plugins usually lives in the buildSrc directory alongside your project or a standalone project and is distributed as a JAR file. In this chapter, you’ll learn how to use both approaches.
In step two, you’ll transfer the logic you’ve written in task action closures and encapsulate it into custom task classes. By exposing properties, the behavior of the task classes will become highly configurable and reusable. The property values will be provided by an enhanced task, the consumer of a task class.
A script plugin is no different from your ordinary build.gradle file. You can use the same Gradle build language constructs. You’ll create a new script named cloudbees.gradle that will contain the future CloudBees tasks. Because the build script’s filename deviates from the default naming convention, you’ll need to use the –b command-line option to invoke it. Executing gradle –b cloudbees.gradle tasks should only present you with the default help tasks. Before you can write the first task, the build script needs to become aware of the CloudBees API client library.
In the last section, you saw how to create a shared script for interacting with a PaaS provider. By applying a script plugin, you provided your project with tasks for managing and deploying your web application in a cloud environment. Let’s review the pros and cons of this approach.
A simple task is a great solution for developing one-off implementations. Even though you took it to the extreme and provided configurable properties for your tasks, code maintainability and testability fell by the wayside. If you want to go one step further, your best bet is to implement your logic in a custom task. The behavior and properties are defined in a task class implementation. When using the custom task, you define how the task should behave by providing values for the properties. If you see your task code grow, custom tasks can help to structure and encapsulate your build logic.
Object plugins give you the most flexibility to encapsulate highly complex logic and provide a powerful extension mechanism to customize its behavior within your build script. As with custom task classes, you have full access to Gradle’s public API and your project model. Gradle ships with out-of-the-box plugins, called standard plugins, but can be extended by third-party plugins as well. Many plugins are self-contained. This means that they either rely on Gradle’s core API or deliver functionality through its packaged code. More complex plugins may depend on features from other libraries, tools, or plugins. Figure 8.8 shows how plugins fit into the overall architecture of Gradle.
In the previous chapters, you used various standard plugins covering support for programming languages and smooth integration with software development tools. Think back to chapter 3 and remember how applying the Java plugin extended your project’s functionality. As shown in figure 8.9, the plugin can provide a new set of tasks integrated into the execution lifecycle, introduce a new project layout with sensible defaults, add properties to customize its behavior, and expose configurations for dependency management.
In this chapter, you built a Gradle plugin for interacting with the CloudBees backend through an API library. For this purpose, we discussed two useful functionalities: deploying a WAR file to a CloudBees web container and retrieving runtime information about this application. You implemented the plugin’s functionality build step by step. You wrote simple tasks in a script plugin, translated these tasks into custom tasks located in the buildSrc project, and later turned this code into a full-fledged object plugin.
A plugin can expose its own DSL for configuring functionality. Extensions are powerful API elements for introducing the concept of convention over configuration into your plugin. You experienced a typical scenario by registering an extension that serves as a model for capturing user input for overriding default configuration values. Writing test code for your plugin is as important as writing it for application code. Gradle’s ProjectBuilder allows for creating a Project dummy representation that can be used to test custom components. Having tools like this removes impediments to writing tests for build code and encourages developers to aim for high code coverage.