If you are using Android Studio, then you’ve seen Gradle in action. Yep, Gradle is the engine behind the build system used in Android Studio, which is, together with Google’s Android plugin for Gradle, responsible for building your Android application and producing the final .apk files.

Gradle is a powerful build tool, written in a simple script language called Groovy. It takes care of a lot of the plumbing related to building an application – resolving dependencies, downloading artifacts from repositories and determining which tasks need to run in order to build your app. Gradle then runs the tasks that compile, merge, handle resources etc until your app is ready. Gradle is also simple to configure. Typically, with a few declaration lines in your build.gradle you are ready to go.

But this is exactly what makes Gradle somewhat complex to understand, because a lot of the work is done under the hood, typically by plugins, some built-in (like the java plugin), and some being 3rd party plugins, like the Android plugin and the SafeDK plugin. If you are someone like me that likes understanding what’s going on behind the scenes – it’s not always that easy.

There’s of course a lot to tell about Gradle and the Android build system. So much, in fact, that this is just the first out of a two-part story (part 2 coming next week). In these couple of posts I’ll focus on a specific topic – I’ll try to guide you through setting up an environment where you can compile an Android application using Gradle, but without the Android Studio IDE. Why would you want to do that? Well, it has to do with buzzwords like “build server”, “continuous integration” etc. While you can build and deploy your app in Android Studio, if you have multiple people working on the app, you may want a central build server with a consistent environment, where you continuously integrate, run automated tests, produce versions for your QA team and produce the final versions. This is btw one of the nice things in Android Studio – the fact that you can completely decouple the build system from the graphical IDE.

Here I’ll go over some terms and mechanisms which are essential for part 2, where I’ll detail the steps to set up your independent build server.

OK, so let’s dive in.

Repositories

In order to use “artifacts” (SDKs, libraries, plugins etc.) in your apps, Gradle needs to fetch them from somewhere – from “repositories”. Repositories are basically servers that hold artifacts in a well-known structure, allowing Gradle to query those repositories for certain artifacts, find which versions of the artifact exist and download the most appropriate version of an artifact.

When you write in your build.gradle script (also called the “buildscript”) the following lines:

repositories {
    jcenter()
}

you are actually adding Bintray’s JCenter repository to the list of repositories Gradle queries to search for artifacts you later specify. Another well-known repository you may have seen is Maven Central, which you can add to your buildscript by writing mavenCentral() inside the repositories closure. Repositories can also be privately managed, holding only specific artifacts, like the SafeDK respository which holds the SafeDK In-App Protection Plugin for Gradle.

You may be asking yourself whether Gradle downloads artifacts every time you run a build. The answer is of course no; Gradle caches the artifacts it downloads in a repository on your machine under your user account, inside a directory called “.gradle”, and it may check for newer versions of the artifacts – by default once every 24 hours.

Repositories don’t necessarily need to reside on the internet. You can have local repositories hosted on servers in your organization, and you can also have local repositories residing on your file system. This is exactly what happens with the repository holding Google Play Services, which is part of the Android SDK as you’ll see in a minute.

Project Hierarchy

In Gradle, projects may have a hierarchy and Android Studio uses this capability. When you create a new “project” in Android Studio, it will typically include one or more “modules”. From Gradle’s point-of-view each Android “module” is a “project”, and the entire Android “project” is Gradle’s “root project”, as shown in the below table:

Android Studio terminology Gradle terminology
Project Root project
Module Project

A bit confusing, eh?

Gradle’s project hierarchy gives you the ability to build all modules together into a single application, and also allows you to make some definitions in a single place and apply them to all projects in the hierarchy. If you open your Android’s root project’s build.gradle script, you will probably see it includes something like this:

allprojects {
    repositories {
        jcenter()
    }
}

This tells Gradle to add Bintray’s JCenter repository to all sub projects (i.e. in all Modules in your Android app).

By the way, Gradle decides which projects to include in the project hierarchy by looking at the “settings.gradle” file.

Cool. Now let’s understand a little about “dependencies”.

Dependencies

In your build.gradle you may see something similar to this:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.google.android.gms:play-services-maps:7.5.0'
}

This tells Gradle that it needs to compile your source code with some libraries in the classpath. In the above example, the 2nd and 3rd “compile” lines require Gradle to fetch those artifacts from a repository. As you have the jcenter() definition we’ve seen earlier, Gradle will try to fetch them from Bintray’s JCenter, but – surprise – it won’t find them there! Where are those artifacts? Well, they reside within the Android SDK which you installed on your machine. So how does Gradle know where they are? The Android plugin tells it – when the Android plugin is loaded by Gradle (you have in your buildscript the definition: apply plugin: ‘com.android.application’) it adds to Gradle two repositories which reside inside the Android SDK:

<android-sdk>/extras/android/m2repository

<android-sdk>/extras/google/m2repository

How do you make sure you have those repositories installed and updated with the artifacts you need? By using Android’s SDK Manager to download whatever packages you need.

When setting up a build server, you will need to properly set up your Android SDK, as you’ll see in part 2 of this post.

Dependencies vs. Buildscript Dependencies

Another somewhat confusing thing you may have noticed – there are two definitions of repositories and dependencies. One may look like this (take a look at your root project’s build.gradle):

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

While the other may look like this (in your module’s build.gradle):

repositories {
    jcenter()
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.google.android.gms:play-services-maps:7.5.0'
}

They look almost the same, but one definition is inside a “buildscript” closure while the other is not. So what’s the difference? Well, the definitions of repositories and dependencies inside the “buildscript” closure are used for artifacts required for the build system itself, for the Groovy buildscript itself to compile. The Android plugin is such an artifact; when Gradle sees the statement apply plugin: ‘com.android.application’ – it knows where to fetch this plugin due to the buildscript closure. This is also the case with the SafeDK plugin – in order to apply the SafeDK plugin you will need to tell Gradle to look for it in the SafeDK repository. And the latter repositories/dependencies definitions which reside outside of a “buildscript” closure – they are dependencies of your application, i.e. artifacts required in order to properly build your app.

local.properties

Here’s another piece of the puzzle, after which I’ll summarize it all into a hopefully clear picture. You may have noticed in your Android Studio’s root project directory a small file called “local.properties”, which contains something similar to this:

sdk.dir=/home/user/AndroidSDK

This file is holding the location of the Android SDK installed on your machine. What looks for this file and reads it? The Android plugin for Gradle. This is how the Android plugin knows where to find the Android SDK, and where to find the “extras/android” and “extras/google” repositories I mentioned above (Another alternative to this file is to define an ANDROID_HOME environment variable.)

The Full Picture

Now that we’ve covered all the above, let’s see the full picture of how the Android build gets resolved:

Gradle starts, reads settings.gradle, build.gradle files (called “buildscripts”) and starts evaluating them

Based on the “buildscript” block, Gradle downloads the Android plugin (‘com.android.tools.build:gradle:x.y.z’) from Bintray’s JCenter

Gradle sees apply plugin: ‘com.android.application’ and instantiates the Android plugin

The Android plugin looks for the Android SDK by looking at local.properties or in the ANDROID_HOME environment variable

Based on your configuration of the Android SDK (done via the SDK Manager), the Android plugin adds additional repositories to Gradle. It would typically add the following local repositories:
/extras/android/m2repository
/extras/google/m2repository

Gradle completes the evaluation and buildup of the buildscripts, including all plugins which get instantiated. Then build tasks are being configured and executed.

Now that we understand the flow, we are ready for part 2 of this post, where I’ll detail all the steps required to set up a build environment on a build server, without the Android Studio IDE. So stay tuned!