Working on Android open-source I needed to publish two libraries (Android-Image-Cropper and Android-Fast-Image-Loader) to JCenter.
What I will touch in this post:
- Publishing AAR
- "Sources not found" issue with AAR
- AAR vs. JAR
- Publishing JAR
- Handling optional dependency in Gradle
TL;DR see the release gradle script.
Publishing AAR
After not so quick Google search I finally found this blog post that did exactly what I needed, create a zip archive with everything required (aar, javadoc jar, sources jar and md5 hashes) to upload the library to BinTray.
The blog post does an excellent job so I won't repeat it, in the end what I needed to add to the library build.gradle file was the following snippet (note: it uses remote script).
apply plugin: 'com.android.library' ext { PUBLISH_GROUP_ID = 'com.theartofdev.edmodo' PUBLISH_ARTIFACT_ID = 'android-image-cropper' PUBLISH_VERSION = '1.0.4' } android { // flavor config etc. } dependencies { // dependencies } apply from: 'https://raw.githubusercontent.com/ArthurHub/release-android-library/master/android-release-aar.gradle'
The output "release-1.0.3.zip" file can be uploaded and extracted to BinTray, just make sure to check the "Explode this archive" checkbox. Then you can request publishing the library to JCenter so the user won't have to reference your private repository.
"Sources not found" issue
Publishing AAR package works perfect except when, as a user, you try to look at the library javadoc or source, you get "Sources not found" even though the JARs were uploaded in the package.
Apparently the issue here is with IDEA (Android Studio) and AAR format, IDEA downloads the sources JAR into the wrong location so it doesn't finds them. The issue is specific to AAR and doesn't occurs if JAR is used.
There are two ways to solve it, either deploy the library as JAR and not AAR or use AARLinkSources Plugin.
The first approach is on the library developer and not always possible as I will explain shortly while the second is on the user of the library so it creates more friction to use the library and many users won't bother.
AAR vs. JAR
The 'aar' bundle is the binary distribution of an Android Library Project.
AAR is simply a zip archive that includes the JAR (classes.jar) with additional Android resources including the manifest xml and R.txt.
So publishing Android library as JAR will work fine as long as it doesn't define any resources like strings, dimensions, drawables, layouts or custom attributes. If any of those are in use you must publish AAR for the library to work properly. Unfortunately publishing AAR will not allow the users easy access to the sources and documentation as a explained above.
Publishing JAR
To publish a JAR there is an additional task we need to add to the gradle script. This will create a JAR archive in addition to the AAR archive already created by the script and they both will be bundled and can be uploaded together, JAR have higher priority so it will be used even if AAR exists in the package.
android.libraryVariants.all { variant -> def name = variant.buildType.name if (!name.equals("debug")) { def task = project.tasks.create "jar${name.capitalize()}", Jar task.dependsOn variant.javaCompile task.from variant.javaCompile.destinationDir artifacts.add('archives', task); } }
I created a new gradle script, based on the original, so all you need to do is use different URL in the "apply from" part (note the name is "android-release-jar .gradle"):
apply plugin: 'com.android.library' ext { PUBLISH_GROUP_ID = 'com.theartofdev.edmodo' PUBLISH_ARTIFACT_ID = 'android-image-cropper' PUBLISH_VERSION = '1.0.4' } android { // flavor config etc. } dependencies { // dependencies } apply from: 'https://raw.githubusercontent.com/ArthurHub/release-android-library/master/android-release-jar.gradle'
Handling optional dependency in Gradle
Not directly on the subject of publishing a library package, though the use of optional dependency is only relevant when publishing a library to be used by others.
The idea is to be able to use a dependency in library code but not forcing the users of the library to be dependent on the optional dependency. So if the user doesn't have this dependency the library code will handle it by falling back to alternative implementation.
In Android-Fast-Image-Loader I have an optional dependency on OkHttp network library used for image downloading, but if the user doesn't have that dependency I fallback to native Android URLConnection.
Maven has native support for this feature so all is needed is to have "<optiona>true</optional>" on the dependency in the generated POM file of the library, unfortunately Gradle doesn't support it so a little hack is required.
First we need to create "optional" keyword synonymous to "compile", then in " uploadArchives " " pom.withXml " we will add the optional element for all dependency that use the "optional" keyword:
configurations { optional compile.extendsFrom optional } uploadArchives { repositories.mavenDeployer { pom.groupId = groupId pom.artifactId = artifactId pom.version = version pom.withXml { asNode().dependencies.dependency.findAll { xmlDep -> // mark optional dependencies if (project.configurations.optional.allDependencies.findAll { dep -> xmlDep.groupId.text() == dep.group && xmlDep.artifactId.text() == dep.name }) { def xmlOptional = xmlDep.optional[0]; if (!xmlOptional) { xmlOptional = xmlDep.appendNode('optional') } xmlOptional.value = 'true'; } } } // Add other pom properties here if you want (developer details / licenses) repository(url: "file://${localReleaseDest}") } }
I have updated the script, the only difference is that not the "apply from" part needs to be before the "dependencies", otherwise it will not recognize the "optional" keyword:
apply plugin: 'com.android.library' ext { PUBLISH_GROUP_ID = 'com.theartofdev.edmodo' PUBLISH_ARTIFACT_ID = 'android-image-cropper' PUBLISH_VERSION = '1.0.4' } android { // flavor config etc. } apply from: 'https://raw.githubusercontent.com/ArthurHub/release-android-library/master/android-release-aar.gradle' dependencies { // dependencies optional 'com.something:something:1.0' }