/ DevOps

Learn Gradle Episode 3

The last two episodes are introduce some basic features by using Android Studio. But Android Studio is only a branch of Gradle. This time, we will learn more about Gradle.

Build Script Basics

By download the newest version for Gradle v2.11 and extract to the path for whatever you want. And add to your environment by export PATH=/<your path>/gradle-2.11/bin:$PATH.

Hello world

Regarding running Hello World for gradle, you just need one file called build.gradle. In this file, we define a task.

task helloworld{
    doLast{
        println "Hello World!"
    }
}

Under your build.gradle path, execute gradle -q helloworld, you will see 'Hello World' as expect.
Sometimes, the build.gradle will be like the following

task helloword << {
    println "Hello World!"
}

Note: << is the shortcut for doLast.

Grovvy support & Dependencies

Cause Gradle can support Groovy, therefore in the action closure also can use Groovy. e.g.

task hello << {
    4.times {print "$it "}
}

The output is

0 1 2 3

While compiling the source, we can define task dependencies by using dependsOn.

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

Also can describe like this.

4.times { counter ->
    task "task$counter" << {
        println "I'm task number $counter"
    }
}
task0.dependsOn task2, task3

Above script means create 4 tasks to print task number, and add dependencies task2 and task3 for task0. When you run gradle -q task0, the output is

I'm task number 2

I'm task number 3

I'm task number 0

Extra task properties

We can define own properties to a task, even not exist in the (domain specific language) DSL. e.g.

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties << {
    println myTask.myProperty
}

When you execute gradle -q printTaskProperties, the output is

myValue

You can also check more details in this link.

Declaring variables

Local variables

Local variables are declared with the def keyword. They are only visible in the scope where they have been declared. Local variables are a feature of the underlying Groovy language.

def dest = "dest"

task copy(type: Copy) {
    from "source"
    into dest
}

Extra properties

All enhanced objects in Gradle's domain model can hold extra user-defined properties. This includes, but is not limited to, projects, tasks, and source sets. Extra properties can be added, read and set via the owning object's ext property. Alternatively, an ext block can be used to add multiple properties at once.

apply plugin: "java"

ext {
    springVersion = "3.1.0.RELEASE"
    emailNotification = "build@master.org"
}

sourceSets.all { ext.purpose = null }

sourceSets {
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
    }
    plugin {
        purpose = "production"
    }
}

task printProperties << {
    println springVersion
    println emailNotification
    sourceSets.matching { it.purpose == "production" }.each { println it.name }
}

Output of gradle -q printProperties

3.1.0.RELEASE

build@master.org

main

plugin

The first ext block declared two extra properties for 'Project' object. Additionally, a property named purpose is added to each source set by setting ext.purpose to null.

List and map literals

// List literal
test.includes = ['org/gradle/api/**', 'org/gradle/internal/**']

List<String> list = new ArrayList<String>()
list.add('org/gradle/api/**')
list.add('org/gradle/internal/**')
test.includes = list

// Map literal.
Map<String, String> map = [key1:'value1', key2: 'value2']

// Groovy will coerce named arguments
// into a single map argument
apply plugin: 'java'

More about Tasks

Skipping tasks

Gradle offers multiple ways to skip the execution of a task.

  • Using a predicate
task hello << {
    println 'hello world'
}

hello.onlyIf { !project.hasProperty('skipHello') }

onlyIf() method will make the actions only executed if the predicate evaluates to true. If you execute gradle hello -PskipHello, the hello task will skip.

  • Using StopExecutionException
task compile << {
    println 'We are doing the compile.'
}

compile.doFirst {
    // Here you would put arbitrary conditions in real life.
    // But this is used in an integration test so we want defined behavior.
    if (true) { throw new StopExecutionException() }
}
task myTask(dependsOn: 'compile') << {
   println 'I am not affected'
}

When you throw StopExecutionException, the whole execution will stop. And StopActionException can abort execution of the action and continue to the next action of the task.

  • Enabling and disabling tasks
task disableMe << {
    println 'This should not be printed if the task is disabled.'
}
disableMe.enabled = false

If set enabled to false, this task also will be skip.

Skipping tasks that are up-to-date

Sometimes, when we compile some task a second time and nothing changed, we want just skip this task to reduce the compile time. To support this we need to declare the inputs and outputs of the task.

task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    inputs.file srcFile
    outputs.dir destDir
    doLast {
        println "Transforming source file."
        destDir.mkdirs()
        def mountains = new XmlParser().parse(srcFile)
        mountains.mountain.each { mountain ->
            def name = mountain.name[0].text()
            def height = mountain.height[0].text()
            def destFile = new File(destDir, "${name}.txt")
            destFile.text = "$name -> ${height}\n"
        }
    }
}

The task above execute the second time, will got :trasform UP-TO-DATE. The mechanism is Gradle task a snapshot at the first time when you execute a task for input and output and got a hash for the all files. The second time, will snapshot again and check the hash to see if it's up-to-date. If not, Gradle will execute again. Otherwise, just skip the task with UP-TO-DATE. And if you have more complex task need to check up-to-date, please choose TaskOutputs.upToDateWhen() to calculate programmatically.

Task rules

tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) << {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}

With the definition above, you will have a lot of tasks with number value range. e.g. pingServer1, pingServer2, pingXXX

gradle -q pingServer1 will got

Pinging: Server1

Or you can add dependsOn rules.

task groupPing {
    dependsOn pingServer1, pingServer2
}

Gradle Build Language Reference

Cause Gradle scripts are configuration scripts. As the script executes, it configures an object of a particular type. e.g. as a build script executes, it configures an object of Project.

Like we used above, doLast is a method of object task. In the definition for Task object, we can find the doLast in Methods, the parameter action can be Action or given closure. Also we can find doFirst(action), leftShift(action) which is <<.