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.

Freitag, 16. März 2018

Gradle Kotlin DSL: Using the Maven Publish Plugin

If you want to publish your libraries built with Gradle to a Maven Repository you need to create the necessary artifacts and a corresponding POM-file. To do that Gradle offers the Maven Plugin and the new incubating Maven Publish Plugin which is used in this post.
Please note that the Maven Publish Plugin does not yet support all the features of the Maven Plugin which it will eventually replace.
plugins {
    ...
    `maven-publish`
}
After declaring the plugin, a publication needs to be defined inside the publishing block. In the Kotlin DSL you have two ways to create an actual publication: as String or as a variable. When using a String to declare a publication the syntax changes slightly:
publishing {
    (publications) {
        "mavenJava"(MavenPublication::class) {
            from(components["java"])
        }
    }
}
The publications function cannot be used since it does not declare the necessary invoke() function on Strings. By putting parenthesis around publications we instead invoke getPublications() which enables the use of Strings as a publication.
When using a variable instead of the String the publications block can be used:
publishing {
    publications {
        val mavenJava by creating(MavenPublication::class) {
            from(components["java"])
        }
    }
}
Additional artifacts can be added to the publication by defining and adding an artifact creating task to it. In this example another JAR file is created containing all the source files from the main source set:
tasks {
    "sourcesJar"(Jar::class) {
        classifier = "sources"
        from(java.sourceSets["main"].allSource)
        dependsOn("classes")
    }  
}

publishing {
    (publications) {
        "mavenJava"(MavenPublication::class) {
            from(components["java"])
            artifact(tasks["sourcesJar"])
        }
    }
}

Gradle Kotlin DSL: Creating additional jars

Sometimes you want to create more than one JAR file for your project. It could for example contain some source code or your compiled test classes. To do that in Gradle you first need create a task that inherits from the Gradle Jar task and will produce your JAR file:

"testJar"(Jar::class) {
    classifier = "tests"
    from(java.sourceSets["test"].output)
    dependsOn("testClasses")
}
Then you add the artifact itself:
artifacts {
    add("archives", tasks["testJar"])
}

Your build will now create an additional JAR file with the specified content which are the compiled class files for tests in this example.

Samstag, 10. März 2018

Gradle Kotlin DSL: Using JUnit 5


Since I ended up spending a lot of time figuring out how to do certain things in the Kotlin DSL for Gradle, I decided to share my findings in a series of small posts.

On todays menu is JUnit 5 and the built-in support in Gradle 4.6. Use the following code snippet to enable JUnit 5 tests in your Gradle build:

tasks {    
    withType<Test>{        
        useJUnitPlatform()
    }
}

UPDATE:
Gradle 4.9 introduced a new API for creating tasks which reflects in the Gradle Kotlin DSL in Gradle 4.10.

Using the new API to configure tasks of type Test looks the following:
tasks {
    withType<Test>().configureEach {
        useJUnitPlatform()
    }
}

Alternatively the test task can be configured directly:
tasks {
    "test"(Test::class) {
        useJUnitPlatform()
    }
}
This is now possible because the invoke() function on Strings was changed to look up existing tasks.