DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Transitioning From Groovy to Kotlin for Gradle Android Projects
  • Containerize Gradle Apps and Deploy to Kubernetes With JKube Kubernetes Gradle Plugin
  • Replace your Scripts with Gradle Tasks
  • Deploying a Kotlin App to Heroku

Trending

  • Automatic Code Transformation With OpenRewrite
  • Testing SingleStore's MCP Server
  • A Developer's Guide to Mastering Agentic AI: From Theory to Practice
  • Is Agile Right for Every Project? When To Use It and When To Avoid It
  1. DZone
  2. Coding
  3. Languages
  4. The Complete Gradle Plugin Tutorial

The Complete Gradle Plugin Tutorial

Let's learn how to build, configure, and apply a Gradle plugin to your project using Kotlin as the programming language of choice.

By 
Sergii Gnatiuk user avatar
Sergii Gnatiuk
·
Updated Jul. 20, 21 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
21.8K Views

Join the DZone community and get the full member experience.

Join For Free

Gradle is a very powerful tool that allows you to set up a build process for your project, no matter how complex it is. But, when I faced the need of interacting with a Gradle project (set up from scratch, extending, or just fix a few lines of code) I hardly managed to do it without additional Googling. If you’ve ever felt the same, you should build your own plugin that might help understand how Gradle works.

This tutorial is useful for developers who also want to build their own plugins. I will describe how to do it in detail from creating a plugin project up to applying a plugin to a project.

Create a Standalone Gradle Plugin Project

First, choose what programming language to use. In general, you can use any language you like that compiles JVM bytecode.

I recommend using Kotlin as the most suitable language for the task.

Here’s why:

  • It’s statically typed.
  • Allows you to produce expressive code.
  • Has a lot of "sugar" that allows building a nice DSL.

Kotlin has borrowed a lot of features from Groovy, so you can improve your Groovy skills as well.

Fish bowl cartoon.

We will build a sample plugin for counting the lines of code in a project.

Let’s start!

1. Create a New Kotlin Project

Creating a Kotlin Project screenshot 2.
  • Name: code-lines-counter
  • ArtifactId: code-lines-counter
  • GroupId: com.github

2. Gradle Project Configuration

Time to add Gradle API tooling. To do this, apply the Java Gradle Plugin. The plugin automatically adds the Gradle API dependency, TestKit, and applies the Java Library plugin. It's a basic setup that helps us to build and test our plugin.

Open your build.gradle file located in the root of the project and add the Java Gradle plugin:

Groovy
 
plugins {
   id 'java'
   id 'java-gradle-plugin'
   id 'org.jetbrains.kotlin.jvm' version '1.5.21'
   id 'maven-publish'
}


Now we can remove the 'java' plugin because we implicitly got the Java Library plugin. We also applied a 'maven-publish' plugin that will be used to publish our plugin to the local maven repository.

The final step is adding a pluginId that will help Gradle identify and apply your plugin. Configure the 'java-gradle-plugin' that will generate a META-INF file:

Groovy
 




x



1
gradlePlugin {
2
   plugins {
3
       simplePlugin {
4
           id = 'com.github.code-lines'
5
           implementationClass = 'com.github.CodeLinesCounterPlugin'
6
       }
7
   }
8
}



In the code above, we specified the plugin's id as 'com.github.code-lines' and the plugin's main class as 'com.github.CodeLinesCounterPlugin', which will be created later.

That’s it, we finished configuring our plugin! That’s how full build.gradle file looks like now:

Groovy
 
plugins {
   id 'java-gradle-plugin'
   id 'org.jetbrains.kotlin.jvm' version '1.5.21'
   id 'maven'
}

group 'com.github'
version '0.0.1'

sourceCompatibility = 1.8

repositories {
   mavenCentral()
}

dependencies {
   implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
   testImplementation 'junit:junit:4.12'
}

compileKotlin {
   kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
   kotlinOptions.jvmTarget = "1.8"
}

gradlePlugin {
   plugins {
       simplePlugin {
           id = 'com.github.code-lines'
           implementationClass = 'com.github.CodeLinesCounterPlugin'
       }
   }
}


3. Coding

Create your com.github package under the src/main/kotlin folder and add a Kotlin class with the name  CodeLinesCounterPlugin (specified earlier for 'java-gradle-plugin') implementing the  Plugin<Project>  interface. Let’s implement it and create our first Gradle task:

Kotlin
 




xxxxxxxxxx
1
19


 
1
package com.github
2

          
3
import org.gradle.api.Plugin
4
import org.gradle.api.Project
5

          
6
class CodeLinesCounterPlugin : Plugin<Project> {
7

          
8
   override fun apply(project: Project) {
9
       project.tasks.create("codeLines") {
10
           task.doLast {
11
               println("Hello from CodeLinesCounterPlugin")
12
           }
13
       }.apply {
14
           group = "stat"
15
       }
16
   }
17
}



We created a task with the name codeLines that prints the "Hello from CodeLinesCounterPlugin" message. 

Let’s test our plugin. To do this we need to publish the plugin to maven local repository. Call publishToMavenLocal Gradle task.  When the publishing is complete, go to {HOME-DIR}/.m2/repository/com/github/code-lines-counter and check that the plugin's artifacts were created.

Applying the "code-lines-counter" Plugin to a Project

Open build.gradle and add "code-lines-counter" dependency in the buildscript block:

Groovy
 




x


 
1
buildscript {
2
   repositories {
3
       mavenLocal() // plugin published to maven local
4
   }
5
   dependencies {
6
       classpath 'com.github:code-lines-counter:0.0.1' // plugin’s artifact
7
   }
8
}
9

          
10
...
11
   
12
apply plugin: 'com.github.code-lines' // applying plugin



And execute the task:

Shell
 




x


 
1
gradlew codeLines



The output:

Plain Text
 




x


 
1
> Task :codeLines
2

          
3
Hello from CodeLinesCounterPlugin
4

          
5
BUILD SUCCESSFUL in 2s



Let’s replace the dummy output with ‘real-world’ logic. We need to walk through source sets, read files with code and sum lines:

Kotlin
 




xxxxxxxxxx
1
10
9


 
1
private fun printCodeLinesCount(project: Project) {
2
   var totalCount = 0
3
   project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.forEach { sourceSet ->
4
       sourceSet.allSource.forEach { file ->
5
           totalCount += file.readLines().count()
6
       }
7
   }
8
   println("Total lines: $totalCount")
9
}



Let’s update our plugin with the function:

Kotlin
 




xxxxxxxxxx
1
22


 
1
class CodeLinesCounterPlugin : Plugin<Project> {
2

          
3
   override fun apply(project: Project) {
4
       project.tasks.create("codeLines") { task ->
5
           task.doLast {
6
               printCodeLinesCount(project)
7
           }
8
       }.apply {
9
           group = "stat"
10
       }
11
   }
12

          
13
   private fun printCodeLinesCount(project: Project) {
14
       var totalCount = 0
15
       project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.forEach { sourceSet ->
16
           sourceSet.allSource.forEach { file ->
17
               totalCount += file.readLines().count()
18
           }
19
       }
20
       println("Total lines: $totalCount")
21
   }
22
}



Checking it. First, reinstall the plugin with install task and run codeLines task again:

Plain Text
 




x


 
1
> Task :codeLines
2

          
3
Total lines: 22
4

          
5
BUILD SUCCESSFUL in 2s



Here we go!

Let’s make our plugin configurable. Suppose you want to count only Java/Kotlin code stats or to skip blank lines. We’d like the plugin to be configurable via build.gradle file in the following way:

Groovy
 




xxxxxxxxxx
1


 
1
codeLinesStat {
2
   sourceFilters.skipBlankLines = true
3
   fileExtensions = ['java', 'kt', 'groovy']
4
}



To make it work, create two data classes that will keep all the settings:

Kotlin
 




xxxxxxxxxx
1


 
1
open class CodeLinesExtension(
2
    var sourceFilters: SourceFiltersExtension = SourceFiltersExtension(),
3
    var fileExtensions: MutableList<String> = mutableListOf()
4
)
5

          
6
open class SourceFiltersExtension(
7
    var skipBlankLines: Boolean = false
8
)



Important! Kotlin classes are final by default. Declare a configuration class as open (those that can be inherited) to make the Gradle processing successful. Also, class fields must be mutable, so a plugin’s user can change the defaults.

Then, we ask Gradle to build an instance of the CodeLinesExtension class based on the build.gradle file.

Kotlin
 




x


 
1
val codeLinesExtension: CodeLinesExtension = project.extensions.create(
2
        "codeLinesStat", 
3
        CodeLinesExtension::class.java
4
)



Now we can process the task according to a user's configuration!

CodeLinesCounterPlugin.kt
Kotlin
 




x


 
1
class CodeLinesCounterPlugin : Plugin<Project> {
2
   override fun apply(project: Project) {
3
       project.tasks.create("codeLines") { task ->
4
           // build extension instance
5
           val codeLinesExtension = project.extensions.create(
6
                   "codeLinesStat", CodeLinesExtension::class.java
7
           )
8
           task.doLast {
9
               printCodeLinesCount(project, codeLinesExtension)
10
           }
11
       }.apply { group = "stat" }
12
   }
13

           
14
   private fun printCodeLinesCount(project: Project, codeLinesExtension: CodeLinesExtension) {
15
       val fileFilter = codeLinesExtension.buildFileFilter()
16
       var totalCount = 0
17
       project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.forEach { sourceSet ->
18
           sourceSet.allSource
19
               .filter(fileFilter) // filters files according to desired list of extensions
20
               .forEach { file ->
21
                   val lines = file.readLines()
22
                   totalCount += if (codeLinesExtension.sourceFilters.skipBlankLines) {
23
                       lines.count(CharSequence::isNotBlank) // skips blank lines
24
                   } else {
25
                       lines.count()
26
                   }
27
               }
28
       }
29
       println("Total lines: $totalCount")
30
   }
31

           
32
   private fun CodeLinesExtension.buildFileFilter(): (File) -> Boolean = if (fileExtensions.isEmpty()) {
33
       { true } // no-op filter
34
   } else {
35
       { fileExtensions.contains(it.extension) } // filter by extension
36
   }
37

           
38
    open class CodeLinesExtension(
39
        var sourceFilters: SourceFiltersExtension = SourceFiltersExtension(),
40
        var fileExtensions: MutableList<String> = mutableListOf()
41
    )
42
    open class SourceFiltersExtension(
43
        var skipBlankLines: Boolean = false
44
    )
45
}



That’s it, we created a simple plugin in 45 lines of code!

If your plugin requires a more complex configuration, you can provide functions to make the plugin API more user friendly:

Kotlin
 




x



1
open class CodeLinesExtension(
2
    var sourceFilters: SourceFiltersExtension = SourceFiltersExtension(),
3
    var fileExtensions: MutableList<String> = mutableListOf()
4
) {
5
    // consumes `action` that contains a configuration for `sourceFilters`
6
    // and overrides `sourceFilters` fields
7
    fun sourceFilters(action: Action<in SourceFiltersExtension>) {
8
      action.execute(sourceFilters)
9
    }
10
}



After this improvement, the plugin can be configured this way:

Groovy
 




x


 
1
// All five variants are equivalent
2
codeLinesStat {
3
    // 1. direct property override
4
    sourceFilters.skipBlankLines = true
5

          
6
    // 2. override using function `fun sourceFilters(action: Action<in SourceFiltersExtension>)`
7
    sourceFilters({
8
        skipBlankLines = true
9
    })
10
    // 3. omit parentheses
11
    sourceFilters {
12
        skipBlankLines = true
13
    }
14
    
15
    // 4. override using setter
16
    sourceFilters.setSkipBlankLines(true)
17
    // 5. omit parentheses
18
    sourceFilters.setSkipBlankLines true
19
}



Check a fully working example here.

Feel free to clone and play with the project 

Shell
 




xxxxxxxxxx
1


 
1
git clone -b chapter-1 https://github.com/SurpSG/code-lines-counter-gradle-plugin.git


Cover the plugin With Functional Tests

There is a separate article where you could find detailed instructions about configuring and writing functional tests for your plugin.

References

  • https://docs.gradle.org/current/userguide/custom_plugins.html
  • https://docs.gradle.org/current/userguide/java_gradle_plugin.html
  • https://docs.gradle.org/current/userguide/test_kit.html#test_kit
  • https://kotlinlang.org/docs/reference/server-overview.html
Gradle Kotlin (programming language) Groovy (programming language) Task (computing)

Opinions expressed by DZone contributors are their own.

Related

  • Transitioning From Groovy to Kotlin for Gradle Android Projects
  • Containerize Gradle Apps and Deploy to Kubernetes With JKube Kubernetes Gradle Plugin
  • Replace your Scripts with Gradle Tasks
  • Deploying a Kotlin App to Heroku

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!