Distribute an AAR via a local Maven repository

PUBLISHED ON DECEMBER 8, 2017 — ANDROID, GRADLE, MAVEN

This tutorial shows how to distribute a local Gradle module of your app in AAR form, along with its dependencies, via a local Maven repository.

This is particularly useful if you cannot deploy your library’s AAR to a remote Maven repository, either public or private, for example because the library is closed-source, so you cannot rely on JCenter, Maven Central or Jitpack, or the library will be used on a machine without Internet connectivity. In the latter case, a local Maven repository will be particularly useful because it will provide all the necessary dependencies of your library.

Library setup

Credits: this is heavily based on this gist posted by Robyer.

We will use the new maven-publish plugin to produce the AAR artifact of our library.

In your library build.gradle file, add the following:

apply plugin: 'maven-publish'

android {
    buildTypes {
        distributable.initWith(buildTypes.release)
        distributable {
            minifyEnabled true
            proguardFiles 'proguard-rules-distributable.txt' // if you want to use a specific proguard configuration for the AAR artifact
        }
    }
}

The distributable build type will use the given progard file for obfuscation. Since it is important not to lose the generated mapping file, we will define a task to give it a descriptive name and copy it somewhere safe:

android.libraryVariants.all { variant ->
    if (variant.buildType.name == 'distributable') {
        if (variant.mappingFile != null) {
            tasks.create(name: "copyDistributableAarProguardMapping", type: Copy) {
                group "distribution"
                String today = new Date().format('yyyyMMdd_HHmmss')
                from variant.mappingFile.path
                rename '.*', "${project.name}_${today}_mapping.txt"
                into rootDir
            }
            bundleDistributable.finalizedBy copyDistributableAarProguardMapping
        }
    }
}

Next, we will configure the maven-publish plugin to package and deploy the AAR to our local Maven repo. Its path is passed as an URL representing a directory.

publishing {
    repositories {
        maven {
            url "/path/to/our/local/maven/repo"
        }
    }
    publications {
        maven(MavenPublication) {
            // Customize these
            groupId 'com.my.groupid'
            artifactId 'my-artifact-id'
            version '1.0'

            artifact bundleDistributable {
                classifier ""
            }

            pom.withXml {
				def node = asNode()

				// ugly hack to set the packaging property in the POM as 'aar'
				((NodeList) node.get('packaging')).get(0).value = 'aar'

				def dependenciesNode = node.appendNode('dependencies')

				def cl = { Dependency dep ->
					if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified")
						return // ignore invalid dependencies

					def dependencyNode = dependenciesNode.appendNode('dependency')
					dependencyNode.appendNode('groupId', dep.group)
					dependencyNode.appendNode('artifactId', dep.name)
					dependencyNode.appendNode('version', dep.version)

					if (!dep.transitive) {
						// If this dependency is transitive, we should force exclude all its dependencies them from the POM
						def exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
						exclusionNode.appendNode('groupId', '*')
						exclusionNode.appendNode('artifactId', '*')
					} else if (!dep.properties.excludeRules.empty) {
						// Otherwise add specified exclude rules
						def exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
						dep.properties.excludeRules.each { ExcludeRule rule ->
							exclusionNode.appendNode('groupId', rule.group ?: '*')
							exclusionNode.appendNode('artifactId', rule.module ?: '*')
						}
					}
				}

				// List all dependencies and write to POM
				configurations.api.getAllDependencies().each cl
				configurations.implementation.getAllDependencies().each cl
			}
        }
    }
}

Now, by invoking the following command, Gradle will package an obfuscated AAR of our library and deploy it to our local Maven repo:

gradle :mylibrary:publish

Bonus #1: include other libraries in the local Maven repo

You can also add other libraries, along with their dependencies, to our local Maven repo. This is useful if you plan to distribute this repo to a machine that cannot access the public Maven repos on the Internet.

To do so, you can use maven-repository-provisioner. Download the jar with dependencies, then use it like so to download, for example, okhttp:

java -jar maven-repository-provisioner-1.2.0-jar-with-dependencies.jar \
    -s http://jcenter.bintray.com/ \
    -t /path/to/our/local/maven/repo \
    -a "com.squareup.okhttp3:okhttp:3.6.0" \
    -ij false -is false

Bonus #2: include an AAR in the local Maven repo

To add an AAR to the local Maven repo, just use Maven itself, as shown in the following command. I have had some headaches with using plain AARs in a project, and I find this way much more reliable and hassle-free.

mvn install:install-file -Dfile=/path/to/library.aar \
    -DgroupId=com.my.groupid \
    -DartifactId=my-artifact-id \
    -Dversion=1.0.0 \
    -Dpackaging=aar \
    -DlocalRepositoryPath=/path/to/our/local/maven/repo

Usage of the local Maven repo

In your project build.gradle file, simply add a new repository:

allprojects {
    repositories {
        maven {
            url "/path/to/our/local/maven/repo"
        }
    }
}

Then, in your app build.gradle file, you will be able to declare a dependency on your library simply via:

implementation 'com.my.groupid:my-artifact-id:1.0'
TAGS: ANDROID, GRADLE, MAVEN