グラフライブラリ6. Maven Centralにデプロイする

*この記事は韓国語から翻訳したものです。不十分な部分があれば、いつでもフィードバックをいただければありがたいです！ ([オリジナル記事](https://blog.chungjungsoo.dev/dev-posts/graph-library-6/), 同じく私が作成しました。)*

グループプロジェクトでグラフライブラリを実装する過程をまとめてみました。この記事では完成したグラフライブラリをMaven Central Repositoryへ配布する過程について説明します。([project repo](https://github.com/boostcampwm2023/and09-PriceGuard), [library repo](https://github.com/Taewan-P/material-android-chart))


## Sonatype 加入、課題生成

まず、アカウントがない場合は[https://issues.sonatype.org/](https://issues.sonatype.org/)に入って会員登録をします。

![sonatype_signup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/af067405-ee90-31b3-635b-9fba233b3c4d.png)


会員登録が完了したら、ログインしたページに課題を一つ作成する必要があります。

![create_issue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/2996f963-22c7-5b99-19d9-ce35ed56fc73.png)


課題は新しく作るプロジェクトなので、New Projectを選択します。

Summaryには普通ライブラリ名を入れます。Descriptionもあまり気にする必要はありません。他の必須入力欄は全部入力すればいいのですが、一番重要な部分はgroup idです。

これはよく私たちがgradleファイルからライブラリをimportする時、`implementation("packagename:version")`と同じように使うのですが、ここでpackage nameに該当する部分です。通常、Javaのパッケージ命名規則のように、自分がコントロールするドメイン名を反転して使うのですが、ライブラリ名より前のドメインアドレスがgroup idになります。例えば、`com.example.materialchart`というライブラリがあれば、ライブラリの識別は `materialchart` となり、group idは `com.example` となります。

この部分が重要な理由は、現在配布しようとしているライブラリのパッケージ名は、自分がコントロールしているドメインの逆順でなければならないからです。問題が生成されると、システムで自動的にDNSレコードを調べて認証する方式です。 したがって、ライブラリを配布する時、ソースでパッケージ名が自分がコントロールするドメインでない場合は、今すぐ変更するようにしましょう。

私たちのチームは `priceguard.app` というドメインを所有しており、パッケージ名は `app.priceguard.materialchart` に設定しているので、group idは `app.priceguard` と入力しました。

もし、自分が所有してるドメインがない場合、GitHub Pagesを使って認証する方法があるので、group idを `io.github.<USERNAME>` に設定しましょう。 ([参考](https://central.sonatype.org/publish/requirements/coordinates/#supported-code-hosting-services-for-personal-groupid))

Issueを生成したら、group idが自分の所有であることを認証しないとIssueが正常に閉じられ、本格的にパッケージをrepositoryにアップロードすることができます。

group idの認証は domain rootにTXTレコードで該当のissue番号を追加しておけば、自動的に認証されます。


![dns_query_result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/f045cf4f-a1cf-18a3-afde-a8f8291b56ff.jpeg)



認証が完了すると、Issueに自動的にコメントが追加され、Issueが閉じられます。


![groupid_verification_complete.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/274efded-25e5-cca7-efdd-230ffd776a57.png)




## プロジェクトデプロイ設定

次はプロジェクトのビルド設定やデプロイ設定をする必要があります。

まず、デプロイを簡単にするためMaven Publish Pluginを使う予定です。元々Maven Centralへアップロードをするためには色んな要件を満たしてpom.xmlのようなファイルも作って一度にアップロードする必要があります。しかし、このプラグインを使うとgradleで設定した値で必要な情報とファイルを自動で入れてくれます。

まず、library側のbuild.gradleファイルに下記のようにプラグインを追加します。後でパッケージ署名に使うSigningプラグインも一緒に追加します。

```kotlin
plugins {
    `maven-publish`
    signing
}
```

groupIdは上で認証する時使ったドメインの逆順で、artifactIdはこのライブラリのパッケージ名です。実際に配布された時、ユーザーがimportをする時、 `groupId:artifactId:version` のような文字列でインポートします。私たちのような場合、groupIdは `app.priceguard`, artifactIdは `materialchart` で importをする時 `implementation("app.priceguard:materialchart:<version>")` で使うことになります。

```gradle
publishing {
    publications {
        afterEvaluate {
            create<MavenPublication>("release") {
                from(components["release"])
                groupId = groupName
                artifactId = packageName
                version = versionCode

                pom {
                    name.set(artifactId)
                    description.set("ライブラリの簡単な説明")
                    url.set("https://github.com/ライブラリレポアドレス")
                    licenses {
                        license {
                            name.set("MIT License")
                            url.set("https://opensource.org/license/mit/")
                        }
                    }
                    developers {
                        developer {
                            name.set("ディストリビューター名")
                            email.set("連絡先メールアドレス")
                        }
                    }
                    scm {
                        url.set(pom.url.get())
                        connection.set("scm:git:${url.get()}.git")
                        developerConnection.set("scm:git:${url.get()}.git")
                    }
                }
            }
        }
    }

    repositories {
        maven {
            name = "SonatypeSnapshot"
            setUrl("https://s01.oss.sonatype.org/content/repositories/snapshots/")
            credentials {
                username = getExtraString("ossrhUsername")
                password = getExtraString("ossrhPassword")
            }
        }

        maven {
            name = "Sonatype"
            setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = getExtraString("ossrhUsername")
                password = getExtraString("ossrhPassword")
            }
        }
    }
}

```

Repositoriesで設定したSonatypeSnapshotとSonatypeは別々のリポジトリです。Snapshotは該当ライブラリのsnapshotバージョンをアップロードする場所であり、SonatypeはReleaseビルドをアップロードする場所です。Snapshotのような場合にはversionCodeが無条件にSNAPSHOTで終わらないと後でアップロードする時エラーが出ます。逆も同じです。Release repoにSNAPSHOTをアップロードしようとするとエラーが出るのでversionCodeを設定する時注意しましょう。

`getExtraString` 関数はlocal.propertiesを解析して値を取得するように設定しました。ossrhUsernameとPasswordは先ほどSonatypeでログインする時使ったcredentialを入力します。


```gradle
extra["signing.keyId"] = null
ext["signing.password"] = null
ext["signing.secretKeyRingFile"] = null
ext["ossrhUsername"] = null
ext["ossrhPassword"] = null

val secretPropsFile = project.rootProject.file("local.properties")
if (secretPropsFile.exists()) {
    secretPropsFile.reader().use {
        Properties().apply {
            load(it)
        }
    }.onEach { (name, value) ->
        ext[name.toString()] = value
    }
} else {
    ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
    ext["signing.password"] = System.getenv("SIGNING_PASSWORD")
    ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE")
    ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME")
    ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD")
    println(ext["ossrhUsername"])
}

fun getExtraString(name: String) = ext[name]?.toString()
```


ちょうどlocal.propertiesでSIGNING_KEYに関する内容が出てきたので、SIGNING_KEYについても追加します。GPGでSigning Keyを作る方法は[公式ドキュメント](https://central.sonatype.org/publish/requirements/gpg/#distributing-your-public-key)に詳しく説明されています。Keyを生成して必ずPublic KeyをKey Serverに送ることも忘れないでください。これは後でそのキーでパッケージサインをする時、外部からPublic Keyで検証をするため、必ず必要です。

鍵を生成したら、keyIdには `gpg --keyid-format SHORT --list-keys` でSigingする時使う公開鍵の前に表示される8桁のIDを入力します。以下の例では `F36F5BBD` になります。


```
pub   rsa4096/F36F5BBD 2023-12-07 [SC]
      DDA6DA5453AC1A14260621A9A5764C8DF36F5BBD
```


secretKeyRingFileは`gpg --export-secret-keys [keyid]` コマンドを使って`.gpg`ファイルをexportした後、そのkeyの位置を絶対パスで入力します。

最後にgradleでreleaseビルドをする時、signをするように設定します。


```gradle
signing {
    afterEvaluate {
        sign(publishing.publications["release"])
    }
}
```

## Nexusにアップロード

上の設定が終わったら、下記のコマンドを実行してNexus Repositoryへアップロードします。このコマンドを実行したら、ビルド後repositoryへアップロードまでやってくれます。

```bash
./gradlew publishReleasePublicationToSonatypeRepository
```

![gradle_publish.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/f3dc3242-4497-6307-95a1-33b86abb27f4.png)



![nexus_repository_manager.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/ec220a80-d7a4-3cfa-2cdb-90317c28bdcc.png)


アップロードが正常に完了したら、[https://s01.oss.sonatype.org/](https://s01.oss.sonatype.org/#stagingRepositories)に入ってStaging Repositoriesに上のようにアップロードされたか確認します。

もし、正常にアップロードされてアップロードされたバージョンをデプロイしたい -> Close

間違いがあってデプロイしたくない -> Drop

を選択します。最初用語が分かりにくくてなぜアップロードをしたのに配布ができないのか気になって何十分も待っていましたが、Closeをしないと実際にRelease Repositoryに反映できるように検査をします。


## 配布

上でRepositoryをCloseしたら、そのパッケージがMaven Central Repositoryに入るのに適しているかどうか検証をします。無効であることが確認されたらこのようにCloseする途中でエラーが出ます。

![validation_failed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/3205f458-6f70-53dc-3f2c-0b95ec79162d.png)



もし正常にCloseされたら、さっきのStage RepositoryのオプションでReleaseボタンが有効になります。Releaseボタンを押すとRelease Repositoryに反映されます。しかし、これはMaven Central RepositoryとSyncする時時間がかかるので、すぐに確認できない場合があります。


![nexus_released.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/dfb6cb62-0b9a-9f14-7b9a-9dbd15665f5c.png)


正常にデプロイされたら、左側のArtifact Searchでパッケージ名を検索して確認することができます。


![maven_central_synced.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3896189/389f55b5-8941-a32f-c8f2-12eb9f847731.png)


少し後、Maven Centralでパッケージ名を検査してみると、正常にアップロードされてることが確認できます。Maven Centralに正常にアップロードされたことが確認されたら、あとはどこでもimportして使うことができます。