Graph)とsbtsbtsbtを使ったことがある人なら、たいていSettingKeyやTaskKeyという言葉を聞いたことがあるはずです。聞いたことがなくても、1度でもsbtを使ったことがあるなら、すでに知らないうちにそれらに触れているはずでしょう。それらSettingKeyやTaskKeyを含んだsbtの内部DSLについては、その独特さゆえに、好みが分かれることが多いと思います。そもそもなぜSettingKeyやTaskKeyという概念が必要なのか?それらは一体何なのか?ということに関して、少し別の視点から考察や解説をしてみることによって、sbt自体の理解の手助けとなることを目的として、このエントリを書いてみます。なんだか、少しばかり堅苦しい始まり(?)にしてみましたが、つまり「sbtのKeyは有向非巡回グラフにおけるNodeであり、build.sbtは有向非巡回グラフを組み立てるDSLである」みたいな話をするだけです。今の一文を聞いただけで完全に理解した人は、この続きは読まなくていいでしょう。sbt自体のアーキテクチャの説明としては、以前もわりと頑張って以下のようなものを書いたことがありますがsbtの密結合な内部アーキテクチャ今回説明するのは、その部分と比べればフロント寄り?というか、その部分とは基本的に独立して考えることが可能な部分であり、sbtの根本的な重要な概念の一つだと思っています。SettingKeyやTaskKeyそのものについては、このエントリではそんなに詳しく解説しません。sbtの公式ドキュメントや、最近だと、このまえのj5ik2oさんのScala関西での発表資料をみるといいかもしれません。Graph)って聞いたことありますか?言葉だけだと少しすごそうに見える(?)かもしれませんが、プログラミングをやっていると、いたるところにあふれていると言っても過言ではないかもしれません。Scalaのclassやtraitの継承関係あたりもそうじゃないでしょうか?sbtのシステムとの関係というか少し似ている(?)ものでいえば、SparkのJobの依存関係なんかも、DAGが使われているはずです。*1あとは、最近話題?なakka-streamのFlowGraphあたりも、おそらくDAGでしょうhttps://github.com/akka/akka/blob/releasing-akka-stream-and-http-experimental-1.0/akka-stream-tests/src/test/scala/akka/stream/scaladsl/FlowGraphCompileSpec.scala#L148例を上げていったらキリがないほど身の回りにあふれているはず?なので、他にも自分で探してみてください。有向非巡回グラフいま「SparkのJobの依存関係」と言いましたが、sbtでもつまり「Task同士の依存関係」があります。sbtには、代表的なものとして、SettingKeyとTaskKeyという2種類のKeyがあります。*2どちらで説明しても本質は同じなので、あまり区別せずに適当に例を挙げていきますが、たとえばcompileというTaskがあるtest:compile(テストコードのコンパイル)というTaskもあるtestというTaskがあるというときにtest:compileはcompileというTaskに依存するtestはtest:compileに依存するという関係のはずですね?これだと単に一直線な依存関係です。b)cのコードをcompileするには、まずaとbをコンパイルする必要があるしかし、aとb自体には依存関係はないので、aを先にコンパイルしても、bを先にコンパイルしてもどちらでもよいし、並列に実行しても良いというような関係ですね?*3今は、compileやtestくらいしか挙げていませんが、他にも「プロジェクトをビルドする」という際には、色んな行為を行うはずです。依存ライブラリの解決scaladocの生成jarの生成pom.xmlの生成その他、任意の色々な独自tasksbtがデフォルトで用意しているKey一覧はKeys.scalaというファイルにあるので、暇な時になんとなく眺めるのをオススメしますhttps://github.com/sbt/sbt/blob/v0.13.8/main/src/main/scala/sbt/Keys.scalaそれらは、相互に複雑に依存し合っているはずですね?しかしどんなに複雑であろうとも、基本的にそれは「有向非巡回グラフで表現できる」というのがポイントです。valかdefのようなものではなく「全体の関係をデータ構造としてグラフで保持」する必要があります。さきほど、さり気なく「並列に実行しても良い」と書きましたが、sbtは事実、並列に実行可能な部分は並列に実行します。仮に並列に実行しないとしても、依存関係を把握して、無駄なTaskを再実行しないためには、グラフとして保持するのは必要不可欠です。用いたタスクの逐次化あたりも、興味があれば読んでみるとよいでしょう。(最近この機能はsbt本体に入りました)ここまでの説明でえ、そんなこと当たり前だろ?何言ってるかわからないなるほどそういうことだったのか!?とそれぞれ思った人が何割ずつなのか不明ですが、そういうことなのです!(強引sbtのDSLが独特すぎて意味不明だと感じていた人も、その「有向非巡回グラフ」という点を意識すると、sbtと少しだけ仲良くなれるかもしれません。}これら、Keyの依存関係は、データ構造としてずっと保持しておかないといけません。依存関係の解決は、すぐその場でおこなわれるのではなく、ある程度必要になるまで遅延されるはずです。sbtのコンソール上で"set"というコマンドを使って、インタラクティブにグラフ構造を変更することも可能です。いうメソッドもあり、それが「グラフの依存関係を解決して、ソートする(一直線に並べる)」ためのものです。Aを評価するといったことも可能です。immutable」なので、すこし独特に感じるのかもしれません*4そのような「タスク同士の依存関係」を表現するには、DAGは最適なデータ構造だと思うので、他のビルドツールでも多かれ少なかれ、内部的には似たような構造を持っていると思うのですが、他のビルドツール詳しくないのでわかりません。a.valueTaskやSettingの依存関係は「有向非巡回グラフ」でなければならないので、sbtはある意味その「グラフが循環している」という、内部のエラーをそのまま投げている、ということなのです。用いた四次元空間内の移動を読むと、少しは理解が進むのではないかと期待してるのですがどうでしょう?というか、逆にこのDAGの構造を全く意識したことがない人が「4次元」と言われても、たぶん困惑するだけな気はします。このように見ていくと、このTaskKeyとTaskがDAGとして表現されている、という部分だけみると「ビルドツール」というよりは、少し大げさに言えば「依存関係を独自DSLによって宣言的に定義できる、別のプログラミング環境」みたいな捉え方ができるかもしれません(?)。1注目記事はてなブログをはじめよう!xuweiさんは、はてなブログを使っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。