2021年1月22日 星期五

[Gradle in action] Ch4. Build script essentials - Part2

 (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.
Don’t be afraid of making good use of lifecycle hooks. They’re not considered a secret backdoor to Gradle’s API. Instead, they’re provided intentionally because Gradle can’t predict the requirements for your enterprise build.

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 1the 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
  1. gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->  
  2.     if(taskGraph.hasTask(release)) {  
  3.         if(!version.release) {  
  4.             version.release = true  
  5.             ant.propertyfile(file: versionFile) {  
  6.                 entry(key: 'release', type: 'string', operation: '=', value: 'true')  
  7.             }  
  8.         }  
  9.     }  
  10. }  
Alternatively, you can implement this logic as a listener, which you’ll do next.

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
  1. class ReleaseVersionListener implements TaskExecutionGraphListener {  
  2.     final static String releaseTaskPath = ':release'  
  3.     @Override  
  4.     void graphPopulated(TaskExecutionGraph taskGraph) {  
  5.         if(taskGraph.hasTask(releaseTaskPath)) {  
  6.             List<Task> allTasks = taskGraph.allTasks  
  7.             Task releaseTask = allTasks.find {it.path == releaseTaskPath }  
  8.             Project project = releaseTask.project  
  9.             if(!project.version.release) {  
  10.                 project.version.release = true  
  11.                 project.ant.propertyfile(file: project.versionFile) {  
  12.                     entry(key: 'release', type: 'string', operation: '=', value: 'true')  
  13.                 }  
  14.             }  
  15.         }  
  16.     }  
  17. }  
  18.   
  19. def releaseVersionListener = new ReleaseVersionListener()  
  20. gradle.taskGraph.addTaskExecutionGraphListener(releaseVersionListener)  
You’re not limited to registering a lifecycle listener in your build script. Lifecycle logic can be applied to listen to Gradle events even before any of your project’s tasks are executed. In the next section, we’ll explore options for hooking into the lifecycle via initialization scripts to customize the build environment.

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:
  1. root@localhost:ch4# tree ~/.gradle/init.d/  
  2. /root/.gradle/init.d/  
  3. └── build-announcements.gradle  
  4.   
  5. 0 directories, 1 file  
Gradle will execute every initialization script it finds under init.d as long as the file extension matches .gradle. Because you want to apply the plugin before any other build script code is executed, you’ll pick the lifecycle callback method that’s most appropriate for handling this situation: Gradle#projectLoaded(Closure). The following code snippet shows how to apply the build-announcements plugin to the build’s root project:
  1. gradle.projectsLoaded { Gradle gradle ->  
  2.     gradle.rootProject {  
  3.         apply plugin: 'build-announcements'  
  4.     }  
  5. }  
An important lesson to learn in this context is that some lifecycle events are only fired if they’re declared in the appropriate location. For example, the closure for the lifecycle hook Gradle#projectsLoaded(Closure) wouldn’t get fired if you declared it in your build.gradle, because the project creation happens during the initialization phase.

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...