Gradle Custom Plugin
- Creating task, and using it in Custom plugin
- Stand alone Custom plugin
- Short plugin id
- Customize Gradle setting using settings.gradle
Project info :
Gradle version : 1.1
OS platform : Ubuntu 12.10
Prerequisite : Basic understanding of Gradle script.
Creating the Stand alone custom plugin
- create the directory structure
|-custom-plugin | |-plugin | |-src | |-main | | |-groovy | | | |-com | | | |-code4reference | | | |-gradle | | |-resources | | | |-META-INF | | | |-gradle-plugins | |-test | | |-groovy | | | |-com | | | |-code4reference | | | |-gradle |-user
Here plugin directory contains all source code and resource files whereas the user directory contains the consumer script which uses custom plugin. Execute the following command to create the directory structure. Here groovy folder contains the source code package.
$ mkdir -p custom-plugin/plugin/src/main/groovy/com/code4reference/gradle $ mkdir -p custom-plugin/plugin/src/main/resources/META-INF/gradle-plugins $ mkdir -p custom-plugin/user
- Custom plugin source code
Every plugin should have a implementation class to extend the Plugin class. Let’s define the plugin class.
package com.code4reference.gradle; import org.gradle.api.*; class Code4ReferencePlugin implements Plugin { def void apply(Project project) { //c4rTask task has been defined below. project.task('c4rTask') << { println 'Hi from Code4Reference plugin!' } } }
Put this file in custom-plugin/plugin/src/main/groovy/com/code4reference/gradle directory. Here, c4rTask task has been defined to print a simple line.
- Short plugin ID
In order to apply a plugin, we usually use a short ID e.g apply plugin : ‘java’. Here ‘java’ is the short plugin id for the class org.gradle.api.plugins.JavaPlugin. The short plugin id can be defined in easy steps. For this, we need to create a property file and put it in the META-INF/gradle-plugins directory which comes under the class path. The name of the file will be our short id. This property file must contain the line shown below and it should point to the plugin implementation class. Let’s create the property file as code4reference.properties and point it to the Code4ReferencePlugin class.
implementation-class=com.code4reference.gradle.Code4ReferencePlugin
- Gradle script to generate the plugin
For compiling and building this plugin, we will write the gradle script. Create the file named build.gradle in plugin directory and copy the content below in it.
apply plugin: 'groovy' apply plugin: 'maven' dependencies { compile gradleApi() groovy localGroovy() } repositories { mavenCentral() } group='com.code4reference' //Group name makes easier to manager the packages. version='1.1-SNAPSHOT' uploadArchives { repositories { mavenDeployer { repository(url: uri('../repo')) } } }
In this gradle script, we use groovy plugin to compile groovy source code and declare gradleAPI as the compile time dependencies. You may have noticed that we use maven plugin. It basically creates the plugin jar file and stores in the maven repository. Here we create the maven repository named repo in the parent directory and store the jar file in it.
- Building plugin and putting in repository
$ gradle uploadArchives #This will put the plugin-version.jar in maven repository. :compileJava UP-TO-DATE :compileGroovy UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :jar :uploadArchives Uploading: com/code4reference/plugin/1.1-SNAPSHOT/plugin-1.1-20120816.163101-1.jar to repository remote at file:/home/rakesh/programming/mygitrepo/Code4Reference/GradleExample/custom-plugin-1/repo/ Transferring 5K from remote Uploaded 5K BUILD SUCCESSFUL Total time: 34.892 secs
- Porject settings using settings.gradle
When the above command is executed, gradle tires to get the project name from the settings.gradle. If settings.gradle file is not present in the current directory, then it gets the name of the current directory and assumes it as the project name. It then forms the path to store the jar file. The file path convention is as following /group/name/projectName/version/projectname-version-timestamp.jar. You may notice in the above output that the jar path name and the jar file name have plugin word because the the current directory name is plugin and gradle assumes it as project name. If we want to override this property and put code4ReferencePlugin as the project name, we need to create a settings.gradle file in the plugin directory and put the following line.
rootProject.name = 'code4ReferencePlugin'
Now again execute the command to generate the plugin jar file.
$gradle uploadArchives compileJava UP-TO-DATE :compileGroovy UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :jar UP-TO-DATE :uploadArchives Uploading: com/code4reference/code4ReferencePlugin/1.1-SNAPSHOT/code4ReferencePlugin-1.1-20120816.164441-5.jar to repository remote at file:/home/rakesh/programming/mygitrepo/Code4Reference/GradleExample/custom-plugin-1/repo/ Transferring 5K from remote Uploaded 5K BUILD SUCCESSFUL Total time: 8.61 secs
Now the problem is solved. The jar is getting generated with name code4ReferencePlugin-[version]-timestamp.jar . If you want to find more about the gradle and system properties, find it here.
Using the custom plugin
This is really a simple step. Although we use the other plugin, the custom plugin can also be used in similar way. Now create another build.gradle file in user directory and copy the code given below.
buildscript { repositories { maven { url uri('../repo') } } dependencies { classpath group: 'com.code4reference', name: 'code4ReferencePlugin', version: '1.1-SNAPSHOT' } } apply plugin: 'code4reference'
build.gradle script accesses maven repository present in the parent directory. We have also defined dependency which basically accesses the particular version of jar file from the maven. Last but not the least, we apply the short plugin id “code4reference”. To run this gradle script, execute the command below on the terminal in the user directory.
$ gradle c4rTask #Remember we have created c4rTask in Code4ReferencePlugin class. #You will get the following output. :c4rTask Hi from Code4Reference plugin! BUILD SUCCESSFUL Total time: 3.908 secs
Voilà!! you just created custom plugin and used it in a different project script. You can find the source code for this tutorial over here. Code4Reference
Now, will cover the following topics.
- Define custom Task class
- Passing arguments to custom plugin task
- Nested arguments
- Testing the custom plugin
Project info :
Project name : Gradle custom plugin
Gradle version : 1.1
OS platform : Ubuntu 12.10
Prerequisite : Basic understanding of Gradle script.
Here, we will follow the same directory hierarchy listed in the first part.
- Define custom Task
Let’s define a custom class named Code4ReferenceTask which extends DefaultTask class and put this file in the same folder where Code4ReferencePlugin.groovy is kept. This class contains a method named showMessage() which is annotated with @TaskAction. Gradle calls this method when the task is executed.
package com.code4reference.gradle; import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class Code4ReferenceTask extends DefaultTask { @TaskAction def showMessage() { println '----------showMessage-------------' } }
Now we need to do some minor modifications in the Code4ReferencePlugin.groovy to include the custom task. The modified Code4ReferencePlugin class is as following.
package com.code4reference.gradle; import org.gradle.api.*; class Code4ReferencePlugin implements Plugin { def void apply(Project project) { //Define the task named c4rTask of type Code4ReferenceTask project.task('c4rTask', type: Code4ReferenceTask) } }
You may notice that only the highlighted line has been changed from the past implementation. Now the “c4rTask” is of Code4ReferenceTask type. Execute the gradle uploadArchives command in the plugin directory. This will update the jar file in Maven repo. Now execute the command below in user directory with the same old build.gradle. We will get the following output.
$gradle c4rTask :c4rTask ----------showMessage------------- BUILD SUCCESSFUL Total time: 14.057 secs
- Passing arguments to custom plugin task
The above implementation is the simplest one and doesn’t do much. What if we want to pass the arguments from Gradle script to this task? We can achieve it by accessing extension object. The Gradle Project has an associated ExtensionContainer object that helps keep track of all the settings and properties being passed to plugins class. Let’s define an extension class which can hold the arguments and pass those to the Task class. The highlighted lines in the Code4ReferencePlugin class help to pass the arguments to the Task class.
package com.code4reference.gradle; import org.gradle.api.*; //For passing arguments from gradle script. class Code4ReferencePluginExtension { String message = 'Hello from Code4Reference' String sender = 'Code4Reference' } class Code4ReferencePlugin implements Plugin { def void apply(Project project) { project.extensions.create('c4rArgs', Code4ReferencePluginExtension) project.task('c4rTask', type: Code4ReferenceTask) } }
We have defined Code4ReferencePluginExtension as Extension class which contains two variables message and sender. These serve as the arguments for the custom defined task. We need to modify the Code4RefernceTask class to access the arguments. The highlighted lines have been added to the previous Code4ReferenceTask class implementation.
package com.code4reference.gradle; import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class Code4ReferenceTask extends DefaultTask { @TaskAction def showMessage() { println '------------showMessage-------------------' println 'From : ${project.c4rArgs.sender},\ message : ${project.c4rArgs.message}' } }
Execute the gradle uploadArchives command in the plugin directory. This will update the jar file in Maven repo. Also, we need to update the build.gradle in the user directory.
//custom-plugin-2/user buildscript { repositories { maven { url uri('../repo') } } dependencies { classpath group: 'com.code4reference', name: 'code4ReferencePlugin', version: '1.2-SNAPSHOT' } } apply plugin: 'code4reference' c4rArgs { sender = 'Rakesh' message = 'Hello there !!!!' }
You may have noticed that c4rArgs closure has been added and sender and message variables are set in the closure. These two variables are accessible in the showMessage() method. Now run the build.gradle present in user directory. we get the following output.
$gradle c4rTask :c4rTask -------------------------showMessage----------------------------- From : Rakesh, message : Hello there !!!! BUILD SUCCESSFUL Total time: 15.817 secs
- Nested arguments
What if we want to pass the nested arguments? We can achieve this by nesting the Extension objects. Here is the code for Code4ReferencePlugin class. Only highlighted lines have been added in this class.
package com.code4reference.gradle; import org.gradle.api.*; //Extension class for nested argumetns class C4RNestedPluginExtention { String receiver = 'Admin' String email = 'admin@code4reference.com' } //For keeping passing arguments from gradle script. class Code4ReferencePluginExtension { String message = 'Hello from Code4Reference' String sender = 'Code4Reference' C4RNestedPluginExtention nested = new C4RNestedPluginExtention() } class Code4ReferencePlugin implements Plugin { def void apply(Project project) { project.extensions.create('c4rArgs', Code4ReferencePluginExtension) project.c4rArgs.extensions.create('nestedArgs',C4RNestedPluginExtention) project.task('c4rTask', type: Code4ReferenceTask) } }
It’s time to modify the Code4ReferenceTask class as well. Highlighted lines have been added in this class to access the nested arguments.
package com.code4reference.gradle; import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class Code4ReferenceTask extends DefaultTask { @TaskAction def showMessage() { println '------------showMessage-------------------' println 'From : ${project.c4rArgs.sender},\ message : ${project.c4rArgs.message}' println 'To : ${project.c4rArgs.nestedArgs.receiver},\ email : ${project.c4rArgs.nestedArgs.email}' } }
Execute the gradle uploadArchives command again in the plugin directory to update the jar file in Maven repo. Now modify the build.gradle file present in user directory to pass the nested arguments.
buildscript { repositories { maven { url uri('../repo') } } dependencies { classpath group: 'com.code4reference', name: 'code4ReferencePlugin', version: '1.2-SNAPSHOT' } } apply plugin: 'code4reference' c4rArgs { sender = 'Rakesh' message = 'Hello there !!!!' nestedArgs{ receiver = 'gradleAdmin' email = 'gradleAdmin@code4reference.com' } }
We have added the highlighted line in the build.gradle file.
- Testing plugin and task
Testing of code is an important aspect of code development. Now we are going to add the unit test for the custom task and plugin. For this, we need to create the directory structure for the test classes. We need to put the test folder in the src directory. Execute the command below in plugin directory to create the test directories.
$mkdir -p src/test/groovy/com/code4reference/gradle/
Test directory structure follows the same package directory structure which has been used for source code package directory. In this directory, put the test classes for Code4ReferencePlugin and Code4ReferenceTask. In test class, ProjectBuilder is used to access the project object. These test cases are easy to write, similar to the Junit test cases. The code of test classes is as following:
package com.code4reference.gradle; import org.junit.Test import org.gradle.testfixtures.ProjectBuilder import org.gradle.api.Project import static org.junit.Assert.* class Code4ReferenceTaskTest { @Test public void canAddTaskToProject() { Project project = ProjectBuilder.builder().build() def task = project.task('c4rtakstest', type: Code4ReferenceTask) assertTrue(task instanceof Code4ReferenceTask) } }
package com.code4reference.gradle; import org.junit.Test import org.gradle.testfixtures.ProjectBuilder import org.gradle.api.Project import static org.junit.Assert.* class Code4ReferencePluginTest { @Test public void code4referencePluginAddsCode4ReferenceTaskToProject() { Project project = ProjectBuilder.builder().build() project.apply plugin: 'code4reference' println 'code4referencePluginAddsCode4ReferenceTaskToProject' assertTrue(project.tasks.c4rTask instanceof Code4ReferenceTask) } }
To run the test, execute the following command in plugin folder.
$gradle test #For success test cases. :compileJava UP-TO-DATE :compileGroovy UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :compileTestJava UP-TO-DATE :compileTestGroovy :processTestResources UP-TO-DATE :testClasses :test BUILD SUCCESSFUL Total time: 42.799 secs $gradle test #In case of test case failure, #you can expect output similar to given below. :compileJava UP-TO-DATE :compileGroovy UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :compileTestJava UP-TO-DATE :compileTestGroovy :processTestResources UP-TO-DATE :testClasses :test com.code4reference.gradle.Code4ReferencePluginTest > code4referencePluginAddsCode4ReferenceTaskToProject FAILED java.lang.AssertionError at Code4ReferencePluginTest.groovy:14 2 tests completed, 1 failed FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':test'. > There were failing tests. See the report at: file:///home/rakesh/programming/mygitrepo/Code4Reference/GradleExample/custom-plugin-2/plugin/build/reports/tests/index.html * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. BUILD FAILED
Gradle test provides the test report and its location. This file can be opened using any browser to examine the stack trace.
You can find the source code here.
Reference: Gradle custom plugin (Part-1), Gradle custom plugin (Part-2) from our JCG partner Rakesh Cusat at the Code4Reference blog.
Hi, thanks for the tutorial it’s quite interesting.
There is small mistake on some strings declaration you show.
In Groovy, to use expressions inside a string you need to define it with double quotes (“) and not simple quotes (‘).
For example:
println ‘From : ${project.c4rArgs.sender}, message : ${project.c4rArgs.message}’
Needs to be:
println “From : ${project.c4rArgs.sender}, message : ${project.c4rArgs.message}”
Hi, Thanks for the tutorial.