sbtで指定されたプロジェクトのtaskのみを並列に実行する

(sbt標準にあったらしい?ので)下の方に追記したよ!

すごく簡単に説明すると、以下のような定義があったときに

lazy val a = project

lazy val b = project

lazy val c = project

"a"と"b"の"compile"だけを"並列に"実行したい、みたいな。(cのcompileは実行したくない)

なぜかsbt標準にも、外部のsbt pluginでも存在していない気がする・・・?(あったら教えてください) なので、それを書いたのを説明する記事

もう少しポイントを説明すると

  • "並列" ではなく "直列" でいいなら sbt a/compile b/compile とだけすればいい
  • "指定されたものだけ" ではなく "(多少無駄があっても)とにかく全部実行してもいい" なら、単純に sbt compile でいい*1
  • 指定するパターンが数え上げられるほどごく少数なら、事前にそれ用のrootプロジェクト作ってaggregateなどでもいい?(もしくはaggregate部分をsbt起動時に -D で引数渡したもので無理やりフィルターとか?)

などがあります。compileで説明しましたが、実際仕事で遭遇したのは、docker:publishでした。 (それなりな容量なので個々のpublish時間かかるし、色々な意味で無駄にpublishしたくない、みたいな理由で?)

とりあえず下に現状作ったものを貼っておきます。(一部?)マクロ使わない版のメソッド使ったけど、他の書き方で書けるのか?は謎。

parserの部分は、以前書いたこれ

xuwei-k.hatenablog.com

val allProjects = Seq[ProjectReference](a, b, c)

の部分を明示的に列挙してるのがダサいので、自動で取得できないかな?と思ったけど、今sbtにその機能あるのかないのかよくわからなかったので、一旦諦めました。

read-only def projects: Seq[Project] or scopefilter available in build.sbt · Issue #3030 · sbt/sbt · GitHub

いずれにせよ、その列挙の部分は、tab補完用のparserでしか使ってないので、別に必ずしもなくてもいい部分です。

肝心の並列化は、sbt自体が並列化可能なものは勝手にやるので、taskを組み立てているだけで、こちらとしては明示的に並列にする処理は何も書いていません。 個々のprojectのtaskをSeq[Initialize[Task[T]]] として取得したものを、joinというメソッドで Initialize[Task[Seq[T]]] という型に変えているのが、ある意味最大のポイントです。

Scala標準のFutureのsequenctとか、scalaz.Traverse (HaskellのTraversable Functor) のsequenceみたいなことをして、型の入れ子を逆にして、複数のTaskを一つのTaskにしています。

https://github.com/sbt/sbt/blob/v1.2.8/main-settings/src/main/scala/sbt/Structure.scala#L463

追記: sbt標準にallという名前で存在しているらしい・・・!?

sbt all command · GitHub

gist.github.com

*1:明示的にroot定義しなければ、自動でsbtが生成する暗黙のrootでa, b, cは全部aggregateされているので