Publishing a variant-aware Android library with its sources
How to publish variant-aware Android libraries with their sources
As you probably know already, a com.android.library
module by default has two default buildType
s: 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:
- 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
- Allow our developers to see the fully documented source code of our libraries is also a MUST ⇒ Source code needs to be published
- Gradle/IntelliJ seems to ignore Gradle’s metadata when resolving sources (at least for now) and only honors Maven’s
sources
classifier convention - 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:
Into an artifact per variant publication, plus a default variant-aware POM in the original coordinates:
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`
}