🤪

bintray/JCenter に上げていたJVMライブラリを楽して Maven Central へ移行する

2021/02/09に公開

2021/02/11 追記) Staged Repository を Release するスクリプトも追加しました。

JCenter のシャットダウンが告知されました。移行先として jitpack なども候補に上がるとは思いますが、負荷の上昇は目に見えており、その影響で flaky になると大変困ります。せっかく移行するのなら Maven Central に移行しましょう。出来る限り楽に。

https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/

2021/02/09 時点で当初より期限が延長されましたが、ライブラリ開発者側目線では来月末に新規アップロードが停止することからそう余裕はないかと思います。

基本の移行手順

  1. JCenter または bintray の maven repo からアーティファクトをダウンロードする
  2. Maven Central の要求するアーティファクトを追加で用意する
  3. アーティファクトに PGP 署名を行う
  4. Sonatype (Staging Repository) にアップロードする
  5. Release する

bintray に上げている時点で PGP 署名を行っていても、アーティファクトまたはその内容が不足している場合は署名をし直す必要があることに注意してください。

問題1: アーティファクトのダウンロードが凄く面倒くさい

1バージョン程度なら全く問題ありませんが、バージョン数に対して虚無を感じる機会が明確に増える作業です。

問題2: Maven Central Requirements

Maven Central に上げるにあたって、以下の要件を満たす必要があります。

  • リリース版のみ上げること
  • javadoc.jar と sources.jar を用意する
  • PGP 署名
  • ドメイン所有証明
  • 最低限の POM ファイル

リリース版のみ上げることについては特に問題はないかと思います。

javadoc.jar や sources.jar についても、bintray に上げる際にエラーを握り潰していなければ大体のケースで存在しているでしょう。

PGP署名についてはCIでの自動化を考えなければそう難しいものではなく、ドメイン所有証明についても昔に比べて非常に簡単になっています。こちらについては多くの方が以前より、また今回改めて解説してくださっているので割愛します。

面倒臭いのは最低限の POM ファイルです。Maven Central の要求する最低限の POM ファイルは JCenter の要求よりも情報量が少しだけ多いです。手作業でちまちま node を追加してもいいんですが、量が多いとやってられません。

問題3: 署名する対象

アーティファクト数だけ存在します。

問題4: Sonatype の最高のUI

最高に使い勝手が悪いです。1個ずつやっていると冗談抜きに日が暮れます。

自動化スクリプトで楽をする

各ステップを自動化するためのスクリプト群を書きました。実行については自己責任でお願いします。また自分(と所属会社)のケースでしか動作確認をしていないので、プルリクエストは大歓迎です。

最低動作要件は調べていませんが、Bash 5.1.4, Ruby 2.6.6 で確認しています。

また各スクリプトを順番に実行することを想定しているため、ディレクトリ構造を信頼している点に注意してください。また意図しない上書きを避けるため、上書きに相当するケースでは追記またはスキップの動作をします。ここのトリアージは各自の手作業が必要です。

https://github.com/jmatsu/mc-migration

1. JCenter または bintray の maven repo からアーティファクトをダウンロードする

特定のバージョン、または全てのバージョンを一括でダウンロードするスクリプトで対応します。

./download_artifacts.sh jcenter <group id> <artifact id> [version]

# e.g. "com.github.jmatsu:multipreference:0.3.1" をダウンロードする
./download_artifacts.sh jcenter com.github.jmatsu multipreference 0.3.1

2. Maven Central の要求するアーティファクトを追加で用意する

POM ファイルの補完が可能です。まず <group id><artifact id> の組に対応する補完用の構成ファイル <group id>:<artifact id>.yml をカレントディレクトリに作成してください。

ref: 構成ファイルの骨組み https://github.com/jmatsu/mc-migration/blob/main/com.example:example.yml

その後、以下を実行することで、特定のバージョンまたは全てのバージョンの pom に必要な要素を転写することができます。すでに要素が存在する場合、上書きはされません。

./complete_pom.rb <group id> <artifact id> [version]

3. アーティファクトに PGP 署名を行う

特定のグループ以下全てのアーティファクトとバージョン、特定のアーティファクトID以下の全てのバージョン、特定のバージョンに対して署名を実行します。また次のアップロードに備えて、single bundle jar を生成します。

署名鍵は標準入力または環境変数経由(SIGNING_PASSPHRASE)で渡す必要があります。すでに分遣署名が存在する場合はスキップとなるので、予め削除しておいてください。 e.g. find </path/to/version> -name "*.asc" -type f -print0 | xargs -0 -I{} rm -i {}

./sign_and_bundle.sh <key id> <group id> [artifact id] [version]

GNUPGHOME など、gpg ツールがサポートしている環境変数は動作します。

4. Sonatype (Staging Repository) にアップロードする

特定のグループ以下全てのアーティファクトとバージョン、特定のアーティファクトID以下の全てのバージョン、特定のバージョンの single bundle jar をアップロードします。署名ステップで作成するか、手動で <artifact id>-<version>-bundle.jar という single jar を作成してください。

ログイン情報は標準入力または環境変数経由(NEXUS_USERNAME, NEXUS_PASSWORD)で渡す必要があります。

./upload_to_sonatype.sh <group id> [artifact id] [version]

2021/02/11 追記) この方法で作成された Staged Repository は自動で closed 状態に遷移します。

5. Release する

2021/02/11 追記) single bundle jar では maven-metadata.xml が生成できず、startship の検証機構に合わなかったため新たにスクリプトを追加しました。

ステップ4 でアップロードした Staged Repository を Release します。

ログイン情報は標準入力または環境変数経由(NEXUS_USERNAME, NEXUS_PASSWORD)で渡す必要があります。

./sync_to_maven_central.sh <group id> [artifact id] [version]

https://github.com/jmatsu/mc-migration/commit/be9d8782e6ebe03ff5369c9624d7f58d322ead3e 以降の upload_to_sonatype.sh を利用する必要があります。

single bundle jar によるアップデートを行わない場合、すでに素晴らしいツールがあるのでこちらを使うと良いでしょう。

https://github.com/saket/startship

追記: 今回のように bundle jar で上げると startship の動作に必要な maven-metadata.xml が存在せず、利用出来ない可能性があります。その場合は Web UI の一括選択から実行しましょう。条件の切り分けが出来ていませんが、例えば maven-publish を使ったフローであれば利用出来るはずです。(結局今回の手順では UI を使ってしまったので確認不足でした。申し訳ありません。)

6. Drop する (2021/02/11 追記)

力尽きました。Web UI から一括選択してください。

蛇足

Gradle Plugin では Gradle Plugin Portal にホストするかと思いますが、plugin id の制約が Gradle Plugin Portal に存在するためソースコードの変更を余儀なくされるケースがあります。

参考資料

Discussion