Publish an Android library on Maven Central

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 is net.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 as jar, 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
In theory all those operations can be performed from the GPG Kleopatra application but you will most certainly want to automate the signing itself as part of you build system. Moreover for some reason my upload could not be validated when I tried through the application. Therefore I really recommend the command line solution and Gradle integration below.

From user interface

Create key pair

Go to menu File > New OpenPGP Key Pair…
1712242318923.png

Publish public key

From your certificate context menu select Publish on Server…
1712242540506.png

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.
1712240939119.png

Then click Sign/Encrypt select the files you want to sign, disable encryption and enable Sign each file separately as shown below.

1712243047568.png


From command line

Everything you need to know about signing from the command line is explained there and sum up below.

Create key pair

Run gpg --gen-key and provide a name, an email and a password.

Publish public key

Run gpg --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

Run gpg -ab <filename>. Then type in your password. This will generate an asc file you must provide in your zip upload. Not 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:
Back
Top