The custom plugin

In this section, we will discuss how to create a custom plugin. A plugin can be created by implementing the org.gradle.api.Plugin<T> interface. This interface has one method named apply(T target), which must be implemented in the plugin class. Typically, we write a plugin for the Gradle projects. In that situation, T becomes the Project. However, T can be any type of object.

The class that implements the plugin interface can be placed in various locations, such as:

  • The same build file
  • The buildSrc directory
  • A standalone project

This is similar to creating a custom task that we discussed in the last chapter. When we define a plugin in the same build file, the scope is limited to the defining project only. This means, this plugin cannot be reused in any other projects. This is not a good idea, if we want to distribute our plugin for other projects. For a multiproject Gradle build, the plugin code can be placed in the buildSrc folder of the root project or build file of the root project. All the subprojects will have access to this custom plugin. The most elegant way to create a plugin is to create a standalone Groovy project, create a jar file from it and share the plugin across projects and teams. Now, we will explore how to create a custom plugin with examples.

The build file

In the following example, we have added a FilePlugin class, which implements a Plugin interface in the build file. In the apply method, we have added two tasks, copy and move. These tasks are simple tasks, which print a line in the console. Now, we need to add this plugin to the build file if we want to execute the copy or move tasks. In this example, the plugin name is FilePlugin. We add this plugin using the apply plugin statement. Without adding the plugin, you will find Could not find property 'copy' on root project 'PROJECT_NAME'. if you try to execute the copy task:

apply plugin: FilePlugin

class FilePlugin implements Plugin<Project> {
  void apply(Project project) {
    project.task('copy') << {
      println "Task copy is running"
        //....
      }
    project.task('move') << {
      println "Task move is running"
      //...
    }
  }
}
copy.doLast { println "Copy Task ending .." }

On executing the copy task (for the Ch04_CustomPlugin1 project) from the command-line, we find the following two lines printed in the console as expected:

$ gradle copy
:copy
Task copy is running
Copy Task ending ..
BUILD SUCCESSFUL

The buildSrc directory

Similar to Task, to keep the plugin code separate from the build file, we can create a buildSrc folder inside the project root directory and any common code, task or plugin can be placed in this folder. In the following example, the Plugin is created in the buildSrc folder, which can be reused in the root build file and in all the subprojects. We have created a FilePlugin.groovy class under buildSrc/src/main/groovy. This class implements the plugin interface and adds two tasks: the copy task and the move task in the apply method. This FilePlugin.groovy class is similar to what we have done in the previous example. For this example, we will create a project Ch04_CustomPlugin2. Additionally, in the FilePlugin.groovy class, we need to add the package declaration and import statements (import org.gradle.api.*).

During build execution, this plugin class will be compiled automatically by Gradle and added to the classpath of the project. As the plugin definition is not in the build file, we need a mechanism to declare plugin information in the build file. This is done by importing the Plugin class and adding the plugin with the apply plugin statement. The following snippet shows the content of the main build file. In the file, additionally, we have added a doLast method in the copy task just for logging purposes:

import ch4.FilePlugin
apply plugin: FilePlugin

copy.doLast {   
println "This is main project copy dolast"
}

Next, we create two subprojects: project1 and project2. Each project has a simple build file. This build file is similar to the main build file. The build file imports and applies the FilePlugin and adds a doLast method to the copy task for logging. The content of build.gradle of project1 is shown in the following code. The build file of project2 is also similar to this:

import ch4.FilePlugin
apply plugin: FilePlugin

copy.doLast {
  println "Additional doLast for project1"
}

We need another settings.gradle file, which includes the subprojects in the main project:

include 'project1', 'project2'

Do not get confused with the settings.gradle file. We will discuss multiproject builds in detail in Chapter 6, Working with Gradle.

For convenience, the directory structure of the Ch04_CustomPlugin2 project is displayed in Figure 4.2:

The buildSrc directory

Figure 4.2

When we execute the copy task, we find three copy tasks being executed: one from the main project and two other copy tasks from subprojects project 1 and project 2.

$ gradle copy
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes
:buildSrc:jar
:buildSrc:assemble
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build
:copy
Task copy is running
This is main project copy dolast
:project1:copy
Task copy is running
Additional doLast for project1
:project2:copy
Task copy is running
Additional doLast for project2

BUILD SUCCESSFUL

The Standalone project

In the last section, we placed the plugin code in the buildSrc directory and we used the plugin in the root build file and all the subprojects build files. It was just one step towards modularizing the plugin code from the build logic. However, this plugin is not reusable in other projects. Ideally, a plugin should be created in a standalone Groovy project. Then we create a JAR file and include that JAR file in the classpath of other build files. In this section, we will explore how to create a standalone plugin project.

We will start by creating a simple Groovy project. We will add a plugin class FilePlugin.groovy and two tasks CopyTask and MoveTask in the src/main/groovy. We will also add a properties file in the resource folder. The snapshot of the project (Ch04_CustomPlugin3) is displayed in Figure 4.3:

The Standalone project

Figure 4.3

The FilePlugin.groovy class creates two tasks named copy and move by referring to the CopyTask and MoveTask classes. These tasks are created by calling the create(...) method on the TaskContainer object with the taskname and task classes as method parameters. Both tasks extend DefaultTask and define their own implementation. This is just an example of creating a custom task that we learned about in the last chapter. We have created one more additional task customTask, which will print the sourceFile property value. The sourceFile property is defined using the extension object. Plugin extensions are plain old Groovy objects used to add properties to plugins. You can provide properties/configuration information to Plugins using extension objects. You can create more than one extension object in the plugin to group the related properties together. Gradle adds a configuration closure block for each extension object.

The code snippet of the FilePlugin.groovy class is as follows:

package ch4.custom.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import ch4.custom.tasks.CopyTask
import ch4.custom.tasks.MoveTask

class FilePlugin implements Plugin<Project> {

  @Override
  public void apply(Project project) {

    def extension = project.extensions.create("simpleExt", FilePluginRootExtension)

    project.tasks.create("copy", CopyTask.class)
    project.tasks.create("move", MoveTask.class)
    project.task('customTask') << {
    println "Source file is "+project.filePluginExtension.sourceFile
    }
  }
}

The following is the source code for the AbstractTask, CopyTask, MoveTask, and extension classes.

File: AbstractTask.groovy

package ch4.custom.tasks

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class AbstractTask extends DefaultTask {

}

File: CopyTask.groovy

package ch4.custom.tasks

import org.gradle.api.tasks.TaskAction

class CopyTask extends AbstractTask {
  
  @TaskAction
  def action1() {
    println "Copy Task Running"
  }
}

File: MoveTask.groovy

package ch4.custom.tasks

import org.gradle.api.tasks.TaskAction

class MoveTask extends AbstractTask {

  @TaskAction
  def action1() {
    println "Move Task Running"
  }

}

File: FilePluginRootExtension.groovy

package ch4.custom.plugin

class FilePluginRootExtension {

  def sourceFile = "/home/tmp"
  def destinationFile

}

Now, we need a plugin ID so that Gradle can find this plugin information. This is done by creating a properties file under src/main/resources/META-INF/gradle-plugins. The name of the file becomes the plugin ID. In our example, we have named the file fileplugin.properties. So, the plugin ID is fileplugin. In any other build file, we can now apply the plugin as:

apply plugin: 'fileplugin'

In the fileplugin.properties file, we need to add the implementation-class property, which maps to the main plug in the implementing class:

implementation-class=ch4.custom.plugin.FilePlugin

That's all you need. Now, we can build this project to create a jar file and then we can use this jar in any other project. In our example, the jar file is named Ch04_CustomPlugin3-1.0.jar. If you wish to publish a plugin in https://plugins.gradle.org/, you need to make sure the plugin ID is unique. In such cases, you might want to rename fileplugin.properties to something like mastering.gradle.ch4.properties to ensure uniqueness of the plugin ID.

Once the jar file is created, the plugin can be used in any other build file. The code snippet shows how the buildscript closure can define a local directory as the repository. The plugin jar file can be included in the classpath by the dependencies closure. In the example, we are using the plugin from the local directory. Ideally, we should publish the plugin jar to a private or public repository and reference it via the Maven or Ivy URL:

buildscript {
  repositories {
    flatDir {dirs "../Ch04_CustomPlugin3/build/libs/"}
  }
dependencies {
  classpath group: 'ch4.custom.plugin', name: 'Ch04_CustomPlugin3',version: '1.0'
}
}
apply plugin: 'fileplugin'

copy.doLast {
  println "This is from project $project.name"
}

We have added a dolast in the copy task, which prints the project name. Try to execute the following command:

$ gradle copy cT
:copy
Copy Task Running
This is from project UsingPlugin
:customTask
Source file is /home/tmp

BUILD SUCCESSFUL

Total time: 3.59 secs

From the output, you can understand that the copy task has two statements. One we mentioned in plugin definition and the other we added in the build.gradle file. The output of the customTask prints the default value of the source file, which is /home/tmp. This value was set in the FilePluginRootExtension.groovy class. If you want to update the property to some other value, add the following configuration closure in the build file:

filePluginExtension {
  sourceFile = "/home/user1"
}

After adding the preceding closure, try to execute the following command:

$ gradle cT
:customTask
Source file is /home/user1

BUILD SUCCESSFUL

Total time: 3.437 secs

Now, the output is changed to the new value mentioned in the filePluginExtension closure.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.181.252