アカウンティング・サース・ジャパン Advent Calendar の15日目!
はじめに
自分がsbtを使い始める上で、まず公式のGetting Startedを読んでみたのですが、なかなか理解しにくい部分や、今後のためにまとめておきたい部分などがあったので、一部についてまとめ直しや補足をしています。これからsbtを始める方は公式のGetting Startedの補足に見ていただけばと思います。今回はsbtのプロジェクト構成の部分をまとめていきます。
公式の Getting Started
http://www.scala-sbt.org/0.13/docs/Getting-Started.html
sbtプロジェクトとビルド定義の関係
sbtプロジェクトの構成
sbtのプロジェクトは、マルチプロジェクトとして構成できます。単一のプロジェクトももちろん可能ですが、マルチプロジェクトにして嬉しいこととしては、複数のプロジェクトを1つのビルドにまとめたり、プロジェクト間にコンパイル時の依存関係をもたせらるところです。
例えば、lib, web, domain の3つのプロジェクトをもつ myapp プロジェクトの構造は以下のようになります。(testやresourceのディレクトリは省略)
myapp/
lib/
src/main/scala
build.sbt
web/
src/main/scala
build.sbt
domain/
src/main/scala
build.sbt
project/
build.sbt
ここで、build.sbt や project/ はsbtのビルド定義のために配置します。詳しくは後ほど。
ちなみに、単一のプロジェクトの構成は以下のようになります。
myapp/
src/main/scala
project/
build.sbt
sbtのビルド定義
sbtは利用者が設定したビルド定義を、sbtコマンド実行時に読み込んで、ビルド情報のMapを構築します。このMapをもとに、ビルドにまつわるさまざまなタスクが実行されます。このビルド定義はどこで指定されるのかというと、
- project/ 配下で, ビルドオブジェクト(sbt.Buildをextendsしたobject)を用意して定義する
- 現在は.sbtによる定義の方が推奨されているようです
- グローバルなビルド定義: ~/.sbt/0.13/global.sbt
- すべてのプロジェクトのビルドに適用される
- プラグインによるビルド定義
- 各プロジェクトの .sbt ファイルに定義されたビルド定義
となっているようです。上記の順番で、エントリが追加されたビルド情報のMapが生成されていき、重複する定義情報は後勝ちになります。(つまり、下の方が優先される)
project/ にビルド定義の詳細を押し込む
project/ は親プロジェクトに対するビルド定義プロジェクトになっていて、ここに.scalaファイルを配置することで、親プロジェクトのビルド定義(.sbt)から参照できるようになります。
myprj
project/
Build.scala
build.sbt -- project/Build.scalaを参照できる
project/*.scala もビルドされる必要があるので、sbtでは project/もsbtプロジェクトとしてあつかいます。(project/に対するビルドはメタビルドと呼びます)では、project/ に対するビルド定義はどのように行うかというと、project/*.sbtだったり、project/project/*.scalaを(必要に応じて)用意します。ということで、ビルド定義プロジェクトについては、再帰的に扱っているようです。
myprj
project/
project/
Build.scala
build.sbt -- project/project/Build.scalaを参照する
build.sbt
また、ビルド定義プロジェクト(project/)では、グローバルプラグイン(~/.sbt/0.13/plugins/) からビルド定義の影響を受けます。
では、project/*.scalaはどのような場合に使うべきかというと、build.sbtの見通しをよくするために、ビルドタスクの実装や共有する値の定義など、詳細にあたる部分を外に出しておきたい時に、project/*.scalaに押し込むというイメージだと理解しています。
ビルド定義(build.sbt)を記述する
sbtは上記のように各種ビルド定義からビルド情報のMapを構築しますが、それをどのようにbuild.sbtに定義するのかという話です。
キーとSetting[T]
ビルド情報Mapの元ネタになるのが、ビルド定義に記述されたSetting[T]です。Tはビルド定義情報Mapでの値の型を表しています。Setting[T]はキー := 値 のように定義できます。例えば、ルートプロジェクトにプロジェクト名(Setting[String])を定義するには、以下のようにbuild.sbtに記述します
lazy val root = (project in file(".")).
settings(
name := "myapp"
)
ここで、nameはsbtで事前に定義されているキーで、SettingKey[String]の型をもっています。また、SettingKeyでは以下の := メソッドが定義されています。
def :=(v: T): Setting[T] = ...
つまり、SettingKey[T]に対する値を定義することで、Setting[T]を生成してビルド定義に追加していくというイメージです。
キーには3つの種類があり、その種類に応じてSetting[T]の定義となります。
キー | Setting | 説明 |
---|---|---|
SettingKey[T] | Setting[T] | ビルド定義のロード時に一度だけ評価される |
TaskKey[T] | Setting[Task[T]] | タスクの定義に用いる。タスクの実行時に毎回評価される |
InputKey[T] | Setting[InputTask[T]] | コマンドラインからの入力を受け付けるタスクを定義する |
さらに、 複数の値を持つキー(SettingKey[Seq[String]]など)については、+= や ++= で Setting[T]を生成できるようになっています。例えば定義済みのlibraryDependenciesキーは、SettingKey[Seq[ModuleID]]という型になっているので、ビルド定義では以下のように記載できます。
lazy val root = (project in file(".")).
settings(
name := "myapp",
libraryDependencies ++= Seq(
"xxx" % "aaa" % "1.0",
"yyy" % "bbb" % "2.0"
)
)
キーはsbtで定義済みのキーに加え、自分で定義して利用することもできます。
マルチプロジェクト構成を定義する
ビルド定義(build.sbt)で、依存関係をもたせたマルチプロジェクトを定義するための要素としては、以下の2点です。
- Aggregationを使って、複数のサブプロジェクトに対してタスクの実行をまとめる。
- プロジェクト間のクラスパスの依存関係を定義する。
- コンパイル時に自動的に依存関係に沿ったコンパイルが実行される。
例として、以下の構造のmyappプロジェクトを考えます。
myapp/ -- タスク実行時に、web,cliのタスクをまとめて実行したい
lib/
test_lib/
web/ -- Compileではlib、Testではtest_libを参照
cli/ -- Compileではlib、Testではtest_libを参照
project/
build.sbt
このマルチプロジェクトを実現するためには、以下のようにbuild.sbtに記載します。
lazy val root = (project in file(".")).
settings(
name := "myapp",
...
).
aggregate(web, cli)
lazy val lib = (project in file("lib")).
settings(
name := "lib",
...
)
lazy val testLib = (project in file("test_lib")).
settings(
name := "test-lib",
...
)
lazy val web = (project in file("web")).
settings(
name := "web",
...
).
dependsOn(
lib,
testLib % "compile->test")
lazy val cli = (project in file("cli")).
settings(
name := "cli",
...
).
dependsOn(
lib,
testLib % "compile->test")
Project#aggregateや、Project#dependsOnによって、プロジェクト間の依存関係を定義しています。
おわりに
今回はsbtのプロジェクト構成の組み方と、キーの種類という基本的な部分についてまとめました。
mavenやgradleにもマルチプロジェクトは出てくるので、そこは何となく理解できましたが、
キーというのはsbt特有な考え方ですね。しかもこのキーはスコープの概念と合わさって実は奥が深かったりするのです。ということで次回は、キーのスコープ周りについてまとめたいと思います。最後に自分がsbtを試す時によく使ったコマンドをまとめておきます。
sbtコマンドまとめ
コマンド | 説明 |
---|---|
projects | マルチプロジェクトの場合にプロジェクト一覧を表示 |
project <projectname> | マルチプロジェクトの場合に指定プロジェクトに移動 |
plugins | 有効なプラグインの一覧を表示 |
tasks | タスクの一覧を表示 |
inspect <keyname> | キーをインスペクトする。適用されるスコープやデリゲートの確認など |
show <keyname> | TaskKeyの実行結果を確認 |
<keyname> | SettingKeyの値を確認 |