天天看點

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

釋出到 Maven Central 相關的教程挺多的,但是大部分存在問題。這篇文章是我在解決了很多問題的基礎之上總結的,用來幫助需要的同學避免重蹈覆徹。需要的可收藏,萬一用到了呢~

1、Farewell to Bintray jCenter

首先,告别下 bintray jcenter. 相比于 Maven Central,bintray jcenter 的性能和友善性确實好得多。可惜,jcenter 将要關閉了。jcenter 還是給我提供了很多的便利,對于其關閉深表可惜:

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

2、釋出到 Maven Central

Step 1: 注冊和激活 sonatype

釋出到 Maven Central 之前,首先要到 sonatype 注冊一個賬号,

https://issues.sonatype.org/

新增賬號完賬号之後需要通過建立 issue 激活賬号,頁面如下:

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

這裡有幾個需要注意的地方:

  1. 項目選擇 Community Support - Open Source Project Repository Hosting
  2. 問題類型選擇 New Project
  3. 概要:描述項目功能,不重要
  4. Group Id 比較重要,我們後續說明
  5. Project URL 填寫自己開源項目位址即可,要與 Group Id 有一定的關聯性
  6. SCM url 版本倉庫的拉取位址,填寫自己的項目的 git 連結位址,通常是項目位址後加

    .git

對于這裡的 group id,它就是釋出完成之後,引用時的 group id. 你可以使用自己的域名,但是需要證明域名是你自己的。如果沒用自己的域名,一個簡單而通用的方式是使用 Github 的位址,比如我的 GitHub 位址是 https://github.com/Shouheng88 ,就可以寫作 com.github.Shouheng88. 建立完 issue 之後幾分鐘内就會收到對方發送的郵件,郵件内會提示通過在 Github 裡面建立項目來驗證激活:

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

當我們按照郵件說明建立完項目之後,修改問題狀态,過幾分鐘之後就激活成功了。

Step 2: 申請密鑰

為了確定中央存儲庫中可用元件的品質水準,OSSRH 對送出的檔案有明确的要求。除了 jar 包和 pom 檔案,Javadoc 和 Sources 是必須的(這點和 bintray jcenter 類似),并且每個檔案都要有一個對應的 asc 檔案,即 GPG 簽名檔案,用于校驗檔案。是以,這步驟中我們需要申請密鑰。

OSX 下面可以使用 brew 安裝,

$ brew update
$ brew install -v gpg
           

Windows 下面也可以直接下載下傳安裝,

https://www.gpg4win.org/get-gpg4win.html

安裝完畢之後,可以通過如下指令生成密鑰:

gpg --generate-key
           

建立密鑰的過程中會要求輸入密碼,這裡的密碼非常重要,我們釋出的時候會使用到它。

建立完畢之後可以通過

gpg -k

顯示所有已建立的密鑰:

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

這裡的字元串

AB7FxxxxxxxxxxxxABA846D44F7B66

叫做密鑰指紋。後面 8 位 D44F7B66,叫做 KEY ID,我們釋出的時候将使用到它。

另外,進行檔案簽名的時候需要用到名為 secretKeyRingFile 的檔案,我們可以通過如下指令生成:

gpg --export-secret-keys [密鑰指紋] > secret.gpg
           

在 sonatype 的倉庫送出後,需要從多個公鑰伺服器上下載下傳比對的公鑰,然後來校驗你上傳的檔案的簽名。是以,我們需要通過下面的指令上傳公鑰到公鑰伺服器:

gpg --keyserver keyserver.ubuntu.com --send-keys [密鑰指紋]
           

Step 3: 準備釋出腳本

首先需要引入兩個插件,

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

然後編寫釋出的 gralde 腳本如下

task androidSourcesJar(type: Jar) {
    archiveClassifier.set("sources")
    from android.sourceSets.main.java.source
    exclude "**/R.class"
    exclude "**/BuildConfig.class"
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            // group id,釋出後引用的依賴的 group id
            groupId 'com.github.Shouheng88'
            // 釋出後引用的依賴的 artifact id
            artifactId 'sil'
            // 釋出的版本
            version '0.1.0'
            // 釋出的 arr 的檔案和源碼檔案
            artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
            artifact androidSourcesJar
            pom {
                // 構件名稱,可以自定義
                name = 'uix-common'
                // 構件描述
                description = 'Android UIX'
                // 構件首頁
                url = 'https://github.com/Shouheng88/Android-uix'
                // 許可證名稱和位址
                licenses {
                    license {
                        name = 'The Apache License, Version 2.0'
                        url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    }
                }
                // 開發者資訊
                developers {
                    developer {
                        name = 'ShouHeng'
                        email = '[email protected]'
                    }
                }
                // 版本控制倉庫位址
                scm {
                    url = 'https://github.com/Shouheng88/Android-uix'
                    connection = 'scm:git:github.com/Shouheng88/Android-uix.git'
                    developerConnection = 'scm:git:ssh://[email protected]/Shouheng88/Android-uix.git'
                }
            }
        }
    }
    repositories {
        maven {
            // 釋出的位置,這裡根據釋出的版本區分了 SNAPSHOT 和最終版本兩種情況
            def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
            def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
            credentials {
                // 這裡就是之前在 issues.sonatype.org 注冊的賬号
                username ossrhUsername
                password ossrhPassword
            }
        }
    }
}

signing {
    sign publishing.publications
}
           

注意:這裡的 group id 必須和申請的 sonatype 申請賬号時申請的 group id 一緻。這裡的要釋出到的倉庫的位址可能是每個使用者不同的,具體是多少要看檢視自己的激活郵件(可檢視下面的“到 Sonatype 處理釋出結果”小節)。

此外,我們需要在 gradle.properties 中定義我們的賬戶相關的資訊:

signing.keyId=密鑰keyId
signing.password=密鑰password
signing.secretKeyRingFile=私鑰keyRingFile路徑

sonatypeUsername=sonatype賬号
sonatypePassword=sonatype密碼
           

Step 4: 釋出

配置完成之後釋出就比較簡單了,隻需要在 AS 的右邊欄的 gradle 工作列裡指定的 module 下面選擇 publishing 任務,然後選擇

publishMavenJavaPublishingToMavenRepository

即可:

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

Step 5:到 Sonatype 處理釋出結果

實際上每個使用者釋出到的分區可能是不同的,一個準确的檢視的方法是,當你完成賬号激活之後會收到一封郵件,這裡寫明了你的檔案釋出到的位置,

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

比如我的在 https://s01.oss.sonatype.org 域名下,而其他使用者的可能在 https://oss.sonatype.org/ 域名下,如果我用自己注冊的賬号登入其他的域名就會報權限相關的錯誤,這裡也是一個坑。

登入到自己的倉庫之後點選左側的

Staging Repositories

就可以看到我們剛剛釋出的檔案。

建議收藏,從 jCenter 遷移到 MavenCentral 完整方案

勾選我們的檔案之後點選 Close,snoatype 将對我們釋出的檔案進行校驗,校驗需要一分鐘左右的時間,校驗完畢之後點選 Release,就最終釋出了我們的檔案。

Step 6: 引用我們的庫

釋出完成之後就可以引用了,引用依賴的方式和其他倉庫完全一樣,需要說明的是,需要先添加 Maven Central 的倉庫的位址:

repositories {
    //推薦: release成功後會直接從mavenCentral拉取aar
    mavenCentral()
    //或者
    maven {url "https://s01.oss.sonatype.org/content/groups/public"}
    //或者
    maven {url "https://s01.oss.sonatype.org/content/repositories/releases"}
}
           

Step 7: 處理引用的問題

我發現大部分教程都是介紹到上面就結束了,但是實際上還有很重要的一個步驟需要解決。

通常我們的庫并不是獨立的,它可能通過各種方式引用其他的庫,比如 implementation、api、compile 或者 compileOnly 等。使用 Bintray Jcenter 的時候,它可以自動根據我們項目的依賴關系生成 pom 檔案中的依賴。但是釋出到 Maven Central 的時候需要我們自己解決這個問題。如果不解決這個問題,引用的時候就隻引用到了我們自己釋出的這個 aar 檔案。這顯然是不行的。

我通過對比 Bintray JCenter 對各種引用方式,編寫了下面的代碼用來生成 pom 中的依賴關系:

withXml {
    def dependenciesNode = asNode().appendNode('dependencies')
    project.configurations.all { configuration ->
        def name = configuration.name
        if (name != "implementation" && name != "compile" && name != "api") {
            return
        }
        println(configuration)
        configuration.dependencies.each {
            println(it)
            if (it.name == "unspecified") {
                // 忽略無法識别的
                return
            }
            def dependencyNode = dependenciesNode.appendNode('dependency')
            dependencyNode.appendNode('groupId', it.group)
            dependencyNode.appendNode('artifactId', it.name)
            dependencyNode.appendNode('version', it.version)
            if (name == "api" || name == "compile") {
                dependencyNode.appendNode("scope", "compile")
            } else { // implementation
                dependencyNode.appendNode("scope", "runtime")
            }
        }
    }
}
           

這裡會先對引用的方式做一個判斷,然後根據引用的方式選擇對應的 scope,同時處理了一些異常的情況。

最終的釋出腳本

task androidSourcesJar(type: Jar) {
    archiveClassifier.set("sources")
    from android.sourceSets.main.java.source
    exclude "**/R.class"
    exclude "**/BuildConfig.class"
}

publishing {
    // 定義釋出什麼
    publications {
        mavenJava(MavenPublication) {
            // group id,釋出後引用的依賴的 group id
            groupId 'com.github.Shouheng88'
            // 釋出後引用的依賴的 artifact id
            artifactId 'sil'
            // 釋出的版本
            version '0.1.0'
            // 釋出的 arr 的檔案和源碼檔案
            artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
            artifact androidSourcesJar
            pom {
                // 構件名稱,可以自定義
                name = 'uix-common'
                // 構件描述
                description = 'Android UIX'
                // 構件首頁
                url = 'https://github.com/Shouheng88/Android-uix'
                // 許可證名稱和位址
                licenses {
                    license {
                        name = 'The Apache License, Version 2.0'
                        url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    }
                }
                // 開發者資訊
                developers {
                    developer {
                        name = 'ShouHeng'
                        email = '[email protected]'
                    }
                }
                // 版本控制倉庫位址
                scm {
                    url = 'https://github.com/Shouheng88/Android-uix'
                    connection = 'scm:git:github.com/Shouheng88/Android-uix.git'
                    developerConnection = 'scm:git:ssh://[email protected]/Shouheng88/Android-uix.git'
                }
                // 解決依賴關系
                withXml {
                    def dependenciesNode = asNode().appendNode('dependencies')
                    project.configurations.all { configuration ->
                        def name = configuration.name
                        if (name != "implementation" && name != "compile" && name != "api") {
                            return
                        }
                        println(configuration)
                        configuration.dependencies.each {
                            println(it)
                            if (it.name == "unspecified") {
                                // 忽略無法識别的
                                return
                            }
                            def dependencyNode = dependenciesNode.appendNode('dependency')
                            dependencyNode.appendNode('groupId', it.group)
                            dependencyNode.appendNode('artifactId', it.name)
                            dependencyNode.appendNode('version', it.version)
                            if (name == "api" || name == "compile") {
                                dependencyNode.appendNode("scope", "compile")
                            } else { // implementation
                                dependencyNode.appendNode("scope", "runtime")
                            }
                        }
                    }
                }
            }
        }
    }
    // 定義釋出到哪裡
    repositories {
        maven {
            // 釋出的位置,這裡根據釋出的版本區分了 SNAPSHOT 和最終版本兩種情況
            def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
            def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
            credentials {
                // 這裡就是之前在 issues.sonatype.org 注冊的賬号
                username ossrhUsername
                password ossrhPassword
            }
        }
    }
}

signing {
    sign publishing.publications
}
           

總結

以上是一個簡單的解決方案,經過上述操作已經可以幫助我們解決釋出過程中的許多問題。當然,這裡還是有可以優化的空間,比如處理項目依賴等各種情況。

希望本文對你有所幫助,如有疑問可在下方留言。