In the introductory tutorial you learned how to create simple tasks. You also learned how to add additional behavior to these tasks later on, and you learned how to create dependencies between tasks. This was all about simple tasks, but Gradle takes the concept of tasks further. Gradle supports tasks that have their own properties and methods. Such tasks are either provided by you or built into Gradle. Show
Task outcomesWhen Gradle executes a task, it can label the task with different outcomes in the console UI and via the Tooling API. These labels are based on if a task has actions to execute, if it should execute those actions, if it did execute those actions and if those actions made any changes.
Task executed its actions.
UP-TO-DATE Task’s outputs did not change.
FROM-CACHE Task’s outputs could be found from a previous execution.
SKIPPED Task did not execute its actions.
NO-SOURCE Task did not need to execute its actions.
Defining tasksWe have already seen how to define tasks using strings for task names in this chapter. There are a few variations on this style, which you may need to use in certain situations. Example 1. Defining tasks using strings for task names build.gradle
build.gradle.kts
We add the tasks to the In the Kotlin DSL there is also a specific delegated properties syntax that is useful if you need the registered task for further reference. Example 2. Assigning tasks to variables with DSL specific syntax build.gradle
build.gradle.kts
Locating tasksYou often need to locate the tasks that you have defined in the build file, for example, to configure them or use them for dependencies. There are a number of ways of doing this. Firstly, just like with defining tasks there are language specific syntaxes for the Groovy and Kotlin DSL: In general, tasks are available through the Example 3. Accessing tasks via tasks collection build.gradle
build.gradle.kts
Tasks of a specific type can also be accessed by using the Example 4. Accessing tasks by their type build.gradle
build.gradle.kts
You can access tasks from
any project using the task’s path using the Example 5. Accessing tasks by path build.gradle
project-a/build.gradle.kts build.gradle.kts
Output of > gradle -q hello :hello :hello :project-a:hello :project-a:hello Have a look at TaskContainer for more options for locating tasks. Configuring tasksAs an example, let’s look
at the Example 6. Registering a copy task build.gradle
build.gradle.kts
This registers a copy task with no default behavior. The task can be configured using its API (see Copy). The following examples show several different ways to achieve the same configuration. Just to be clear, realize that the name of this task is Example 7. Configuring a task build.gradle
build.gradle.kts
You can also store the task reference in a variable and use to configure the task further at a later point in the script. Example 8. Retrieve a task reference and use it to configuring the task build.gradle
build.gradle.kts
Have a look at TaskContainer for more options for configuring tasks.
You can also use a configuration block when you define a task. Example 9. Defining a task with a configuration block build.gradle
build.gradle.kts
Passing arguments to a task constructorAs opposed to configuring the mutable properties of a Example 10. Task class with build.gradle
build.gradle.kts
You can then create a task, passing the constructor arguments at the end of the parameter list. Example 11. Registering a task with constructor arguments using TaskContainer build.gradle
build.gradle.kts
In all circumstances, the values passed as constructor arguments must be non-null. If you attempt to pass a Adding dependencies to a taskThere are several ways you can define the dependencies of a task. In Task dependencies
you were introduced to defining dependencies using task names. Task names can refer to tasks in the same project as the task, or to tasks in other projects. To refer to a task in another project, you prefix the name of the task with the path of the project it belongs to. The following is an example which adds a dependency from Example 12. Adding dependency on task from another project build.gradle
build.gradle.kts
Output of > gradle -q taskX taskY taskX Instead of using a task name, you can define a dependency using a Example 13. Adding dependency using task provider object build.gradle
build.gradle.kts
Output of > gradle -q taskX taskY taskX For more
advanced uses, you can define a task dependency using a lazy block. When evaluated, the block is passed the task whose dependencies are being calculated. The lazy block should return a single Example 14. Adding dependency using a lazy block build.gradle
build.gradle.kts
Output of > gradle -q taskX lib1 lib2 taskX For more information about task dependencies, see the Task API. Ordering tasksIn some cases it is useful to control the order in which 2 tasks will execute, without introducing an explicit dependency between those tasks. The primary difference between a task ordering and a task dependency is that an ordering rule does not influence which tasks will be executed, only the order in which they will be executed. Task ordering can be useful in a number of scenarios:
There are two ordering rules available: “must run after” and “should run after”. When you use the “must run after” ordering rule you specify that With these rules present it is still possible to execute Example 15. Adding a 'must run after' task ordering build.gradle
build.gradle.kts
Output of > gradle -q taskY taskX taskX taskY Example 16. Adding a 'should run after' task ordering build.gradle
build.gradle.kts
Output of > gradle -q taskY taskX taskX taskY In the examples above, it is still possible to execute Example 17. Task ordering does not imply task execution Output of Note that “
As mentioned before, the “should run after” ordering rule will be ignored if it introduces an ordering cycle: Example 18. A 'should run after' task ordering is ignored if it introduces an ordering cycle build.gradle
build.gradle.kts
Output of > gradle -q taskX taskZ taskY taskX Adding a description to a taskYou can add a description to your task. This description is displayed when executing Example 19. Adding a description to a task build.gradle
build.gradle.kts
Skipping tasksGradle offers multiple ways to skip the execution of a task. Using a predicateYou can use the Example 20. Skipping a task using a predicate build.gradle
build.gradle.kts
Output of > gradle hello -PskipHello > Task :hello SKIPPED BUILD SUCCESSFUL in 0s Using StopExecutionExceptionIf the logic for skipping a task can’t be expressed with a predicate, you can use the StopExecutionException. If this exception is thrown by an action, the further execution of this action as well as the execution of any following action of this task is skipped. The build continues with executing the next task. Example 21. Skipping tasks with StopExecutionException build.gradle
build.gradle.kts
Output of > gradle -q myTask I am not affected This feature is helpful if you work with tasks provided by Gradle. It allows you to add conditional execution of the built-in actions of such a task.[1] Enabling and disabling tasksEvery task has an Example 22. Enabling and disabling tasks build.gradle
build.gradle.kts
Output of > gradle disableMe > Task :disableMe SKIPPED BUILD SUCCESSFUL in 0s Task timeoutsEvery task
has a Example 23. Specifying task timeouts build.gradle
build.gradle.kts
Up-to-date checks (AKA Incremental Build)An important part of any build tool is the ability to avoid doing work that has already been done. Consider the process of compilation. Once your source files have been compiled, there should be no need to recompile them unless something has changed that affects the output, such as the modification of a source file or the removal of an output file. And compilation can take a significant amount of time, so skipping the step when it’s not needed saves a lot of time. Gradle supports this behavior out of the box through a feature it calls incremental build. You have
almost certainly already seen it in action: it’s active nearly every time the How does incremental build work? And what does it take to make use of it in your own tasks? Let’s take a look. Task inputs and outputsIn the most common case, a task takes some inputs and generates some outputs. If we use the compilation example from earlier, we can see that the source files are the inputs and, in the case of Java, the generated class files are the outputs. Other inputs might include things like whether debug information should be included. Figure 1. Example task inputs and outputs An important characteristic of an input is that it affects one or more outputs, as you can see from the previous figure. Different bytecode is generated depending on the content of the source files and the minimum version of the
Java runtime you want to run the code on. That makes them task inputs. But whether compilation has 500MB or 600MB of maximum memory available, determined by the As part of incremental build, Gradle tests whether any of the task inputs or outputs has changed since the last build. If they haven’t, Gradle can consider the task up to date and therefore skip executing its actions. Also note that incremental build won’t work unless a task has at least one task output, although tasks usually have at least one input as well. What this means for build authors is simple: you need to tell Gradle which task properties are inputs and which are outputs. If a task property affects the output, be sure to register it as an input, otherwise the task will be considered up to date when it’s not. Conversely, don’t register properties as inputs if they don’t affect the output, otherwise the task will potentially execute when it doesn’t need to. Also be careful of non-deterministic tasks that may generate different output for exactly the same inputs: these should not be configured for incremental build as the up-to-date checks won’t work. Let’s now look at how you can register task properties as inputs and outputs. Custom task typesIf you’re implementing a custom task as a class, then it takes just two steps to make it work with incremental build:
Gradle supports four main categories of inputs and outputs:
As an example, imagine you have a task that processes templates of varying types, such as FreeMarker, Velocity, Moustache, etc. It takes template source files and combines them with some model data to generate populated versions of the template files. This task will have three inputs and one output:
When you’re writing a custom task class, it’s easy to register properties as inputs or outputs via annotations. To demonstrate, here is a skeleton task implementation with some suitable inputs and outputs, along with their annotations: Example 24. Custom task class buildSrc/src/main/java/org/example/ProcessTemplates.java
buildSrc/src/main/java/org/example/TemplateData.java
Output of > gradle processTemplates > Task :processTemplates BUILD SUCCESSFUL in 0s 3 actionable tasks: 3 up-to-date Output of > gradle processTemplates > Task :processTemplates UP-TO-DATE BUILD SUCCESSFUL in 0s 3 actionable tasks: 3 up-to-date There’s plenty to talk about in this example, so let’s work through each of the input and output properties in turn:
These annotated properties mean that Gradle will skip the task if none of the source files, template engine, model data or generated files has changed since the previous time Gradle executed the task. This will often save a significant amount of time. You can learn how Gradle detects changes later. This example is particularly interesting because it works with collections of source files. What happens if only one source file changes? Does the task process all the source files again or just the modified one? That depends on the task implementation. If the latter, then the task itself is incremental, but that’s a different feature to the one we’re discussing here. Gradle does help task implementers with this via its incremental task inputs feature. Now that you have seen some of the input and output annotations in practice, let’s take a look at all the annotations available to you and when you should use them. The table below lists the available annotations and the corresponding property type you can use with each one. Table 1. Incremental build property type annotations
Annotations are inherited from all parent types including implemented interfaces. Property type annotations override any other property type annotation declared in a parent type. This way an Annotations on a property declared in a type override similar annotations declared by the superclass and in any implemented interfaces. Superclass annotations take precedence over annotations declared in implemented interfaces. The Console and Internal annotations in the table are special cases as they don’t declare either task inputs or task outputs. So why use them? It’s so that you can take advantage of the Java Gradle Plugin Development plugin to help you develop and publish your own plugins. This plugin checks whether any properties of your custom task classes lack an incremental build annotation. This protects you from forgetting to add an appropriate annotation during development. Using dependency resolution resultsDependency resolution results can be consumed as task inputs in two ways. First by consuming the graph of the resolved metadata using ResolvedComponentResult. Second by consuming the flat set of the resolved artifacts using ResolvedArtifactResult. A resolved graph can be obtained lazily from the incoming resolution result of a Example 25. Resolved graph as task input Task declaration
Task configuration
The resolved set of artifacts can be obtained lazily from the incoming artifacts of a Example 26. Resolved artifacts as task input Task declaration
Task configuration
Using the classpath annotationsBesides As opposed to input properties annotated with Input properties annotated with Nested inputsWhen analyzing When adding When adding When adding The type and classpath of nested inputs is tracked, too. This ensures that changes to the implementation of a nested input causes the build to be out of date. By this it is also possible to add user provided code as an input, e.g. by annotating an This allows us to model the JaCoCo Java agent, thus declaring the necessary JVM arguments and providing the inputs and outputs to Gradle: JacocoAgent.java
For this to work, There are other task types where this kind of nested inputs are available:
In the same way, this kind of modelling is available to custom tasks. Runtime validationWhen executing the build Gradle checks if task types are declared with the proper annotations. It tries to identify problems where e.g. annotations are used on incompatible types, or on setters etc. Any getter not annotated with an input/output annotation is also flagged. These problems then fail the build or are turned into deprecation warnings when the task is executed.
Runtime APICustom task classes are an easy way to bring your own build logic into the arena of incremental build, but you don’t always have that option. That’s why Gradle also provides an alternative API that can be used with any tasks, which we look at next. When you don’t have access to the source for a custom task class, there is no way to add any of the annotations we covered in the previous section. Fortunately, Gradle provides a runtime API for scenarios just like that. It can also be used for ad-hoc tasks, as you’ll see next. Using it for ad-hoc tasksThis runtime API is provided through a couple of aptly named properties that are available on every Gradle task:
These objects have methods that allow you to specify
files, directories and values which constitute the task’s inputs and outputs. In fact, the runtime API has almost feature parity with the annotations. All it lacks is an equivalent for Let’s take the template processing example from before and see how it would look as an ad-hoc task that uses the runtime API: Example 27. Ad-hoc task build.gradle
build.gradle.kts
Output of
> gradle processTemplatesAdHoc > Task :processTemplatesAdHoc BUILD SUCCESSFUL in 0s 3 actionable tasks: 3 executed As before, there’s much to talk about. To begin with, you should really write a custom task class for this as it’s a non-trivial implementation that has several configuration options. In this case, there are no task properties to store the root source folder, the location of the output directory or any of the other settings. That’s deliberate to highlight the fact that the runtime API doesn’t require the task to have any state. In terms of incremental build, the above ad-hoc task will behave the same as the custom task class. All the input and output definitions are done through the methods on The files that a task removes can be specified through Example 28. Ad-hoc task declaring a destroyable build.gradle
build.gradle.kts
One notable difference between the runtime API and the annotations is the lack of a method that corresponds directly to Fine-grained configurationThe runtime API methods only allow you to declare your inputs and outputs in themselves. However, the file-oriented ones return a builder — of type TaskInputFilePropertyBuilder — that lets you provide additional information about those inputs and outputs. You can learn about all the options provided by the builder in its API documentation, but we’ll show you a simple example here to give you an idea of what you can do. Let’s say we don’t want to run the Example 29. Using skipWhenEmpty() via the runtime API build.gradle
build.gradle.kts
Output of > gradle clean processTemplatesAdHocSkipWhenEmpty > Task :processTemplatesAdHocSkipWhenEmpty NO-SOURCE BUILD SUCCESSFUL in 0s 3 actionable tasks: 2 executed, 1 up-to-date The Now that you have seen both the annotations and the runtime API, you may be wondering which API you should be using. Our recommendation is to use the annotations wherever possible, and it’s sometimes worth creating a custom task class just so that you can make use of them. The runtime API is more for situations in which you can’t use the annotations. Using it for custom task typesAnother type of example involves registering additional inputs and outputs for instances of a custom
task class. For example, imagine that the Example 30. Using runtime API with custom task type build.gradle
build.gradle.kts
Using
the runtime API like this is a little like using
Important beneficial side effectsOnce you declare a task’s formal inputs and outputs, Gradle can then infer things about those properties. For example, if an input of one task is set to the output of another, that means the first task depends on the second, right? Gradle knows this and can act upon it. We’ll look at this feature next and also some other features that come from Gradle knowing things about inputs and outputs. Inferred task dependenciesConsider an archive task that packages the output of the Example 31. Inferred task dependency via task outputs build.gradle
build.gradle.kts
Output of > gradle clean packageFiles > Task :processTemplates > Task :packageFiles BUILD SUCCESSFUL in 0s 5 actionable tasks: 4 executed, 1 up-to-date Gradle will automatically make The above example can also be written as Example 32. Inferred task dependency via a task argument build.gradle
build.gradle.kts
Output of > gradle clean packageFiles2 > Task :processTemplates > Task :packageFiles2 BUILD SUCCESSFUL in 0s 5 actionable tasks: 4 executed, 1 up-to-date This is because the Input and output validationThe incremental build annotations provide enough information for Gradle to perform some basic validation on the annotated properties. In particular, it does the following for each property before the task executes:
If one task produces an output in a location and another task consumes that location by referring to it as an input, then Gradle checks that the consumer task depends on the producer task. When the producer and the consumer tasks are executing at the same time, the build fails to avoid capturing an incorrect state. Such validation improves the robustness of the build, allowing you to identify issues related to inputs and outputs quickly. You will
occasionally want to disable some of this validation, specifically when an input file may validly not exist. That’s why Gradle provides the Continuous buildAnother benefit of defining task inputs and outputs is continuous build. Since Gradle knows what files a task depends on, it can automatically run a task again if any of its inputs change. By activating continuous
build when you run Gradle — through the Task parallelismOne last benefit of defining task inputs and outputs is that Gradle can use this information to make decisions about how to run tasks when the "--parallel" option is used. For instance, Gradle will inspect the outputs of
tasks when selecting the next task to run and will avoid concurrent execution of tasks that write to the same output directory. Similarly, Gradle will use the information about what files a task destroys (e.g. specified by the How does it work?Before a task is executed for the first time, Gradle takes a fingerprint of the inputs. This fingerprint contains the paths of input files and a hash of the contents of each file. Gradle then executes the task. If the task completes successfully, Gradle takes a fingerprint of the outputs. This fingerprint contains the set of output files and a hash of the contents of each file. Gradle persists both fingerprints for the next time the task is executed. Each time after that, before the task is executed, Gradle takes a new fingerprint of the inputs and outputs. If the new fingerprints are the same as the previous fingerprints, Gradle assumes that the outputs are up to date and skips the task. If they are not the same, Gradle executes the task. Gradle persists both fingerprints for the next time the task is executed. If the stats of a file (i.e. Gradle also considers the code of the task as part of the inputs to the task. When a task, its actions, or its dependencies change between executions, Gradle considers the task as out-of-date. Gradle understands if a file property (e.g. one holding a Java classpath) is order-sensitive. When comparing the fingerprint of such a property, even a change in the order of the files will result in the task becoming out-of-date. Note that if a task has an output directory specified, any files added to that directory since the last time it was executed are ignored and will NOT cause the task to be out of date. This is so unrelated tasks may share an output directory without interfering with each other. If this is not the behaviour you want for some reason, consider using TaskOutputs.upToDateWhen(groovy.lang.Closure) Note also that changing the availability of an unavailable file (e.g. modifying the target of a broken symlink to a valid file, or vice versa), will be detected and handled by up-to-date check. The inputs for the task are also used to calculate the build cache key used to load task outputs when enabled. For more details see Task output caching.
Advanced techniquesEverything you’ve seen so far in this section will cover most of the use cases you’ll encounter, but there are some scenarios that need special treatment. We’ll present a few of those next with the appropriate solutions. Adding your own cached input/output methodsHave you ever wondered how the The implementation is quite simple and you can use the same technique for your own tasks to improve their APIs. Write your methods so that they add files directly to the appropriate annotated property. As an example, here’s how to add a Example 33. Declaring a method to add task inputs
build.gradle
build.gradle.kts
ProcessTemplates.java
Output of > gradle processTemplates > Task :processTemplates BUILD SUCCESSFUL in 0s 3 actionable tasks: 3 executed In other words, as long as you add values and files to formal task inputs and outputs during the configuration phase, they will be treated as such regardless from where in the build you add them. If we want to support tasks as arguments as well and treat their outputs as
the inputs, we can use the Example 34. Declaring a method to add a task as an input build.gradle
build.gradle.kts
ProcessTemplates.java
Output of > gradle processTemplates2 > Task :copyTemplates > Task :processTemplates2 BUILD SUCCESSFUL in 0s 4 actionable tasks: 4 executed This technique can make your custom task easier to use and result in cleaner build files. As an added benefit, our use of One last thing to note: if you are developing a task that takes collections of source files as inputs, like this example, consider using the built-in SourceTask. It will save you having to implement some of the plumbing that we put into Linking an |
Automatic clean-up of stale output directories has only been implemented for the output of source sets (Java/Groovy/Scala compilation). |
Task rules
Sometimes you want to have a task whose behavior depends on a large or infinite number value range of parameters. A very nice and expressive way to provide such tasks are task rules:
Example 48. Task rule
build.gradle
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast {
println "Pinging: " + (taskName - 'ping')
}
}
}
}
build.gradle.kts
tasks.addRule("Pattern: ping<ID>") {
val taskName = this
if (startsWith("ping")) {
task(taskName) {
doLast {
println("Pinging: " + (taskName.replace("ping", "")))
}
}
}
}
Output of gradle -q pingServer1
> gradle -q pingServer1 Pinging: Server1
The String parameter is used as a description for the rule, which is shown with gradle tasks
.
Rules are not only used when calling tasks from the command line. You can also create dependsOn relations on rule based tasks:
Example 49. Dependency on rule based tasks
build.gradle
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast {
println "Pinging: " + (taskName - 'ping')
}
}
}
}
tasks.register('groupPing') {
dependsOn 'pingServer1', 'pingServer2'
}
build.gradle.kts
tasks.addRule("Pattern: ping<ID>") {
val taskName = this
if (startsWith("ping")) {
task(taskName) {
doLast {
println("Pinging: " + (taskName.replace("ping", "")))
}
}
}
}
tasks.register("groupPing") {
dependsOn("pingServer1", "pingServer2")
}
Output of gradle -q groupPing
> gradle -q groupPing Pinging: Server1 Pinging: Server2
If you run gradle -q tasks
you won’t find a task named pingServer1
or pingServer2
, but this script is executing logic based on the request to run those tasks.
Finalizer tasks
Finalizer tasks are automatically added to the task graph when the finalized task is scheduled to run.
Example 50. Adding a task finalizer
build.gradle
def taskX = tasks.register('taskX') {
doLast {
println 'taskX'
}
}
def taskY = tasks.register('taskY') {
doLast {
println 'taskY'
}
}
taskX.configure { finalizedBy taskY }
build.gradle.kts
val taskX by tasks.registering {
doLast {
println("taskX")
}
}
val taskY by tasks.registering {
doLast {
println("taskY")
}
}
taskX { finalizedBy(taskY) }
Output of gradle -q taskX
> gradle -q taskX taskX taskY
Finalizer tasks will be executed even if the finalized task fails or if the finalized task is considered up to date.
Example 51. Task finalizer for a failing task
build.gradle
def taskX = tasks.register('taskX') {
doLast {
println 'taskX'
throw new RuntimeException()
}
}
def taskY = tasks.register('taskY') {
doLast {
println 'taskY'
}
}
taskX.configure { finalizedBy taskY }
build.gradle.kts
val taskX by tasks.registering {
doLast {
println("taskX")
throw RuntimeException()
}
}
val taskY by tasks.registering {
doLast {
println("taskY")
}
}
taskX { finalizedBy(taskY) }
Output of gradle -q taskX
> gradle -q taskX taskX taskY FAILURE: Build failed with an exception. * Where: Build file '/home/user/gradle/samples/build.gradle' line: 4 * What went wrong: Execution failed for task ':taskX'. > java.lang.RuntimeException (no error message) * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 0s
Finalizer tasks are useful in situations where the build creates a resource that has to be cleaned up regardless of the build failing or succeeding. An example of such a resource is a web container that is started before an integration test task and which should be always shut down, even if some of the tests fail.
Lifecycle tasks
Lifecycle tasks are tasks that do not do work themselves. They typically do not have any task actions. Lifecycle tasks can represent several concepts:
a work-flow step (e.g., run all checks with
check
)a buildable thing (e.g., create a debug 32-bit executable for native components with
debug32MainExecutable
)a convenience task to execute many of the same logical tasks (e.g., run all compilation tasks with
compileAll
)
The Base Plugin defines several standard lifecycle tasks, such as build
, assemble
, and check
. All the core language plugins, like the
Java Plugin, apply the Base Plugin and hence have the same base set of lifecycle tasks.
Unless a lifecycle task has actions, its outcome is determined by its task dependencies. If any of those dependencies are executed, the lifecycle task will be considered EXECUTED
. If all of the
task dependencies are up to date, skipped or from cache, the lifecycle task will be considered UP-TO-DATE
.
Summary
If you are coming from Ant, an enhanced Gradle task like Copy seems like a cross between an Ant target and an Ant task. Although Ant’s tasks and targets are really different entities, Gradle combines these notions into a single entity. Simple Gradle tasks are like Ant’s targets, but enhanced Gradle tasks also include aspects of Ant tasks. All of Gradle’s tasks share a common API and you can create dependencies between them. These tasks are much easier to configure than an Ant task. They make full use of the type system, and are more expressive and easier to maintain.