How to publish Android library to Maven central warehouse

preface

This article is used to record how to upload your library to maven central warehouse,

  • First, we need to register the jira account of sonatype, and then apply to create our own repo. After the official review, we can have our own space;
  • We use gradle's maven publish and signing plug-ins to simplify the operation of packaging and uploading. After configuration, we can upload to the maven warehouse through gradle task;
  • When uploading, you can choose to upload to the snapshot storage area or staging storage area. These two storage areas can be accessed immediately after uploading. The snapshot area can be accessed publicly, while staging can only be used by yourself or someone with permission. You need to verify the user name and password;
  • If you need to publish the version of staging area to everyone, you can publish it through the release operation on sonatype website;

Create sonatype account and group

reference resources: OSSRH Guide - The Central Repository Documentation (sonatype.org)

  1. Create Jira account

  2. Create issues: Create question - Sonatype JIRA

The warehouse can be uploaded only after the issues are created and approved by the administrator. After creation, it is equivalent to having a group. After that, other items can be uploaded to the group without creating issues again;

After creating new issues, the system will reply and process them according to the requirements of the reply. After passing the reply, there will be a similar reply:

com.github.hanlyjiang has been prepared, now user(s) hanlyjiang can:
Deploy snapshot artifacts into repository https://oss.sonatype.org/content/repositories/snapshots
Deploy release artifacts into the staging repository https://oss.sonatype.org/service/local/staging/deploy/maven2
Release staged artifacts into repository 'Releases'
please comment on this ticket when you promoted your first release, thanks

That means we have our own group;

Generate signature key

All files uploaded to the warehouse must be signed, otherwise they cannot be published. Therefore, we need to generate keys for signature, and push the keys to the public key server, which can be accessed by the sonatype server for verification;

Generate key

You need to enter the password in the process. Please remember the password after entering

gpg --gen-key
gpg (GnuPG) 2.2.22; Copyright (C) 2020 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: Key box'/Users/hanlyjiang/.gnupg/pubring.kbx'Created
 Note: use“ gpg --full-generate-key" To get a fully functional key generation dialog box.

GnuPG You need to build a user ID to identify your key.

Real name: hanlyjiang
 e-mail address: hanlyjiang@outlook.com
 You have selected this user ID:
    "hanlyjiang <hanlyjiang@outlook.com>"

Change name( N),Notes( C),Email address( E)Or determine( O)/Quit( Q)? o
 We need to generate a large number of random bytes. Do something else during prime generation (tap the keyboard)
,It would be a good idea to move the mouse, read and write hard disk, etc; This makes random numbers
 The generator has a better chance of obtaining enough entropy.
We need to generate a large number of random bytes. Do something else during prime generation (tap the keyboard)
,It would be a good idea to move the mouse, read and write hard disk, etc; This makes random numbers
 The generator has a better chance of obtaining enough entropy.
gpg: secret key E8A99FE282B70849 Marked as absolute trust
gpg: Revocation certificate has been stored as'/Users/hanlyjiang/.gnupg/openpgp-revocs.d/0B372361CC1A9AE2452D43FDE8A99FE282B70849.rev'
The public and private keys have been generated and signed.

pub   rsa3072 2021-05-24 [SC] [Valid until: 2023-05-24]
      0B372361CC1A9AE2452D43FDE8A99FE282B70849
uid                      hanlyjiang <hanlyjiang@outlook.com>
sub   rsa3072 2021-05-24 [E] [Valid until: 2023-05-24]

List key s

gpg --list-keys
gpg: Checking trust database
gpg: Absolute trust key 8 F5EC255E5A0D063 Public key not found for
gpg: Absolute trust key 4150 E419D483B9A6 Public key not found for
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: Depth: 0 validity: 3 signed: 0 Trust: 0-,0q,0n,0m,0f,3u
gpg: The next trust database check will take place in 2023-05-24 conduct
/Users/hanlyjiang/.gnupg/pubring.kbx
------------------------------------
pub   rsa3072 2021-05-24 [SC] [Valid until: 2023-05-24]
      0B372361CC1A9AE2452D43FDE8A99FE282B70849
uid           [ absolutely ] hanlyjiang <hanlyjiang@outlook.com>
sub   rsa3072 2021-05-24 [E] [Valid until: 2023-05-24]

Send key to server

Tips:

If a key server doesn't work, you can change it and do it again. As long as you upload one successfully.

The key needs to be sent to the server so that sonatype can obtain and verify the signature. Upload it through the following command:

Set key information:

KEY_SERVER=hkp://pool.sks-keyservers.net
KEY_ID=0B372361CC1A9AE2452D43FDE8A99FE282B70849

Upload:

$ gpg --keyserver $KEY_SERVER --send-keys $KEY_ID
gpg: Sending key E8A99FE282B70849 reach hkp://pool.sks-keyservers.net

Check for success:

KEY_SERVER=hkp://pool.sks-keyservers.net
gpg --keyserver $KEY_SERVER --recv-keys $KEY_ID

Available key servers:

hkp://keyserver.ubuntu.com
hkp://pool.sks-keyservers.net
hkp://keys.openpgp.org
hkp://keys.gnupg.net
hkp://keys.openpgp.org

Export key

Export public key:

gpg -a -o ~/.gnupg/maven-pub.key --export $KEY_ID

Export private key: (password required)

gpg -a -o ~/.gnupg/maven-prv.key --export-secret-keys $KEY_ID

Export gpgkey:

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

gradle configuration

The official instructions for uploading under Gradle are here Gradle - The Central Repository Documentation (sonatype.org) , maven plug-in is used in this article, and there is another maven publish plug-in. We use maven publish plug-in;

gradle.properties

Modify gradle configuration:

#  ~/. gradle/gradle.properties write the following:

ossrhUsername=hanlyjiang # jira's user name
ossrhPassword=#jira's password

# Last 8 bits of public key ID: 0B372361CC1A9AE2452D43FDE8A99FE282B70849
signing.keyId=82B70849
signing.password=generate key Password at
signing.secretKeyRingFile=/Users/hanlyjiang/.gnupg/secring.gpg

The user name and password can be named at will, as long as you are in build Gradle corresponds to

The configuration of signing needs to keep the same name.

build.gradle:

reference resources:

  • [using Maven Publish plugin | Android developers | Android developers (Google. CN)]( https://developer.android.google.cn/studio/build/maven-publish-plugin?hl=zh -Cn#: ~: text = use Maven Publish plug-in Android Gradle plug-in 3.6.0. Gradle plug-in will create a component for each build variant artifact in the application or library module. You can use it to customize the publishing content to be published to Maven code base.)
  • Maven Publish Plugin (gradle.org)

We need to configure in the items that need to be uploaded. Note that the information in the pom also needs to be completed. Otherwise, it cannot pass the inspection of sonatype and cannot be published after uploading;

⚠️ Note: some people's upload address may be https://s01.oss.sonatype.org Domain name, such as:

  • https://s01.oss.sonatype.org/content/repositories/snapshots
    
  • https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
    
plugins {
    id 'com.android.library'
    id 'signing'
    id 'maven-publish'
}

def VERSION="1.0.1"
android {
    defaultConfig {
        minSdkVersion 22
        targetSdkVersion 30
        versionCode 1
        versionName VERSION
    }
}


// Because the components are created only during the afterEvaluate phase, you must
// configure your publications using the afterEvaluate() lifecycle method.
afterEvaluate {
    publishing {
        repositories {
            maven {
                name "local"
                // change to point to your repo, e.g. http://my.org/repo
                url = "$buildDir/repo"
            }
            maven {
                name "sonartype-Staging"
                // change to point to your repo, e.g. http://my.org/repo
                url = "https://oss.sonatype.org/service/local/staging/deploy/maven2"
              //  https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
                credentials {
                    username = ossrhUsername
                    password = ossrhPassword
                }
            }
			// Define snapshot warehouse
            maven {
                name "sonatype-Snapshots"
                // change to point to your repo, e.g. http://my.org/repo
                url = "https://oss.sonatype.org/content/repositories/snapshots/"
                credentials {
                    username = ossrhUsername
                    password = ossrhPassword
                }
            }
        }
        publications {
            // Creates a Maven publication called "release".
            release(MavenPublication) {
                // Applies the component for the release build variant.
                from components.release

                // You can then customize attributes of the publication as shown below.
                groupId = 'com.github.hanlyjiang'
                artifactId = 'apf_library'
                version = VERSION
                pom {
                    name = 'HJ Android Plugin Framework'
                    description = 'A Android Plugin Framework'
                    url = 'https://github.com/hanlyjiang/apf-library'
                    licenses {
                        license {
                            name='The Apache Software License, Version 2.0'
                            url='http://www.apache.org/licenses/LICENSE-2.0.txt'
                        }
                    }
                    developers {
                        developer {
                            id = 'hanlyjiang'
                            name = 'hanly jiang'
                            email = 'hanlyjiang@outlook.com'
                        }
                    }
                    scm {
                        connection = 'https://github.com/hanlyjiang/apf-library'
                        developerConnection = 'https://github.com/hanlyjiang/apf-library.git'
                        url = 'https://github.com/hanlyjiang/apf-library'
                    }
                }
            }
            // Creates a Maven publication called "debug".
            debug(MavenPublication) {
                // Applies the component for the debug build variant.
                from components.debug

                groupId = 'com.github.hanlyjiang'
                artifactId = 'apf_library-debug'
                version = String.format("%s-SNAPSHOT",VERSION)

                pom {
                    name = 'HJ Android Plugin Framework'
                    description = 'A Android Plugin Framework'
                    url = 'https://github.com/hanlyjiang/apf-library'
                    licenses {
                        license {
                            name='The Apache Software License, Version 2.0'
                            url='http://www.apache.org/licenses/LICENSE-2.0.txt'
                        }
                    }
                    developers {
                        developer {
                            id = 'hanlyjiang'
                            name = 'hanly jiang'
                            email = 'hanlyjiang@outlook.com'
                        }
                    }
                    scm {
                        connection = 'https://github.com/hanlyjiang/apf-library'
                        developerConnection = 'https://github.com/hanlyjiang/apf-library.git'
                        url = 'https://github.com/hanlyjiang/apf-library'
                    }
                }
            }
        }

        signing {
            sign publishing.publications.release , publishing.publications.debug
        }
    }
}

Note the following in the publishing script configuration:

  • groupId: you need to configure the groupId you applied for;
  • artifactId: it needs to be modified to the artifactId of your own project;
  • The file description in pom needs to be modified to the description of your own project;
  • The repositories section is configured with the user name and password corresponding to the remote warehouse. The publishing address needs to be modified according to whether it is a new project. The domain name of the old project is oss.sonatype.org , the domain name of the new project is: s01.oss.sonatype.org
  • The signing part needs to configure the corresponding gpg key and account information

Notes on maven warehouse:

  1. The version number of the library uploaded from the snapshots warehouse needs to end with - SNAPSHOT, otherwise a 400 error may occur;

Execute upload task

Execute the task corresponding to gradle to upload

$ module=apf-library; ./gradlew "$module":publishReleasePublicationToCenterRepository

For the specific generated tasks, you can view the publishing grouped tasks in the Gradle tool window of Android studio; Or view through the following command

$ module=apf-library; ./gradlew "$module":tasks| grep -E "publish|generate"

generateMetadataFileForDebugPublication - Generates the Gradle metadata file for publication 'debug'.
generateMetadataFileForReleasePublication - Generates the Gradle metadata file for publication 'release'.
generatePomFileForDebugPublication - Generates the Maven POM file for publication 'debug'.
generatePomFileForReleasePublication - Generates the Maven POM file for publication 'release'.
publish - Publishes all publications produced by this project.
publishAllPublicationsToCenterRepository - Publishes all Maven publications produced by this project to the center repository.
publishAllPublicationsToLocalRepository - Publishes all Maven publications produced by this project to the local repository.
publishDebugPublicationToCenterRepository - Publishes Maven publication 'debug' to Maven repository 'center'.
publishDebugPublicationToLocalRepository - Publishes Maven publication 'debug' to Maven repository 'local'.
publishDebugPublicationToMavenLocal - Publishes Maven publication 'debug' to the local Maven repository.
publishReleasePublicationToCenterRepository - Publishes Maven publication 'release' to Maven repository 'center'.
publishReleasePublicationToLocalRepository - Publishes Maven publication 'release' to Maven repository 'local'.
publishReleasePublicationToMavenLocal - Publishes Maven publication 'release' to the local Maven repository.
publishToMavenLocal - Publishes all Maven publications produced by this project to the local Maven cache.

release

  • close
  • release

reference resources:

First of all, we need to explain what the release here means: after our warehouse is uploaded, it is actually a place where it is stored with a temporary independent and public warehouse, which can only be accessed by ourselves. If we need to provide the warehouse to others, we need to release it; The publishing process can be operated manually on the web page or through the command line;

Publish the operation on the website that needs to take sonatype. The following are the operation steps:

  • open Nexus Repository Manager (sonatype.org)

  • Then log in. After logging in, you can see the Build Promotion menu, and then open the Staging Repository, where the uploaded warehouses will be displayed:

  • Select a staging repo, click Close, and confirm

  • The results can be viewed in the Activity

The above error is that the key cannot be found. We upload the key to ubuntu again:

gpg --keyserver $KEY_SERVER --send-keys 0B372361CC1A9AE2452D43FDE8A99FE282B70849
gpg --keyserver $KEY_SERVER --recv-keys 0B372361CC1A9AE2452D43FDE8A99FE282B70849

Then close again

  • Then release

  • After completion, an email notification will be sent, and then the issues of the project created on jira will be updated:

Introduction and use

Introducing a snapshot or staging version

You can confirm whether your library has been uploaded successfully through the following path (please replace the following path with your own):

  • Snapshot: https://oss.sonatype.org/content/repositories/snapshots/com/github/hanlyjiang/
  • Release: https://repo1.maven.org/maven2/com/github/hanlyjiang/

The versions in the repository of snapshot and staging can be accessed immediately after push, but they can only be accessed by themselves. The user name and password need to be verified.

Add the following repo configuration:

allprojects {
    repositories {
        maven {
            name = "Sonatype-Snapshots"
            setUrl("https://oss.sonatype.org/content/repositories/snapshots")
//            setUrl("https://s01.oss.sonatype.org/content/repositories/snapshots")
            credentials(PasswordCredentials::class.java) {
                username = property("ossrhUsername").toString()
                password = property("ossrhPassword").toString()
            }
        }
        maven {
            name = "Sonatype-Staging"
            setUrl("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
//            setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials(PasswordCredentials::class.java) {
                username = property("ossrhUsername").toString()
                password = property("ossrhPassword").toString()
            }
        }
        google()
        jcenter()
        mavenCentral()
    }
}

Introduce release version

If you need to release it publicly to yourself or others, you need to release it. There is a certain time period before you can access it after the release operation. The following is the official email received after a release:

Central sync is activated for com.github.hanlyjiang. After you successfully release, your component will be published to Central https://repo1.maven.org/maven2/, typically within 10 minutes, though updates to https://search.maven.org can take up to two hours.

In other words, it takes about 10 minutes to query from the Maven central warehouse and 2 hours to search from the web. You can visit: https://search.maven.org To search, you can access https://repo1.maven.org/maven2/ To confirm whether it is indexed. If it is indexed, it can be introduced into the project;

For use in gradle, you only need to import mavenCenter();

rootProject build.gradle:

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}

app build.gradle:

implementation("com.github.hanlyjiang:apf_library:1.0")

Used in kotlin

to configure

import org.gradle.api.publish.maven.MavenPom

plugins {
    id("com.android.library")
    id("signing")
    `maven-publish`
//    kotlin("android")
//    kotlin("android.extensions")
}

android {
    // Omit android configuration
}

fun getMyPom(): Action<in MavenPom> {
    return Action<MavenPom> {
        name.set("Android Common Utils Lib")
        description.set("Android Common Utils Library For HJ")
        url.set("https://github.com/hanlyjiang/lib_common_utils")
        properties.set(mapOf(
                "myProp" to "value",
                "prop.with.dots" to "anotherValue"
        ))
        licenses {
            license {
                name.set("The Apache License, Version 2.0")
                url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
            }
        }
        developers {
            developer {
                id.set("hanlyjiang")
                name.set("Hanly Jiang")
                email.set("hanlyjiang@outlook.com")
            }
        }
        scm {
            connection.set("scm:git:git://github.com/hanlyjiang/lib_common_utils.git")
            developerConnection.set("scm:git:ssh://github.com/hanlyjiang/lib_common_utils.git")
            url.set("https://github.com/hanlyjiang/lib_common_utils")
        }
    }
}

afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("release") {
                from(components.getByName("release"))
                groupId = "com.github.hanlyjiang"
                artifactId = "android_common_utils"
                version = android.defaultConfig.versionName
                pom(getMyPom())
            }
        }

        repositories {
            val ossrhCredentials = Action<PasswordCredentials> {
                username = properties["ossrhUsername"].toString()
                password = properties["ossrhPassword"].toString()
            }
            // The warehouse address of sonar determines whether it is a snapshot or an official warehouse according to the version number of the project
            maven {
                name = "Sonartype"

                val releasesRepoUrl = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
                val snapshotsRepoUrl = uri("https://oss.sonatype.org/content/repositories/snapshots/")
                url = if (android.defaultConfig.versionName.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
                credentials(ossrhCredentials)
                // Address of snapshot:
                // https://oss.sonatype.org/content/repositories/snapshots/com/github/hanlyjiang/android_common_utils/
            }
            // Local warehouse of the project
            maven {
                name = "ProjectLocal"

                val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases"))
                val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots"))
                url = if (android.defaultConfig.versionName.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
            }
        }
    }
    // https://stackoverflow.com/questions/54654376/why-is-publishing-function-not-being-found-in-my-custom-gradle-kts-file-within

    signing {
        sign(publishing.publications.getByName("release"))
    }

}

Question:

A problem occurred configuring project ':lib_common_utils'.
> SoftwareComponentInternal with name 'java' not found.

maven plugin - Why is 'publishing' function not being found in my custom gradle.kts file within buildSrc directory? - Stack Overflow

Maven Publish Plugin (gradle.org)

Upload javadoc and source

Add tasks for javadoc and jarsource

tasks.register("javadoc", Javadoc::class.java) {
    group = "publishing"
    dependsOn("assemble")
    source = android.sourceSets["main"].java.getSourceFiles()
    classpath += project.files(android.bootClasspath + File.pathSeparator)
    if (JavaVersion.current().isJava9Compatible) {
        (options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
    }
    android.libraryVariants.forEach { libraryVariant ->
        classpath += libraryVariant.javaCompileProvider.get().classpath
    }
    options.apply {
        encoding("UTF-8")
        charset("UTF-8")
        isFailOnError = false

        (this as StandardJavadocDocletOptions).apply {
            addStringOption("Xdoclint:none")
            links?.add("https://developer.android.google.cn/reference/")
            links?.add("http://docs.oracle.com/javase/8/docs/api/")
        }
    }
}

tasks.register("jarSource", Jar::class.java) {
    group = "publishing"
    from(android.sourceSets["main"].java.srcDirs)
    archiveClassifier.set("sources")
}

tasks.register("jarJavadoc", Jar::class.java) {
    group = "publishing"
    dependsOn("javadoc")
    val javadoc: Javadoc = tasks.getByName("javadoc") as Javadoc
    from(javadoc.destinationDir)
    archiveClassifier.set("javadoc")
}

Used in publish

afterEvaluate {
    publishing {
        publications {
            create<MavenPublication>("release") {
                from(components.getByName("release"))
                groupId = "com.github.hanlyjiang"
                artifactId = "android_common_utils"
                version = android.defaultConfig.versionName
                pom(getMyPom())
                // Add javadoc
                artifact(tasks.getByName("jarJavadoc") as Jar)
                // Add source
                 artifact(tasks.getByName("jarSource") as Jar)
            }
        }

        repositories {
            val ossrhCredentials = Action<PasswordCredentials> {
                username = properties["ossrhUsername"].toString()
                password = properties["ossrhPassword"].toString()
            }
            // The warehouse address of sonar determines whether it is a snapshot or an official warehouse according to the version number of the project
            maven {
                name = "Sonartype"

                val releasesRepoUrl = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2")
                val snapshotsRepoUrl = uri("https://oss.sonatype.org/content/repositories/snapshots/")
                url = if (android.defaultConfig.versionName.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
                credentials(ossrhCredentials)
                // Address of snapshot:
                // https://oss.sonatype.org/content/repositories/snapshots/com/github/hanlyjiang/android_common_utils/
            }
            // Local warehouse of the project
            maven {
                name = "ProjectLocal"

                val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases"))
                val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots"))
                url = if (android.defaultConfig.versionName.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
            }
        }
    }
    // https://stackoverflow.com/questions/54654376/why-is-publishing-function-not-being-found-in-my-custom-gradle-kts-file-within


    signing {
        sign(publishing.publications.getByName("release"))
    }

}

❌ Error record

400 error

maven publish Received status code 400 from server

Possible reasons: maven warehouse is divided into two, one is snapshot warehouse and the other is Release warehouse. If the package of snapshot Version (version number with snapshot) is uploaded to the address of Release warehouse, an error will be reported.

Reference articles

Keywords: Android Gradle Maven

Added by Caps on Thu, 24 Feb 2022 17:13:41 +0200