sbt を始めるためのまとめ -キーのインペクトとスコープ-

[アカウンティング・サース・ジャパン Advent Calendar](http://qiita.com/advent-calendar/2016/accounting_saas_japan) の18日目！

## はじめに
自分がsbtを使い始める上で、まず公式のGetting Startedを読んでみたのですが、なかなか理解しにくい部分や、今後のためにまとめておきたい部分などがあったので、一部についてまとめ直しや補足をしています。これからsbtを始める方は公式のGetting Startedの補足に見ていただけばと思います。[前回](https://qiita.com/mtn81/items/39d25179bdcc79c4ef1a)はsbtのプロジェクト構成について書きました。今回はsbtのキーのスコープとインスペクトについてまとめていきます。

公式の Getting Started
http://www.scala-sbt.org/0.13/docs/Getting-Started.html



## キーのスコープについて理解する
まずは前回のおさらいですが、sbtは各種ビルド定義情報から、ビルドに必要なMapを作成するのでした。そしてビルド定義をbuild.sbtに記述する際は、キー(SettingKey[T])に値を定義して、Setting[T]を作成していくことで、ビルド情報のMapに必要な元ネタを設定していくという流れでしたね。
このキーに対する値は、コンテキストに応じて変わることがあります。いきなりコンテキストと言われても何じゃそれという感じですが、例を見てみると案外わかりやすいです。例えば定義済みのキーとして sourceDirectories: SettingKey[Seq[File]]がありますが、これはコンパイル時とテスト時では別の値をとります。sbtのコンソールで見てみます。

<pre>
> compile:sourceDirectories
[info] * /xxx/sbt_study/src/main/scala-2.10
[info] * /xxx/sbt_study/src/main/scala
[info] * /xxx/sbt_study/src/main/java
[info] * /xxx/sbt_study/target/scala-2.10/src_managed/main
</pre>
<pre>
> test:sourceDirectories
[info] * /xxx/sbt_study/src/test/scala-2.10
[info] * /xxx/sbt_study/src/test/scala
[info] * /xxx/sbt_study/src/test/java
[info] * /xxx/sbt_study/target/scala-2.10/src_managed/test
</pre>

ここで、指定している compile: とか test: とかがキーのコンテキストを指定している部分になっています。sbtでは、このコンテキストのことをスコープ(Scope)といいます。あるキーに対しては、スコープを特定しなければ一意に値を取得できないということです。そして、このスコープを特定するための情報には３つの種類があり、sbtでは軸(Axis)とよばれます。

|軸|説明|
|---|---|
|Project|マルチプロジェクト構成の場合に、プロジェクトごとにキーの値を変えられる|
|Configuration|Compile,Test,Runtimeなどのコンフィギュレーション（Ivyから来ている概念らしい）ごとに、キーの値を変えられる|
|Task|タスクごとにキーの値を変えられる。ex. packageSrcタスクとpackageBinタスク実行時で、artifactNameを変える|

sbtのコマンドラインでキーを指定する時には、この３つの軸を以下のような形式で指定することで、スコープを限定することができます。
```
{<build-uri>}<project-id>/config:intask::key
```

またビルド定義で、キーのスコープを指定するには例えば以下のように記述します。

```scala
lazy val lib = (project in file("lib")).
  settings(
    unmanagedBase in (Compile, compile) := baseDirectory.value / "compilelib"
  )
```

上記の例では、unmanagedBaseキーのスコープが、Project=lib, Configuration=Compile, Task=compile で限定されていることになります。
（unmanagedBaseは、sbt定義済みのキーで、手動でjarを配置するパスを指します。）

### 実際に適用されるキーを調べてみる
ある特定のキーについて調べるにはinspectコマンドが便利です。例えば上記のunmanagedBaseについて、inspectを打ってみると

```
> inspect compile:compile::unmanagedBase
[info] Setting: java.io.File = /xxx/study/scala/sbt_study/compilelib
[info] Description:
[info]  The default directory for manually managed libraries.
[info] Provided by:
[info]  {file:/xxx/study/scala/sbt_study/}root/compile:compile::unmanagedBase
[info] Defined at:
[info]  /xxx/study/scala/sbt_study/build.sbt:4
[info] Dependencies:
[info]  root/*:baseDirectory
[info] Delegates:
[info]  root/compile:compile::unmanagedBase
[info]  root/compile:unmanagedBase
[info]  root/*:compile::unmanagedBase
[info]  root/*:unmanagedBase
[info]  {.}/compile:compile::unmanagedBase
[info]  {.}/compile:unmanagedBase
[info]  {.}/*:compile::unmanagedBase
[info]  {.}/*:unmanagedBase
[info]  */compile:compile::unmanagedBase
[info]  */compile:unmanagedBase
[info]  */*:compile::unmanagedBase
[info]  */*:unmanagedBase
[info] Related:
```

スコープの指定なしで打ってみると

```
> inspect unmanagedBase
[info] Setting: java.io.File = /xxx/study/scala/sbt_study/lib
[info] Description:
[info]  The default directory for manually managed libraries.
[info] Provided by:
[info]  {file:/xxx/study/scala/sbt_study/}root/*:unmanagedBase
[info] Defined at:
[info]  (sbt.Classpaths) Defaults.scala:1169
[info] Dependencies:
[info]  root/*:baseDirectory
[info] Delegates:
[info]  root/*:unmanagedBase
[info]  {.}/*:unmanagedBase
[info]  */*:unmanagedBase
[info] Related:
```

スコープを指定しない場合は、結果が変わっています。この理由はinspectの出力をみるとなんとなく想像できます。sbtは指定されたキーの値を、"Delegates"の順に見ていき、はじめに値が見つかったものを返します。実際に見つかったキーは "Provided by"で確認できます。ここで、\* や \{.\}という記号ができていますが、

* {.}はProject軸の値でビルド全体を表しています。build.sbtではThisBuildで指定できます。マルチプロジェクトの場合に使えそうですね。
* \*は各軸でのGlobal値を表してします。build.sbtではGlobalで軸を指定して値を定義すると、その軸の任意の値で適用されるデフォルト値になります。

### タスクの依存関係を追いかけてみる
inspectコマンドの"Dependencies"をみると、キーの依存関係がわかります。例えば、testタスクを実行すると、テストコードを実行する前に、メインのソースがコンパイルされますが、これはどんな依存関係で行なわれているのでしょうか。実際にinspectを打ちながら、Dependenciesを遡っていくと以下のような感じになっていました。（関係ありそうな部分のみ記載しています。）

```
test -> root/test:executeTests -> root/test:fullClasspath -> root/test:exportedProducts
```

root/test:exportedProductsをコマンドラインから実行すると確かにソースがコンパイルされるのが確認できます。

## おわりに
今回はキーのスコープについてまとめてみました。プロジェクトごと、コンフィグレーションごと、タスクごとにキーの値を変えることで、柔軟にビルドをカスタマイズできるようになっているんですね。
合わせて、inspectコマンド重要ですね〜。実際にどんなキーがあって、どんなカスタマイズができるかは、公式のドキュメントやその他情報を漁りながら少しずつ覚えていくしかないと思いますが。
そろそろ自前のタスクを作りたくなってきたので、次回はタスクの実装について書く予定です。