sbt-sonatypeとsbt-release pluginを組み合わせて使うサンプル

追記
sbt-sonatype0.2.1から、sonatypeReleaseAllがInputKeyではなくTaskKeyになったらしいので、sonatypeReleaseAllを使う場合は「わざわざTaskKeyを作ってInputTaskからTaskKeyに変換」というのはいらなくなったよ!
追記おわり




versionは

  • sbt-sonatype 0.2.0
  • sbt-release 0.8.2
  • sbt-pgp 0.8.1
  • sbt 0.13.1

です。以下、このサンプルのリポジトリ

https://github.com/xuwei-k/sbt-sonatype-and-sbt-release-sample

を説明するだけです。

説明より、とにかくコード読みたいひとは、先にコード読んだほうがいいでしょう。ある程度色々設定書いてありますが、誰でも設定するようなオプションや、sonatypeからmaven centralにsyncするための必要な設定を書くと、このくらいの分量になってしまいます。*1


まず、sbt-releaseの大雑把な説明を貼り付けておきます。(もっと詳しくはsbt-releaseのREADME読みましょう)


さて、べつにsbt-sonatypeとsbt-releaseを組み合わせ使う際にひどい罠があるわけではありません。直交性はあるので普通にそれぞれの使い方を知っていれば使えます。
しかし、sbt-releaseは、リリースのプロセスをカスタマイズできるので
「最後のほうにsbt-sonatypeのタスクもsbt-releaseのプロセスと統合して、全部自動で行うようにしよう!」
というだけのサンプルです。


で、その際に

  • InputTaskをTaskに変換する必要があった最新のsbt-sonatypeは必要ない
  • sbt-releaseで、一つのプロジェクトの場合と、マルチプロジェクトの場合に設定方法が微妙に異なる

ので、そのあたりを簡単に説明するだです。


説明とはいっても、上記の2点のポイントを抑えれば、あとはとにかくコード見てもらったほうがはやいかもしれません。
sbt-releaseで、デフォルトのReleaseのプロセスは以下のようになってます

https://github.com/sbt/sbt-release/blob/v0.8.2/src/main/scala/ReleasePlugin.scala#L79-L91

  checkSnapshotDependencies,
  inquireVersions,
  runClean,
  runTest,
  setReleaseVersion,
  commitReleaseVersion,
  tagRelease,
  publishArtifacts,
  setNextVersion,
  commitNextVersion,
  pushChanges

独自のReleaseのProcess*2を追加する場合には、(いくつか方法がありますが)一番単純なものとしては、Stateを引数にとってStateを返す関数を定義します。たとえば、publishSignedを実行するProcessを定義するのは以下のようになります。

https://github.com/xuwei-k/sbt-sonatype-and-sbt-release-sample/blob/423506/build.sbt#L58

ReleaseStep(state => Project.extract(state).runTask(PgpKeys.publishSigned, state)._1)

このような形でTaskKeyを渡せば、ほかの任意のTaskをsbt-releaseのProcessにすることができます。
ちなみに、publishSignedの場合は、プロジェクトがクロスビルドの場合は以下のようになります(複数のscalaのversionでビルドしてる場合、それぞれpublishSignedする必要がある)

ReleaseStep(
  action = state => Project.extract(state).runTask(PgpKeys.publishSigned, state)._1,
  enableCrossBuild = true
)

さて、sbt-sonatypeの一番基本的というか全部やってくれるコマンドであるsonatypeReleaseまたはsonatypeReleaseAllは、InputTaskです。

これどうやってsbt-releaseのProcessにすればいいのだろうか?
と考えてしらべたところ、sbt自体に
"任意のInputTaskをTaskに変換できる機能"
があるらしいので、それを使うことにしました*3
*4


https://github.com/sbt/sbt/blob/v0.13.1/main/settings/src/main/scala/sbt/InputTask.scala#L35
http://www.scala-sbt.org/0.13.1/docs/Extending/Input-Tasks.html#get-a-task-from-an-inputtask

Initialize[ InputTask[T] ] に対して、toTaskというメソッドを呼ぶと、Initialize[ Task[T] ]になるらしいです。InputTaskはタスクの実行直前にユーザーからの入力を取るわけですが、この場合toTaskのメソッドを呼ぶ時点で、「ユーザーからの入力」に相当する文字列をStringで渡してしまいます、というか渡さないとTaskに変換できません。

というわけで、結果的にsonatypeReleaseAllをsbt-releaseのProcessにする方法は以下のようになりました

val sonatypeReleaseAllTask = taskKey[Unit]("sonatypeReleaseAllTask")

sonatypeReleaseAllTask := SonatypeKeys.sonatypeReleaseAll.toTask("").value

ReleaseStep(state => Project.extract(state).runTask(sonatypeReleaseAllTask, state)._1),

わざわざTaskKeyを一旦つくらないといけないのが微妙に面倒なんですが、もっといい方法ないのかな・・・。




マルチプロジェクトの場合も、InputKeyをTaskKeyにするのは同じようにやります。ただ、ルートのプロジェクトからaggregateしたそれぞれのサブプロジェクトのTaskを実行する場合は、Project.extract(state).runTaskではだめで、以下のように書くらしいです。*5

https://github.com/xuwei-k/sbt-sonatype-and-sbt-release-sample/blob/af26a6904/project/Build.scala#L56-L59

ReleaseStep{ state =>
  val extracted = Project extract state
  extracted.runAggregated(sonatypeReleaseAllTask in Global in extracted.get(thisProjectRef), state)
}

あとは、このsonatypeReleaseAllをsbt-releaseのProcessにしたものを、最後の方に追加(publishSignedからpushChangesの間ならどこでもいいと思う)すればいいだけです。


というわけで、組み合わせるとより便利なので、みんな*6sbt-releaseやsbt-sonatype使いましょう。

*1:あれでも、それほど無駄な設定は書いたつもりはなく、ある程度シンプルにしたつもり

*2:実態は、こんなcase classです https://github.com/sbt/sbt-release/blob/v0.8.2/src/main/scala/ReleasePlugin.scala#L116

*3:もっと良いやり方あったら教えて下さい

*4: ところで、この機能わりと最近入ったばかりなんですね。sbt0.13.1からの機能で、0.13.0には存在しない。 https://github.com/sbt/sbt/commit/9dcb8727d819fb8992

*5:自分もよくわかってないので、他のプロジェクトの使用例からコピペしました

*6:といっても、sbt使っててsonatypeのアカウント持ってる日本人、数えるくらいしかしらないけど・・・