Android, Gradle & IDE Integration
In this post I’m describing how we use the new “custom model” feature, introduced in Gradle 1.6, to provide IDE integration for Android projects that use the new Gradle-based build system. This feature was designed to allow Gradle plug-in developers provide a custom representation of a Gradle project through the Gradle’s Tooling API.
This post is intended to be a “note to self,” to have it handy once we start implementing Gradle support for Android projects in the Android Development Tools for Eclipse (ADT.)
Android has a new Gradle-based build system that makes it easier to accomplish tasks that were previously hard or impossible to do. We want build.gradle files to be the one and only source of truth. This means that building from the command line, an IDE or a continuous-integration server should always result in the same output.
Before Gradle 1.6, Gradle integration with Java IDEs (Eclipse & IDEA) was been taken care of by Gradle’s tooling API: it provided (and still provides) a IDE-specific representation of a Gradle project that was good enough for regular Java projects. Android’s Gradle plug-in contains a rich set of project-related information that is needed for IDE integration, but also Android-specific data (e.g. additional source folders, resource folders, etc.) Unfortunately, the pre-Gradle-1.6 tooling API model did not allow us to pass that information to IDEs.
To help us achieve our goal, the Gradle folks implemented the changes we needed. The custom model mechanism was released in Gradle 1.6. Thanks, guys!
Android’s Gradle Model
Instead of creating IDE-specific Android/Gradle models, we decided to have an IDE-agnostic representation of a Gradle project. This way, we have a single source of information that is easier to maintain. IDE integration will be implemented as a plug-in for each supported IDE (in our case, Eclipse and IDEA.) The main benefit of this approach is that we can release the Gradle plug-in independently of the IDE integration plug-ins. For example, we can ship a new version of the Eclipse plug-in that has several bug fixes, without affecting the Gradle side of Android.
The Android Gradle model is a rich and detailed representation of an Android project. The core of it is the concept of build variants. A build variant is a combination of build type (e.g. “debug” vs. “release”) and an application flavor (e.g. “free” vs. “paid”.) Of course, this is a very high-level explanation. To learn more about build variants, please read the Android Gradle build system docs.
Build variants are important for IDE integration because each build variant has its own project structure. For example, a build variant has its own output file (.aar file) and its own set of generated source directories. To set up an IDE project we need to know all the source folders, dependencies and outputs of the selected variant in an Android project.
On one hand, build variants are a powerful concept that makes it easy to create multiple types of the same application. On the other hand, this new concept pushes the limits of what an IDE can do. IDEs were not designed to handle multiple versions of a same project (not their fault: probably this is the first time that such thing exists!) To properly use build variants in an IDE, some fundamental changes need to be made.
- Display all variants and allow user to select the variant to work on.
This is not really a “fundamental” change. An IDE needs to show all the available variants, the selected variant and allow the user to select a different variant to work on. In Android Studio we implemented this as a “Build Variants” tool window:
- React to changes in build variant selection.
As difficult as it is to implement this feature, it is not a fundamental change either. Once the user selects a different build variant, the IDE needs to react accordingly:
- adjust source directories: only mark as “source” the directories specified by the selected build variant
- adjust dependencies to other projects or 3rd. party libraries
- opened editors need to show contents that are relevant to the selected build variant
- point to the correct output file (.aar file) for launching and debugging
- trigger a project build
All of this have to be done correctly and, of course, pronto.
This is where all the fundamental changes need to happen. Even though we haven’t fully explored the scope of changes in refactoring that build variants need, we have some ideas.
Take class renaming for example. Let’s say we have class
com.example.Class1in the project’s default configuration (i.e. the code that doesn’t change regardless of the selected build variant.) Our sample project has 2 build variants and each have their own version of class
com.example.Class2. Both versions of
Class2have a reference to
Class1(let’s say has a field of type
What should happen if I rename
Yep, both versions of
Class2need to be included in the rename refactoring. The problem is that the IDE only knows about one
Class: the one that comes from the selected variant. The IDE has no notion of all the other versions of that class. To make this refactoring work we need to let the IDE know about all the possible versions of a class or resource. That could mean making changes to how an IDE indexes files. This is a lot more complex than it sounds. I’m sure this is just the tip of the iceberg.
May the force be with us.
Other simpler (but equally important) features that IDE integration should include are:
- Create Grade-based Android projects
- Import existing Gradle-based Android projects
- Automatically detect changes in Gradle files and update the IDE project structure accordingly
- Use Gradle as the only mechanism for building
The new Android Gradle build system is one of the best additions to an Android app developer’s toolbox. It makes it a lot easier to create different versions of the same app (not an uncommon use case.) IDE integration with the new build system is crucial, but not trivial to implement.
In this post I shared some ideas of what needs to be done to make it happen. Some of the points discussed in this post are already implemented in Android Studio. By the time you read this post, we should have started work on Eclipse support.