gradle is a build framework implemented in java. It has supports both groovy and kotlin for defining build tasks. Before you venture any further, it's probably worth taking a look at my Kotlin language notes (if you're not familliar with the language), because that's the preferred build DSL for this guide.

gradle is implemented using the following configuration files:

FileDescription
build.gradlethe script file in which tasks are implemented
gradle.propertiesconfiguration settings for the java build processIncluding the buildDir.
settings.gradlealso specifies configuration settings the java build process

gradle.properties and settings.gradle differ in two very important ways. The former is a simple java properties file, whereas the latter is a complete scriptable file which is compiled and executed before any build scripts. Only one settings.gradle file will be sourced per build. You can find more information about the differences here.

#Tasks

A gradle build script consists of projects & tasks. A task consists of inputs and outputs; alongside a series of actions which turn inputs into outputs. When you invoke a gradle build, you specify some tasks to be run, which're then processed and ordered by:

  • The task dependencies
  • The 'must run before' and 'should run before' rules
  • The appearence of the task in the command line

#Creating & Accessing Tasks

Every build consists of a configuration and a build phase. Task instances are created and populated during the configuration phase, whereas the actual building (invocation of tasks) takes place in a build phase.

tasks.register("foo") {
    println("woohoo, a new task")
}

val foo = tasks["foo"]

This registers new task (creates a DefaultTask instance) with the name foo and then evaluates the body of the {braces} within the context of the object. You can both register a task and store the created task object using delegated properties; however the delegated registerer doesn't return a task instance, it instead returns a Provider<T> instance. You can access the Task instance from it by using the [Provider<T>.get()] method, however you can generally use the Provider<T> anywhere you would use a task object.

val foo = tasks.registering {
    println("woohoo, a new task")
}

// tasks.named("foo") // is an alternative
// to tasks["foo"] but returns a Provider.

You can configure something to be done for a task during the build phase by registering actions. Two of the most common actions are doLast and doFirst.

tasks.register("foo") {
    doLast  { println("hello world") }
    doLast  { println("goodbye world") }
    doFirst { println("lo and behold") }

    println("here's a new object")
}
> ./gradlew foo
here's a new object

- Task :foo
lo and behold
hello world
goodbye world

BUILD SUCCESSFUL in 9s
1 actionable task: 1 executed

NOTE gradlew is a shell script standing for gradle wrapper; a gradle project can use a wrapper instead of the gradle executeable. What a wrapper does, is ensure the the same gradle version is always used to build/run the project (installing other versions if your current version doesn't match).

#Path Syntax

There's a specified path syntax for selecting tasks to be run. A task by itself is identified by it's name. A task within a project is identified by the project name joined by :. A : suffix on a path represents one starting from the root project, whereas the absence of it means relative to the current project.

#Inter Task Dependencies

Consider a gradle build using the command line ./gradlew foo bar baz. The order in which these three tasks are executed will, excluding any other relationships, match the order in which they're stated. If you reorder them to baz bar foo the order of outputs will also be reversed. One way of ordering the execution of some tasks are literal dependencies, meaning for one task to complete, another should be run first. This is done using the dependsOn method of org.gradle.api.DefaultTask.

tasks.register("baz") {
    doLast {
        println("running task baz")
    }
}

tasks.register("bar") {
    dependsOn("baz")

    doLast {
        println("running task bar")
    }
}

tasks.register("foo") {
    dependsOn("bar", "baz")

    doLast {
        println("running task foo")
    }
}

File: build.gradle.kts

Now if we try to run all three tasks, regardless of which order we specify them, they will be run with baz first (because it doesn't depend on any other task), followed by bar (because baz has already been run and bar has no other dependencies) and lastly foo.

> ./gradlew -q foo bar baz
running task baz
running task bar
running task foo
> ./gradlew -q bar foo baz
running task baz
running task bar
running task foo

#Avoiding Dependencies

An annoying consequence of explicit dependencies, is that running one command may inadvertently invoke other commands you didn't intend to be invoked. For example, in the previous build ./gradlew foo bar baz could just as well be written ./gradlew foo and the tasks bar & baz will also be run. What we may instead want is a relationship between tasks stating if two tasks, taskA & taskB, need to be run taskA should always be run before taskB. Lo and behold, gradle has a mechanism for this.

mustRunAfter & shouldRunAfter specify an ordering between tasks. The former demands the ordering between two tasks, wheras the latter simply suggests it. Both are needed in case mustRunAfter causes a cyclic ordering, in which case the build would fail.

You should use “should run after” where the ordering is helpful but not strictly required.

tasks.register("foo") {
    doLast {
        println("running task foo")
    }
}

tasks.register("bar") {
    shouldRunAfter(tasks["foo"])

    doLast {
        println("running task bar")
    }
}

File: build.gradle.kts

> ./gradlew -q foo
running task foo
> ./gradlew -q bar
running task bar
> ./gradlew -q bar foo
running task foo
running task bar

Notice that no task other the ones specified in the command line are run. foo does not cause bar to be run and vice versa. When both are specified, foo is run before bar even when bar is specified before foo.

#Default Tasks

A default task or tasks is a set of tasks which is run for a project (or a build) when no other information is present. For example, when you run ./gradlew what task is to be invoked for the project.

project("projectFoo") {
    tasks.register("foo") {
        doLast {
            println("running task foo")
        }
    }

    defaultTasks("foo")
}

defaultTasks(":projectFoo:foo")

File: build.gradle.kts

Now invoking ./gradlew will run the task foo in the projectFoo directory. You cant invoke the default tasks for a subproject from a higher level project yet; however if you want to execute the default tasks for projectFoo as specified by projectFoo, invoke gradle or gradlew from the projectFoo directory.

#Skipping Tasks

You can predicate the execution of a task using the onlyIf method of the task instance. You can pass a block to it & if the block evaluates to false, the task will be skipped.

tasks.register("foo") {
    doLast {
        println("running task foo")
    }

    onlyIf { !project.hasProperty("skipFoo") }
}

File: build.gradle.kts

The foo task will be skipped, unless the project doesn't does have the property skipFoo. NOTE you can set a property of a build using the -P flag on the gradle command line.

Alternatively, you can skip a task during it's execution by throwing the StopExecutionException. This'll skip both the current action and any of the remaining actions for the given task; however, it doesn't skip any of the following tasks or the parent task for which the skipped task may depend.

tasks.register("foo") {
    doLast {
        println("running task foo")
    }

    doFirst {
        throw StopExecutionException()
    }
}

tasks.register("bar") {
    dependsOn(tasks["foo"])
    doLast {
        println("running task bar")
    }
}

File: build.gradle.kts

Now if we invoke the bar task, the foo task will attempt to be run, skipped, and then bar will complete.

> ./gradlew -q bar
running task bar

Lastly, every task instace has an enabled flag, which if set to false will automatically skip the task... no questions asked.

#Other Neat Tricks

#Extra Task Properties

val foo by tasks.registering {
    extra["secret"] = "uryyb unpxre sevraq"
}

tasks.register("bar") {
    val secret = foo.get().extra["secret"]
    println("secret: $secret")
}