Creating tasks in gradle - Tasks API

Every 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 command gradle 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 because first 2 was added with doFirst and doFirst adds action to the begining 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 tasks a and b
// 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 be non-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

  1. Task.mustRunAfter()
  2. 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

top