Publishing a variant-aware Android library with its sources

How to publish variant-aware Android libraries with their sources

Guillermo Mazzola
The Glovo Tech Blog

--

As you probably know already, a com.android.library module by default has two default buildTypes: debug and release. If you don’t declare any productFlavor those are mapped directly as your libraryVariants.

If your android library module is consumed in the same build, then you are all set up with Gradle’s variant selection feature, which Android Gradle Plugin (a.k.a. AGP) uses.
You only need to make your app module depend on your lib module, and that’s all. The correct debug variant from lib will be picked up when the app is building in debug mode. The same applies for release and any further configuration you may have.
Variants itself is not the topic of this article. Check Configure Build variants for extra details on this matter.

How to publish a variant-aware library

At Glovo, we have many internal Android libraries that our main apps consume, and sometimes, we do want to have a different behavior if we are targeting a debug or a release build.
The best example is our logger library (a wrapper interface with very basic log methods) that will print to the logcat debug and use Firebase for release

AGP 7.0 was a big milestone for us, because it introduced the new Gradle component all , which exposes all Android variants as Gradle variants, allowing a library to be consumed in the same way than a local module.
The right debug, release (or whatever) variant will be picked up based on the matching attribute’s metadata.

It can easily be set up like this:

And will generate the following artifacts in your Maven repository:

Basically, instead of having one default AAR artifact, each variant will be published as a classifier.

The problem arises as soon you consume the library because they lack of asources.jar artifact.

Attaching a source per classifier

Publishing a source artifact per each classifier is not a big issue, we actually have logic for it assuming a ${variant.name}-sources pattern:

And will generate the following artifacts in your Maven repository:

This is the relevant content of logger-0.1.0.module (Gradle’s metadata):

The problem: sources artifact resolution

For historical reasons, there is a wide convention that Maven artifacts will publish its sources in a sources classifier, with type jar (the same applies for javadocs but it’s not our topic).

For instance, in our example, our Maven coordinates are com.glovoapp.android:logger:0.1.0, the sources should be attached to com.glovoapp.android:logger:0.1.0:sources.

With the introduction of “variant-aware” dependencies by Gradle, this convention is no longer enough because you can have different variants, produced by different source code, so a unique sources artifact is not accurate. Variants are basically two different artifacts and it can be potentially two different libraries at all.

In addition to this conceptual limitation, there is a technical one:
AGP defines that each variant will be a classifier and for some reason, Gradle/IntelliJ are unable to resolve the sources of a dependency, when that dependency is already a classifier.

For instance, having this set of artifacts (even with the right metadata) just does not work either:

If you try to use the “Download Sources” feature from IntelliJ, you will also note their script builds an invalid dependency notation com.glovoapp.android:logger:0.1.0:debug@aar:sources, revealing the lack of support (for now) to this use case:

FAILURE: Build failed with an exception.* Where:
Initialization script '/private/var/folders/v8/0x2cv3xs1895qcky7hpxhfch0000gn/T/ijmiscinit3.gradle' line: 14
* What went wrong:
Execution failed for task ':app:DownloadSources'.
> Supplied String module notation 'com.glovoapp.android:logger:0.1.0:debug@aar:sources' is invalid. Example notations: 'org.gradle:gradle-core:2.2', 'org.mockito:mockito-core:1.9.5:javadoc'.

I actually had the chance to chat with a couple of Gradle engineers regarding this. They acknowledged the problem, but yet it is not clear what would be the path to fully support this use case in the future.

The solution: Divide and Conquer

So, let’s recap our constraints:

  1. Having the ability to dynamically choose which Android variant will be used based on who is consuming it, is a MUST ⇒ Variant aware Gradle publications
  2. Allow our developers to see the fully documented source code of our libraries is also a MUST ⇒ Source code needs to be published
  3. Gradle/IntelliJ seems to ignore Gradle’s metadata when resolving sources (at least for now) and only honors Maven’s sources classifier convention
  4. Source code of the chosen variant will be automatically attached when the Gradle project when sync in IntelliJ is a NICE TO HAVE

How to fulfill all constraints? well, basically by migrating from AGP’s default publication:

AGP’s default publication: components[“all”]

Into an artifact per variant publication, plus a default variant-aware POM in the original coordinates:

Multi-artifact variant-aware library publication

This is how the Maven repository will look like for our logger library:

In case you are looking to implement this solution, you can download our android-publish.gradle.kts script in put it directly in your buildSrc project (under src/main/kotlin)

Make sure to apply kotlin-dsl plugin in your buildSrc module, and then you can apply the plugin at any Android module by adding:

plugins {
`android-publish`
}

--

--