A day with Android and Gradle

Update 2014-Jan-02: Some corrections after feedback from @Vampire.

Once in a while I have an issue with my Android phone which is painful enough that I decide to fix it. Since I am not a regular Android developer, every time I do this I find that I have lots of new stuff to learn. This time I get to learn about Android, Gradle, and Groovy.


FAILURE: Build failed with an exception.

* Where:
Build file '/home/drizzd/src/offline-calendar/Offline-Calendar/build.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':Offline-Calendar'.
> Gradle version 1.8 is required. Current version is 1.9. If using the gradle wrapper, try editing the distributionUrl in /home/drizzd/src/offline-calendar/gradle/wrapper/gradle-wrapper.properties to gradle-1.8-all.zip

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 12.569 secs

Long story short, the android plugin selected in build.gradle checks that the gradle version is exactly 1.8 and refuses to function otherwise. If you actually want to build offline-calender successfully, downgrade to gradle 1.8 or use ./gradlew from the latest git version.

That fixes it, but we are as clueless as before. Why does this happen? From the message it looks as if gradle refuses to run its own version.

What’s going on?

Passing --stacktrace to gradle gives us (cut to the interesting parts):

* Exception is:
org.gradle.api.GradleScriptException: A problem occurred evaluating project ':Offline-Calendar'.
	at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:54)
[... many more ...]
Caused by: org.gradle.tooling.BuildException: Gradle version 1.8 is required. Current version is 1.9. If using the gradle wrapper, try editing the distributionUrl in /home/drizzd/src/offline-calendar/gradle/wrapper/gradle-wrapper.properties to gradle-1.8-all.zip
	at com.android.build.gradle.BasePlugin.checkGradleVersion(BasePlugin.groovy:246)
	at com.android.build.gradle.BasePlugin.apply(BasePlugin.groovy:184)
	at com.android.build.gradle.AppPlugin.super$2$apply(AppPlugin.groovy)
	at com.android.build.gradle.AppPlugin.apply(AppPlugin.groovy:80)
	at com.android.build.gradle.AppPlugin.apply(AppPlugin.groovy)
	at org.gradle.api.internal.plugins.DefaultPluginContainer.providePlugin(DefaultPluginContainer.java:104)
[...]

This was not obvious to me, but @Vampire tells me on #gradle that BasePlugin.groovy:246 is where things go awry. But where does com.android.build.gradle.BasePlugin come from? It is not part of gradle, it is not part of offline-calendar, and it is not part of anything else installed on the system either. And why do we decide to run it in the first place?

Here is what gradle is does (not necessarily in exactly this order due to lazy evaluation):

public static final String GRADLE_MIN_VERSION = "1.8";

private void checkGradleVersion() {
    [...]
    throw new BuildException(
        String.format(
            "Gradle version %s is required. Current version is %s. " +
            "If using the gradle wrapper, try editing the distributionUrl in %s " +
            "to gradle-%s-all.zip",
            GRADLE_MIN_VERSION, project.getGradle().gradleVersion, file.getAbsolutePath(),
            GRADLE_MIN_VERSION), null);
}

I extracted this snippet from gradle-0.6.3-source.jar. Do not let GRADLE_MIN_VERSION fool you. It is the only accepted version.

The latest git version of com.android.tools.build already sets GRADLE_MIN_VERSION to 1.19. Now I wonder what I would have to do to actually use it. I suppose I would have to compile it locally and point the buildscript repositories and dependencies clauses at it.

drizzd, 2013-Dec-01