Chapter 4. Build Script Basics

You run a build using the gradle command. When run, gradle normally looks for a file called build.gradle in the current directory. [5] We call this build.gradle file a build script, although strictly speaking it is a build configuration script, as we will see later. In Gradle the location of the build script file defines a project. The name of the directory containing the build script is used as the name of the project.

4.1. Hello World

In Gradle the most basic building block is the task. The tasks for your build are defined in the build script. To try this out, create the following build script named build.gradle.

Example 4.1.  build.gradle

task hello {
    println 'Hello world!'
}

In a command-line shell, enter into the containing directory and execute the build script by running gradle -q hello: [6] [7]

Example 4.2. Output of gradle -q hello

> gradle -q hello
Hello world!

If you think this looks damn similar to Ant's targets, well, you are right. Gradle tasks are the equivalent to Ant targets. But as you will see, they are much more powerful. We have used a different terminology to Ant as we think the word task is more expressive than the word target . Unfortunately this introduces a terminology clash with Ant, as Ant calls its commands, such as javac or copy, tasks. So when we talk about tasks, we always mean Gradle tasks, which are the equivalent to Ant's targets. If we talk about Ant tasks (Ant commands), we explicitly say ant task.

4.2. Build scripts are code

Gradle's build scripts expose to you the full power of Groovy. As an appetizer, have a look at this:

Example 4.3.  build.gradle

task upper {
    String someString = 'mY_nAmE'
    println "Original: " + someString 
    println "Upper case: " + someString.toUpperCase()
}

Example 4.4. Output of gradle -q upper

> gradle -q upper
Original: mY_nAmE
Upper case: MY_NAME

or

Example 4.5.  build.gradle

task count {
    4.times { print "$it " }
}

Example 4.6. Output of gradle -q count

> gradle -q count
0 1 2 3

4.3. Task dependencies

As you probably have guessed, you can declare dependencies between your tasks.

Example 4.7.  build.gradle

task hello {
    println 'Hello world!'
}
task intro(dependsOn: hello) {
    println "I'm Gradle"
}

Example 4.8. Output of gradle -q intro

> gradle -q intro
Hello world!
I'm Gradle

To add a dependency, the corresponding task does not need to exist.

Example 4.9.  build.gradle

task taskX(dependsOn: 'taskY') {
    println 'taskX'
}
task taskY {
    println 'taskY'
}

Example 4.10. Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

The dependency of taskX to taskY is declared before taskY is created. This is very important for multi-project builds. Task dependencies are discussed in more detail in Section 12.4, “Adding Dependencies to Tasks”.

4.4. Dynamic tasks

The power of Groovy be used for more than definining what a task does. For example, you can also use it to dynamically create tasks.

Example 4.11.  build.gradle

4.times { counter ->
    task "task_$counter" {
        println "I'm task number $counter"
    }
}

Example 4.12. Output of gradle -q task_1

> gradle -q task_1
I'm task number 1

4.5. Manipulating existing tasks

Once tasks are created they can be accessed via an API. This is different to Ant. For example you can create additional dependencies.

Example 4.13.  build.gradle

4.times { counter ->
    task "task_$counter" {
        println "I'm task number $counter"
    }
}
task('task_0').dependsOn 'task_2', 'task_3'

Example 4.14. Output of gradle -q task_0

> gradle -q task_0
I'm task number 2
I'm task number 3
I'm task number 0

Or you can add behavior to an existing task.

Example 4.15.  build.gradle

task hello {
    println 'Hello Earth'
}
task('hello').doFirst {
    println 'Hello Venus'
}
task('hello').doLast {
    println 'Hello Mars'
}

Example 4.16. Output of gradle -q hello

> gradle -q hello
Hello Venus
Hello Earth
Hello Mars

The calls doFirst and doLast can be executed multiple times. They add an action to the beginning or the end of the task's actions list. When the task executes, the actions in the action list are executed in order.

4.6. Shortcut notations

There is a convenient notation for accessing existing tasks.

Example 4.17.  build.gradle

task hello {
    println 'Hello world!'
}
hello.doLast {
    println 'Hello again!'
}

Example 4.18. Output of gradle -q hello

> gradle -q hello
Hello world!
Hello again!

This enables very readable code. Especially when using the out of the box tasks provided by the plugins (e.g. compile).

4.7. Dynamic Task Properties

You can assign arbitrary new properties to any task.

Example 4.19.  build.gradle

task myTask
myTask.myProperty = 'myCustomPropValue'

task showProps {
    println myTask.myProperty
}

Example 4.20. Output of gradle -q showProps

> gradle -q showProps
myCustomPropValue

4.8. Ant

Let's talk a little bit about Gradle's Ant integration. Ant can be divided into two layers. The first layer is the Ant language. It contains the syntax for the build.xml, the handling of the targets, special constructs like macrodefs, etc. Basically everything except the Ant tasks and types. Gradle does not offer any special integration for this first layer. Of course you can in your build script execute an Ant build as an external process. Your build script may contain statements like: "ant clean compile".execute(). [8]

The second layer of Ant is its wealth of Ant tasks and types, like javac, copy or jar. For this layer Gradle provides excellent integration simply by relying on Groovy. Groovy is shipped with the fantastic AntBuilder. Using Ant tasks from Gradle is as convenient and more powerful than using Ant tasks from a build.xml file. Let's look at an example:

Example 4.21.  build.gradle

task checksum {
    File[] files = new File('../antChecksumFiles').listFiles()
    Arrays.sort(files)
    files.each { File file ->
        ant.checksum(file: file.canonicalPath, property: file.name)
        println "$file.name Checksum: ${ant.antProject.properties[file.name]}"
    }
}

Example 4.22. Output of gradle -q checksum

> gradle -q checksum
agile_manifesto.html Checksum: 2dd24e01676046d8dedc2009a1a8f563
agile_principles.html Checksum: 659d204c8c7ccb5d633de0b0d26cd104
dylan_thomas.txt Checksum: 91040ca1cefcbfdc8016b1b3e51f23d3

In your build script, a property called ant is provided by Gradle. It is a reference to an instance of Groovy's AntBuilder. The AntBuilder is used the following way:

  • Ant task names corresponds to AntBuilder method names.

  • Ant tasks attributes are arguments for this methods. The arguments are passed in from of a map.

  • Nested Ant tasks corresponds to method calls of the passed closure.

To learn more about the Ant Builder have a look in GINA 8.4 or at the Groovy Wiki

4.9. Using methods

Gradle scales in how you can organize your build logic. The first level of organizing your build logic for the example above, is extracting a method.

Example 4.23.  build.gradle

task checksum {
    fileList('../antChecksumFiles').each { File file ->
        ant.checksum(file: file.canonicalPath, property: file.name)
        println "$file.name Checksum: ${ant.antProject.properties[file.name]}"
    }
}

task length {
    fileList('../antChecksumFiles').each { File file ->
        ant.length(file: file.canonicalPath, property: file.name)
        println "$file.name Length: ${ant.antProject.properties[file.name]}"
    }
}

File[] fileList(String dir) {
    File[] files = new File(dir).listFiles()
    Arrays.sort(files)
    files
}

Example 4.24. Output of gradle -q checksum

> gradle -q checksum
agile_manifesto.html Checksum: 2dd24e01676046d8dedc2009a1a8f563
agile_principles.html Checksum: 659d204c8c7ccb5d633de0b0d26cd104
dylan_thomas.txt Checksum: 91040ca1cefcbfdc8016b1b3e51f23d3

Later you will see that such methods can be shared among subprojects in multi-project builds. If your build logic becomes more complex, Gradle offers you other very convenient ways to organize it. We have devoted a whole chapter to this. See Chapter 24, Organizing Build Logic.

4.10. Default Tasks

Gradle allows you to define one or more default tasks for your build.

Example 4.25.  build.gradle

defaultTasks "clean", "run"

task clean {
    println "Default Cleaning!"
}

task run {
    println "Default Running!"
}

task other {
    println "I'm not a default task!"
}

Example 4.26. Output of gradle -q

> gradle -q
Default Cleaning!
Default Running!

This is equivalent to running gradle clean run. In a multi-project build every subproject can have its own specific default tasks. If a subproject does not specify default tasks, the default tasks of the parent project are used (if defined).

4.11. Configure By DAG

As we describe in full detail later (See Chapter 22, The Build Lifecycle) Gradle has a configuration phase and an execution phase. After the configuration phase Gradle knows all tasks that should be executed. Gradle offers you a hook to make use of this information. A usecase for this would be to check if the release task is part of the tasks to be executed. Depending on this you can assign different values to some variables.

Example 4.27.  build.gradle

build.taskGraph.whenReady {taskGraph ->
    if (taskGraph.hasTask(':release')) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}

task distribution {
    println "We build the zip with version=$version"
}
task release(dependsOn: 'distribution') {
    println 'We release now'
}

Example 4.28. Output of gradle -q release

> gradle -q release
We build the zip with version=1.0
We release now

The important thing is, that the fact that the release task has been choosen, has an effect before the release task gets executed. Nor has the release task to be the primary task (i.e. the task passed to the gradle command).

4.12. Summary

This is not the end of the story for tasks. So far we have worked with simple tasks. Tasks will be revisited in Chapter 12, More about Tasks and when we look at the Java Plugin in Chapter 15, The Java Plugin.



[5] There are command line switches to change this behavior. See Appendix B, Gradle Command Line)

[6] Every code sample in this chapter can be found in the samples dir of your Gradle distribution. The output box always denotes the directory name relative to the samples dir.

[7] The scripts are executed with the -q option which suppresses the Gradle logging. See Chapter 13, Logging for more details.

[8] In Groovy you can execute Strings. To learn more about executing external processes with Groovy have a look in GINA 9.3.2 or at the Groovy wiki