Publish an Android library to Maven with AAR and sources JAR

AndroidGradleAndroid Gradle-PluginAndroid LibraryMaven Publish

Android Problem Overview


Can somebody give me a hint on how to use the maven-publish Gradle plugin to publish a com.android.library project/module with AAR and source jar? I am able to do this with the old maven plugin - but I would like to use the new maven-publish plugin.

Android Solutions


Solution 1 - Android

Here's a sample using the new maven-publish plugin.

apply plugin: 'maven-publish'

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier "sources"
}

publishing {
    publications {
        bar(MavenPublication) {
            groupId 'com.foo'
            artifactId 'bar'
            version '0.1'
            artifact(sourceJar)
            artifact("$buildDir/outputs/aar/bar-release.aar")
        }
    }
    repositories {
        maven {
            url "$buildDir/repo"
        }
    }
}

Publish with ./gradlew clean build publish

Solution 2 - Android

Current answer

With Android Gradle Plugin 7.1 it is now very simple to do this without needing any complicated scripts. AGP now also handles creating source and javadocs jar.

You don't need any separate scripts, just write everything into your build.gradle file of your module:

plugins {
    ...
    id 'maven-publish'
}
android {
    ...
    publishing {
        singleVariant("release") {
            // if you don't want sources/javadoc, remove these lines
            withSourcesJar()
            withJavadocJar()
        }
    }
}
afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                from components.release
                groupId 'com.example'
                artifactId 'mylibrary'
                version = android.defaultConfig.versionName // or manually '1.0'
            }
        }
    }
}

See also: https://developer.android.google.cn/studio/build/maven-publish-plugin


Old answer

Updated 3.3.2020:

Since release of Android Studio 3.6 the support for building AAR (or even APK and AAB) is implemented in Android Gradle plugin 3.6.0 (and newer).

We don't need to handle the XML dependencies and stuff ourselves anymore.

Here is my updated Gist for Android Studio 3.6.0: https://gist.github.com/Robyer/a6578e60127418b380ca133a1291f017

Code from gist:

apply plugin: 'maven-publish'

task androidJavadocs(type: Javadoc) {
	source = android.sourceSets.main.java.srcDirs
	classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
	android.libraryVariants.all { variant ->
		if (variant.name == 'release') {
			owner.classpath += variant.javaCompileProvider.get().classpath
		}
	}
	exclude '**/R.html', '**/R.*.html', '**/index.html'
}

task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
	archiveClassifier.set('javadoc')
	from androidJavadocs.destinationDir
}

task androidSourcesJar(type: Jar) {
	archiveClassifier.set('sources')
	from android.sourceSets.main.java.srcDirs
}

// Because the components are created only during the afterEvaluate phase, you must
// configure your publications using the afterEvaluate() lifecycle method.
afterEvaluate {
	publishing {
		publications {
			// Creates a Maven publication called "release".
			release(MavenPublication) {
				// Applies the component for the release build variant.
				from components.release

				// Adds javadocs and sources as separate jars.
				artifact androidJavadocsJar
				artifact androidSourcesJar

				// You can customize attributes of the publication here or in module's build.gradle file (if you save this as script and include it build.gradle file, then you can just replicate this whole block there only with changed fields).
				//groupId = 'com.example'
				//artifactId = 'custom-artifact'
				version = android.defaultConfig.versionName // or just '1.0'
			}
		}
	}
}

Old answer:

Here is my improved solution, based on other answers.

Gist: https://gist.github.com/Robyer/a6578e60127418b380ca133a1291f017

Changes from other answers:

  • Changed classifier - it must be "sources" (not "source")
  • Handles dependencies
    • Supports also @aar and transitive: false. In that case we set exclusion in POM to ignore all transitive dependencies of this dependency.

    • Supports also custom exclude rules on dependencies, e.g.:

        compile('com.example:something:1.0', {
        	exclude group: 'com.exclude.this', module: 'some-module'
        })
      
  • Doesn't need to specify artifact path manually.

Changelog:

  • 27.3.2018 - Added support for api / implementation dependencies in new Gradle
  • 23.11.2018 - Renamed bundleRelease to bundleReleaseAar as it was changed in new Gradle (see this answer)
  • 23.11.2018 - Changed getAllDependencies to getDependencies to fix duplicated resulting entries (as mentioned in comments on my Gist).
  • 23.04.2019 - Wrapped in project.afterEvaluate{...} to fix it for new Gradle.

apply plugin: 'maven-publish'

task androidJavadocs(type: Javadoc) {
	source = android.sourceSets.main.java.srcDirs
	classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
	android.libraryVariants.all { variant ->
		if (variant.name == 'release') {
			owner.classpath += variant.javaCompile.classpath
		}
	}
	exclude '**/R.html', '**/R.*.html', '**/index.html'
}

task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
	classifier = 'javadoc'
	from androidJavadocs.destinationDir
}

task androidSourcesJar(type: Jar) {
	classifier = 'sources'
	from android.sourceSets.main.java.srcDirs
}

project.afterEvaluate {
    publishing {
    	publications {
    		maven(MavenPublication) {
    			//groupId 'cz.example'
    			//artifactId 'custom-artifact'
    			//version = android.defaultConfig.versionName
    
    			artifact bundleReleaseAar
    			artifact androidJavadocsJar
    			artifact androidSourcesJar
    
    			pom.withXml {
    				final dependenciesNode = asNode().appendNode('dependencies')
    
    				ext.addDependency = { Dependency dep, String scope ->
    					if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified")
    						return // ignore invalid dependencies
    
    					final dependencyNode = dependenciesNode.appendNode('dependency')
    					dependencyNode.appendNode('groupId', dep.group)
    					dependencyNode.appendNode('artifactId', dep.name)
    					dependencyNode.appendNode('version', dep.version)
    					dependencyNode.appendNode('scope', scope)
    
    					if (!dep.transitive) {
    						// If this dependency is transitive, we should force exclude all its dependencies them from the POM
    						final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
    						exclusionNode.appendNode('groupId', '*')
    						exclusionNode.appendNode('artifactId', '*')
    					} else if (!dep.properties.excludeRules.empty) {
    						// Otherwise add specified exclude rules
    						final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
    						dep.properties.excludeRules.each { ExcludeRule rule ->
    							exclusionNode.appendNode('groupId', rule.group ?: '*')
    							exclusionNode.appendNode('artifactId', rule.module ?: '*')
    						}
    					}
    				}
    
    				// List all "compile" dependencies (for old Gradle)
    				configurations.compile.getDependencies().each { dep -> addDependency(dep, "compile") }
    				// List all "api" dependencies (for new Gradle) as "compile" dependencies
    				configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
    				// List all "implementation" dependencies (for new Gradle) as "runtime" dependencies
    				configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
    			}
    		}
    	}
    }
}

Solution 3 - Android

A little tweak to dskinners answer with correct dependency generation:

apply plugin: 'maven-publish'

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier "source"
}

publishing {
    publications {
        bar(MavenPublication) {
            groupId 'com.foo'
            artifactId 'bar'
            version '0.1'
            artifact(sourceJar)
            artifact("$buildDir/outputs/aar/bar-release.aar")
            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')
                //Iterate over the compile dependencies (we don't want the test ones), adding a <dependency> node for each
                configurations.compile.allDependencies.each {
                    if(it.group != null && (it.name != null || "unspecified".equals(it.name)) && it.version != null)
                    {
                        def dependencyNode = dependenciesNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', it.group)
                        dependencyNode.appendNode('artifactId', it.name)
                        dependencyNode.appendNode('version', it.version)
                    }
                }
            }
        }
    }
    repositories {
        maven {
            url "$buildDir/repo"
        }
    }
}

And you can change version and groupId by defining:

version = '1.0.0'
group = 'foo.bar'

Solution 4 - Android

If you want to avoid boilerplate codes, because the maven-publish plugin do not write dependencies into pom.xml

Try this plugin: android-maven-publish

publishing {
    publications {
        mavenAar(MavenPublication) {
            groupId 'com.example'
            artifactId 'mylibrary'
            version '1.0.0'
            from components.android
        }
    }

    repositories {
        maven {
            url "$buildDir/releases"
        }
    }
}

Update:

android-maven-publish plugin is deprecated, since maven-publish is officially supported by AGP.

Solution 5 - Android

This is how I included Dokka (view it online) and sources JARs for my Android Kotlin library using Kotlin DSL (build.gradle.kts):

plugins {
    // ...
    id("org.jetbrains.dokka") version "1.4.32"
    id("maven-publish")
}

lateinit var sourcesArtifact: PublishArtifact
lateinit var javadocArtifact: PublishArtifact

tasks {
    val sourcesJar by creating(Jar::class) {
        archiveClassifier.set("sources")
        from(android.sourceSets["main"].java.srcDirs)
    }
    val dokkaHtml by getting(org.jetbrains.dokka.gradle.DokkaTask::class)
    val javadocJar by creating(Jar::class) {
        dependsOn(dokkaHtml)
        archiveClassifier.set("javadoc")
        from(dokkaHtml.outputDirectory)
    }
    artifacts {
        sourcesArtifact = archives(sourcesJar)
        javadocArtifact = archives(javadocJar)
    }
}

publishing {
    // ...
    publications {
        create<MavenPublication>("MyPublication") {
            from(components["release"])
            artifact(sourcesArtifact)
            artifact(javadocArtifact)
            // ...
        }
    }
}

Solution 6 - Android

You can also use the android maven plugin. It creates the .aar, javadoc.jar, sources.jar and .pom and updates the maven-metadata.xml after uploading the files to the maven repository. I also put the script on GitHub.

apply plugin: 'com.android.library'
apply plugin: 'maven'

//Your android configuration
android {
    //...
}

//maven repository info
group = 'com.example'
version = '1.0.0'

ext {
    //Specify your maven repository url here
    repositoryUrl = 'ftp://your.maven.repository.com/maven2'
    //Or you can use 'file:\\\\C:\\Temp' or 'maven-temp' for a local maven repository
}

//Upload android library to maven with javadoc and android sources
configurations {
    deployerJars
}

//If you want to deploy to an ftp server
dependencies {
    deployerJars "org.apache.maven.wagon:wagon-ftp:2.2"
}

// custom tasks for creating source/javadoc jars
task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
    destinationDir = file("../javadoc/")
    failOnError false
}

task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}

//Creating sources with comments
task androidSourcesJar(type: Jar) {
    classifier = 'sources'
    from android.sourceSets.main.java.srcDirs
}

//Put the androidSources and javadoc to the artifacts
artifacts {
    archives androidSourcesJar
    archives javadocJar
}

uploadArchives {
    repositories {
        mavenDeployer {
            configuration = configurations.deployerJars
            repository(url: repositoryUrl) {
            	//if your repository needs authentication
                authentication(userName: "username", password: "password")
            }
        }
    }
}

Call it with

./gradlew uploadArchives

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionligiView Question on Stackoverflow
Solution 1 - AndroiddskinnerView Answer on Stackoverflow
Solution 2 - AndroidRobyerView Answer on Stackoverflow
Solution 3 - AndroidjoecksView Answer on Stackoverflow
Solution 4 - AndroidbvargaView Answer on Stackoverflow
Solution 5 - AndroidMahozadView Answer on Stackoverflow
Solution 6 - AndroidPhilipp HofmannView Answer on Stackoverflow