Gradle - Build lifecycle

Before executing any task, gradle creates Directed Acyclic Graph of the all the tasks and makes sure tasks are executed only once and in order of their dependencies.

Build Lifecycle

Gradle goes through following three phases before executing the tasks

  1. Initialization
  2. Configuration
  3. Execution

Initialization

During initialization, gradle checks which projects are going to be part of the build and creates Projectinstance for each of these projects i.e a Project instance is created for each build.gradle file. Information about which projects will take part in the build is fetched from settings.gradle file in case of multi-project build.

Configuration

During this phase, previously created project objects are configured i.e. build scripts (build.gradle) of all the projects taking part in the build are run against these project objects. All property access and methods in build.gradle file are delegate to these Project instances.

Execution

At this point, gradle has already created the list of tasks and their dependencies for each project. Now, during this phase, gradle determines the task name arguments passed to gradle command and executes each of the tasks in order specified on command line

Settings.gradle file

settings.gradle file is required in case of multi-project build and is executed during initialization phase.

Gradle determines this file by naming convention, so it should be named as settings.gradle in the root project of multi-project build. This file is not required in case of single-project build.

settings.gradle file defines which projects will take part in this build. Just like instance ofProject, an instance of Settings class is created during initialization phase for settings.gradle and all method calls & property access in setting.gradle file are delegated to this instance.

Example

// settings.gradle
rootProject.name = "demo"
println("This is executed during the initialization phase.")
// build.gradle
println("This is executed during the configuration phase.")

tasks.register("test") {
    doLast {
        println("This is executed during the execution phase.")
    }
}
  • Run gradle -q (no task is specified)
// output
This is executed during the initialization phase.
This is executed during the configuration phase.
:help SKIPPED
  • Run gradle -q test (run test task)
// output

This is executed during the initialization phase.
This is executed during the configuration phase.
This is executed during the execution phase.

Initialization

In case of multi-project build, if build is triggered from a directory with settings.gradle file, gradle uses it to configure the build, however, if the build is triggered from any sub-project then gradle will try to find the settings.gradle file in following ways

  • It looks for settings.gradle in parent directories.
  • If not found, the build is executed as a single project build.
  • If a settings.gradle file is found, Gradle checks if the current project is part of the multi-project hierarchy defined in the found settings.gradle file. If not, the build is executed as a single project build. Otherwise a multi-project build is executed.

From docs

What is the purpose of this behavior? Gradle needs to determine whether the project you are in is a subproject of a multi-project build or not. Of course, if it is a subproject, only the subproject and its dependent projects are built, but Gradle needs to create the build configuration for the whole multi-project build.

If current project contains settings.gradle file then build is always executed according to this file either single or multi-project build.

The automatic search for a settings.gradle file only works for multi-project builds with a default project layout where project paths match the physical subproject layout on disk

Gradle creates a Project object for every project taking part in the build. For a multi-project build these are the projects specified in the Settings object (plus the root project)

Each project object has by default a name equal to the name of its top level directory, and every project except the root project has a parent project. Any project may have child projects.

Detecting lifecycle in build scripts

Gradle fires notifications as build progress through its lifecycle. You can either add closure to execute when notification is fired or use a listener interface. We will be using closures here

// build.gradle.kts

// for the same build.gradle as above
allprojects {
// executed when project is evaluated i.e. at the end of configuration phase
    afterEvaluate {
        println("after evaluation ${this.name}")
    }
}

// output

This is executed during the initialization phase.
This is executed during the configuration phase.
after evaluation demo

evaluation block can be used to add extra configuration once all definitions in a build script have been applied.

 // executed when any project is evaluated, it doesn't matter if evaluation was successful or not
gradle.afterProject {
    if (state.failure != null) {
        println("Evaluation of $project FAILED")
    } else {
        println("Evaluation of $project succeeded")
    }
}

Detecting task creation

// build.gradle.kts

// run this closure when a task is added to tasks list
tasks.whenTaskAdded {
    // create srcDir extra for the task with was added
    extra["srcDir"] = "src/main/java"
}

val a by tasks.registering

println("source dir is ${a.get().extra["srcDir"]}")

top