(https://www.manning.com/books/gradle-in-action)
Hooking into the build lifecycle
As a build script developer, you’re not limited to writing task actions or configuration logic, which are evaluated during a distinct build phase. Sometimes you’ll want to execute code when a specific lifecycle event occurs. A lifecycle event can occur before, during, or after a specific build phase. An example of a lifecycle event that happens after the execution phase would be the completion of a build.Suppose you want to get feedback about failed builds as early as possible in the development cycle. A typical reaction to a failed build could be that you send an email to all developers on the team to restore the sanity of your code. There are two ways to write a callback to build lifecycle events: within a closure, or with an implementation of a listener interface provided by the Gradle API. Gradle doesn’t steer you toward one of the options to listen to lifecycle events. The choice is up to you. The big advantage you have with a listener implementation is that you’re dealing with a class that’s fully testable by writing unit tests. To give you an idea of some of the useful lifecycle hooks, see figure 4.11.
An extensive list of all available lifecycle hooks is beyond the scope of this book. Many of the lifecycle callback methods are defined in the interfaces [b]Project and Gradle. [/b]Gradle’s Javadocs are a great starting point to find the appropriate event callback for your use case.
In the following two sections, I’ll demonstrate how to receive notifications immediately after the task execution graph has been populated. To fully understand what’s happening under the hood when this graph is built, we’ll first look at Gradle’s inner workings.
INTERNAL TASK GRAPH REPRESENTATION
At configuration time, Gradle determines the order of tasks that need to be run during the execution phase. As noted in chapter 1, the internal structure that represents these task dependencies is modeled as a directed acyclic graph (DAG). Each task in the graph is called a node, and each node is connected by directed edges. You’ve most likely created these connections between nodes by declaring a dependsOn relationship for a task or by leveraging the implicit task dependency interference mechanism. It’s important to note that DAGs never contain a cycle. In other words, a task that has been executed before will never be executed again. Figure 4.12 demonstrates the DAG representation of the release process modeled earlier.
Now that you have a better idea of Gradle’s internal task graph representation, you’ll write some code in your build script to react to it.
Hooking into the task execution graph
Recall the task makeReleaseVersion you implemented that was automatically executed as a dependency of the task release. Instead of writing a task to change the project’s version to indicate production-readiness, you could also achieve the same goal by writing a lifecycle hook. Because the build knows exactly which tasks will take part in the build before they get executed, you can query the task graph to check for its existence. Figure 4.13 shows the relevant interfaces and their methods to access the task execution graph.
Next you’ll put the lifecycle hook in place. Listing 4.13 extends the build script by the method call whenReady to register a closure that’s executed immediately after the task graph has been populated. Because you know that the logic is run before any of the tasks in the graph are executed, you can completely remove the task makeReleaseVersion and omit the dependsOn declaration from createDistribution.
- Listing 4.13 Release version functionality implemented as lifecycle hook
- gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
- if(taskGraph.hasTask(release)) {
- if(!version.release) {
- version.release = true
- ant.propertyfile(file: versionFile) {
- entry(key: 'release', type: 'string', operation: '=', value: 'true')
- }
- }
- }
- }
Implementing a task execution graph listener
Hooking into the build lifecycle via a listener is done in two simple steps. First, you implement the specific listener interface by writing a class within your build script. Second, you register the listener implementation with the build.
The interface for listening to task execution graph events is provided by the interface TaskExecutionGraphListener. At the time of writing, you only need to implement one method: graphPopulate(TaskExecutionGraph). Figure 4.14 shows the listener implementation named ReleaseVersionListener.
Keep in mind that you don’t have direct access to the Project instance if you add the listener to your build script. Instead, you can work Gradle’s API to its fullest. The following listing shows how to access the project by calling the getProject() method on the release task.
- Listing 4.14 Release version functionality implemented as lifecycle listener
- class ReleaseVersionListener implements TaskExecutionGraphListener {
- final static String releaseTaskPath = ':release'
- @Override
- void graphPopulated(TaskExecutionGraph taskGraph) {
- if(taskGraph.hasTask(releaseTaskPath)) {
- List<Task> allTasks = taskGraph.allTasks
- Task releaseTask = allTasks.find {it.path == releaseTaskPath }
- Project project = releaseTask.project
- if(!project.version.release) {
- project.version.release = true
- project.ant.propertyfile(file: project.versionFile) {
- entry(key: 'release', type: 'string', operation: '=', value: 'true')
- }
- }
- }
- }
- }
- def releaseVersionListener = new ReleaseVersionListener()
- gradle.taskGraph.addTaskExecutionGraphListener(releaseVersionListener)
Initializing the build environment
Let’s say you want to be notified about the outcome of a build. Whenever a build finishes, you’ll want to know whether it was successful or failed. You also want to be able to identify how many tasks have been executed. One of Gradle’s core plugins, the build-announcements plugin, provides a way to send announcements to a local notification system like Snarl (Windows) or Growl (Mac OS X). The plugin automatically picks the correct notification system based on your OS. Figure 4.15 shows a notification rendered by Growl.
You could apply the plugin to every project individually, but why not use the powerful mechanisms Gradle provides? Initialization scripts are run before any of your build script logic has been evaluated and executed. You’ll write an initialization script that applies the plugin to any of your projects without manual intervention. Create the initialization script under <USER_HOME>/.gradle/init.d, as shown in the following directory tree:
- root@localhost:ch4# tree ~/.gradle/init.d/
- /root/.gradle/init.d/
- └── build-announcements.gradle
- 0 directories, 1 file
- gradle.projectsLoaded { Gradle gradle ->
- gradle.rootProject {
- apply plugin: 'build-announcements'
- }
- }
沒有留言:
張貼留言