Here are useful information and links about publishing an Android library on Maven Central. For years I have been publishing Android applications through slions.net, Google Play and F-Droid. However I had never published an Android library. This article is written by an outsider. In fact, a lot of that knowledge comes from the Java world whereas I'm more familiar with C# and Microsoft environments.
Maven
Maven is a software project management tool part of the Apache Software Foundation. My understanding is that it notably allows you to publish artifacts, such as library binaries, together with associated metadata. For those coming from the Microsoft world it is like NuGet for Java I suppose. There is a company called Sonatype who manages that Maven Central Repository which is where Android developers download most their dependencies through the tool chain.Publishing
Create your account
First of all you will need to create an account with Sonatype as explained on the register to publish via the central portal page.Create a namespace
Your library needs to belong to a namespace so you need to create it. For me this isnet.slions.android
, see namespace registration. The namespace verification process will require you to add a TXT record to your DNS as explained there.Signing
To pass the upload validation process you need to sign files such asjar
, aar
, module
and pom
. This must be done using GPG as explained there. You will need to:- Create OpenPGP key pair, public and private
- Publish your public key, so that Maven Central can validate your uploads
- Sign relevant files using ASCII format
From user interface
Create key pair
Go to menu File > New OpenPGP Key Pair…Publish public key
From your certificate context menu select Publish on Server…Sign files
Before signing go to Settings > Configure Kleopatra… > Crypto Operations and enable Create signed or encrypted files as text files.Failing to do so will generate
sig
files instead of the asc
files needed by Maven Central. That trick was notably mentioned on this forum thread.Then click Sign/Encrypt select the files you want to sign, disable encryption and enable Sign each file separately as shown below.
From command line
Everything you need to know about signing from the command line is explained there and summed up below.Create key pair
Rungpg --gen-key
and provide a name, an email and a password.Publish public key
Rungpg --keyserver keyserver.ubuntu.com --send-keys <key-id>
where key-id
is the one provided after generating your key or by running gpg --list-keys
.Sign files
Rungpg -ab <filename>
. Then type in your password. This will generate an asc
file you must provide in your zip upload. Not sure if and how you can specify the key you want to use for signing. I only had one key installed so it probably just picked that one.Gradle
We want to integrate the above with our Gradle tool chain so that our build process can output a zip file ready to upload to Maven Central for publishing.Your
build.gradle.kts
file should contain something like the following. You can also take a look at the latest version from our Preference library.
Kotlin:
plugins {
…
id("maven-publish")
id("signing")
}
val libVersion = "0.0.5"
android {
// Notably define R class namespace
namespace = "slions.pref"
compileSdk = 34
defaultConfig {
minSdk = 21
aarMetadata {
minCompileSdk = 29
}
}
publishing {
multipleVariants {
allVariants()
withJavadocJar()
}
}
}
// Define our publishing tasks which will generate our upload archive folder layout and content
// See: https://developer.android.com/build/publish-library/upload-library#create-pub
// See: https://docs.gradle.org/current/userguide/publishing_maven.html
publishing {
publications {
register<MavenPublication>("release") {
groupId = "net.slions.android"
artifactId = "preference"
version = libVersion
pom {
name = "Preference"
description = "Android preference extensions"
url = "https://github.com/Slion/Preference"
/*properties = mapOf(
"myProp" to "value",
"prop.with.dots" to "anotherValue"
)*/
licenses {
license {
name = "GNU Lesser General Public License v3.0"
url = "https://github.com/Slion/Preference/blob/main/LICENSE"
}
}
developers {
developer {
id = "Slion"
name = "Stéphane Lenclud"
email = "github@lenclud.com"
}
}
scm {
//connection = "scm:git:git://example.com/my-library.git"
//developerConnection = "scm:git:ssh://example.com/my-library.git"
url = "https://github.com/Slion/Preference"
}
}
afterEvaluate {
from(components["release"])
}
}
}
// That gives us a task named publishAllPublicationsToMavenRepository
repositories {
maven {
name = "maven"
url = uri(layout.buildDirectory.dir("maven"))
}
}
}
// Take care of signing our build artifacts.
// For each of our AAR, JAR, POM and MODULE files it will create a corresponding ASC file.
// For this to work you should setup your user level gradle.properties as explained there:
// https://docs.gradle.org/7.4.2/userguide/signing_plugin.html#sec:using_gpg_agent
// Should just specify key ID and password like that:
// signing.gnupg.keyName=<key-id>
// signing.gnupg.passphrase=<key-password>
signing {
// Use installed GPG rather than built-in outdated version
useGpgCmd()
// Sign all publications I guess
sign(publishing.publications)
//sign(publishing.publications["release"])
}
// Define a task to generate the ZIP we can upload to Maven Central
// It will create a file named preference.zip inside \build\distributions folder
// You can then upload it to https://central.sonatype.com/publishing/deployments for publishing
tasks.register<Zip>("generateUploadPackage") {
// Take the output of our publishing
val publishTask = tasks.named(
"publishReleasePublicationToMavenRepository",
PublishToMavenRepository::class.java)
from(publishTask.map { it.repository.url })
// Exclude maven-metadata.xml as Sonatype fails upload validation otherwise
exclude {
// Exclude left over directories not matching current version
// That was needed otherwise older versions empty directories would be include in our ZIP
if (it.file.isDirectory && it.path.matches(Regex(""".*\d+\.\d+.\d+$""")) && !it.path.contains(libVersion)) {
return@exclude true
}
// Only take files inside current version directory
// Notably excludes maven-metadata.xml which Maven Central upload validation does not like
(it.file.isFile && !it.path.contains(libVersion))
}
// Name our zip file
archiveFileName.set("preference.zip")
}
References
Last edited: