Locked History Actions

sbt/Getting-Started-Multi-Project

マルチプロジェクト

https://github.com/harrah/xsbt/wiki/Getting-Started-Multi-Projectの訳(2011/10/30時点)

ここでは、一つのビルドで複数のプロジェクトを扱う方法を紹介する。

これまでのページを読んでおいて欲しい。特にこのページの前にbuild.sbtと.scalaビルド定義の知識が必要だ。

マルチプロジェクト

一つのビルドの中に、互いに関連する複数のプロジェクトを格納できれば便利だろう。 特に、それらが依存しあっており、しばしば同時に変更することがあるなら。

各サブプロジェクトは、それ自身のsrc/main/scalaを持ち、packageコマンドを実行すると、それ自身のjarファイルを生成し、 一般に他のプロジェクトと同様に機能する(訳注:?)

.scala中でのプロジェクト定義

複数のプロジェクトを扱うには、まず各プロジェクトを宣言し、それらがどのように関連するかを.scalaファイルに記述しなければならない。 .sbtファイルではこれはできないのだが、ただし各プロジェクトのsettingについては.sbtファイルに記述することができる。 以下に.scalaファイルの例をあげるが、ここでは、helloというルートプロジェクトがあり、これは二つのサブプロジェクトhello-fooとhello-barを集約している。

import sbt._
import Keys._

object HelloBuild extends Build {
    lazy val root = Project(id = "hello",
                            base = file(".")) aggregate(foo, bar)

    lazy val foo = Project(id = "hello-foo",
                           base = file("foo"))

    lazy val bar = Project(id = "hello-bar",
                           base = file("bar"))
}

sbtはリフレクションを使ってProjectタイプのフィールドを探し、Projectのリストを作成する。

hello-fooはbase = file("foo")として定義されているので、fooというサブディレクトリに含まれる。 そのソースはfooの直下にあってもよい、例えばfoo/Foo.scalaでもよいし、foo/src/main/scalaにあってもよい 通常のsbtディレクトリ構造がfoo以下に適用される。ただし、ビルド定義ファイルは除く。

fooにある.sbtファイル、例えばfoo/build.sbtは全体ビルド定義にマージされる。ただし、スコープはhello-fooプロジェクトになる。

もし君の全プロジェクトがhello以下にあるなら、 hello/build.sbt, hello/foo/build.sbt, and hello/bar/build.sbtで別のバージョン(version := "0.6"など) を指定してみよう。 そして、sbt対話モードで「show version」とする。 こんな風に表示されるだろう。

> show version
[info] hello-foo/*:version
[info]  0.7
[info] hello-bar/*:version
[info]  0.9
[info] hello/*:version
[info]  0.5

hello-foo/*:versionはhello/foo/build.sbt中で定義され、hello-bar/*:versionはhello/bar/build.sbt中で定義され、hello/*:versionはhello/build.sbt中で定義されている。 スコープキーの構文を思い起こそう。 各versionキーはプロジェクトにスコープ付されているのだが、これはbuild.sbtの位置に基づいている。 しかし、これら三つのbuild.sbtは同じ一つのビルド定義の一部なのである。

各プロジェクトのsettingは、そのプロジェクトのベースディレクトリ中の.sbtファイルに記述される。 その一方で、.scalaファイルは上にしめしたように、可能な限り単純にすることができる。 単に、プロジェクトをリストしてそのベースディレクトリを設定するだけだ。 .scalaファイルにsettingを格納する必要はない。

しかし、settingも含めてすべてを.scalaファイルに格納する方がすっきりすると思うかもしれない。それは君におまかせする。

しかし、サブプロジェクトに、projectサブディレクトリ、つまりproject/*.scalaファイルを持たせることはできない。 つまり、foo/project/Build.scalaは無視される。

集約(aggregate)

望むなら、ビルド中のプロジェクトは完全に他から独立させることもできる。

しかし、上記の例では、aggregate(foo, bar)というメソッド呼び出しが存在するののがわかると思う。 これは、ルートプロジェクトしたにhello-foo,hello-barを集約するということだ。

集約の意味するところとしては、集約プロジェクトについてタスクを実行すると、集約されたプロジェクトについても同じタスクが実行されるということだ。 例えば、サンプルのように二つのサブプロジェクトを持つプロジェクトでsbtを実行し、compileしてみよう。 三つの全プロジェクトがコンパイルされることがわかると思う。

集約を行うプロジェクトにおいて、この場合はhelloプロジェクトだが、 タスクごとの集約をコントロールすることができる。 例えば、hello/build.sbtでupdateタスクを集約しないようにすることができる。

aggregate in update := false

「aggregate in update」はupdateタスクにスコープ付されたaggregateキーだ。「スコープ」を参照のこと。

注意:集約は集約されたタスクを並列に実行する、定義された順序というものはない。

クラスパスの依存

プロジェクトは他のプロジェクトのコードに依存することがある。 これをdependsOnメソッドコールで追加することができる。 例えば、hello-fooがgello-barのクラスパスを必要とするなら、 Build.scalaに以下のように記述することができる。

  lazy val foo = Project(id = "hello-foo",
                         base = file("foo")) dependsOn(bar)

これで、hello-fooはhello-barのクラスを使用することができる。 これは同時に、プロジェクトのコンパイル順序を決めることでもある。 hello-barはhello-fooがコンパイルされる前にupdateされていなければならない。

複数のプロジェクトに依存させたいのであれば、dependsOnに対する複数引数を使う。dependsOn(bar, baz)のように。

コンフィギュレーション毎クラスパス依存

「foo dependsOn(bar)」はfooにおけるCompileコンフィギュレーションがbarのCompileコンフィギュレーションに依存するということだ。これを明示的に「dependsOn(bar % "compile->compile")」と記述することもできる。

"compile->compile"の「->」は「依存する」を意味するので、 "test->compile"はfooのTestコンフィギュレーションはbarのCompileコンフィギュレーションに依存するという意味になる。

「->config」パートを省略すると「->compile」と解釈されるので、dependsOn(bar % "test")はfooのTestコンフィギュレーションがbarのCompileコンフィギュレーションに依存することになる。

"test->test"は有用な宣言だ、これはTestがTestに依存するという意味になる。 これによって例えば、bar/src/test/scalaにユーティリティを格納して、foo/src/test/scalaから利用するということができる。

また、依存についてセミコロンで区切られた複数のコンフィギュレーションを指定することができる。 例えば、 dependsOn(bar % "test->test;compile->compile")のように。

プロジェクトを対話的にナビゲートする

対話モードで、projectsと入力すると、プロジェクトをリストすることができ、「project <プロジェクト名>」と入力すると、それを現在のプロジェクトとして指定する。 compileのようなタスクを入力すると、それは現在のプロジェクト上で動作する。 したがって、必ずしもルートプロジェクトをコンパイルする必要は無い。サブプロジェクトのみをコンパイルすることもできる。

次はカスタム設定とタスク