I’ve been spending a lot of time with Gradle build files in Android projects, which probably isn’t a big surprise given that I’m working on a book called Gradle Recipes for Android (coming soon to all your better ebook stores and (who knows?) maybe an actual, physical bookstore somewhere (but probably not), but you can always get it at O’Reilly or Amazon). In one chapter, I talk about excluding certain tasks in Gradle builds, and that led me to write an interesting custom task.

Gradle builds on Android have tons of tasks, and that number goes up and up when you add new build types or flavors. For example, on a trivial Android project, asking for the list of tasks gives:

[code language=”bash”]

> ./gradlew tasks

Starting a new Gradle Daemon for this build (subsequent builds will be faster).

Incremental java compilation is an incubating feature.

:tasks

————————————————————

All tasks runnable from root project

————————————————————

Android tasks

————-

androidDependencies – Displays the Android dependencies of the project.

signingReport – Displays the signing info for each variant.

sourceSets – Prints out all the source sets defined in this project.

Build tasks

———–

assemble – Assembles all variants of all applications and secondary packages.

assembleAndroidTest – Assembles all the Test applications.

assembleDebug – Assembles all Debug builds.

assembleRelease – Assembles all Release builds.

build – Assembles and tests this project.

buildDependents – Assembles and tests this project and all projects that depend on it.

buildNeeded – Assembles and tests this project and all projects it depends on.

clean – Deletes the build directory.

compileDebugAndroidTestSources

compileDebugSources

compileDebugUnitTestSources

compileReleaseSources

compileReleaseUnitTestSources

mockableAndroidJar – Creates a version of android.jar that’s suitable for unit tests.

Build Setup tasks

—————–

init – Initializes a new Gradle build. [incubating]

wrapper – Generates Gradle wrapper files. [incubating]

Help tasks

———-

buildEnvironment – Displays all buildscript dependencies declared in root project ‘HelloWorldAS’.

components – Displays the components produced by root project ‘HelloWorldAS’. [incubating]

dependencies – Displays all dependencies declared in root project ‘HelloWorldAS’.

dependencyInsight – Displays the insight into a specific dependency in root project ‘HelloWorldAS’.

help – Displays a help message.

model – Displays the configuration model of root project ‘HelloWorldAS’. [incubating]

projects – Displays the sub-projects of root project ‘HelloWorldAS’.

properties – Displays the properties of root project ‘HelloWorldAS’.

tasks – Displays the tasks runnable from root project ‘HelloWorldAS’ (some of the displayed tasks may belong to subprojects).

Install tasks

————-

installDebug – Installs the Debug build.

installDebugAndroidTest – Installs the android (on device) tests for the Debug build.

uninstallAll – Uninstall all applications.

uninstallDebug – Uninstalls the Debug build.

uninstallDebugAndroidTest – Uninstalls the android (on device) tests for the Debug build.

uninstallRelease – Uninstalls the Release build.

Verification tasks

——————

check – Runs all checks.

connectedAndroidTest – Installs and runs instrumentation tests for all flavors on connected devices.

connectedCheck – Runs all device checks on currently connected devices.

connectedDebugAndroidTest – Installs and runs the tests for debug on connected devices.

deviceAndroidTest – Installs and runs instrumentation tests using all Device Providers.

deviceCheck – Runs all device checks using Device Providers and Test Servers.

lint – Runs lint on all variants.

lintDebug – Runs lint on the Debug build.

lintRelease – Runs lint on the Release build.

test – Run unit tests for all variants.

testDebugUnitTest – Run unit tests for the debug build.

testReleaseUnitTest – Run unit tests for the release build.

Other tasks

———–

jarDebugClasses

jarReleaseClasses

transformResourcesWithMergeJavaResForDebugUnitTest

transformResourcesWithMergeJavaResForReleaseUnitTest

To see all tasks and more detail, run gradlew tasks –all

To see more detail about a task, run gradlew help –task <task>

BUILD SUCCESSFUL

[/code]

That’s about 50 tasks, and I haven’t added anything yet.

Android projects also include variants, which are combinations of build types and flavors. A build type describes whether you want to use debug or release configuration or define one of your own. Flavors allow you to build multiple similar applications that vary only in look and feel or minor code changes.

For example, in my giant Hello, World example (the same one I used in my O’Reilly video courses Learning Android and Practical Android), I use just the debug and release build types, but I have three flavors: arrogant, friendly, and obsequious.

Obsequious is such a good word. I rarely get to use it, though probably for good reason. If you’re trying to remember what it means, think Dobby from the Harry Potter novels:

Under those circumstances, the number of tasks increases considerably:

[code language=”bash”]

> ./gradlew tasks

Incremental java compilation is an incubating feature.

:tasks

————————————————————

All tasks runnable from root project

————————————————————

Android tasks

————-

androidDependencies – Displays the Android dependencies of the project.

signingReport – Displays the signing info for each variant.

sourceSets – Prints out all the source sets defined in this project.

Build tasks

———–

assemble – Assembles all variants of all applications and secondary packages.

assembleAndroidTest – Assembles all the Test applications.

assembleArrogant – Assembles all Arrogant builds.

assembleDebug – Assembles all Debug builds.

assembleFriendly – Assembles all Friendly builds.

assembleObsequious – Assembles all Obsequious builds.

assembleRelease – Assembles all Release builds.

build – Assembles and tests this project.

buildDependents – Assembles and tests this project and all projects that depend on it.

buildNeeded – Assembles and tests this project and all projects it depends on.

clean – Deletes the build directory.

compileArrogantDebugAndroidTestSources

compileArrogantDebugSources

compileArrogantDebugUnitTestSources

compileArrogantReleaseSources

compileArrogantReleaseUnitTestSources

compileFriendlyDebugAndroidTestSources

compileFriendlyDebugSources

compileFriendlyDebugUnitTestSources

compileFriendlyReleaseSources

compileFriendlyReleaseUnitTestSources

compileObsequiousDebugAndroidTestSources

compileObsequiousDebugSources

compileObsequiousDebugUnitTestSources

compileObsequiousReleaseSources

compileObsequiousReleaseUnitTestSources

mockableAndroidJar – Creates a version of android.jar that’s suitable for unit tests.

Build Setup tasks

—————–

init – Initializes a new Gradle build. [incubating]

wrapper – Generates Gradle wrapper files. [incubating]

Help tasks

———-

buildEnvironment – Displays all buildscript dependencies declared in root project ‘HelloWorldAS’.

components – Displays the components produced by root project ‘HelloWorldAS’. [incubating]

dependencies – Displays all dependencies declared in root project ‘HelloWorldAS’.

dependencyInsight – Displays the insight into a specific dependency in root project ‘HelloWorldAS’.

help – Displays a help message.

model – Displays the configuration model of root project ‘HelloWorldAS’. [incubating]

projects – Displays the sub-projects of root project ‘HelloWorldAS’.

properties – Displays the properties of root project ‘HelloWorldAS’.

tasks – Displays the tasks runnable from root project ‘HelloWorldAS’ (some of the displayed tasks may belong to subprojects).

Install tasks

————-

installArrogantDebug – Installs the DebugArrogant build.

installArrogantDebugAndroidTest – Installs the android (on device) tests for the ArrogantDebug build.

installFriendlyDebug – Installs the DebugFriendly build.

installFriendlyDebugAndroidTest – Installs the android (on device) tests for the FriendlyDebug build.

installObsequiousDebug – Installs the DebugObsequious build.

installObsequiousDebugAndroidTest – Installs the android (on device) tests for the ObsequiousDebug build.

uninstallAll – Uninstall all applications.

uninstallArrogantDebug – Uninstalls the DebugArrogant build.

uninstallArrogantDebugAndroidTest – Uninstalls the android (on device) tests for the ArrogantDebug build.

uninstallArrogantRelease – Uninstalls the ReleaseArrogant build.

uninstallFriendlyDebug – Uninstalls the DebugFriendly build.

uninstallFriendlyDebugAndroidTest – Uninstalls the android (on device) tests for the FriendlyDebug build.

uninstallFriendlyRelease – Uninstalls the ReleaseFriendly build.

uninstallObsequiousDebug – Uninstalls the DebugObsequious build.

uninstallObsequiousDebugAndroidTest – Uninstalls the android (on device) tests for the ObsequiousDebug build.

uninstallObsequiousRelease – Uninstalls the ReleaseObsequious build.

Verification tasks

——————

check – Runs all checks.

connectedAndroidTest – Installs and runs instrumentation tests for all flavors on connected devices.

connectedArrogantDebugAndroidTest – Installs and runs the tests for arrogantDebug on connected devices.

connectedCheck – Runs all device checks on currently connected devices.

connectedFriendlyDebugAndroidTest – Installs and runs the tests for friendlyDebug on connected devices.

connectedObsequiousDebugAndroidTest – Installs and runs the tests for obsequiousDebug on connected devices.

deviceAndroidTest – Installs and runs instrumentation tests using all Device Providers.

deviceCheck – Runs all device checks using Device Providers and Test Servers.

lint – Runs lint on all variants.

lintArrogantDebug – Runs lint on the ArrogantDebug build.

lintArrogantRelease – Runs lint on the ArrogantRelease build.

lintFriendlyDebug – Runs lint on the FriendlyDebug build.

lintFriendlyRelease – Runs lint on the FriendlyRelease build.

lintObsequiousDebug – Runs lint on the ObsequiousDebug build.

lintObsequiousRelease – Runs lint on the ObsequiousRelease build.

test – Run unit tests for all variants.

testArrogantDebugUnitTest – Run unit tests for the arrogantDebug build.

testArrogantReleaseUnitTest – Run unit tests for the arrogantRelease build.

testFriendlyDebugUnitTest – Run unit tests for the friendlyDebug build.

testFriendlyReleaseUnitTest – Run unit tests for the friendlyRelease build.

testObsequiousDebugUnitTest – Run unit tests for the obsequiousDebug build.

testObsequiousReleaseUnitTest – Run unit tests for the obsequiousRelease build.

Other tasks

———–

jarArrogantDebugClasses

jarArrogantReleaseClasses

jarFriendlyDebugClasses

jarFriendlyReleaseClasses

jarObsequiousDebugClasses

jarObsequiousReleaseClasses

transformResourcesWithMergeJavaResForArrogantDebugUnitTest

transformResourcesWithMergeJavaResForArrogantReleaseUnitTest

transformResourcesWithMergeJavaResForFriendlyDebugUnitTest

transformResourcesWithMergeJavaResForFriendlyReleaseUnitTest

transformResourcesWithMergeJavaResForObsequiousDebugUnitTest

transformResourcesWithMergeJavaResForObsequiousReleaseUnitTest

To see all tasks and more detail, run gradlew tasks –all

To see more detail about a task, run gradlew help –task

BUILD SUCCESSFUL

[/code]

That’s just under 100, and the problem only gets worse if you add flavor dimensions. In the book, I add client flavors — one for Wayne Enterprises and one for Stark Industries. That gives me 3 x 2 = 6 different flavors and 2 build types, or 12 different variants, with all the (nearly 200) associated tasks. Whew.

Here’s a sample of the build file, just to show what this looks like:

[code language=”groovy”]

android {

compileSdkVersion 23

buildToolsVersion "23.0.3"

defaultConfig {

applicationId "com.kousenit.helloworld"

minSdkVersion 16

targetSdkVersion 23

versionCode 1

versionName "1.0"

}

buildTypes {

// no changes to debug type, so no need to list it here

release {

minifyEnabled false

proguardFiles getDefaultProguardFile(‘proguard-android.txt’),

‘proguard-rules.pro’

}

}

flavorDimensions ‘attitude’, ‘client’

productFlavors {

arrogant {

dimension ‘attitude’

applicationId ‘com.kousenit.helloworld.arrg’

}

friendly {

dimension ‘attitude’

applicationId ‘com.kousenit.helloworld.frnd’

}

obsequious {

dimension ‘attitude’

applicationId ‘com.kousenit.helloworld.obsq’

}

stark {

dimension ‘client’

}

wayne {

dimension ‘client’

}

}

}

[/code]

Say I want to skip a task. For example, when I’m doing a regular build, I don’t always need to run the lint task, which gives interesting results but takes time. In Gradle, excluding a particular task from the build is as simple as using the -x flag.

That sounds good, but unfortunately there are many lint tasks:

[code language=”bash”]

> ./gradlew tasks | grep lint

lint – Runs lint on all variants.

lintArrogantStarkDebug – Runs lint on the ArrogantStarkDebug build.

lintArrogantStarkRelease – Runs lint on the ArrogantStarkRelease build.

lintArrogantWayneDebug – Runs lint on the ArrogantWayneDebug build.

lintArrogantWayneRelease – Runs lint on the ArrogantWayneRelease build.

lintFriendlyStarkDebug – Runs lint on the FriendlyStarkDebug build.

lintFriendlyStarkRelease – Runs lint on the FriendlyStarkRelease build.

lintFriendlyWayneDebug – Runs lint on the FriendlyWayneDebug build.

lintFriendlyWayneRelease – Runs lint on the FriendlyWayneRelease build.

lintObsequiousStarkDebug – Runs lint on the ObsequiousStarkDebug build.

lintObsequiousStarkRelease – Runs lint on the ObsequiousStarkRelease build.

lintObsequiousWayneDebug – Runs lint on the ObsequiousWayneDebug build.

lintObsequiousWayneRelease – Runs lint on the ObsequiousWayneRelease build.

[/code]

Excluding lint leaves out some of them, but runs others.

[code language=”bash”]

> ./gradlew build -x lint | grep lint

:app:lintVitalArrogantStarkRelease

:app:lintVitalArrogantWayneRelease

:app:lintVitalFriendlyStarkRelease

:app:lintVitalFriendlyWayneRelease

:app:lintVitalObsequiousStarkRelease

:app:lintVitalObsequiousWayneRelease

[/code]

I’m not sure what the “vital” part of those release tasks is, but I don’t want it. I suppose I could try excluding the tasks one by one, but that’s starting to feel like a lot of work.

Instead, I can add the following to the build.gradle file, which waits for the task graph to be assembled and then removes the undesired name pattern.

[code language=”groovy”]

gradle.taskGraph.whenReady { graph ->

graph.allTasks.findAll { it.name ==~ /lint.*/ }*.enabled = false

}

[/code]

Gradle assembles a directed acyclic graph of tasks, available through the gradle object via its taskGraph property. By calling the whenReady method, I wait until that graph is assembled before modifying it.

The whenReady method takes a closure, whose argument is the graph . I retrieve all the tasks into a list, find all the tasks whose name matches the given regex (meaning the name starts with the letters lint ), and disable them all.

[code language=”bash”]

> ./gradlew build | grep lint

:app:lintVitalArrogantStarkRelease SKIPPED

:app:lintVitalArrogantWayneRelease SKIPPED

:app:lintVitalFriendlyStarkRelease SKIPPED

:app:lintVitalFriendlyWayneRelease SKIPPED

:app:lintVitalObsequiousStarkRelease SKIPPED

:app:lintVitalObsequiousWayneRelease SKIPPED

:app:lint SKIPPED

[/code]

This works, but it’s a permanent solution to a temporary problem. I’d rather make excluding those tasks optional. Fortunately, I can do that through a project property.

[code language=”groovy”]

gradle.taskGraph.whenReady { graph ->

if (project.hasProperty(‘noLint’)) {

graph.allTasks.findAll { it.name ==~ /lint.*/ }*.enabled = false

}

}

[/code]

Now I can exclude the lint tasks by specifying a -P flag on the command line:

[code language=”bash”]

> ./gradlew build -PnoLint | grep lint

:app:lintVitalArrogantStarkRelease SKIPPED

:app:lintVitalArrogantWayneRelease SKIPPED

:app:lintVitalFriendlyStarkRelease SKIPPED

:app:lintVitalFriendlyWayneRelease SKIPPED

:app:lintVitalObsequiousStarkRelease SKIPPED

:app:lintVitalObsequiousWayneRelease SKIPPED

:app:lint SKIPPED

[/code]

This strikes me as a clean, elegant solution to the problem, but maybe only because I can’t think of anything easier. If you can, please let me know, because I turned in the complete draft of the stupid book this week (!!) and this is in one of the chapters. If you find an error or a better idea, there’s still (barely) enough time to update it and even give you credit (if not necessarily cashy money, though I will be happy to purchase for you the libation of your choice next time I see you).

Either way, the idea of manipulating the task graph inside the build file is a really useful one, so you shouldn’t exclude it (get it?).

Share this:

Tweet

Email





Like this: Like Loading...