ettings.gradle</code> script:</p><blockquote id="690a"><p>declares the configuration required to instantiate and configure the hierarchy of <a href="https://docs.gradle.org/current/dsl/org.gradle.api.Project.html"><code>Proj</code>ect</a> instances which are to participate in a build.</p></blockquote><p id="7edc">Further we read that we can add projects to the build using <code>void include(<a href="http://download.oracle.com/javase/7/docs/api/java/lang/String.html">String</a>[] projectPaths)</code> method. Let’s add a <code>app</code> subproject then:</p><div id="5cdb"><pre><span class="hljs-meta prompt_"> </span><span class="language-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"include ':app'"</span> > settings.gradle</span>
<span class="hljs-meta prompt_"> </span><span class="language-bash">gradle projects</span></pre></div><div id="35ad"><pre><span class="hljs-meta"># ...</span></pre></div><div id="6900"><pre><span class="hljs-code">------------------------------------------------------------
Root project
------------------------------------------------------------</span>
Total time: 0.666 secs</pre></div><blockquote id="2388"><p>Colon (<code>:</code>) is used in Gradle to separate paths to subprojects, what we can see <a href="https://docs.gradle.org/current/javadoc/constant-values.html#org.gradle.api.Project.PATH_SEPARATOR">here</a>. That’s why we write <code><i>:app</i></code> and not <code><i>app</i></code> (although in this case <code><i>app</i></code> would work as well)</p></blockquote><blockquote id="8529"><p>It’s also good practice to include <code>rootProject.name = <<name>></code> in <code>settings.gradle</code> file. Without it, root project’s name defaults to the name of the folder in which the project is, which may be different for example on a CI server.</p></blockquote><h2 id="ba54">Setting up Android subproject</h2><p id="bf81">Now we’d normally set up root project’s <code>build.gradle</code> file, but what do we need to put there? Let’s find out by trying to set up an Android project instead.</p><p id="d81e">From the <a href="http://tools.android.com/tech-docs/new-build-system/user-guide">user guide</a> we know that we need to apply <code>com.android.application</code> plugin to our <code>app</code> project. Let’s have a look at <code>apply</code> <a href="https://docs.gradle.org/current/dsl/org.gradle.api.plugins.PluginAware.html#org.gradle.api.plugins.PluginAware:apply(groovy.lang.Closure)">method signatures</a>:</p><div id="2331"><pre><span class="hljs-keyword">void</span> <span class="hljs-title function_">apply</span><span class="hljs-params">(Closure closure)</span>
<span class="hljs-keyword">void</span> <span class="hljs-title function_">apply</span><span class="hljs-params">(Map<String, ?> options)</span>
<span class="hljs-keyword">void</span> <span class="hljs-title function_">apply</span><span class="hljs-params">(Action<? <span class="hljs-built_in">super</span> ObjectConfigurationAction> action)</span></pre></div><p id="5557">While the third one is the one that’s important — it uses statically typed API — we usually only use the second one, as it takes advantage of feature that we’ve mentioned before — named parameters are passed to the method as a map. To know what keys (parameters names) we can use, we peek into the documentation:</p><blockquote id="f176"><p><code>void apply(<a href="http://download.oracle.com/javase/7/docs/api/java/util/Map.html">Map</a><<a href="http://download.oracle.com/javase/7/docs/api/java/lang/String.html">String</a>, ?> options)</code></p></blockquote><blockquote id="49f3"><p>The following options are available:
<code>from</code>: A script to apply. (…)
<code>plugin</code>: The id or implementation class of the plugin to apply.
<code>to</code>: The target delegate object or objects. (…)</p></blockquote><p id="2850">We now know we need to pass our plugin id as <code>plugin</code> parameter. We <i>could</i> write <code>apply(plugin: 'com.android.application')</code>, but we also know we can omit parentheses if the invocation is non-ambiguous, which it is. Let’s add <code>apply plugin: ‘com.android.application’</code> to app’s <code>build.gradle</code> file, then:</p><div id="c6cc"><pre><span class="hljs-meta prompt_"> </span><span class="language-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"apply plugin: 'com.android.application'"</span> > app/build.gradle</span></pre></div><p id="3d9b">What now?</p><div id="1561"><pre><span class="hljs-variable"> </span>gradle <span class="hljs-symbol">app:</span>tasks</pre></div><div id="262e"><pre>FAILURE: Build failed <span class="hljs-keyword">with</span> <span class="hljs-keyword">an</span> exception.
What went wrong:
A problem occurred evaluating project <span class="hljs-string">':app'</span>.
> Plugin <span class="hljs-keyword">with</span> id <span class="hljs-string">'com.android.application'</span> <span class="hljs-keyword">not</span> found.
BUILD FAILED
Total <span class="hljs-built_in">time</span>: <span class="hljs-number">0.69</span> <span class="hljs-built_in">secs</span></pre></div><p id="a390">Okay then, there’s no <code>com.android.application</code> plugin defined. Well, we’re not surprised — how would Gradle find Android plugin’s <code>jar</code> file? We can see in the <a href="http://tools.android.com/tech-docs/new-build-system/user-guide">user guide</a> we need to add plugin’s classpath, and the repository in which it can be found.</p><p id="3bf3">Currently we can configure this classpath either in app’s or in root project’s <code>build.gradle</code> file, because <code>buildscript</code> closure is executed against <code>ScriptHandler</code>, which subprojects also use. This is not recommended though — all plugin dependencies should be declared at root project’s <code>build.gradle</code> instead. Let’s put <code>buildscript</code> block there, then, and discuss what it does:</p>
<figure id="0160">
<div>
<div>
<iframe class="gist-iframe" src="/gist/lwasyl/82c83fa4f443cc3dcaa7f21060665f66.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="b496">If we add parentheses in our heads, we see all of these are simple method calls, of which some pass <code>Closure</code> as a parameter. If we then dig into <a href="https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:buildscript(groovy.lang.Closure)">the documentation</a>, we read what objects these closures are executed against. In summary:</p><ul><li><code>buildscript(Closure)</code> is called on <code>Project</code> instance, and passed closure is executed against <code>ScriptHandler</code> object</li><li><code>repositories(Closure)</code> is called on <code>ScriptHandler</code> instance, while passed closure is executed against <code>RepositoryHandler</code></li><li><code>dependencies(Closure)</code> is also called on <code>ScriptHandler</code>, but its argument is executed against <code>DependencyHandler</code></li></ul><p id="302c">Which means that:</p><ul><li><code>jcenter()</code> is called within <code>RepositoryHandler</code></li><li><code>classpath(String)</code> is called on <code>DependencyHandler</code> (*)</li></ul><p id="7756">We only need to know that the first call — <code>buildscript</code> — is executed against <code>Project</code> instance. For the rest, the documentation specifies the delegates explicitly.</p><blockquote id="1a1d"><p>(*) If you inspect <code>DependencyHandler</code> code, you’ll notice there’s no <code>classpath</code> method. This is a special type of call, which we’ll discuss later on with dependencies.</p></blockquote><h2 id="edbc">Configuring Android subproject</h2><p id="9905">If we now try to execute some Gradle task, we’ll be greeted with an error:</p><div id="823f"><pre><span class="hljs-variable">$ </span>gradle projects</pre></div><div id="eb36"><pre><span class="hljs-keyword">FAILURE: </span>Build failed with an exc
Options
eption.
What went wrong:
A problem occurred configuring project ':app'.
> buildToolsVersion is not specified.</pre></div><p id="d540">Obviously, we haven’t put any Android-related configuration yet, but we can see that Android plugin is now applied correctly! Let’s add some configuration:</p>
<figure id="d174">
<div>
<div>
<iframe class="gist-iframe" src="/gist/lwasyl/3febee2d3f1d8b76879322c42e279c10.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="3696">We already see what’s happening — somehow there’s <code>android</code> method added to the<code>Project</code> instance, that delegates whatever closure it’s passed to some object (<code>AppExtension</code> in this case), which has <code>buildToolsVersion</code> and <code>compileSdkVersion</code> methods defined. This way Android plugin receives all configurations passed, including default configuration, flavors etc.</p><blockquote id="6724"><p>In order to run any tasks now we still need two things —<code> AndroidManifest.xml</code> file, and a <code>local.properties</code> file with <code>sdk.dir</code> property (or <code>ANDROID_HOME</code> environment variable) pointing to Android SDK location on our machine.</p></blockquote><h2 id="6740">Extensions</h2><p id="597d">But how did <code>android</code> method suddenly appear in the <code>Project</code> instance, against which our <code>build.gradle</code> is executed? In short, Android plugin registered <code>AppExtension</code> class as an <code>extension</code> with <code>android</code> name. This goes out of scope of this post, but what’s important for us is that Gradle adds configuration closure block for each extension object registered by the plugins.</p><h2 id="81f9">Dependencies</h2><p id="acd7">There’s one last block that’s always there, that haven’t been discussed yet - <code>dependencies</code>. Here’s an example:</p>
<figure id="0251">
<div>
<div>
<iframe class="gist-iframe" src="/gist/lwasyl/e453e6b797a96badf97469f446f9007f.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="414a">Why is this block special? Well, if you look into <a href="https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html#N162FA"><code>DependencyHand</code>ler</a>, to which <code>dependencies</code> method delegates the passed closure, you’ll see there’s no <code>compile</code> method on it, nor <code>testCompile</code> or any of those we usually use. Which makes sense — if we add <code>free</code> flavor, we can write <code>freeCompile 'somelib'</code> — <code>DependencyHandler</code> can’t define methods for all possible flavors now, can it? Instead, it uses another feature of Groovy language — <a href="http://www.groovy-lang.org/metaprogramming.html#_methodmissing"><i>methodMissing</i></a>, which allows for catching calls to undefined methods in runtime (*).</p><blockquote id="1e94"><p>(*) Actually Gradle uses abstraction over <code>methodMissing</code> declared in <a href="https://github.com/gradle/gradle/blob/master/subprojects/core/src/main/java/org/gradle/internal/metaobject/MethodMixIn.java"><code>MethodMi</code>xIn</a>, but the effect is mostly the same. Similar mechanism can also be applied to undefined properties.</p></blockquote><p id="bd61">The relevant fragment of the default dependency handler implementation can be found <a href="https://github.com/gradle/gradle/blob/master/subprojects/core/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java#L200-L227">here</a>, and it does the following:</p><ul><li>If <i>any undefined</i> method is called with more than <code>0</code> arguments, and</li><li>if there exists <code>configuration</code>(*) with the name of that method, then</li><li>depending on number of parameters and their type, call <code>doAdd</code> method with relevant parameters.</li></ul><blockquote id="ff28"><p>(*) Each plugin can add configurations to dependencies handler. For example <code>java</code> plugin defines <code>compile</code>, <code>compileClasspath</code>, <code>testCompile</code> and some other configurations, specified <a href="https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management">here</a>. Android plugin on the other hand adds <code>annotationProcessor</code> configuration, as well as <code><variant>Compile</code>, <code><variant>TestCompile</code> etc., based on defined build types and product flavors.</p></blockquote><p id="5378">While <code>doAdd</code> method is private, it’s being called by <code>add</code> method which is <code>public</code>. Thus, we could(*) rewrite above dependencies block as:</p>
<figure id="8e5c">
<div>
<div>
<iframe class="gist-iframe" src="/gist/lwasyl/7c056744be4cd8a040d127fe7af81640.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><blockquote id="fcd8"><p>(*) But please, don’t do that.</p></blockquote><h2 id="d4cc">Flavors, build types, signing configs</h2><p id="16e7">Let’s consider this piece of code:</p>
<figure id="9444">
<div>
<div>
<iframe class="gist-iframe" src="/gist/lwasyl/8fee554ad7b8f66af60af0e07343b3a5.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="8a88">What does <code>productFlavors</code> method delegate to? If we look into source code, <code>productFlavors</code> is declared like so:</p>
<figure id="3c11">
<div>
<div>
<iframe class="gist-iframe" src="/gist/lwasyl/beb6c4bf5b953f52893cfb75fcf85f46.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><blockquote id="33fd"><p><code>Action<T></code> in Gradle world is a closure executed against <code>T</code></p></blockquote><p id="5bbd">So, here we have some <code>NamedDomainObjectContainer</code> which creates and configures objects of type <code>ProductFlavorDsl</code> and stores them alongside their names.</p><p id="d2f3">This container also uses dynamic method dispatch to create an object of a given type (here <code>ProductFlavorDsl</code>) and put it into the container along with its (<i>method</i>) name. So if we call method <code>prod</code> with parameter <code>{}</code>, it’s executed against <code>productFlavors</code> instance, which is <code>NamedDomainObjectContainer</code>. Here’s what happens:</p><ul><li><code>NamedDomainObjectContainer</code> captures called method’s name,</li><li>creates <code>ProductFlavorDsl</code> object,</li><li>configures it against given closure,</li><li>stores mapping from method name to newly configured <code>ProductFlavorDsl</code> object</li></ul><p id="f6e5">We (and Android plugin) can then retrieve <code>ProductFlavorDsl</code> objects from <code>productFlavors</code>. What’s important, we can access them as properties, so in our case we can write <code>productFlavors.dev</code>, and we’ll retrieve <code>ProductFlavorDsl</code> that we’ve put with <code>dev</code> name. This is why we can write <code>signingConfig signingConfigs.debug</code> for example.</p><h1 id="ac53">Summary</h1><p id="3cd2">Gradle files are ubiquitous for Android developers, yet they’re often treated as a necessary evil, or at least as a magic black box that does <i>things</i>. But while there’s lots of conventions when it comes to writing Gradle scripts, and Gradle itself adds <i>some</i> complexity over Groovy language, when we get to know both, Gradle files aren’t that magical. I hope after reading this post and applying some curiosity, even that obscure code pasted from Stack Overflow will start to make sense now!</p><p id="d765">P.S. Many thanks to <a href="undefined">Stefan Oehme</a> and Android team at <a href="http://tooploox.com">Tooploox</a> for valuable input and proof-reading this article!</p></article></body>
Understanding Android Gradle build files
Taking the magic away
For most purposes we don’t need to modify our *.gradle files much — we add dependencies, modify target and minimum API levels, maybe set up signing configs and build types. Anything more complicated than that, and we end up copy-pasting mysterious snippets from Stack Overflow, without understanding how they work. In this post we’ll write, step by step, Gradle configuration files for a single Android project in order to take some of the magic away.
Groovy
Syntax
Gradle files are basically Groovy scripts. Syntax is easy to grasp if you know Java, and for us it’s important that:
there’s no need for parentheses when calling methods with at least one parameter (if it’s unambiguous):
if the last parameter is a closure (for now think lambda), it can be written outside the parentheses:
If you invoke Groovy method with named parameters, they are converted into a map and passed as first argument of the method. Other (non-named arguments) are then appended to parameters list:
This will print "John is 24 years old" followed by John works as Android developer at Tooploox. Mind that in both cases the result will be the same regardless of parameters order! Also notice omitted parentheses in both calls.
Closures
One important feature that needs some explaining are closures. If you’re familiar with Kotlin, you might find below explanation somewhat similar to function literals with receiver.
Closures in Groovy can be thought of as lambdas on steroids. They’re blocks of code that can be executed, can have parameters, and return values. What’s different is that we can change the delegate of a closure. Let’s consider following code:
We can see that printClosure calls printText method on the delegate it is provided (the same goes for properties). We will see later why this is crucial in Gradle.
There’s actually a bit more to delegates and how closure’s statements are executed. You can read more about delegation and delegation strategies in Groovy documentation.
Gradle
Script files
There are three main script files that Gradle uses. Each one is a block of code (closure, anyone?) executed against various objects:
build scripts in build.gradle files. These are executed against Project objects;
settings scripts in settings.gradle files, executed against Settings object;
init scripts used for global configuration (executed against Gradle instance).
Projects
Gradle build consists of one or more projects, and projects consist of tasks. There is always at least the root project, which may contain subprojects, which in turn can have nested subprojects as well. Common convention is that the root project’s role is only to orchestrate group projects, provide common configuration, plugins classpaths etc.
From now on project will refer to whatever subproject we’re currently interested in, and root project will be used when referring to root project specifically.
Creating Gradle-based Android project
In typical Android project we have the following folder structure:
To see a list ofthe tasks ofa project, run gradle <project-path>:tasks
For example, try running gradle :tasks
BUILD SUCCESSFUL
Total time: 0.741 secs
If you don’t have Gradle installed locally, you can install it using macports or homebrew, or download an installer from official webpage. You can also create a new project in Android Studio and remove everything except for Gradle wrapper.
Setting up projects hierarchy
If we want similar structure to a default Android project (empty root project and an app project with our application), we need a settings.gradle file. From the documentation we know that settings.gradle script:
declares the configuration required to instantiate and configure the hierarchy of Project instances which are to participate in a build.
Further we read that we can add projects to the build using void include(String[] projectPaths) method. Let’s add a app subproject then:
Colon (:) is used in Gradle to separate paths to subprojects, what we can see here. That’s why we write :app and not app (although in this case app would work as well)
It’s also good practice to include rootProject.name = <<name>> in settings.gradle file. Without it, root project’s name defaults to the name of the folder in which the project is, which may be different for example on a CI server.
Setting up Android subproject
Now we’d normally set up root project’s build.gradle file, but what do we need to put there? Let’s find out by trying to set up an Android project instead.
From the user guide we know that we need to apply com.android.application plugin to our app project. Let’s have a look at applymethod signatures:
voidapply(Closure closure)voidapply(Map<String, ?> options)voidapply(Action<? super ObjectConfigurationAction> action)
While the third one is the one that’s important — it uses statically typed API — we usually only use the second one, as it takes advantage of feature that we’ve mentioned before — named parameters are passed to the method as a map. To know what keys (parameters names) we can use, we peek into the documentation:
The following options are available:
from: A script to apply. (…)
plugin: The id or implementation class of the plugin to apply.
to: The target delegate object or objects. (…)
We now know we need to pass our plugin id as plugin parameter. We could write apply(plugin: 'com.android.application'), but we also know we can omit parentheses if the invocation is non-ambiguous, which it is. Let’s add apply plugin: ‘com.android.application’ to app’s build.gradle file, then:
FAILURE: Build failed withan exception.
* Where:
Build file'(...)/example/app/build.gradle'line: 1
* What went wrong:
A problem occurred evaluating project ':app'.
> Plugin with id 'com.android.application'not found.
BUILD FAILED
Total time: 0.69secs
Okay then, there’s no com.android.application plugin defined. Well, we’re not surprised — how would Gradle find Android plugin’s jar file? We can see in the user guide we need to add plugin’s classpath, and the repository in which it can be found.
Currently we can configure this classpath either in app’s or in root project’s build.gradle file, because buildscript closure is executed against ScriptHandler, which subprojects also use. This is not recommended though — all plugin dependencies should be declared at root project’s build.gradle instead. Let’s put buildscript block there, then, and discuss what it does:
If we add parentheses in our heads, we see all of these are simple method calls, of which some pass Closure as a parameter. If we then dig into the documentation, we read what objects these closures are executed against. In summary:
buildscript(Closure) is called on Project instance, and passed closure is executed against ScriptHandler object
repositories(Closure) is called on ScriptHandler instance, while passed closure is executed against RepositoryHandler
dependencies(Closure) is also called on ScriptHandler, but its argument is executed against DependencyHandler
Which means that:
jcenter() is called within RepositoryHandler
classpath(String) is called on DependencyHandler (*)
We only need to know that the first call — buildscript — is executed against Project instance. For the rest, the documentation specifies the delegates explicitly.
(*) If you inspect DependencyHandler code, you’ll notice there’s no classpath method. This is a special type of call, which we’ll discuss later on with dependencies.
Configuring Android subproject
If we now try to execute some Gradle task, we’ll be greeted with an error:
$ gradle projects
FAILURE: Build failed with an exception.
* What went wrong:
A problem occurred configuring project ':app'.
> buildToolsVersion is not specified.
Obviously, we haven’t put any Android-related configuration yet, but we can see that Android plugin is now applied correctly! Let’s add some configuration:
We already see what’s happening — somehow there’s android method added to theProject instance, that delegates whatever closure it’s passed to some object (AppExtension in this case), which has buildToolsVersion and compileSdkVersion methods defined. This way Android plugin receives all configurations passed, including default configuration, flavors etc.
In order to run any tasks now we still need two things — AndroidManifest.xml file, and a local.properties file with sdk.dir property (or ANDROID_HOME environment variable) pointing to Android SDK location on our machine.
Extensions
But how did android method suddenly appear in the Project instance, against which our build.gradle is executed? In short, Android plugin registered AppExtension class as an extension with android name. This goes out of scope of this post, but what’s important for us is that Gradle adds configuration closure block for each extension object registered by the plugins.
Dependencies
There’s one last block that’s always there, that haven’t been discussed yet - dependencies. Here’s an example:
Why is this block special? Well, if you look into DependencyHandler, to which dependencies method delegates the passed closure, you’ll see there’s no compile method on it, nor testCompile or any of those we usually use. Which makes sense — if we add free flavor, we can write freeCompile 'somelib' — DependencyHandler can’t define methods for all possible flavors now, can it? Instead, it uses another feature of Groovy language — methodMissing, which allows for catching calls to undefined methods in runtime (*).
(*) Actually Gradle uses abstraction over methodMissing declared in MethodMixIn, but the effect is mostly the same. Similar mechanism can also be applied to undefined properties.
The relevant fragment of the default dependency handler implementation can be found here, and it does the following:
If any undefined method is called with more than 0 arguments, and
if there exists configuration(*) with the name of that method, then
depending on number of parameters and their type, call doAdd method with relevant parameters.
(*) Each plugin can add configurations to dependencies handler. For example java plugin defines compile, compileClasspath, testCompile and some other configurations, specified here. Android plugin on the other hand adds annotationProcessor configuration, as well as <variant>Compile, <variant>TestCompile etc., based on defined build types and product flavors.
While doAdd method is private, it’s being called by add method which is public. Thus, we could(*) rewrite above dependencies block as:
(*) But please, don’t do that.
Flavors, build types, signing configs
Let’s consider this piece of code:
What does productFlavors method delegate to? If we look into source code, productFlavors is declared like so:
Action<T> in Gradle world is a closure executed against T
So, here we have some NamedDomainObjectContainer which creates and configures objects of type ProductFlavorDsl and stores them alongside their names.
This container also uses dynamic method dispatch to create an object of a given type (here ProductFlavorDsl) and put it into the container along with its (method) name. So if we call method prod with parameter {}, it’s executed against productFlavors instance, which is NamedDomainObjectContainer. Here’s what happens:
NamedDomainObjectContainer captures called method’s name,
creates ProductFlavorDsl object,
configures it against given closure,
stores mapping from method name to newly configured ProductFlavorDsl object
We (and Android plugin) can then retrieve ProductFlavorDsl objects from productFlavors. What’s important, we can access them as properties, so in our case we can write productFlavors.dev, and we’ll retrieve ProductFlavorDsl that we’ve put with dev name. This is why we can write signingConfig signingConfigs.debug for example.
Summary
Gradle files are ubiquitous for Android developers, yet they’re often treated as a necessary evil, or at least as a magic black box that does things. But while there’s lots of conventions when it comes to writing Gradle scripts, and Gradle itself adds some complexity over Groovy language, when we get to know both, Gradle files aren’t that magical. I hope after reading this post and applying some curiosity, even that obscure code pasted from Stack Overflow will start to make sense now!
P.S. Many thanks to Stefan Oehme and Android team at Tooploox for valuable input and proof-reading this article!