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
task
represents 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
gradle
commandgradle 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 2
is printed first although it was added later in the task becausefirst 2
was added withdoFirst
anddoFirst
adds action to thebegining
of 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
b
will 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
gradle
command without any tasks will run default tasksa
andb
// 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
@Inject
and 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/must
execute 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()
mustRunAfter
creates 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,shouldRunAfter
will 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