Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

ScalaプロジェクトをMaven Centralにpublishするメモ

この記事はScalaプロジェクトをMaven Central Repositoryにリリースして全世界から使えるようにする手順をまとめたものである。作業は大別してSonatype側とビルドツール側とに分かれる。今回はビルドツールとしてMillを利用したが、作業の過半は同じである。数時間でこの作業は終わるし、Sonatype側の作業は一度だけやればよい。


ScalaはJVM言語だから、もちろん生成したJARファイルをMaven Centralに送って、OSSとして公開できるようになっている。Maven Centralに公開すると、よく見る以下のような形式で自分のソフトウェアをインターネットを介してsbtやMillなどのプロジェクトから使えるようになる:

// sbt
"io.github.windymelt" %%% "qw" % "0.0.1"
// mill
ivy"io.github.windymelt::qw:0.0.1"

現実問題として、制作したScalaのOSSを使ってもらうにはMaven Centralへの公開がほぼ必須事項となる。Maven Centralに頼らない場合、リポジトリサーバを利用者のbuild.sbtなどに記載したり、直接JARファイルをコピペする必要があるからだ。

Maven Centralへの公開手順は比較的簡単になっている。今回自作のOSSqwを公開したので、その手順を公開する。

index.scala-lang.org

用語

混同しないように、登場人物を整理しておこう。

  • Maven
    • Apache Maven。Javaのためのビルドツール。
  • Maven Central
    • Mavenがデフォルトでコードリポジトリとして使うリポジトリ。
    • Sonatypeが運営している。
    • 普通のHTTPプロトコルで配信するため、ブラウザからも様子を見ることができる。
    • sbtやmillもここを標準リポジトリとして利用する。
  • mvnrepository.com
    • Maven Centralを検索できるサービス。
    • 個人がメンテしており、Apache財団やSonatype社が管理しているわけではない。
    • 非公式なサードパーティのフロントエンドくらいに思っておけばよい。
  • central.sonatype.com
    • Maven Centralを検索できるサービス。
    • こちらはSonatypeが直々に提供している。
    • mvnrepository.comと比べるとたいそう遅く、あまり使いものにならない。
  • Mill
    • Scalaのビルドツール。
    • sbtより後発。
  • sbt
    • Scalaのビルドツール。
    • 先発でドキュメントが多い。

方針と前提

以下の方針でMaven Centralにリポジトリを登録する。

  • JARファイルにはPGP署名が必須なので、YubiKeyなどの署名用デバイスか鍵を作っている前提とする
    • 今回は自分はYubikeyにPGP鍵を登録しています
  • ソースコードのリポジトリはGitHubであるものとする
    • GitLabなどでもいけるはず。参考文献などを読んでね
  • グループIDはio.github.ユーザ名とする
    • こうすることでbotを使った自動ワークフローに乗ることができる
    • この形式のグループIDを使う場合、Sonatype側からすればGitHubのオーナーシップだけ確認すればよいので管理が楽。ウィンウィン
    • Scalaコード上のパッケージ名もこれに倣ってio.github.windymelt.foobar....を使う
  • アーティファクトのバージョンはGit上のタグを利用する
    • v0.0.1というGitタグにバージョン0.0.1が自動的に対応するようにする

Sonatype側の作業(Group IDを作るときに1回だけ必要)

Maven Centralがどのような形でリリースを制御しているか軽く確認しておこう。Maven Central上ではグループID単位でアップロード権が与えられる、というふうに考えておけばよい。そしてその権限の取得はJIRAにチケット(Issue)を作成して自動化フローに乗ることで行う。

例えば、今回自分はio.github.windymeltへのリリース権をもらうことになる。すると、io.github.windymelt以下にアーティファクトをpublishできるようになる。もちろんこれ以外のグループIDにpublishする権限はもらえないが、普通は1つあれば十分だ。

Sonatype JIRAへの登録

Maven CentralはSonatype社が管理しているのだが、Maven Centralへの登録やそのサポートには、JIRAというチケット管理システムを利用する。まずはここのアカウントを作成して、チケット作成の準備をしよう。

issues.sonatype.org

Issue作成

次にIssueを作成してグループIDの権限申請を行う。JIRAには新たなプロジェクト作成用のIssueテンプレートが用意されているので、それを使う。

https://issues.sonatype.org/secure/CreateIssue!default.jspa

ログイン後に上掲のページに行く。課題タイプNew Projectになっていることを確認したら、次へを押下する。

次に埋めるべきフォームが登場する。フィールドが多くて面食らうが、埋めなければならない項目は4つだけだ。

要約にはCreate library for <パッケージ名>くらいの文言を書いておく。実際はbotが処理するので、あまり内容は気にしなくていいが、一応どのソフトウェアなのか分かるようにしておく。

Group Idにはio.github.<GitHubのユーザ名>を記入する。

Project URLにはGitHub上のリポジトリのURLを記入する。

SCM urlにはGitHubのGitプロトコルのURL(ほとんどの場合リポジトリのURLに.gitをつけたもの)を記入する。

これで作成を押下する。しばらくするとbotが反応してコメントをするはずだ。しばらく待とう。

ダミーリポジトリ作成

するとSonatypeのbotが、OSSRH-xxxxxx(xxxxxxは数字)という名前の空リポジトリをGitHub上に作成するように要求してくるので、その通りの名前で空リポジトリを作成する。これは実際にそのグループIDに対応するGitHubのorganizationのオーナーシップをユーザが持っているかどうか確認するためのものだ。

空リポジトリの作成が完了したら、Waiting for Responseとなっている青いボタンをクリックし、オープンを選択する。これで今度はbotのターンになる。

認証完了

するとbotがオーナーシップの確認を行ってくれてグループIDが発行される。JIRA側の作業はこれで完了だ。同じグループIDを使っている間は、もうIssueを作らなくてよい。ビルドツールがパブリッシュ先に使うためのURLが表示されるので、メモしておこう(たまに変わることがあるらしい)。

同じグループIDで他のリポジトリをpublishするときは、特に申請はいらないみたい。

PGP鍵の準備(こちらも1回だけ作成すればよい)

ここからはローカルの作業だ。

https://www.scala-sbt.org/release/docs/Using-Sonatype.html

Maven Central RepositoryはアーティファクトにPGP署名を付与することを必須としている。したがって、publishするためには自分用のPGP鍵を持っておく必要がある。鍵の作成については割愛する。一般的なRSAやed25519の鍵があれば十分だ。

【令和最新版】PGP鍵の作り方から管理方法、Git Commitへの署名まで - Qiita

手元に作成したPGP公開鍵を、リモートに公開しておく(Sonatypeがサポートしている鍵サーバにアップロードする必要がある):

gpg --keyserver keyserver.ubuntu.com --send-keys 鍵ID

Millの場合

ここからはビルドツール側でpublishのための設定を行い、実際にpublishするという操作になる。今回は設定が簡潔なMillを使った。

Millでは手数が少なく済むかわりに、現在Millは激しく開発されているのでここでの情報が古いものになっている可能性がある。今回はMill v0.11.2を利用した。

以下のような具合で、pomSettingsを定義する:

import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.4.0`
// ↑Gitのタグをそのままバージョニングに使うためのプラグイン
import mill._, scalalib._
import publish._

object qw extends RootModule with ScalaModule with PublishModule {
  def scalaVersion = "3.3.0"

  // ここの名前が "io.github.windymelt" %% "ここ" % "1.2.3" に使われる
  override def artifactName = "ここにアーティファクト名を入れる"
  // SonatypeでもらったURLに/service/localをつけたもの
  override def sonatypeUri = "https://s01.oss.sonatype.org/service/local"
  // SonatypeでもらったURLに/content/repositories/snapshotsをつけたもの
  override def sonatypeSnapshotUri =
    "https://s01.oss.sonatype.org/content/repositories/snapshots"
  def pomSettings = PomSettings(
      description = artifactName(),
      // もらったGroup ID
      organization = "io.github.windymelt",
      // プロジェクトURL
      url = "https://github.com/windymelt/qw.scala",
      // ライセンス
      licenses = Seq(License.MIT),
      // GitHub管理下であることを宣言する
      versionControl = VersionControl.github("windymelt", "qw.scala"),
      // 開発者情報
      developers = Seq(
        Developer("windymelt", "windymelt", "https://github.com/windymelt")
      )
    )
  // バージョニングはGitのタグからvを取ったものとする(勝手に取ってくれる)
  override def publishVersion: T[String] = VcsVersion.vcsState().format()
}

これでリリース準備が整った。GitHub側でリリースタグを打ち、手元でpullしてそのタグに移動しておく。Gitリポジトリをクリーンな状態にし、自分が正しいタグのコミットを見ていることを確認しよう。

最後にmill publishを実行するとpublishが行われて仮リリース状態になる。この操作にはSONATYPE_USERNAMESONATYPE_PASSWORDという2つの環境変数が必要である。それぞれSonatypeのユーザ名とパスワードを渡す必要がある。

SONATYPE_USERNAME=windymelt SONATYPE_PASSWORD="..." mill publish

必要に応じてGPGがパスフレーズを要求してくる。

% SONATYPE_USERNAME=windymelt SONATYPE_PASSWORD="xxx" mill publish
[64/64] publish 
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.jar
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.jar.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.jar.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-sources.jar
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-sources.jar.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-sources.jar.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-javadoc.jar
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-javadoc.jar.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-javadoc.jar.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.pom
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.pom.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.pom.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.jar.asc
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.jar.asc.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.jar.asc.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-sources.jar.asc
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-sources.jar.asc.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-sources.jar.asc.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-javadoc.jar.asc
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-javadoc.jar.asc.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3-javadoc.jar.asc.sha1
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.pom.asc
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.pom.asc.md5
Uploading io/github/windymelt/qw_3/0.1.3/qw_3-0.1.3.pom.asc.sha1
Published qw_3 to Sonatype

(sbtの場合)

sbtでMaven Central Repositoryにpublishするにはいくつかのプラグインが必要となる。

github.com

qiita.com

sbtの場合は上掲の記事に詳しいので参考にしてほしい。Millとやることは本質的には変わりがない。

Nexus Repository Managerで様子を見る

この時点ではアーティファクトは仮リリース状態になっている。全世界に公開するためには、publishしたアーティファクトをcloseし、さらにreleaseする必要がある。もし間違えてpublishした場合はdropする。ちなみに一度releaseするともう二度と削除できなくなるので、失敗しないようにしよう。

https://s01.oss.sonatype.org/ に行ってSonatypeのパスワードでログインする。Staging Repositoriesというラベルが画面左にあるので、それを使って先程publishしたアーティファクトを見ることができる。

するとアーティファクトのリストとClose Release Dropというボタンがあるので、リリースして問題なければチェックをつけてCloseを押そう。CloseといってもIssueを閉じるみたいな意味合いではなく、修正をフリーズするくらいの意味合い。

closeすると裏で作業が走るのでしばらく待たされる。のんびり待とう。たまにRefreshボタンを押して更新するのを忘れないこと。

しばらくするとReleaseが押せるようになっているはずなので、Releaseを押下すると全世界にアーティファクトが公開される。おめでとう。

Nexus Repository Managerで様子を見る代わりに・・・

先程のNexus Repository Manager側の手順は、Millでは以下のように--release trueを渡すことで自動化できる(しばらく時間がかかる点については同じ)。こちらを使ったほうがよいだろう。

SONATYPE_USERNAME="username" SONATYPE_PASSWORD="..." mill publish --release true

使う

検索サービスにインデックスされるまでには時間がかかるし、リポジトリに反映されるのにも1時間ほどかかるようだが、例えばScala Scriptから依存性を定義するとちゃんと使えるようになっているはず。

//> using scala 3.3.0
//> using dep io.github.windymelt::qw::0.1.3

import io.github.windymelt.qw.Syntax.{*, given}

println(qw"foo bar buzz")
% scala-cli qw.scala.sc
Downloading 2 dependencies
Compiling project (Scala 3.3.0, JVM)
Compiled project (Scala 3.3.0, JVM)
List(foo, bar, buzz)

動いた。

そのうち、Scaladexに登録されるので楽しみに待とう。

qw.scala (scala-lang.org)

それでは良いScalaライフを!

参考文献

yuki312.blogspot.com

central.sonatype.org

mill-build.com

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?