Gradle custom task kotlin
This post is about a basic but very powerful feature of Gradle (which Maven is missing): Custom task creation.
The Kotlin DSL introduced a breaking change with Gradle 4.10 which included the new API for lazy task declaration and configuration. This new API will eventually replace the existing one, so I will not be covering the old API in this post.
- Create a completely new task
- Extend an existing task
- Change the configuration of existing tasks
it must first be registered:
tasks val myTask by registering >
val myTask by tasks.registering
By declaring the variable outside the tasks block it can be accessed anywhere in the build script which is useful for some tasks (like a task for creating a sources JAR for the Maven Publish Plugin). In most cases it will make sense to declare all tasks inside one tasks block.
A registered tasks can then be configured:
tasks register("myTask") "myTask" doLast println("Hello World") > > >
tasks.named("myTask") // logic goes here >
Most pre-existing tasks, like the ones introduced through plugins can also be accessed this way.
It is possible to combine declaration and configuration into one command:
tasks register("myTask") // logic goes here > >
tasks.register("myTask") // logic goes here >
val myTask by tasks.registering // logic goes here >
tasks val myTask by registering // logic goes here > >
Note that the variable will hold a TaskProvider instead of a Task due to the new lazy API. In most cases it still can be used anywhere where a Task reference was used before. In the rare cases where this is not possible, the task can be obtained by calling get() on the provider.
References for tasks can also later be obtained via the existing delegate:
val myTask by tasks.existing
By using the existing delegate it is possible to accurately define the scope in which a reference should exist.
2. To inherit from an existing task,
the class of the parent task must be passed to the register() function or the registering delegate:
tasks.register("myTestTask", Test::class) // logic goes here >
val myTestTask by tasks.registering(Test::class) // logic goes here >
This is possible for all cases mentioned under 1.
Existing options of the parent task can then be reconfigured and/or logic can be added:
tasks.register("myTestTask", Test::class) setForkEvery(1) doLast println("my test task was run") > >
Note that this task won’t run instead of the test tasks or at all if not explicitly called or configured!
3. To configure existing tasks,
it is possible to reconfigure all existing tasks:
tasks configureEach group = "myGroup" doLast println("running task $name") > > >
tasks withType().configureEach useJUnitPlatform() setForkEvery(1) > >
"test"(Test::class) useJUnitPlatform() >
val test by existing(Test::class) useJUnitPlatform() >
This will modify the actual test task that is being executed during a build. Tasks like wrapper or init are not accessible in this way.
UPDATE: Starting with Gradle 5.0, most existing tasks can be accessed in a type safe manner:
Note that all configuration and logic outside of a doLast or a doFirst block will be executed at configuration time. Logic inside these blocks will be executed when the task is executed.
Organize Kotlin Scripts as Gradle tasks
In this article, you will learn how you can organize multiple Kotlin scripts as Gradle tasks and make them easily executable this way. I’ve found a discussion about this here. Somebody wanted to execute Kotlin scripts with Gradle build scripts which is, of course, possible by using kotlinc as shown in this (Groovy) build script. This doesn’t look very pretty though, and, as described in the corresponding thread, isn’t very performant and manageable. Another solution would be to use Gradle scripts written with the Kotlin DSL and define custom tasks within a build.gradle.kts file, which obviously can hold and run Kotlin code naturally:
// build.gradle.kts // // Execute Kotlin task with: gradle -q foo task("foo") < group = "com.kotlinexpertise" description = "my foo task" doLast < println("Hello from foo task") >>
The problem with this approach is that, in my case, I had multiple large Kotlin scripts I wanted to make executable this way. If I had put all of them into task s, the script would have been too bloated and hard to maintain. Note that in my case, these tasks would not contribute to the build logic directly but rather provide business-relevant tasks, which I wanted to make executable via Gradle.
Gradle buildSrc to the rescue
As described in the Gradle documentation, build logic and especially custom tasks shouldn’t live within the build script directly. Gradle, therefore, offers the possibility to use a so-called buildSrc directory. This directory is treated as an included build, i.e. Gradle automatically compiles and tests this code and makes it available on the build script classpath. The following shows a typical project structure using this special buildSrc directory:
├── build.gradle //main build ├── buildSrc │ ├── build.gradle //build for buildSrc │ └── src //custom plugins, taks etc. │ ├── main │ │ └── java │ │ └── com │ │ └── enterprise │ │ ├── Deploy.java │ │ └── DeploymentPlugin.java │ └── test │ └── java │ └── com │ └── enterprise │ └── DeploymentPluginTest.java └── settings.gradle
As you can see here, the buildSrc has its own build.gradle , in which we define dependencies and plugins for buildSrc itself. As mentioned, the compiled code will be available for the surrounding build so that you can for instance define custom tasks in the buildSrc and use them in the main build.gradle .
A Kotlin example: Execute Kotlin Scripts with Gradle
Let’s say we have two bigger tasks we want to make available as Gradle tasks, which we call task1 and task2. Both tasks are good fits to be implemented with Kotlin.
The original project build looks like this:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins < kotlin("jvm") version "1.2.51" >java.sourceSets < getByName("main").java.srcDirs(". ") >repositories < mavenCentral() >dependencies < compile(kotlin("stdlib-jdk8")) >tasks.withType
This is very basic and there’s nothing special in it. Now, the goal is to define two custom tasks taks1 and task2 within this script. Both tasks are supposed to be executable via gradle -q task1|task2 . To make this possible, we create the buildSrc directory structure as shown above on the same level the build script already exists. Within the buildSrc , we now create the custom tasks as Kotlin classes:
package com.kotlinexpertise.tasks import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import java.sql.DriverManager open class Task1 : DefaultTask() < init < group = "com.kotlinexpertise" description = "task1" >@TaskAction fun run() < Class.forName("com.mysql.jdbc.Driver") //heavy task implementation >>
Just like we would define a task within a Gradle build script directly, we define group , description and the task itself in a method annotated with TaskAction , which we call run . Learn more about custom tasks here. To make this a bit more interesting, we want to load a MySQL driver in this task, which requires us to make the corresponding dependency available for the buildSrc build. Let’s take a look at its build script (existing directly in buildSrc ):
plugins < `kotlin-dsl` >repositories < mavenCentral() >dependencies
As mentioned, we add the dependencies we want to use within the buildSrc . Another thing to note is that the org.gradle.kotlin.kotlin-dsl plugin is applied to this build in order to set up the Kotlin compiler features and dependencies to match the ones shipped with Gradle. We used the nicer alias kotlin-dsl in this example.
Now, after defining both Task1 and Task2 as subclasses of DefaultTask , they can be used in the main build.gradle.kts script like this:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import com.kotlinexpertise.tasks.* //. task("task1") task("task2") //.
Note that both probably need to be imported as shown here. In the console, gradle tasks will list them, and you can execute them by running gradle -q task1 and gradle -q task2 respectively. You can also see the tasks listed in IntelliJ IDEA:
You may want to read another article of mine about the Gradle Kotlin DSL to get more information about the topic. Enjoy.
Simon is a software engineer with 9+ years of experience developing software on multiple platforms including the JVM and Serverless environments. He currently builds scalable distributed services for a decision automation SaaS platform. Simon is a self-appointed Kotlin enthusiast.