Creating tasks in gradle - Tasks API
Gradle #gradleEvery gradle build is composed on one or more projects. What a project can do is defined by one or more tasks.
A
taskrepresents some atomic piece of work which a build performs i.e. compiling some classes, deploying artifacts, generating java docs etc
Defining new tasks
A gradle task is an instance of Task class which will have some actions to perform. An action is simply a block of code. There are multiple ways to create new tasks in gradle.
New tasks are added to tasks collections.
// register new task "a"
tasks.register("a") {
doLast {
println("this is task a")
}
}
// add new task "b" by using property delegate
val b by tasks.registering
// add action to "b" task
tasks.named("b") {
doLast {
println("this is task b")
}
}
// register copy-sources task of type Copy
tasks.register<Copy>("copy-sources") {
from(file("srcDir"))
into(buildDir)
}
// register copy-jars task using property delegate
val copy-jars by tasks.registering(Copy::class) {
from(file("srcDir"))
into(buildDir)
}
// extend from default task
abstract class CustomTask : DefaultTask() {
// define action
@TaskAction
protected open fun doSomething() {
println("action called")
}
}
// rister task "custom" of type CustomTask
tasks.register<CustomTask>("custom")
- Tasks can be executed using
gradlecommandgradle a b
// output
> Task :a
this is task a
> Task :b
this is task b
you can use -q switch with gradle to remove unwanted output. gradle -q a b will output follwing text
this is task a
this is task b
doFirst and doLast
doFirst {} and doLast {} in a task adds actions to the begining or end of tasks action list resepectively. You can add multiple doFirst
and doLast blocks in a task.
tasks.register("a") {
doFirst {
println("this is task a first 1")
}
doLast {
println("this is task a last 1")
}
doFirst {
println("this is task a first 2")
}
doLast {
println("this is task a last 2")
}
}
- Output
this is task a first 2
this is task a first 1
this is task a last 1
this is task a last 2
Note that
first 2is printed first although it was added later in the task becausefirst 2was added withdoFirstanddoFirstadds action to thebeginingof the action list.
Locating and manipulating existing tasks
Registered tasks can be accessed via tasks collections. You can configure and add more actions to existing tasks.
tasks.register("hello")
tasks.register<Copy>("copy")
tasks.named("hello") { dependsOn("copy") }
println(tasks.named("hello").get().name)
println(tasks.named<Copy>("copy").get().destinationDir)
Getting tasks by path
// relative path to current project
println(tasks.getByPath("hello").path)
// hello task from project-a
println(tasks.getByPath(":project-a:hello").path)
Accessing tasks with type
tasks.withType<Tar>().configureEach {
enabled = false
}
tasks.register("test") {
dependsOn(tasks.withType<Copy>())
}
Task dependencies
As tasks can depend on other tasks so the task dependencies can be declared using dependsOn
tasks.register("a") {
doLast {
println("this is task a ")
}
}
tasks.register("b") {
dependsOn("a")
doLast {
println("this is task b")
}
}
// configure project-a, register taskX which depends on taskY of project-b
project("project-a") {
tasks.register("taskX") {
dependsOn(":project-b:taskY")
doLast {
println("taskX")
}
}
}
- Running task
bwill generate following output
this is task a
this is task b
Declaring task dependency using lazy block
val taskX by tasks.registering {
dependsOn(provider {
// use provider block to return list of dependencies
tasks.filter { task -> task.name.startsWith("lib") }
})
doLast {
println("taskX")
}
}
Default tasks
Gradle allows to register default tasks which will be run when no tasks are specified on command line
// register default tasks
defaultTasks("a", "b")
tasks.register("a") {
doLast {
println("this is task a")
}
}
tasks.register("b") {
doLast {
println("this is task b")
}
}
- Running
gradlecommand without any tasks will run default tasksaandb
// output
this is task a
this is task b
Configuring tasks
// get task myCopy of type Copy and configure it
tasks.named<Copy>("myCopy") {
from("resources")
into("target")
include("**/*.txt", "**/*.xml", "**/*.properties")
}
// get reference to Copy task and configure it
val myCopy by tasks.existing(Copy::class) {
from("resources")
into("target")
}
myCopy {
include("**/*.txt", "**/*.xml", "**/*.properties")
}
Passing argument to task
abstract class CustomTask @Inject constructor(
private val message: String,
private val number: Int
) : DefaultTask() {
@TaskAction
open fun doSomething() {
println("$message $number")
}
}
tasks.register<CustomTask>("myTask", "hello", 42)
While passing argument to a task, the constructor must be annotated with
@Injectand all values should benon-null.
Ordering tasks
The order in which tasks will execute can be controlled in gradle without introducing explicit dependency between the tasks.
Defining a dependency between the tasks creates a strict excution order for those tasks. For example, if taskA depends on taskB then taskB will always execute when running taskA.
With task ordering, we can control which task
should/mustexecute before the other task without defining dependency between them, which means that, you will still be able to run those tasks separately. The ordering will only be applied if both tasks need to be run.
There are two ways to define tasks ordering
Task.mustRunAfter()Task.shouldRunAfter()
mustRunAftercreates strict ordering between the tasks i.e. the ordering must be followed, gradle will throw error if tasks can not be in order. On the other hand,shouldRunAfterwill define the order but if ordering can not be fulfilled gradle will simple ignore it.
- Example
val taskA by tasks.registering {
doLast {
println("running taskA")
}
}
val taskB by tasks.registering {
doLast {
println("running taskB")
}
shouldRunAfter(taskA)
}
tasks.named("taskA") {
mustRunAfter("taskB")
}
gradle -q taskA taskB
// output
running taskB
running taskA
Here taskA and taskB has circular ordering dependency. taskA mustRunAfter taskB but also taskB shouldRunAfter taskA. As mentioned previously, gradle will ignore shouldRunAfter if order can be fulfilled. If you replace shouldRunAfter with mustRunAfter
in taskB then gradle will throw exception
// output
FAILURE: Build failed with an exception.
* What went wrong:
Circular dependency between the following tasks:
:taskA
\--- :taskB
\--- :taskA (*)
(*) - details omitted (listed previously)
Skipping tasks
Task.onlyIf(predicate(thisTask)) can be attached to task which will be run just before executing the task. The task will be only be executed if predicte evaluates to true. The task instance is passed to predicate.
// taskA will only run if project does not have property skipTaskA
val taskA by tasks.registering {
doLast {
println("running taskA")
}
onlyIf {
!project.hasProperty("skipTaskA")
}
}
A task can also be skipped at runtime by throwing StopExecutionException() if onlyIf can not be defined. After StopExecutionException is thrown gradle will skip current task and will continue executing other tasks.
val taskA by tasks.registering {
doLast {
if(someCheck) {
throw StopExecutionException()
}
println("running taskA")
}
}
Disabling task
Every task has an enabled flag which is true by default and can be set to false to prevent the execution of task. Disabled tasks will be marked as SKIPPED by gradle.
val taskA by tasks.registering {
enabled = false // disable task
doLast {
println("running taskA")
}
}
Task timeout
By using timeout property of a task, it’s execution time can be limited. If timeout is reached, the tasks execution thread is interrupted, and it is makred as failed. Finalizer tasks will still run and if --continue is used other tasks will still run. If a task does not respond to timeout interrupts it can not be timedout.
tasks.register("hangingTask") {
doLast {
Thread.sleep(100000)
}
timeout.set(Duration.ofMillis(500))
}
// Output: build failed. Timeout has been exceeded
Task rules
Gradle allows you do define task rules which will be invoked when unknown task is requested. Task rules can become handy when you have to define a behaviour which depends on infinite number of inputs. For example, you want to create a task which will run all unit tests in a specific directory, this can be done using gradle task rules.
- Example
tasks.addRule("RunTest: directory") {
val taskName = this // the task name
if (startsWith("runTest")) {
task(taskName) {
doLast {
println("running tests in dir " + (taskName.replace("runTest", "")))
}
}
}
}
gradle -q runTestRoot
//ouput
running tests in dir Root
tasks.addRule(description) will add a task rule which will be invoked when an unknown task is requested by gradle.
In our example, when runTestRoot task was requested, gradle checked if it is defined or not, because the task was not defined, therule was executed with the task name (runTestRoot) where we checked if name starts with runTest then create a task with that name and a given action doLast.
After the rule execution created the given task, gradle can now fund/run it. If no task with the given name was created, gradle will throw task not found exception.
Note that other tasks can also depend on task rules. For our given example, we can create another task which will run test in different directories. See example below
tasks.register("runAllCoreTests") {
// depends on task rule
dependsOn("runTestRoot", "runTestOther")
doLast {
println("task ${this.name} completed")
}
}
gradle -q runAllCoreTests
// output
running tests in dir Other
running tests in dir Root
task runAllCoreTests completed
Finalizer tasks
If a task is finalized by another task then finalized task will be run after the excution of the task. If doesn’t matter if task execution was successful, failed or skipped. Finalized task will always run.
task("a") {
doLast {
println("${this.name} was run")
}
finalizedBy("b")
}
task("b") {
doLast {
println("${this.name} was run")
}
}
gradle -q a
// output
a was run
b was run