Gradle Task
External
- Task API: org.gradle.api.Task
- Task DSL: https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
- DefaultTask API: https://docs.gradle.org/current/javadoc/org/gradle/api/DefaultTask.html
- Authoring Tasks | https://docs.gradle.org/current/userguide/more_about_tasks.html
Internal
Overview
A task is a core Gradle concept, representing well defined work to be executed during a build, such as compiling Java classes, generating javadoc or creating a distribution file. The work executed by a task is further divided into individual task actions, defined and managed by the task. Task may depend on each other. The goal of a Gradle build is to execute a set of tasks in sequence, after the tasks have been arranged in a directed acyclic graph, computed based on each task's implicit and explicit dependencies. Understanding how the directed acyclic graph is built and how tasks are scheduled for execution is key to understanding how Gradle works.
A task belongs to a specific project. The association is made when the task is declared in build.gradle or when the plugin that declares the task is applied to the project in build.gradle. Tasks are maintained in the project's task container, and the can be looked up and manipulated by the project's build.gradle. The Locating Tasks section below gives more details on how tasks can be located.
Some tasks are built into Gradle core and are available to any project. Other tasks are contributed by plugins. New tasks can be defined by the project itself. For more details see Sources of Tasks section below.
New simple tasks can be defined in-line in build.gradle or in script plugins. Tasks whose implementations are available on the build's classpath, and that usually have their own properties and methods, can be declared and configured in build.gradle, as enhanced tasks, and thus made available for use during the build. Complex tasks can be even fully defined in-line or in script plugins, by providing the task code in-line in the script, though this is not a recommended practice, as it does not encourage modularization, encapsulation and sharing.
Any task instance implements org.gradle.api.Task interface. By default, every newly created task is of type org.gradle.api.DefaultTask, the standard implementation of org.gradle.api.Task.
Declaring, configuring and defining tasks is done via corresponding Project API methods, directly accessible from build.gradle:
Project.task(String name) Project.task(String name, Closure configureClosure) Project.task(String name, Action<? super Task> configureAction) Project.task(Map<String, ?> args, String name) Project.task(Map<String, ?> args, String name, Closure configureClosure)
Internally, the Project instance maintains the tasks in a task container.
A Gradle build executes the task specified on command line, including all explicit and implicit dependencies of the task.
./gradlew build
Task Lifecycle
The tasks are instantiated and configured in the build configuration phase, by executing their configuration closures. The task's actions are executed in the execution phase. A task does not necessarily have to have actions, it could only exist to execute the configuration closure at the build configuration phase. Such a task is called "task configuration".
Task Configuration Closure
Task configuration closures declare code that configures the task during the build configuration phase. The configuration closure usually initializes properties, declares task dependencies and adds actions. Note that the statements that are not part of a task action, declared outside doFirst and doLast closures, will be executed in the configuration phase, when the task is initialized and configured, not in the execution phase. For more details, see Task Lifecycle, above.
The task configuration closure can be applied at the same time the task is declared, as shown in the Simple Task section below. Alternatively, the task can be declared, then configured by applying the configuration closure later, as shown in the Enhanced Task | Configuration separated from declaration below.
Task Structure
Name and Path
Each task has a name, which is unique within the context of the task's project. The name must not contain dashes. Since different projects may contain tasks with the same name, a task is uniquely identified by its path in the context of a multi-project build. The path is the concatenation of the name of the project that owns the task and the task name, separated by the ":" character:
:web:build
Configuration
A task has configuration and actions. The code defined in the configuration section of the task will be executed during the configuration phase of the build.
Group
A task group defines the logical grouping of tasks. The task group is available via the "group" property of the task instance, and can be set within the task constructor or directly setting the property:
task example {
group = "experimental"
doLast {
...
}
}
task example(group: "experimental") {
doLast {
...
}
}
The task grouping is reflected in the output of the tasks task, which is one of the built-in tasks.
Description
Descriptions are displaying when executing the tasks built-in task.
task someTask(type: SomeTask) {
// configuration closure
description "A task that does this and that"
...
}
Enabled
Inputs and Outputs
In most cases, tasks have input and outputs, and whether those inputs and outputs change between successive builds is monitored by Gradle and could be used to optimize builds. More details about declaring and using task inputs and outputs is available here:
Dependencies and Ordering
Tasks can declare explicit dependencies on other tasks, and also have implicit dependencies given by the relationship between their outputs and inputs. More details are available here:
Actions
An action is the place within a task that contains build logic. Each task contains a list of actions, which are executed when the task is executed, in order in which they were added to the task's internal list of actions.
Existing tasks have their own actions, defined when the task is coded. Additional actions can also be added in-line when the task is configured, with corresponding DSL keywords doFirst and doLast, followed by closure that contain executable code, as shown in Defining a Simple Task section. Alternatively, actions can be added to the task by invoking the Task API methods doFirst(Action|Closure), which adds the action at the head of the list, and doLast(Action|Closure), which adds the action at the tail of the list, on the task reference.
Task Outcome
After execution, a task has one of the following outcomes, depending on whether a task has actions to execute, it should execute those actions, it did execute its actions and whether those actions made any changes: EXECUTED, UP-TO-DATE, FROM-CACHE, SKIPPED, NO-SOURCE.
(no label) or EXECUTED
The task has this outcome if:
- the task has actions that should have been executed, and they were executed.
- the task has no actions, but has dependencies and any of its dependencies has been executed.
UP-TO-DATE
The task has this outcome if:
- the task has no actions and no dependencies.
- the task has no actions, it has dependencies, but none of its dependencies have been executed because they are up-to-date, skipped or from cache.
- the task has actions, but the task tells Gradle that it did not change its outputs. How does a task tell Gradle it did not change its outputs?
- the task has inputs and outputs and they have not changed.
FROM-CACHE
The task has this outcome if its output have been generated from a previous execution and have been restored from the build cache.
SKIPPED
The task has this outcome if:
- The task has actions, but did not executed its actions because an onlyIf predicate returns false.
- The task has been explicitly excluded from command line.
NO-SOURCE
The task has this outcome if it did not execute its actions because even if it has inputs and outputs, it has no sources.
Sources of Tasks
Each project comes with a set of pre-defined, built-in tasks, which are part of Gradle core. Each declared plugin may add its own tasks. Simple tasks may be declared by the build, as shown in the Defining Custom Tasks section, below. Also, tasks whose implementations are available on the build's classpath, and that usually have their own properties and methods, can be declared and configured in build.gradle, as enhanced tasks, and thus made available for use during the build.
Built-in Tasks
Each build exposes a set of built-in tasks. The built-in tasks are part of Gradle core and can be used right away without any additional configuration or loading any plugin:
- init
- wrapper
- buildEnvironment
- components
- dependencies
- dependencyInsight
- dependentComponents
- help
- model
- projects
- properties
- tasks
Tasks and Plugins
Plugins usually add specialized tasks. As per this writing (Gradle 6.7), there is no command line command to list the tasks contributed by a certain plugin. However, they can be listed by doing a bit of programming:
Defining Custom Tasks
The simplest way of extending Gradle is to write a custom task. Custom tasks can be declared in-line in the default build script build.gradle, as simple tasks. Simple tasks can also be declared in-line in a separate build script, which is then included from the default build script. The code of the custom task can live in a separate source file or multiple files, which in turn can be declared in buildSrc, which is a special area of the Gradle project, or can be shared with other projects as part of a library, developed in its own project. Such a task is referred to as a enhanced task.
Custom tasks is also how Gradle plugins extend the Gradle functionality. "Custom" in this context means new functionality defined either by plugins, or by Gradle users while writing the build scripts.
Simple Task
A simple task is an implementation of org.gradle.api.DefaultTask that can be declared in-line in build.gradle or in script plugin using DSL keywords ("task", "doFirst", "doLast", etc.) corresponding to Task API methods. The example below shows a syntax where the task name and a task configuration closure are provided.
task myTask { <configuration-closure> }
task('myTask') { <configuration-closure> }
task(myTask) { <configuration-closure> } // this syntax uses Groovy dynamic keywords
tasks.create('myTask') { <configuration-closure> }
Example:
task customSimpleTask {
// this is the task configuration closure, it is executed during the configuration phase
println "this will be displayed during configuration phase"
// will add the action defined in the closure at the head of the action list
doFirst {
println "this will be executed second when the action is executed in the build execution phase"
}
doFirst {
println "this will be executed first when the action is executed in the build execution phase"
}
// will add the action defined in the closure at the tail of the action list
doLast {
println "this will be executed last when the action is executed in the build execution phase"
}
}
Creation Options Map
Simple task state elements can be set by using the task constructor that accepts a map, usually referred to as "map of creation options":
task example(group: "experimental",
description: "Example task",
dependsOn: [task-a, task-b]) {
doFirst {
println "example"
}
}
The following options are available:
Option | Description | Default Value |
"type" | The class of the task to create. | DefaultTask |
"overwrite" | Replace an existing task? | false |
"dependsOn" | A task name or set of task names which this task depends on | [] |
"action" | A closure or Action to add to the task. | null |
"description" | A description of the task. | null |
"group" | A task group which this task belongs to. | null |
Simple Task Examples
- Custom simple task declared in-line in build.gradle
- Custom simple task declared in-line in a script plugin
Enhanced Task
An enhanced task is a Gradle task that has two distinct components: the task type and task instances.
The task type is a class written in Java or Groovy, which encapsulates the task logic. Gradle comes with a number of predefined task types, but new types can be defined: the code can be declared in-line in build.gradle or a script plugin file, or preferably into a source code file (or files) maintained in the project's buildSrc or externally. The behavior, usually configurable, is built into the task type. Also, the task type may expose properties that can be configured from the build script.
The task instance is a particular instantiation of the task type, carrying a particular configuration. During the task instantiation process, values are provided for the properties exposed by the task class, and thus the instance is configured with a specific behavior. Multiple instance of the same type, with different names and configurations, may exist in the same build script.
Developing enhanced task is preferred because separating the implementation code from the configuration of the task increases the maintainability, the testability and the reusability of the enhanced task.
Tasks instances can be created using one of the syntaxes shown below:
task someTask(type: SomeTask)
task('someTask', type: SomeTask)
// this syntax uses Groovy dynamic keywords
task(someTask, type: SomeTask)
tasks.create('someTask', SomeTask)
Upon instantiation, the task instances may be configured using their type's API:
SomeTask myTask = tasks.getByName("myTask")
myTask.someProperty = "something"
myTask.someOtherProperty = "something else"
Alternatively, the configuration can be separated from declaration, using Groovy dynamic tasks configuration block.
task(myTask, type: SomeTask)
myTask {
someProperty = "something"
someOtherProperty = "something else"
}
Note that the blocks used here are for configuring the tasks and are not evaluated when the tasks executes.
Instantiation and configuration can be performed at the same time:
task someTask(type: SomeTask) {
// configuration closure
someProperty = "something"
someOtherProperty = "something else"
}
task('someTask', type: SomeTask) {
// configuration closure
}
// this syntax uses Groovy dynamic keywords
task(someTask, type: SomeTask) {
// configuration closure
}
tasks.create('someTask', SomeTask) {
// configuration closure
}
Predefined Task Types
These predefined task types are exposed by the core plugins.
- Copy What plugin introduces Copy?
Java Plugin Predefined Task Types
- JavaCompile exposed as "compileJava" task
- Jar exposed as "jar" task
Extending Gradle with Custom Enhanced Tasks
Locating Tasks
Tasks may need to be located in build.gradle to configure them or to declare them as dependencies of other tasks.
By Name
The DSL syntax supports locating tasks by name:
task myTask
...
println myTask.name
println project.myTask.name
Via tasks Collection
The tasks are available through the "tasks" collection:
task myTask
...
println tasks.named('myTask').get().name
Note that the named() method returns a NamedDomainObjectProvider<Task> instance, so to get the corresponding Task instance, we need to call get() on the result.
For a multi-project build, tasks from any project can be accessed using task's path and the tasks collection's getByPath() method. The argument can be the tasks name, a relative path or an absolute path:
task myTask
...
println tasks.getByPath('myTask').path
println tasks.getByPath(':myTask').path
println tasks.getByPath('myProject:myTask').path
println tasks.getByPath(':myProject:myTask').path
This method is convenient because it returns directly the Task instance.
Tasks of a specific type can be accessed by using tasks.withType() method:
tasks.witType(Copy).configureEach { ... }
Incremental Builds
An incremental build is a build that can avoid running tasks whose inputs and outputs did not change since the previous build, making the execution of such tasks unnecessary. More details on incremental builds, task inputs and output, incremental tasks and the build cache can be found in:
Lifecycle Tasks
A lifecycle task is a task that does not do work itself.
onlyIf Predicate
Task Operations
- Listing all available tasks
- Display the tasks that are executed by a build, in order in which they are executed
- Exclude a task from command line