Gradle Goodness: Organizing Tasks Using The Task Container
A Gradle build file describes what is needed to build our Java project. We apply one or more plugins, configure the plugins, declare dependencies and create and configure tasks. We have a lot of freedom to organize the build file as Gradle doesn’t really care. So to create maintainable Gradle build files we need to organize our build files and follow some conventions. In this post we focus on organizing the tasks and see if we can find a good way to do this.
It is good to have a single place where all the tasks are created and configured, instead of having all the logic scattered all over the build file. The TaskContainer
is a good place to put all the tasks. To access the TaskContainer
we can use the tasks
property on the Project
object. Within the scope of the tasks
block we can create and configure tasks. Now we have a single place where all the tasks are created and configured. This makes it easier to find the tasks in our project as we have a single place to look for the tasks.
Within the scope of the TaskContainer
we can use a convention to put the task creation methods at the top of the TaskContainer
block. And the task configuration methods are after the task creation in the TaskContainer
block. The tasks that are created at the top of the TaskContainer
scope can be referenced by configuration code for tasks later in the TaskContainer
scope.
The following diagram shows the build file structure and an example of the implementation:
In the example Gradle build file for a Java project we organize the tasks in the TaskContainer
using this convention:
plugins {
java
}
...
tasks {
// ----------------------------------------------
// Task creation at the top of the container.
// ----------------------------------------------
// Register new task "uberJar".
val uberJar by registering(Jar::class) {
archiveClassifier = "uber"
from(sourceSets.main.get().output)
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get()
.filter { it.name.endsWith("jar") }
.map { zipTree(it) }
})
}
// ----------------------------------------------
// Task configuration after task creation.
// ----------------------------------------------
// The output of the "uberJar" tasks is part of
// the output of the "assemble" task.
// We can refer to the "assemble" task directly
// as it is added by the Java plugin.
assemble {
// We can refer to the task name that
// we just created in our
// tasks configuration block.
dependsOn(uberJar)
}
// Configure tasks with type JavaCompile.
withType<JavaCompile>().configureEach {
options.compilerArgs.add("--enable-preview")
}
}
...
Although Gradle doesn’t enforce us to use this convention it can be very helpful as build file authors to use it as it makes it easier to find the tasks in the project.
Written with Gradle 8.6.