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.
There are basically 3 different approaches for introducing custom build logic into Gradle:
- Create a completely new task
- Extend an existing task
- Change the configuration of existing tasks
1. To create a new task,
it must first be registered:
tasks { register("myTask") }or
tasks.register("myTask")
To also obtain a reference to the task to be used for type safe use later:
tasks { val myTask by registering }or
val myTask by tasks.registeringBy 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") } } }or
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 } }or
tasks.register("myTask") { // logic goes here }
And for completeness sake, declaring and configuring a task while obtaining a reference:
val myTask by tasks.registering { // logic goes here }or
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.existingBy 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 }or
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") } } }
or all tasks of a certain type:
tasks { withType<Test>().configureEach { useJUnitPlatform() setForkEvery(1) } }
Like mentioned before, you can also access most tasks that were registered by plugins:
"test"(Test::class) { useJUnitPlatform() }or
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:
test { useJUnitPlatform() }
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.