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:
buildSrc
directoryThis 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.
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
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:
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
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 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.
18.191.181.252