Freitag, 21. September 2018

Gradle Kotlin DSL: Creating Tasks

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.

There are basically 3 different approaches for introducing custom build logic into Gradle:
  1. Create a completely new task
  2. Extend an existing task
  3. 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.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")
        }
    }
}
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.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
}
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.

Freitag, 14. September 2018

Gradle Kotlin DSL: UPDATE Using the Maven Publish Plugin

This post is an update of the old one on using the Maven Publish Plugin which is not incubating anymore.
The main reason for this update is the introduction of the new lazy API for task configuration with Gradle 4.9 and the change of the Strings invoke() function in Gradle Kotlin DSL with Gradle 4.10. Invoke() is now built on top of this new API and won't work the way it is described in the old post anymore. Also, the way source sets are accessed was changed.

The recommended way to declare the task for creating the Jar file with the sources is:
val sourcesJar by tasks.registering(Jar::class) {
    classifier = "sources"
    from(sourceSets["main"].allSource)
}

We will use the new API and its new delegate registering() to lazily create the task. 
The variable for the task is declared outside of the tasks block to make it accessible inside the publishing block.
Also note that the java prefix for accessing source sets is gone.

With the new API the variable sourcesJar is not of type Task anymore but of type TaskProvider. By calling get() on the provider, the task can be accessed:
publishing {
    publications {
        register("mavenJava", MavenPublication::class) {
            from(components["java"])
            artifact(sourcesJar.get())
        }
    }
}
Analogous to the creation of the sourcesJar task we use the new API and its new register() function to create the publishing task. And because of that, the publications block can be used as intended without braces.