🎃

新しいSonatype Central Portal APIに対応したKotlin Multiplatformライブラリテンプレートを作った

2024/06/21に公開5

明日6/22に開催されるKotlinFest '24で「2024年に公開するに相応しいKotlin Multiplatformライブラリを構築する」というタイトルでセッションを担当するので、いろいろ下準備をしているところです。「みんなカジュアルにKMPライブラリを作って公開しよう」というメッセージが主な内容です。

このセッションの1/3くらいで、Maven Centralにパッケージを公開する方法を説明する予定なのですが、ここ数ヶ月でSonatype Nexusがややこしい変更をいろいろ加えていて、これが現状思いのほかややこしい事態をもたらしていて、不本意ながらこの関係の話に重点を置くことになってしまいました。当日ももちろん話すのですが、十分には話しきれないと思うので、ここで文章としてまとめておきます。

半分くらいはKMPに限らずKotlinライブラリ全般のMavenパッケージをSonatype Nexus経由で発行したい人向けの内容になっています。

前提: Maven Centralにパッケージを発行するということの意味

Kotlinライブラリは、ターゲットがJVMであれNativeであれJSであれWasmであれ、Mavenパッケージとして配布するのが一般的です。nativeやJSはそれらとは別にネイティブライブラリやjs/npm, wasmなどを生成できますが、Kotlinプロジェクトで参照する限りはMavenを経由するのが(必須とまでは言わないものの)一般的です。

Mavenパッケージとは元を辿ればApache Mavenのプロジェクトに属するものであり、MavenプロジェクトではMaven Centralというパッケージ リポジトリにある名前(ID)付きパッケージを名前から取得できるようになっています。Maven Central以外のパッケージレジストリを利用する場合は、Gradleの場合はbuild.gradle(.kts)などにそのレジストリを明記しなければなりません。かつてbintrayが生きていた頃はjcenter()などを利用していたものが多かったですが、今はたまにmaven { url = uri("https://www.jitpack.io" ) }のような記述を見ることがあるくらいでしょう(あとローカル リポジトリを参照できるmavenLocal()というのもあります)。

Maven Centralへのパッケージの発行というのは、一般の開発者が直接ユーザー登録して行うことは出来ないようになっています。元々はアカウントさえあれば自由に出来ていたのですが、Mavenの仕組みがスケールしなかったので、今はそのアカウントを作ることができません。その代わりに、パッケージ発行のエージェントのようなものが存在し、それらが開発者からのパッケージ発行リクエストを受け付けて、「Maven Centralに発行するに相応しい」ものだけをMaven Centralに送信できることになっています。

そのようなサービスのうち、OSS開発者が自然に利用できる、Sonatype Nexus OSSRHというものがあります。独占的な立場にある必然性は無いのですが、2024年現在では、ほぼ唯一的な選択肢です。

パッケージ発行エージェントの役割

KotlinライブラリをMaven Centralへ発行する高い壁

Maven Centralからのパッケージ開発者への要求事項はいろいろありますが、主に次の4つが問題になります:

  • sourcesJarを含めること
  • javadocJarを含めること
  • 開発者情報やライセンスなどのメタデータが十分に存在すること
  • 検証できるPGP署名がなされていること

Maven Central以外のパッケージレジストリには、このような問題はありませんでした。なのでこれらを要求しなかったjcenterが隆盛し、またGitHubなどにpushするだけでパッケージをビルドしてくれるjitpack.ioが今でもたまに使われるわけです。残念ながら2024年現在、「KMPのnativeターゲットは完全にクロスコンパイルできるわけではなく、iosやmacos用のnativeモジュール (klib) をWindowsやLinux環境でビルド出来ない」という制約が、jitpackのLinuxベースのビルド環境と相性が悪く、jitpackをKMPライブラリのビルドに使用するとios/macosパッケージが発行できないことになってしまいます。JetBrainsとしてはこの問題を改善したい意思があって、YouTrackにKT-52666, KT-68323, KT-66944といったissueが存在しているので、いずれは改善される可能性がありますが、現時点ではまだ不確定の未来形です。

そういうわけで、パッケージビルドと発行を別々のサービスで行えるMaven Centralに人類は服従せざるを得なくなったわけですが、これらはどれもそこそこ複雑な手順を要するものです。特に署名を付加するためには、gnupgなどを使って公開鍵を生成し、それをUbuntu等のkeyserverに登録して、秘密鍵にアクセスできるパスワードを設定する等の作業が必要で、その辺りの基礎知識があっても手順書が無いとわからなくなるレベルです。

幸いなことにパッケージを発行しているJava開発者はそれなりの規模で存在していて、ビルドツールMavenでこれらを実現する方法はMaven公式の情報も含め、広く共有されています。ところがKotlin開発者が使用するビルドツールは(JetBrains公式のアプローチとして)MavenではなくGradleなので、Gradle用のやり方が必要になるわけです。mvnとMavenの*.pomを使った知見が使えないわけです。

幸いなことに、Gradleには公式のMavenプラグインとしてmaven-publishが存在するので、これを利用すれば、Gradleのビルド記述を維持しながら、Mavenのプロトコルに沿ってパッケージレジストリに発行リクエストを送信できます(あるいはpublishToMavenLocalでローカルリポジトリに発行できます)。

この辺までは、日本語情報もいくつか探すと出てきます。

英語圏でもこの辺の情報はそれなりに限られていて、自分も苦労していました。幸いなことに、JetBrainsは2024年現在はkmp.jetbrains.comでMultiplatform Libraryというテンプレートを提供していて、そのREADME.mdにはMavenパッケージ発行のチュートリアルが詳細に書かれているので、これに従って作業していれば、GitHub ActionsからOSSRHへのリクエストを一気にやってくれます。このテンプレートはライブラリ開発もパッケージ発行まで簡単にできることでしょう。

Sonatype Nexusの新規ユーザーはMavenプロトコルを使えなくなった

…という話であんまり難しい話をせずに終わらせるつもりだったのですが、実はSonatype Nexusに2024年2月1日以降に登録した新規ユーザーはCentral Portalという新しいインターフェースで登録するようになっていて、従来のOSSRHのパッケージ リポジトリではなくなっています。自分は以前からの登録ユーザーなのでhttps://s01.oss.sonatype.orgを利用できるのですが、今は「OSSRHを使う何かしらの理由があって別途申請した人」だけが従来のOSSRHを使える状態です。

GradleUp/nmcpのREADMEには2/1とあるのですが、Central Portalでは3/12以降とも書かれていて「諸説ある」状態にも見えますが、いずれにせよ現在はportalのみで間違いないでしょう。)

OSSRHの画面

新しいCentral PortalはUIがガラッと変わっています。機能はまだまだ貧弱で、たとえばJetBrainsのテンプレートに沿ってKMPライブラリを発行するとtargetの数だけリポジトリが生成されますが、それらをまとめて削除する手段すらありません(!) 「OSSRHのほうが体験がまだマシ」な状態です。

Central Portalの画面

新しいCentral Portal APIというのは、独自のWeb APIとしてSwaggerで構築されたっぽいもので、MavenリポジトリのURLはありません。Mavenビルドプラグインは存在しており、ビルドツールとしてmvnを使っている開発者は特に困らないようです。一方で、Gradleプラグインは公式で用意されていません(!) そのため、この公式ドキュメントにある通り、サードパーティのGradleプラグインのいずれかを使わなければ、パッケージ発行ができない状態です。

既存のmaven-publishになるべく近いかたちでPortal APIに対応する

KotlinFest '24で自分が出そうとしているメッセージは「みんなカジュアルにライブラリ開発者になろう」なので、OSSRHにアクセスできる自分向けに「今まで通りOSSRHを使うのがまだ無難です」で終わらせるわけにはいきません。一方でJetBrains公式のKMPライブラリテンプレートは便利で良い仕組みもたくさん組み込んであるので、なるべくならそのまま流用したいところです。

新しいPortal APIをサポートするサードパーティGradleプラグインがどのようなものか、いくつかチェックしてみましたが、全てのプラグインがKMPをちゃんとサポート対象にしているわけではありません。多くはmaven-publish Gradleプラグインの機能を使ってビルドしたものを送信する仕組みのようですが、pom作成まで独自のGradle task APIで記述しなければならないものは、maven-publish用に(特にpublishToMavenLocalを使うために)pomの内容を残しておくことを考えると、ものによっては二重にメタデータを記述しなければならないことになり、バグの温床になります。

そういうわけで、今回は「maven-publishの上位互換」のようなものを目指しているvanniktech/gradle-maven-publish-pluginを、JetBrainsのKMPライブラリテンプレートの上に組み込んで、Central Portal上に発行できるテンプレートを作成しました。

https://github.com/atsushieno/multiplatform-library-template-nexus-publisher

Kotlin/multiplatform-library-templateからのforkとして作成してあるので、本家からの差分も簡単に確認できます。本当はGradleバージョンなども据え置いたほうが良いのですが、オリジナルで使われているGradle 8.1はすでに現行IDEAでロードできないくらい古いので(Kotlinも1.9.0でした)、その辺だけはmodernizeしています。

もうひとつ、これまた最近のSonatype Nexusの仕様変更なのですが、ビルド時にOSSRHにパッケージ発行するにはユーザー名とパスワードが必要だったのが、今後はaccess tokenと自称する補助的なユーザー名とパスワードの組み合わせによって認証するようです。一般的なPATと違って複数発行できないので、いったん発行したらどこかに記録しておいたほうが良いでしょう。そうしないと、毎回新規パッケージを作成する毎に「これまで作ってきたライブラリのリポジトリの全て」でこのusername/passwordのsecretsをアップデートしなければならない、という非現実的な運用になります(てかまともなPATの仕組みを作れなかったんだろうか…)。このリポジトリのREADMEでは、この認証設定の部分も修正を加えてあるので、引き続き「これに従って作業するだけでパッケージ発行できる」はずです。

これで概ね「やるべきことはやった」に近い状態なのですが、一つだけ現時点で分かっている課題があって、javadocJarをdokkaJarで置き換えるべきところを、このテンプレートでは実現できていません。これはvanniktech/gradle-maven-publish-pluginでjavadocJarを指定できない問題があるためで、まだGitHub上のmainブランチでしか修正されていない(0.28.0には含まれていない)ので、新バージョンを待っているところです。まあJetBrainsのKMPライブラリテンプレートではempty jarが生成されるので(!)、特別にデグレというわけではないのですが、一方でちゃんとdokkaJarを反映させたほうがいいという話もする予定なので、その辺は整合したいところです。

Lastly -

…というわけで、本当はJetBrainsのテンプレートを使って気楽に作ろう!みたいな軽い話にするつもりだったのですが、まあまあ重いネタになってしまったので、当日は可能な限り端折って(時間も20分とかなりギリギリなので)、詳しいことはここにまとめることになりました。KotlinFestに参加される方は、当日はまあまあ早口になると思いますが、こういう事情があったのだと思って(?)、生暖かく見守ってやってください。

Discussion

Tokuhiro MatsunoTokuhiro Matsuno

先日の kotlin fest のセッション拝聴しまして、こちらの記事にたどり着きました。

こちら、com.vanniktech.maven.publish 0.29.0 が出たので、テンプレートの更新予定ありますでしょうか?

Atsushi EnoAtsushi Eno

や、情報ありがとうございます。早速対応を試みますね。

Atsushi EnoAtsushi Eno

お役に立ったようで何よりです…! (dokkaサポートはvanniktech maven pluginが自前でやっているので自分では1行丸投げしただけでした)