Lifecycle tasks are tasks that do not do work themselves. These tasks have no actions. They serve as targets for the build.

writing tasks 2

Lifecycle tasks represent various 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 Gradle base plugin defines several lifecycle tasks, including build, assemble, and check.

A well-organized setup of lifecycle tasks enhances the accessibility of your build for new users and simplifies integration with CI.

Task group and description

Let’s consider a basic Java application as an example. The build contains a subproject called app.

Let’s list the available tasks in app at the moment:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.

When listing available tasks in the app subproject, we see that tasks are grouped. Here, the :run task is part of the Application group with the description Runs this project as a JVM application.

Private and hidden tasks

Gradle doesn’t support marking a task as private.

However, tasks will only show up when running :tasks if task.group is set or no other task depends on it.

For instance, the following task will not appear when running ./gradlew :app:tasks because it does not have a group; it is called a hidden task:

app/build.gradle.kts
tasks.register("helloTask") {
    println("Hello")
}
app/build.gradle
tasks.register("helloTask") {
    println("Hello")
}

Although helloTask is not listed, it can still be executed by Gradle:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.

Let’s add a group to the same task:

app/build.gradle.kts
tasks.register("helloTask") {
    group = "Other"
    println("Hello")
}
app/build.gradle
tasks.register("helloTask") {
    group = "Other"
    println("Hello")
}

Now that the group is added, the task is visible:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.

Other tasks
-----------
helloTask

In contrast, ./gradlew tasks --all will show all tasks; hidden and visible tasks are listed.

Grouping tasks

If you want to customize which tasks are shown to users when listed, you can group tasks and set the visibility of each group.

Remember, even if you hide tasks, they are still available, and Gradle can still run them.

Let’s start with an example built by Gradle init for a Java application with multiple subprojects. The project structure is as follows:

gradle-project
├── app
│   ├── build.gradle.kts
│   └── src                 // some java code
│       └── ...
├── utilities
│   ├── build.gradle.kts
│   └── src                 // some java code
│       └── ...
├── list
│   ├── build.gradle.kts
│   └── src                 // some java code
│       └── ...
├── buildSrc
│   ├── build.gradle.kts
│   ├── settings.gradle.kts
│   └── src                 // common build logic
│       └── ...
├── settings.gradle.kts
├── gradle
├── gradlew
└── gradlew.bat
gradle-project
├── app
│   ├── build.gradle
│   └── src             // some java code
│       └── ...
├── utilities
│   ├── build.gradle
│   └── src             // some java code
│       └── ...
├── list
│   ├── build.gradle
│   └── src             // some java code
│       └── ...
├── buildSrc
│   ├── build.gradle
│   ├── settings.gradle
│   └── src             // common build logic
│       └── ...
├── settings.gradle
├── gradle
├── gradlew
└── gradlew.bat

Run app:tasks to see available tasks in the app subproject:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the classes of the 'main' feature.
testClasses - Assembles test classes.

Distribution tasks
------------------
assembleDist - Assembles the main distributions
distTar - Bundles the project as a distribution.
distZip - Bundles the project as a distribution.
installDist - Installs the project as a distribution as-is.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the 'main' feature.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in project ':app'.
dependencies - Displays all dependencies declared in project ':app'.
dependencyInsight - Displays the insight into a specific dependency in project ':app'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
kotlinDslAccessorsReport - Prints the Kotlin code for accessing the currently available project extensions and conventions.
outgoingVariants - Displays the outgoing variants of project ':app'.
projects - Displays the sub-projects of project ':app'.
properties - Displays the properties of project ':app'.
resolvableConfigurations - Displays the configurations that can be resolved in project ':app'.
tasks - Displays the tasks runnable from project ':app'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the test suite.

If we look at the list of tasks available, even for a standard Java project, it’s extensive. Many of these tasks are rarely required directly by developers using the build.

We can configure the :tasks task and limit the tasks shown to a certain group.

Let’s create our own group so that all tasks are hidden by default by updating the app build script:

app/build.gradle.kts
val myBuildGroup = "my app build"               // Create a group name

tasks.register<TaskReportTask>("tasksAll") {    // Register the tasksAll task
    group = myBuildGroup
    description = "Show additional tasks."
    setShowDetail(true)
}

tasks.named<TaskReportTask>("tasks") {          // Move all existing tasks to the group
    displayGroup = myBuildGroup
}
app/build.gradle
def myBuildGroup = "my app build"               // Create a group name

tasks.register(TaskReportTask, "tasksAll") {    // Register the tasksAll task
    group = myBuildGroup
    description = "Show additional tasks."
    setShowDetail(true)
}

tasks.named(TaskReportTask, "tasks") {          // Move all existing tasks to the group
    displayGroup = myBuildGroup
}

Now, when we list tasks available in app, the list is shorter:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

My app build tasks
------------------
tasksAll - Show additional tasks.

Lifecycle tasks

Lifecycle tasks can be particularly beneficial for separating work between users or machines (CI vs local). For example, a developer on a local machine might not want to run an entire build on every single change.

Let’s expose three additional tasks in our example, the build task, the check task, and the run task by adding the following lines to the app build script:

app/build.gradle.kts
tasks.build {
    group = myBuildGroup
}

tasks.check {
    group = myBuildGroup
    description = "Runs checks (including tests)."
}

tasks.named("run") {
    group = myBuildGroup
}
app/build.gradle
tasks.build {
    group = myBuildGroup
}

tasks.check {
    group = myBuildGroup
    description = "Runs checks (including tests)."
}

tasks.named('run') {
    group = myBuildGroup
}

If we now look at the app:tasks list, we can see the three tasks are available:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

My app build tasks
------------------
build - Assembles and tests this project.
check - Runs checks (including tests).
run - Runs this project as a JVM application
tasksAll - Show additional tasks.

This is already useful if the standard lifecycle tasks are sufficient. Moving the groups around helps clarify the tasks you expect to used in your build.

In many cases, there are more specific requirements that you want to address. One common scenario is running quality checks without running tests. Currently, the :check task runs tests and the code quality checks. Instead, we want to run code quality checks all the time, but not the lengthy test.

To add a quality check lifecycle task, we introduce an additional lifecycle task called qualityCheck and a plugin called spotbugs.

To add a lifecycle task, use tasks.register(). The only thing you need to provide is a name. Put this task in our group and wire the actionable tasks that belong to this new lifecycle task using the dependsOn() method:

app/build.gradle.kts
plugins {
    id("com.github.spotbugs") version "6.0.7"           // spotbugs plugin
}

tasks.register("qualityCheck") {                        // qualityCheck task
    group = myBuildGroup                                // group
    description = "Runs checks (excluding tests)."      // description
    dependsOn(tasks.classes, tasks.spotbugsMain)        // dependencies
    dependsOn(tasks.testClasses, tasks.spotbugsTest)    // dependencies
}
app/build.gradle
plugins {
    id 'com.github.spotbugs' version '6.0.7'            // spotbugs plugin
}

tasks.register('qualityCheck') {                        // qualityCheck task
    group = myBuildGroup                                // group
    description = 'Runs checks (excluding tests).'      // description
    dependsOn tasks.classes, tasks.spotbugsMain         // dependencies
    dependsOn tasks.testClasses, tasks.spotbugsTest     // dependencies
}

Note that you don’t need to list all the tasks that Gradle will execute. Just specify the targets you want to collect here. Gradle will determine which other tasks it needs to call to reach these goals.

In the example, we add the classes task, a lifecycle task to compile all our production code, and the spotbugsMain task, which checks our production code.

We also add a description that will show up in the task list that helps distinguish the two check tasks better.

Now, if run './gradlew :app:tasks', we can see that our new qualityCheck lifecycle task is available:

$ ./gradlew :app:tasks

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

My app build tasks
------------------
build - Assembles and tests this project.
check - Runs checks (including tests).
qualityCheck - Runs checks (excluding tests).
run - Runs this project as a JVM application
tasksAll - Show additional tasks.

If we run it, we can see that it runs checkstyle but not the tests:

$ ./gradlew :app:qualityCheck

> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
> Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
> Task :buildSrc:compilePluginsBlocks UP-TO-DATE
> Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
> Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :app:processResources NO-SOURCE
> Task :app:processTestResources NO-SOURCE
> Task :list:compileJava UP-TO-DATE
> Task :utilities:compileJava UP-TO-DATE
> Task :app:compileJava
> Task :app:classes
> Task :app:compileTestJava
> Task :app:testClasses
> Task :app:spotbugsTest
> Task :app:spotbugsMain
> Task :app:qualityCheck

BUILD SUCCESSFUL in 1s
16 actionable tasks: 5 executed, 11 up-to-date

So far, we have looked at tasks in individual subprojects, which is useful for local development when you work on code in one subproject.

With this setup, developers only need to know that they can call Gradle with :subproject-name:tasks to see which tasks are available and useful for them.

Global lifecycle tasks

Another place to invoke lifecycle tasks is within the root build; this is especially useful for Continuous Integration (CI).

Gradle tasks play a crucial role in CI or CD systems, where activities like compiling all code, running tests, or building and packaging the complete application are typical. To facilitate this, you can include lifecycle tasks that span multiple subprojects.

Gradle has been around for a long time, and you will frequently observe build files in the root directory serving various purposes. In older Gradle versions, many tasks were defined within the root Gradle build file, resulting in various issues. Therefore, exercise caution when determining the content of this file.

One of the few elements that should be placed in the root build file is global lifecycle tasks.

Let’s continue using the Gradle init Java application multi-project as an example.

This time, we’re incorporating a build script in the root project. We’ll establish two groups for our global lifecycle tasks: one for tasks relevant to local development, such as running all checks, and another exclusively for our CI system.

Once again, we narrowed down the tasks listed to our specific groups:

build.gradle.kts
val globalBuildGroup = "My global build"
val ciBuildGroup = "My CI build"

tasks.named<TaskReportTask>("tasks") {
    displayGroups = listOf<String>(globalBuildGroup, ciBuildGroup)
}
build.gradle
def globalBuildGroup = "My global build"
def ciBuildGroup = "My CI build"

tasks.named(TaskReportTask, "tasks") {
    displayGroups = [globalBuildGroup, ciBuildGroup]
}

You could hide the CI tasks if you wanted to by updating displayGroups.

Currently, the root project exposes no tasks:

$ ./gradlew :tasks

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project 'gradle-project'
------------------------------------------------------------

No tasks
In this file, we don’t apply a plugin!

Let’s add a qualityCheckApp task to execute all code quality checks in the app subproject. Similarly, for CI purposes, we implement a checkAll task that runs all tests:

build.gradle.kts
tasks.register("qualityCheckApp") {
    group = globalBuildGroup
    description = "Runs checks on app (globally)"
    dependsOn(":app:qualityCheck" )
}

tasks.register("checkAll") {
    group = ciBuildGroup
    description = "Runs checks for all projects (CI)"
    dependsOn(subprojects.map { ":${it.name}:check" })
    dependsOn(gradle.includedBuilds.map { it.task(":checkAll") })
}
build.gradle
tasks.register("qualityCheckApp") {
    group = globalBuildGroup
    description = "Runs checks on app (globally)"
    dependsOn(":app:qualityCheck")
}

tasks.register("checkAll") {
    group = ciBuildGroup
    description = "Runs checks for all projects (CI)"
    dependsOn subprojects.collect { ":${it.name}:check" }
    dependsOn gradle.includedBuilds.collect { it.task(":checkAll") }
}

So we can now ask Gradle to show us the tasks for the root project and, by default, it will only show us the qualityCheckAll task (and optionally the checkAll task depending on the value of displayGroups).

It should be clear what a user should run locally:

$ ./gradlew :tasks

> Task :tasks

------------------------------------------------------------
Tasks runnable from root project 'gradle-project'
------------------------------------------------------------

My CI build tasks
-----------------
checkAll - Runs checks for all projects (CI)

My global build tasks
---------------------
qualityCheckApp - Runs checks on app (globally)

If we run the :checkAll task, we see that it compiles all the code and runs the code quality checks (including spotbug):

$ ./gradlew :checkAll

> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
> Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
> Task :buildSrc:compilePluginsBlocks UP-TO-DATE
> Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
> Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
> Task :buildSrc:compileKotlin UP-TO-DATE
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :utilities:processResources NO-SOURCE
> Task :app:processResources NO-SOURCE
> Task :utilities:processTestResources NO-SOURCE
> Task :app:processTestResources NO-SOURCE
> Task :list:compileJava
> Task :list:processResources NO-SOURCE
> Task :list:classes
> Task :list:jar
> Task :utilities:compileJava
> Task :utilities:classes
> Task :utilities:jar
> Task :utilities:compileTestJava NO-SOURCE
> Task :utilities:testClasses UP-TO-DATE
> Task :utilities:test NO-SOURCE
> Task :utilities:check UP-TO-DATE
> Task :list:compileTestJava
> Task :list:processTestResources NO-SOURCE
> Task :list:testClasses
> Task :app:compileJava
> Task :app:classes
> Task :app:compileTestJava
> Task :app:testClasses
> Task :list:test
> Task :list:check
> Task :app:test
> Task :app:spotbugsTest
> Task :app:spotbugsMain
> Task :app:check
> Task :checkAll

BUILD SUCCESSFUL in 1s
21 actionable tasks: 12 executed, 9 up-to-date

Task name abbreviation

When specifying tasks on the command line, providing the complete task name is unnecessary. You can provide enough of the task name to identify the task uniquely.

For example, instead of running ./gradlew :checkAll, we can run ./gradlew :chAl:

$ ./gradlew :chAl

> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
...
> Task :app:test UP-TO-DATE
> Task :app:check UP-TO-DATE
> Task :checkAll UP-TO-DATE

BUILD SUCCESSFUL in 412ms
21 actionable tasks: 1 executed, 20 up-to-date

The same applies to project names. You can execute the :check task in the utitlities subproject with the gradle uti:che command.

You can use camel case patterns for more complex abbreviations.

Calling tasks without the colon : in front is not advised. If you do so, Gradle will attempt to find matching tasks in all the subprojects, and you won’t precisely know which tasks will be invoked. For instance, Gradle will skip projects where the task is unavailable.

It is recommended that you adhere to the ':' notation, ensuring the appropriate lifecycle tasks are available so that everyone is aware of the intended usage in your build.

Exclude tasks from execution

You can exclude a task from execution using the -x or --exclude-task command-line option and provide the task’s name to exclude.

For instance, you can run the check task but exclude the test task from running. This approach can lead to unexpected outcomes, particularly if you exclude an actionable task that produces results needed by other tasks. Instead of relying on the -x parameter, defining a suitable lifecycle task for the desired action is recommended.

Using -x is a practice that should be avoided, although still commonly observed.