A Complete Guide to Create and Publish an Android Library to Maven Central

Zubair Rehman
13 min readMay 16, 2018

--

In order for users of Maven to utilize artifacts produced by your project, you must deploy them to a remote repository. Many open source projects want to allow users of their projects who build with Maven to have transparent access to their project’s artifacts. In order to allow for this, a project must have their artifacts deployed to the Central Repository.

For this tutorial I am going to use Android Studio as my development environment, which uses Gradle as a build system, If you don’t have Android Studio then go to developers.android website and download the latest version of it. We will also be using Git, which is distributed version control system.

Terminologies

Before we get started let me define few terminologies that we are going to use in this tutorial.

Project — A project in Android Studio represents a complete Android app. Android Studio projects consist of one or more modules. A project in Android Studio is like a workspace in Eclipse.

Module — A module is a component of your app that you can build, test, or deploy independently. Module contains the source code and resources for your app. A module in Android Studio is like a project in Eclipse.

AAR — The ‘aar’ bundle is the binary distribution of an Android Library Project. (AAR Format) A Library project’s main output is an .aar package (which stands for Android archive). It is a combination of compile code (as a jar file and/or native .so files) and resources (manifest, res, assets).

Maven Central Repository — A repository provided by the Maven community. It contains a large number of commonly used libraries. Maven Central Repository is often referred to as Maven Central or the Central Repository.

OSSRH — Sonatype’s Open Source Software Repository Hosting (OSSRH) service is the primary avenue for project owners and contributors to publish their components to the Central Repository. It is a hosted deployment of Sonatype Nexus Professional with the Nexus Staging Suite used for the deployment process and validation, combined with a sync process to the Central Repository content delivery network.

GPG/GnuPG — GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP). GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories. GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. For more on GPG, see the GNU Privacy Guard website.

Prepare your Android library

For this tutorial I am going to use my Arithmetic library as an example. There are few modifications that we need to make to our project to make it ready to be published as a library to Maven Central.

Separate the code that is core to the library and code that shows sample usage of the library. In my project, I separated these out into two separate modules library and app. Checkout out the tips on setting up a library module.

In the app module’s build.gradle file, make sure to include the library module

Method 1:

  • Right-click your app in project view and select “Open Module Settings
  • Click the “Dependencies” tab and then the ‘+’ button
  • Select “Module Dependency
  • Select your library module and press “OK”.

Method 2:

  • On the root of your project directory create/modify the settings.gradle file. It should contain something like the following:
include 'MyApp', ‘:library' 
  • gradle clean & build/close the project and reopen/re-import it.
  • Edit your project’s build.gradle to add this in the “dependencies” section:
dependencies {
compile project(‘:library')
}

In the library module’s build.gradle file, make sure to include

apply from: 'maven-push.gradle’

In the library module, add a gradle.properties file, and make sure to include

POM_NAME=ProjectName
POM_ARTIFACT_ID=projectname
POM_PACKAGING=aar

In the library module, add a maven-push.gradle file, and make sure to include

/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

apply plugin: 'maven'
apply plugin: 'signing'

def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}

def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}

def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}

def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}

def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}

afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME

repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}

pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL

scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}

licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}

developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}

signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}


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

artifacts {
archives androidSourcesJar
}
}

Modify the .gitignore file that is located at the root level of your project

# [Android] ========================
# Built application files
*.apk
*.ap_

# Files for the Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/

# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Proguard folder generated by Eclipse
proguard/

# Log Files
*.log


## Directory-based project format:
.idea/

## File-based project format:
*.ipr
*.iws

## Plugin-specific files:

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml


# [Maven] ========================
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties


# [Gradle-Android] ========================

# Ignore Gradle GUI config
gradle-app.setting

# Gradle Signing
signing.properties
trestle.keystore

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# Misc
/.idea/workspace.xml
.DS_Store
/captures
**/*.iml
*.class

Modify the settings.gradle file that is located at the root level of your project

include ':sample', ':library’

Modify gradle.properties file which is located at the root level of your project

# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

VERSION_NAME=0.0.1
VERSION_CODE=1
GROUP=com.example.application

POM_DESCRIPTION=A library that does X, Y, and Z
POM_URL=https://github.com/github_username/ProjectName
POM_SCM_URL=https://github.com/github_username/ProjectName
POM_SCM_CONNECTION=scm:git@github.com:github_username/ProjectName.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:github_username/ProjectName.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=github_username
POM_DEVELOPER_NAME=GitHub FullName

Note: You have to replace github_username with the appropriate values.

Add a README.md to explain to other developers what your library does and how to use it. If you need to add any screenshots to your README.mdI highly recommend using an app called Screenr to generate the screenshots.

Setup GPG

You need to sign your artifact so when people download it, they can be sure they are downloading the original artifact. You will need to download GPG if you do not already have. You can get it from here. Once downloaded, you will need to generate a passphrase, sign, generate key pair and distribute your public key.

To create a unique key for encrypting and decrypting files with GPG:

  • Make sure gpg-agent is running; on the command line, enter:
gpg-agent -s --daemon --write-env-file --use-standard-socket

You will see something like the following (in which username is your Network ID username and Machine is the name of the system):

GPG_AGENT_INFO=/N/u/username/Machine/.gnupg/S.gpg-agent:22743:1; export GPG_AGENT_INFO;
  • On the command line, enter:
gpg --gen-key

You will see something like the following (in which username is your Network ID username and Machine is the name of the system):

gpg (GnuPG) 2.0.14; Copyright (C) 2009 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: keyring `/N/u/username/Machine/.gnupg/secring.gpg' created
gpg: keyring `/N/u/username/Machine/.gnupg/pubring.gpg' created
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection?

Enter 1 to select the default key.

  • GPG will prompt you to choose a keysize (in bits). Enter 2048.
  • You will see:
Requested keysize is 1024 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)

Enter a value to specify how long the key should remain valid (e.g., 2 for two days, 3w for three weeks, 10m for 10 months, or 0 for no expiration date).

  • GPG will prompt you to confirm the expiration date. If the correct date is displayed, enter y.
  • GPG will prompt for information it will use to construct a user ID to identify your key. At the prompts, enter your name, email address, and a comment.
  • GPG will prompt you to confirm or correct your information. You’ll see a prompt something like this:
You selected this USER-ID:
"Full Name (comment) <username@iu.edu>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?
  • If the information displayed is correct, enter o to accept the user ID. To correct errors or quit the process, enter the appropriate alternative ( n , c , e, or q ).
  • If you accept the user ID, GPG will prompt you to enter and confirm a password. Afterward, GPG will begin generating your key. You’ll see:
We need to generate a lot of random bytes. It is a good idea to
perform some other action (type on the keyboard, move the mouse,
utilize the disks) during the prime generation; this gives the
random number generator a better chance to gain enough entropy.

This process may take several minutes to complete. When it’s finished, you will see something like:

gpg: key 09D2B839 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 4 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 4u
gpg: next trustdb check due at <expiration_date>
pub 1024R/09D2B839 2013-06-25 [expires: <expiration_date>]
Key fingerprint = 6AB2 7763 0378 9F7E 6242 77D5 F158 CDE5 09D2 B839
uid Full Name (comment) <username@indiana.edu>
sub 1024R/7098E4C2 2013-06-25 [expires: <expiration_date>]

You can now use the key (until it expires) to encrypt files in your account.

In-order to find your keys, type:

$ gpg --list-keys

The first line will be like pub XXXXX/YYYYYYYY <date>. Remember that YYYYYYYY part, it’s you key ID.

Now, publish your keys:

$ gpg --keyserver hkp://keyserver.ubuntu.com --send-keys YYYYYYYY
$ gpg --keyserver hkp://pgp.mit.edu --send-keys YYYYYYYY

You may use other key servers as well. You may also want to ensure your keys were published:

$ gpg --keyserver hkp://pgp.mit.edu --search-keys johndoe@example.com # Use your email

Note: For GPG version 2.2 or above run the below mentioned command after generating your keys

gpg --export-secret-keys >~/.gnupg/secring.gpg

This will produces the old-format file which both Gradle’s signing plugin and sbt-pgp can read

Create a Sonatype JIRA Ticket

Before you can get your artifact into Maven Central, It is needed to deploy it to Nexus repository for it to be synced into Maven Central Repository. Before that, you need to generate a ticket to register your groupId. Sign up here to generate a ticket.This may take up to two days.

Steps:

  1. Create a JIRA account on Sonatype
  2. Once you are logged in, create a new issue.

I have created a GitHub repo for my project Arithmetic . So I filled out the fields in the new issue like so :

Group Id : com.example.application
Project URL : https://github.com/<github_username>/<project_name>
SCM url : https://github.com/<github_username>/<project_name>.git
Username : <sonatype_username>
Already Synced to Central : No

Note: I have added brackets around fields as placeholders. You will need to substitute with the appropriate values.

When you submit the issue, the details of the issue should look similar to the screenshot above. After you submit, it can take up to 2 business days to process your issue. Then you will receive a confirmation that your configuration has been prepared and you can publish your library.

Do not deploy until after you have received an e-mail notice indicating that the ticket is Resolved. One of the most common problems related to new projects is premature deployment, which misroutes your artifacts to a catch-all repository.

Lastly, if your component is already in Central be sure to note this in your ticket, and review how to migrate to OSSRH. The SonaType OOSRH Guide has more details about this process.

The credentials for signing and upload can be stored in your gradle.properties file in your users home directory. The content would look like this

NEXUS_USERNAME=sonatype_username
NEXUS_PASSWORD=sonatype_password
signing.keyId=gpg_key_id
signing.password=gpg_password
signing.secretKeyRingFile=/Users/username/.gnupg/secring.gpg
org.gradle.daemon=true

Authentication is provided by this gradle.properties when publishing the library. Make sure you have provided the correct nexus username and password (this is the Sonatype user name and password) otherwise you get an unauthorized 401 error .

Note : If you have already published a library then you won’t need to create a new issue on JIRA(Sonatype). Only one JIRA issue per top-level groupId is necessary. You should already have all the necessary permissions to deploy any new artifacts to your groupId or to any sub-groups. The sync to Central applies from your top level groupid on down, so any releases to sub groups will automatically sync. There’s no need to tell Sonatype when you release a new component, since there’s nothing left for Sonatype to configure or check once your repo syncs, and they only post to twitter when the sync is first activated.

Releasing Deployment from OSSRH to the Central Repository

Publish

Once you are ready to publish your library, in Android Studio open the Gradle View on the right side. Under Tasks > upload click on uploadArchives. This will upload your library to the Sonatype Staging Repositories .

After a successful deployment to OSSRH your components are stored in a separate, temporary repository, that is private to your projects members. In order to get these components published you will have to ‘release’ them. If you find any issues upon examination in the repository, you can also Drop the staging repository. This allows you to re-run the deployments after fixing any issues found and avoids the publication of these components into the release repository and sub-sequentially into the Central Repository.

Typically this process is done manually, but it is also possible to trigger the actions from the command line.

Login into OSSRH

You need to login to OSSRH available at https://oss.sonatype.org/ in order to access and work with your staging repositories. Use the username and password from your account for the JIRA issue tracking system for OSSRH and the Login link in the top right hand corner of the OSSRH user interface.

Locate and Examine Your Staging Repository

Once you are logged in you will be able to access the Build Promotion menu in the left hand navigation and select the Staging Repositories item. The Staging Repositories tab with a long list of repositories will be displayed.

The staging repository you created during the deployment will have a name starting with the groupId for your projects with the dots removed appended with a dash and a 4 digit number. E.g. if your project groupId is com.example.applications, your staging profile name would start with comexampleapplications. The sequential numbers start at 1000 and are incremented per deployment so you could e.g. have a staging repository name of comexampleapplication-1010.

Select the staging repository and the panel below the list will display further details about the repository. In addition the buttons Close and Release will be activated.

Close and Drop or Release Your Staging Repository

After your deployment the repository will be in an Open status. You can evaluate the deployed components in the repository using the Contents tab. If you believe everything is correct you, can press the Close button above the list. This will trigger the evaluations of the components against the requirements.

Closing will fail if your components do not meet the requirements. If this happens, you can press Drop and the staging repository will be deleted. This allows you to correct any problems with the components and the deployment process and re-run the deployment. Details are available in the Activity tab below the list by selecting. Press on the individual steps for further details.

Once you have successfully closed the staging repository, you can release it by pressing the Release button. This will move the components into the release repository of OSSRH where it will be synced to the Central Repository.

If you are releasing the first time, do not forget to comment on the issue tracker ticket, letting us know that you have completed a release so we can activate your sync process.

Note: that deploying with the Nexus Staging Maven plugin or Ant tasks will, by default, automatically attempt to close the staging repository upon deployment and can be used to release the staging repository from the command line as well. This allows you to avoid logging into the OSSRH user interface altogether. More details can be found in the Maven and Ant sections of this guide.

Special Thanks

Special thanks to Chris Banes, Serge Zaitsev and others for their blog posts which helped me get through this tricky process.

--

--