Build / Gradle

Gradle Build – Part 9

In the previous article we defined a simple build task, but it was of limited use because it could only be invoked as a standalone task. In this article we’ll see how we can attach it to the build lifecycle.

gradle_logoGradle allows us to attach our task to the build lifecycle by allowing us to define task dependencies. This enables us to define a dependency on task A from task B, which will ensure that task A gets run before task B.

The assemble task is defined within the Android plugin, and we can add a dependency to ensure that our custom task gets executed before the assemble task:

.
.
.
task custom(description: 'This is our custom task') << { task ->
    println "Running task ${task.name}"
}

assemble.dependsOn 'custom'

If we now run a build, we can see our task being called:

$ ./gradlew --daemon assemble
.
.
.
GradleTest:packageSimpleRelease UP-TO-DATE
:GradleTest:assembleSimpleRelease UP-TO-DATE
:GradleTest:assembleRelease UP-TO-DATE
:GradleTest:assembleSimple UP-TO-DATE
:GradleTest:custom
Running task custom
:GradleTest:assemble

BUILD SUCCESSFUL

Total time: 2.161 secs
$

It’s important to note that there are other tasks upon which the assemble task also depends which are executed before our custom task. We’ll need to change how we define our dependency a little more if we want to execute our task before these. More on this in a moment.

Adding a dependency in this way has a rather strange side effect. If we run ./gradlew -q tasks again, we’ll see that our task is no longer listed. That is because ./gradlew -q tasks only displays the top level tasks and our custom task is no longer than because of the dependency. However, we can get a more detailed task list by adding the -all switch to the command:

$ ./gradlew -q tasks --all
.
.
.
Build tasks
-----------
GradleLibrary:assemble - Assembles all variants of all applications and secondary packages. [GradleLibrary:assembleDebug, GradleLibrary:assembleRelease]
GradleTest:assemble - Assembles all variants of all applications and secondary packages. [GradleTest:assembleComplex, GradleTest:assembleDebug, GradleTest:assembleRelease, GradleTest:assembleSimple]
    GradleTest:custom - This is our custom task
GradleTest:assembleComplex - Assembles all builds for flavor Complex [GradleTest:assembleComplexDebug, GradleTest:assembleComplexRelease]
.
.
.
$

Our custom task is now listed, but only as a dependency of the assemble task. That may be what we need, but what if we need our custom task to be run before the dynamic assemble tasks which get created for each of the build variants? If we attempt to iterate the build variants at the point where the task is created, it will simply return us an empty list because they don’t yet exist. However we can defer the task creation until later on by using a rule. A rule allows us to defer the definition of the task until it is first accessed:

[groovy] .
.
.
assemble.dependsOn(‘customAssemble’)

tasks.addRule(“Pattern: customAssemble“) { taskName ->
println “Creating task ${taskName}”
if (taskName.equals(“customAssemble”)) {
android.applicationVariants.each { variant ->
println “Adding dependency to assemble${variant.name}”
def targetTask = project.tasks.findByName(“assemble${variant.name}”)
if(targetTask != null) {
targetTask.dependsOn(“customAssemble${variant.name}”)
}
}
}
task(taskName) << { task ->
println “Running custom task ${task.name}”
}
}
[/groovy]

What happens here is that we define a dependency on the assemble task to a task named customAssemble. The customAssemble task has not yet been defined, but when it is required our rule definition is invoked because the name of the task matches the pattern defined for the rule (line 6). When the rule definition is invoked, we check whether the current task is named customAssemble to ensure that we only add the new dependencies once (line 8). We then iterate through the application variants (line 9) and, if an assemble task exists for the variant (lines 11-12), set a dependency on to a new task (line 13). This new task will then be dynamically created using the same rule as it is required. Each time the rule is invoked we create the task (lines 17-19) which will, once again, print a status message when it is invoked.

The println lines within the rule have been left in so that we can see precisely what is happening (normally, you would remove these):

$ ./gradlew --daemon assemble
Creating task customAssemble
Adding dependency to assembleSimpleDebug
Adding dependency to assembleSimpleRelease
Adding dependency to assembleComplexDebug
Adding dependency to assembleComplexRelease
Creating task customAssembleComplexDebug
Creating task customAssembleSimpleDebug
Creating task customAssembleComplexRelease
Creating task customAssembleSimpleRelease
:GradleLibrary:mergeDebugProguardFiles UP-TO-DATE
:GradleLibrary:packageDebugAidl UP-TO-DATE
:GradleLibrary:prepareDebugDependencies
:GradleLibrary:compileDebugAidl UP-TO-DATE
:GradleLibrary:generateDebugBuildConfig UP-TO-DATE
:GradleLibrary:mergeDebugAssets UP-TO-DATE
:GradleLibrary:compileDebugRenderscript UP-TO-DATE
:GradleLibrary:mergeDebugResources UP-TO-DATE
:GradleLibrary:processDebugManifest UP-TO-DATE
:GradleLibrary:processDebugResources UP-TO-DATE
:GradleLibrary:compileDebug UP-TO-DATE
:GradleLibrary:processDebugJavaRes UP-TO-DATE
:GradleLibrary:packageDebugJar UP-TO-DATE
:GradleLibrary:packageDebugLocalJar UP-TO-DATE
:GradleLibrary:packageDebugRenderscript UP-TO-DATE
:GradleLibrary:packageDebugResources UP-TO-DATE
:GradleLibrary:bundleDebug UP-TO-DATE
:GradleLibrary:assembleDebug UP-TO-DATE
:GradleLibrary:mergeReleaseProguardFiles UP-TO-DATE
:GradleLibrary:packageReleaseAidl UP-TO-DATE
:GradleLibrary:prepareReleaseDependencies
:GradleLibrary:compileReleaseAidl UP-TO-DATE
:GradleLibrary:generateReleaseBuildConfig UP-TO-DATE
:GradleLibrary:mergeReleaseAssets UP-TO-DATE
:GradleLibrary:compileReleaseRenderscript UP-TO-DATE
:GradleLibrary:mergeReleaseResources UP-TO-DATE
:GradleLibrary:processReleaseManifest UP-TO-DATE
:GradleLibrary:processReleaseResources UP-TO-DATE
:GradleLibrary:compileRelease UP-TO-DATE
:GradleLibrary:processReleaseJavaRes UP-TO-DATE
:GradleLibrary:packageReleaseJar UP-TO-DATE
:GradleLibrary:packageReleaseLocalJar UP-TO-DATE
:GradleLibrary:packageReleaseRenderscript UP-TO-DATE
:GradleLibrary:packageReleaseResources UP-TO-DATE
:GradleLibrary:bundleRelease UP-TO-DATE
:GradleLibrary:assembleRelease UP-TO-DATE
:GradleLibrary:assemble UP-TO-DATE
:GradleTest:customAssembleComplexDebug
Running custom task customAssembleComplexDebug
:GradleTest:prepareGradleTestProjectGradleLibraryUnspecifiedLibrary UP-TO-DATE
:GradleTest:prepareComplexDebugDependencies
:GradleTest:compileComplexDebugAidl UP-TO-DATE
:GradleTest:generateComplexDebugBuildConfig UP-TO-DATE
:GradleTest:mergeComplexDebugAssets UP-TO-DATE
:GradleTest:compileComplexDebugRenderscript UP-TO-DATE
:GradleTest:mergeComplexDebugResources UP-TO-DATE
:GradleTest:processComplexDebugManifest UP-TO-DATE
:GradleTest:processComplexDebugResources UP-TO-DATE
:GradleTest:compileComplexDebug UP-TO-DATE
:GradleTest:dexComplexDebug UP-TO-DATE
:GradleTest:processComplexDebugJavaRes UP-TO-DATE
:GradleTest:validateDebugSigning
:GradleTest:packageComplexDebug UP-TO-DATE
:GradleTest:assembleComplexDebug
:GradleTest:customAssembleComplexRelease
Running custom task customAssembleComplexRelease
:GradleTest:prepareComplexReleaseDependencies
:GradleTest:compileComplexReleaseAidl UP-TO-DATE
:GradleTest:generateComplexReleaseBuildConfig UP-TO-DATE
:GradleTest:mergeComplexReleaseAssets UP-TO-DATE
:GradleTest:compileComplexReleaseRenderscript UP-TO-DATE
:GradleTest:mergeComplexReleaseResources UP-TO-DATE
:GradleTest:processComplexReleaseManifest UP-TO-DATE
:GradleTest:processComplexReleaseResources UP-TO-DATE
:GradleTest:compileComplexRelease UP-TO-DATE
:GradleTest:dexComplexRelease UP-TO-DATE
:GradleTest:processComplexReleaseJavaRes UP-TO-DATE
:GradleTest:packageComplexRelease UP-TO-DATE
:GradleTest:assembleComplexRelease
:GradleTest:assembleComplex
:GradleTest:customAssembleSimpleDebug
Running custom task customAssembleSimpleDebug
:GradleTest:prepareSimpleDebugDependencies
:GradleTest:compileSimpleDebugAidl UP-TO-DATE
:GradleTest:generateSimpleDebugBuildConfig UP-TO-DATE
:GradleTest:mergeSimpleDebugAssets UP-TO-DATE
:GradleTest:compileSimpleDebugRenderscript UP-TO-DATE
:GradleTest:mergeSimpleDebugResources UP-TO-DATE
:GradleTest:processSimpleDebugManifest UP-TO-DATE
:GradleTest:processSimpleDebugResources UP-TO-DATE
:GradleTest:compileSimpleDebug UP-TO-DATE
:GradleTest:dexSimpleDebug UP-TO-DATE
:GradleTest:processSimpleDebugJavaRes UP-TO-DATE
:GradleTest:packageSimpleDebug UP-TO-DATE
:GradleTest:assembleSimpleDebug
:GradleTest:assembleDebug
:GradleTest:customAssembleSimpleRelease
Running custom task customAssembleSimpleRelease
:GradleTest:prepareSimpleReleaseDependencies
:GradleTest:compileSimpleReleaseAidl UP-TO-DATE
:GradleTest:generateSimpleReleaseBuildConfig UP-TO-DATE
:GradleTest:mergeSimpleReleaseAssets UP-TO-DATE
:GradleTest:compileSimpleReleaseRenderscript UP-TO-DATE
:GradleTest:mergeSimpleReleaseResources UP-TO-DATE
:GradleTest:processSimpleReleaseManifest UP-TO-DATE
:GradleTest:processSimpleReleaseResources UP-TO-DATE
:GradleTest:compileSimpleRelease UP-TO-DATE
:GradleTest:dexSimpleRelease UP-TO-DATE
:GradleTest:processSimpleReleaseJavaRes UP-TO-DATE
:GradleTest:packageSimpleRelease UP-TO-DATE
:GradleTest:assembleSimpleRelease
:GradleTest:assembleRelease
:GradleTest:assembleSimple
:GradleTest:custom
Running task custom
:GradleTest:customAssemble
Running custom task customAssemble
:GradleTest:assemble

BUILD SUCCESSFUL

Total time: 2.127 secs
$

Initially we see the creation of the customAssemble task which caused the dependencies on four new tasks to be created, and then we see the creation of these tasks. The during the execution of the build we see these tasks executed before the tasks which depend on them.

Of course, the actual implementation of your tasks will vary depending on your requirements. All we’re looking at here is how you can attach your tasks to the build lifecycle to ensure that you task gets invoked at the correct point of the build lifecycle.

That concludes our tour of the Android Gradle Build system. We’ve covered a fair bit of material over the last 9 articles, and much of it will see rather alien because it is a real departure from the existing build mechanisms. While it may seem daunting, it is well worth learning to use the new system because it certainly makes life much easier while, if you require it, providing you with the opportunity to inject new tasks in to the build process to enable you to customise the build to your requirements.

The source code for this article is available here.

© 2013 – 2014, Mark Allison. All rights reserved.

Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

3 Comments

  1. Great blog !! One of the best I have seen in Android world 🙂 Makes me come back here time and again!! Keep up the good work.

  2. Hello Mark,

    Thank you the Gradle build system articles, I enjoy reading your blog.

    I found a small typo mistake in this article,

    “$ ./gradlew -q tasks -all” – Mistake
    “$ ./gradlew -q tasks –all” – Correct, all key require — instead of single –

    My gradle version detail

    ————————————————————
    Gradle 1.7
    ————————————————————

    Build time: 2013-08-06 11:19:56 UTC
    Build number: none
    Revision: 9a7199efaf72c620b33f9767874f0ebced135d83

    Groovy: 1.8.6
    Ant: Apache Ant(TM) version 1.8.4 compiled on May 22 2012
    Ivy: 2.2.0
    JVM: 1.6.0_51 (Apple Inc. 20.51-b01-457)
    OS: Mac OS X 10.8.4 x86_64

    Thank You
    KPBird

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.