最近在给我的开源库Aria升级版本时,发现Bintray已经无法上传库了,网上查了下,发现Jcenter要停止服务了,从2021年5月1号还要删除所有开发者上传的包,因此就需要将库移到其它的托管中心,谷歌建议开发者们将库托管到mavenCentral

image-20210414084724985

一、注册

首先,先去Sonatype Jira这个地址注册一个SonatypeJira的账号;

其次,账号创建后,登录,然后在这个页面https://issues.sonatype.org/projects 点击Create创建一个issue,如下图:

image-20210412154546506

二、域名验证

创建完成后,隔段时间,系统就会告诉你需要验证下域名

image-20210412164619506

创建一个TXT解析

image-20210412164550334

然后回复下

image-20210412164954498

审核成功后,系统会告诉你审核成功,并且工程的状态会变成RESOLVED

image-20210413085355532

三、配置GPG

3.1 Mac 配置

3.1.1下载 macGPG (可视化)或 GunPG for OS X(命令行,建议使用这个)

GnuPG - Download

image-20210413094957626

3.1.2 创建证书文件

在终端中输入

gpg --full-gen-key

加密方式选择RSA and RSA,长度输入4096,过期时间直接回车不用管,然后输入一个user ID并且提供一个邮箱,我直接用的我sonatype的用户名和邮箱。最后一步输入’O’,表示OK。
image-20210413133057154

之后会弹出一个对话框,让输入密码。

image-20210413133755882

创建完成后,会在~/.gnupg/openpgp-revocs.d/目录下创建一个.rev文件,记住pub的末尾8位。

image-20210413134020395

3.1.3 secring.gpg文件

终端中输入命令

gpg --export-secret-keys -o secring.gpg

输入证书密码

image-20210413134148529

3.1.4 发送密钥的keyserver

gpg --keyserver hkp://keyserver.ubuntu.com:11371 --send-keys 证书指纹

也就是创建证书时的pubID
image-20210413140619461

如果创建成功,会收到一份邮件

image-20210413140911764

或者使用下面命令查看证书是否上传成功

gpg --search-keys 证书指纹

3.1.5 保存证书文件

secring.gpg、`指纹信息、证书密码保存下来

image-20210413163557154

将来换电脑可以通过搜索密钥,重新倒入证书信息

image-20210413163324051

3.2 win 配置

3.2.1 下载 GPG

打开 https://www.gpg4win.org/ 下载 GPG,然后一步步安装。

安装完成后会看到桌面上多了个 Kleopatra 图标,这个程序是 GnuPG 的前端程序,这里我们将它打开。

3.2.2 配置密钥对

打开 Kleopatra 后,点击新建密钥对

maven-center-1007

输入姓名和邮箱

maven-center-1008

最后确认新建

maven-center-1009

过程中需要自己输入一个8位以上的密码,请记住该密码,后面还需要用到。 输入完成以后就会提示下面信息。

maven-center-1010

3.2.3上传公钥到 GPS key-servers

打开命令提示符窗口,输入下面命令将证书发送到远程服务器

gpg --keyserver hkp://keyserver.ubuntu.com:11371 --send-keys  243EFB60D8F930B391CA194EA40663B00BDC6CA1

maven-center-1011

四、编写上传脚本

4.1 添加依赖

修改项目根build.gradle,添加

classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.30" // kotlin 文档引擎
buildscript {
ext.kotlin_version = "1.4.30"
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// 需要的依赖
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.30" // kotlin 文档引擎
}
}

4.2 配置sonatype信息

打开local.properties,添加以下参数

signing.keyId=刚才获取的秘钥后8位
signing.password=步骤4中设置的密码
signing.secretKeyRingFile=刚才生成的secring.gpg文件路径
ossrhUsername=sonatype用户名
ossrhPassword=sonatype密码

4.3 配置打包信息

修改项目根build.gradle,添加

ext {
website = 'https://github.com/AriaLyy/KeepassA'

// mavenCentral 参数
mavenCentralUserID = 'xxx' // sonatype 用户id
mavenCentralUserName = 'xxx' // sonatype 用户名
mavenCentralGroupId = 'me.laoyuyu.keepassa' // groupId
mavenCentralEmail = 注册邮箱
mavenCentralLicences = 'MPL-2.0' // 开源协议
mavenCentralLicencesURL = 'http://mozilla.org/MPL/2.0/' // 开源协议地址
mavenCentralConnection = 'scm:git:github.com/用户名/项目名.git'
mavenCentralDeveloperConnection = 'scm:git:ssh://github.com/用户名/项目名.git'
mavenCentralTreeURL = 'https://github.com/AriaLyy/项目名/tree/master'

}

4.4 编写脚本

创建一个名为mavenCentral-release.gradle的脚本文件,需要grade 3.6以上的版本

image-20210413155309397

需要grade 3.6以上的版本。

Android脚本打包

maven-publish使用

apply plugin: 'maven-publish'
apply plugin: 'signing'

ext {
PUBLISH_GROUP_ID = rootProject.ext.mavenCentralGroupId
}

//编译groovy代码时采用 UTF-8
tasks.withType(GroovyCompile) {
groovyOptions.encoding = "UTF-8"
}
//编译JAVA文件时采用UTF-8
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}

// java doc 采用utf-8
// https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html#org.gradle.api.tasks.javadoc.Javadoc:options
tasks.withType(Javadoc) {
if(JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
options {
encoding "UTF-8"
charSet 'UTF-8'
links "http://docs.oracle.com/javase/7/docs/api"
addStringOption('Xdoclint:none', '-quiet') // 忽略检查@params 和 @return
}
}

ext["signing.keyId"] = ''
ext["signing.password"] = ''
ext["signing.secretKeyRingFile"] = ''
ext["ossrhUsername"] = ''
ext["ossrhPassword"] = ''

File secretPropsFile = project.rootProject.file('local.properties')
if (secretPropsFile.exists()) {
println "Found secret props file, loading props"
Properties p = new Properties()
p.load(new FileInputStream(secretPropsFile))
p.each { name, value ->
ext[name] = value
}
} else {
println "No props file, loading env vars"
}

static def localMavenRepo() {
'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath
}

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

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

def isLocal(){
return ISLOCAL == 'true'
}

afterEvaluate { project ->
// println("ISLOCAL = ${ISLOCAL}")
if (isLocal()) {
println("上传到本地仓库")
} else {
println("上传到中央仓库")
}
publishing {
def isAndroidProject = project.plugins.hasPlugin('com.android.application') || project.
plugins.
hasPlugin('com.android.library')
publications {
release(MavenPublication) {
// groupId 等信息
groupId PUBLISH_GROUP_ID
artifactId PUBLISH_ARTIFACT_ID
version PUBLISH_VERSION

if (isAndroidProject) {
// 移除R文件,移除BuildConfig文件
generateReleaseBuildConfig.enabled = false
generateDebugBuildConfig.enabled = false
generateReleaseResValues.enabled = false
generateDebugResValues.enabled = false
// 使用了这个组件,就不需要自己打aar、pom.withxml了
from components.release

def variants = project.android.libraryVariants.findAll {
it.buildType.name.equalsIgnoreCase('debug')
}

def getAndroidSdkDirectory = project.android.sdkDirectory

def getAndroidJar = "${getAndroidSdkDirectory}/platforms/${project.android.compileSdkVersion}/android.jar"

task androidJavadocs(type: Javadoc, dependsOn: assembleDebug) {
println("开始打包aar")
source = variants.collect { it.getJavaCompileProvider().get().source }
classpath = files(
getAndroidJar,
project.file("build/intermediates/classes/debug")
)
doFirst {
classpath += files(variants.collect { it.javaCompile.classpath.files })
}
options {
links("http://docs.oracle.com/javase/7/docs/api/")
linksOffline("http://d.android.com/reference",
"${getAndroidSdkDirectory}/docs/reference")
encoding "UTF-8"
charSet 'UTF-8'
addStringOption('Xdoclint:none', '-quiet') // 忽略检查@params 和 @return
}

exclude '**/R.java'
exclude "**/BuildConfig.class"
}

def cleanJavadocTask = task("cleanJavadocTask", type: Delete) {
delete androidJavadocs.destinationDir
} as Task
project.clean.dependsOn(cleanJavadocTask)

task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
baseName "${JAR_PREFIX}${project.name}"
}

task androidSourcesJar(type: Jar) {
classifier = 'sources'
from project.android.sourceSets.main.java.source
baseName "${JAR_PREFIX}${project.name}"

exclude "**/R.class"
exclude "**/BuildConfig.class"
}

task androidLibraryJar(type: Jar, dependsOn: compileDebugJavaWithJavac
/* == variant.javaCompile */) {
// java 编译后的 class文件, build/intermediates/classes/debug/
from compileDebugJavaWithJavac.destinationDir
// kotlin 编译后的 class文件
from 'build/tmp/kotlin-classes/debug/'
// 指定需要被打包成 jar 的文件夹
// include('libs/**')
exclude '**/R.class'
exclude '**/R$*.class'
exclude "**/BuildConfig.class"
baseName "${JAR_PREFIX}${project.name}-cache"
}

artifact androidLibraryJar
artifact androidSourcesJar
artifact androidJavadocsJar
} else if (project.plugins.hasPlugin('java')) {
from components.java

task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}

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

artifact sourcesJar
artifact javadocsJar
}

pom {
name = PUBLISH_ARTIFACT_ID
description = rootProject.ext.desc
url = rootProject.ext.website
licenses {
license {
//协议类型,一般默认Apache License2.0的话不用改:
name = rootProject.ext.mavenCentralLicences
url = rootProject.ext.mavenCentralLicencesURL
}
}
developers {
developer {
id = rootProject.ext.mavenCentralUserID
name = rootProject.ext.mavenCentralUserName
email = rootProject.ext.mavenCentralEmail
}
}
scm {
//修改成你的Git地址:
connection = rootProject.ext.mavenCentralConnection
developerConnection = rootProject.ext.mavenCentralDeveloperConnection
//分支地址:
url = rootProject.ext.mavenCentralTreeURL
}
}
}
}
repositories {
maven {
name = "mavencentral"

def releasesRepoUrl = getReleaseRepositoryUrl()
def snapshotsRepoUrl = getSnapshotRepositoryUrl()
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl

credentials {
username ossrhUsername
password ossrhPassword
}
}
}
}
signing {
sign publishing.publications
}
}

在module的build.gradle文件中添加脚本

ext{
PUBLISH_ARTIFACT_ID = 组件名
PUBLISH_VERSION = 组件版本
}
apply from: 'mavenCentral-release.gradle'

4.5 上传

在build 文件中添加脚本

apply from: 'mavenCentral-release.gradle'

创建运行配置

image-20210413155416672

创建一个Gradle命令

image-20210413155601378

然后创建一个上传任务

image-20210413155955879

点击该任务的执行按钮便可自动上传

image-20210413160018038

TIP,多module上传:

如果需要同时上传多个包,可以在终端中执行以下命令:

./gradlew clean build publishReleasePublicationToMavencentralRepository 

或者在主工程目录创建一个gradle命令,运行就相当于执行了所有module的maven上传脚本

image-20210418182450663

TIP,本地发布:

由于mavencentral同步很慢,因此本地测试就很有必要,执行publishReleasePublicationToMavenLocal便可以上传到本地的maven仓库。

4.6 进行发布

上传成功后,打开Nexus Repository Manager,登录你的sonatype账号,在左侧Staging Repositories页面找到你的group id,选中,点击上边的close,等待几分钟十几分钟后刷新状态,等其状态变为closed后,再点击Release,则所有人都用使用你的库了。

image-20210413160237999

4.7 Issues 中回复已经 Release 完成

image-20210413162607432

4.8 查看自己的项目

发布成功后可以进入 https://search.maven.org 网址查询自己发布的 Jar,如果未查到等几个小时再此查询。

image-20210419162352095

参考地址

使用 Maven Publish 插件 | Android 开发者 | Android Developers

发布Android库到MavenCentral教程 - 知乎 (zhihu.com)

JCenter 弃用和服务终止 | Android 开发者 | Android Developers

将 Java 项目推送到 Maven 中央仓库实践 | 超级小豆丁 (mydlq.club)