Deep into the Task

Tasks work as the smallest atom of Gradle builds and can flexibly define the build of a project through inter-dependencies between tasks.

Task contains the following properties

  • The related properties of the task itself, which contain all getters and setters
  • Extentsions properties
  • Conventions properties
  • Extra attributes

Task allocation

Define the Task

You can define a set of tasks for a Project. Each task has its own set of actions, which can be added via doFirst or doLast

// Create default task
tasks.register("hello") {
  	// The code for the configuration phase
  	group = "custiom"
    doLast {
      	// Execute the code
        println("hello")}}// Create tasks based on the Task template
tasks.register<Copy>("copy") {
  	// Configure the copy source
    from(file("srcDir"))
  	// Set the copy destination
    into(buildDir)
}
Copy the code

To get the Task

You can get a defined task, get its configuration, or reconfigure it

// Get task by name
println(tasks.named("hello").get().name)
// Get the task of the specified type by name
println(tasks.named<Copy>("copy").get().destinationDir)
// Get task by type
tasks.withType<Copy>().configureEach {
    group = "customCopy"
}
Copy the code

You can see more about the API for getting tasks in the TaskContainer source code

Task dependencies and sorting

A task may have dependencies on another task or may need to be executed after a task. Gradle ensures that all task dependencies and ordering rules are followed when executing the task. Use mustRunAfter and shouldRunAfter to manipulate the execution order of tasks.

The following objects are used to specify task dependencies or ordering

  • Task string path
  • Task object
  • TaskDenpendcy object
  • TaskRefrence object
  • RegularFileProperty, File, and DirectoryProperty objects
  • Provider object that contains the return value of the above type
  • A collection containing the above types
  • Contains closures of the above type
  1. Tasks that depend on other projects
project("project-a") {
  	// Depend on project B's taskY
    tasks.register("taskX") {
      	// Task path through: split
        dependsOn(":project-b:taskY")
        doLast {
            println("taskX")
        }
    }
}

project("project-b") {
    tasks.register("taskY") {
        doLast {
            println("taskY")}}}Copy the code

TaskY is executed before taskX

  1. Rely on a closure
val taskX by tasks.registering {
    doLast {
        println("taskX")}}// Rely on a closure
taskX {
    dependsOn(provider {
        tasks.filter { task -> task.name.startsWith("lib") }
    })
}

tasks.register("lib1") {
    doLast {
        println("lib1")
    }
}

tasks.register("lib2") {
    doLast {
        println("lib2")
    }
}

tasks.register("notALib") {
    doLast {
        println("notALib")}}Copy the code

Lib1, lib2 will be executed before taskX

  1. Sort the execution flow of tasks
val taskX by tasks.registering {
    doLast {
        println("taskX")}}val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
taskY {
    mustRunAfter(taskX)
}
Copy the code

The output of the following command

> gradle -q taskY taskX
taskX
taskY
Copy the code
> gradle -q taskY
taskY
Copy the code

You can see the difference between dependency and order

If a task depends on another task, the dependent task is preferentially executed

The order in which tasks are executed does not mean that the reference tasks will be executed, but that they will be executed together in the agreed order

  1. When a Task already has a dependent process, the sort process is ignored
val taskX by tasks.registering {
    doLast {
        println("taskX")}}val taskY by tasks.registering {
    doLast {
        println("taskY")}}val taskZ by tasks.registering {
    doLast {
        println("taskZ")
    }
}
taskX { dependsOn(taskY) }
taskY { dependsOn(taskZ) }
taskZ { shouldRunAfter(taskX) }
Copy the code
> gradle -q taskX
taskZ
taskY
taskX
Copy the code

Skip the task

  1. By judging conditions

    val hello by tasks.registering {
        doLast {
            println("hello world") } } hello { onlyIf { ! project.hasProperty("skipHello")}}Copy the code
  2. With an exception, a StopExecutionException is thrown

  3. Settings unavailable

    val disableMe by tasks.registering {
        doLast {
            println("This should not be printed if the task is disabled.")
        }
    }
    
    disableMe {
        enabled = false
    }
    
    Copy the code
  4. Example Set the task timeout period

    tasks.register("hangingTask") {
        doLast {
            Thread.sleep(100000)
        }
        timeout.set(Duration.ofMillis(500))}Copy the code

Add a Rule to TaskContainer

TaskContainer inherited from NamedDomainObjectCollection, it can add a rule, when given a name unknown domain object, applies to rules, you can to ignore, or to create the named domain object

tasks.addRule("Pattern: ping<ID>") {
    val taskName = this
    if (startsWith("ping")) {
        task(taskName) {
            doLast {
                println("Pinging: " + (taskName.replace("ping".""))}}}}Copy the code

Customize the Task template

Define a simple Task Class

open class GreetingTask : DefaultTask() {
    var greeting = "hello from GreetingTask"
		//TaskAction writes the specific execution logic of the task. This method is executed during the execution phase
    @TaskAction
    fun greet(a) {
        println(greeting)
    }
}

tasks.register<GreetingTask>("hello")
Copy the code

Configure tasks through setter methods

tasks.register<GreetingTask>("greeting") {
  	// Configure the greeting parameters
    greeting = "greetings from GreetingTask"
}
Copy the code

Configure tasks using constructors

open class GreetingTask() : DefaultTask() {
    var greeting = "hello from GreetingTask"

    @javax.inject.Inject
    constructor(greeting: String) : this() {
        this.greeting = greeting
    }

    @TaskAction
    fun greet(a) {
        println(greeting)
    }
}
// Pass the constructor arguments directly
tasks.register<GreetingTask>("greeting"."hello gradle")
Copy the code

Configure tasks using command-line options

open class GreetingTask() : DefaultTask() {
    @Option(option = "m", description = "Configure greeting text")
    var greeting = "hello from GreetingTask"

    @TaskAction
    fun greet(a) {
        println(greeting)
    }
}

tasks.register<GreetingTask>("greeting")
// Run gradlew greeting -m hellogradle
Copy the code

An incremental

To improve Gradle’s build efficiency and avoid repetitive work, Gradle introduces the concept of incremental builds.

In most cases, a task will contain both input and output. Take the ZipResTask from Gradle Level 3 as an example. The resource file is the input and the packaged ZIP file is the output. If the input and output of a Task are the same when executed multiple times, then it is considered unnecessary to repeat the execution of such a Task. Each Task has inputs and outputs, whose types are TaskInputs and TaskOutputs, respectively. In an incremental build, we define inputs and outputs for each Task. Gradle considers a Task TO be UP TO DATE if its inputs and outputs have not changed since the last execution. Therefore Gradle will not execute. Inputs and outputs of a Task can be one or more files, folders, a Property of a Project, or even conditions defined by a closure.

Modify ZipResTask to an incremental build

//custom_build.gradle.kts
import org.gradle.kotlin.dsl.support.zipTo

open class ZipResExtensions {
    var resPath: String = ""
    var outputPath: String = ""
}

extensions.create<ZipResExtensions>("zipRes")

abstract class ZipResTask : DefaultTask() {
    @get:InputDirectory
    abstract val resDir: Property<File>

    @get:OutputFile
    abstract val outputFile: Property<File>

    @TaskAction
    fun zipRes(a) {
        zipTo(outputFile.get(), resDir.get())
    }
}

tasks.register("zipRes", ZipResTask::class)

afterEvaluate {
    tasks.named("zipRes", ZipResTask::class) {
        val zipResExtensions = project.extensions.getByName<ZipResExtensions>("zipRes")
        resDir.set(file(zipResExtensions.resPath))
        outputFile.set(file(zipResExtensions.outputPath))
    }
}
Copy the code

Output from zipRes execution

First execution

16:39:11: Executing task 'zipRes'...

> Task :zipRes

BUILD SUCCESSFUL in 88ms
1 actionable task: 1 executed
16:39:11: Task execution finished 'zipRes'.
Copy the code

Second execution

16:39:57: Executing task 'zipRes'...

> Task :zipRes UP-TO-DATE

BUILD SUCCESSFUL in 83ms
1 actionable task: 1 up-to-date
16:39:57: Task execution finished 'zipRes'.
Copy the code

If no changes are made, the execution of the task is skipped and the tag up-to-date is marked

conclusion

Tasks are the smallest atomic work of Gradle building. We need to be able to create tasks, configure them, and adjust the dependencies between tasks to complete our build