スコープ
https://github.com/harrah/xsbt/wiki/Getting-Started-Scopesの訳(2011/10/27時点)
ここではスコープを説明する。前のページ「.sbtビルド定義」を読んで理解していることを前提にするよ。
キーについてのすべて
nameのようなキーはsbtのキー・値マップの一つのエントリになるといった説明を以前にしたが、これは話を単純化したものだ。 実を言えば、それぞれのキーは複数のコンテキスト、これをスコープというのだが、に関連した値を持つことができるんだ。
例を示そう。
- ビルド定義の中に複数のプロジェクトがあるなら、キーはそれぞれのプロジェクトについて別の値を持ちうる。
- compileキーはメインソースについてと、テスト用ソースについては別の値を持ちうる。もし違ったやり方でコンパイルしたいのであれば。
- package-optionsキー(jarパッケージ生成時のオプションを含んでいる)はクラスファイルのパッケージング(package-bin)時とソースコードのパッケージング(package-src)時では異なる値を持つ。
キー名称に対する唯一の値は無く、スコープによって異なるんだ。
しかし、スコープ付のキーには唯一の値がある。
以前に説明したように、sbtがプロジェクトを表現するキー・値マップを作成するためにsettingのリストを処理するのだとしたら、そのキー・値マップの中のキーはスコープ付のキーだ。 ビルド定義(例えばbuild.sbt)中のそれぞれのsettingもまたスコープ付キーに適用される。
スコープはデフォルトのあることが多いのだが、しかしそれが間違っているなら、build.sbt中で望むスコープを指定しなければならない。
スコープ軸
スコープ軸とはタイプであり、そのタイプの各インスタンスはそれ自身のスコープを定義する。(つまり、各インスタンスはキーに対するそれ自身のユニークな値を持つ)。
スコープ軸としては三つある。
- プロジェクト
- コンフィギュレーション
- タスク
プロジェクト軸によるスコープ
もし一つのビルド中に複数のプロジェクトを格納するなら、各プロジェクトはそれ自身のsettingを持たなければならない。 つまり、キーはプロジェクトによってスコープされるわけだ。
プロジェクト軸は、「全体ビルド」に設定されることもある。このとき、settingは個々のプロジェクトではなく、全体ビルドに適用される。 ビルドレベルsettingは、しばしばプロジェクト特有のsettingの無いときのフォールバックとして使用される。
コンフィギュレーション軸によるスコープ
コンフィギュレーションはビルドの「フレーバー」を定義する。 多くは、それ自身のクラスパス、ソース、生成パッケージ等を持つ。 コンフィギュレーションという概念はIvyから借りてきている which sbt uses for managed dependencies, and from MavenScopes.
sbtにあるコンフィギュレーションの一部としては以下だ。
- Compileはメインビルドを定義する(src/main/scala).
- Testはテストノビルド方法を定義する(src/test/scala).
- Runtimeはrun taskのためのクラスパスを定義する
コンパイル、パッケージング、実行に関連するキーは、デフォルトではコンフィギュレーションにスコープ付されており、それぞれのコンフィギュレーションで異なる働きをする。 最も明らかな例としては、compile,package,runというタスクキーだが、これらに影響を与えるキー(source-directories、scalac-options、full-classpathなど) もまたコンフィギュレーションにスコープ付されている。
タスク軸によるスコープ
settingはタスクの働きに影響を与えることがある。例えば、packag-optionsというsettingはpackage-srcタスクに影響を与える。
これを実現するため、タスクキー(package-srcのような)は別のキー(package-optionのような)のスコープになるようにしてある。
パッケージビルドのためのタスク(package-src, package-bin, package-doc)は関連するパッケージングに関するキー、つまりartifact-namやpackag-optionsを共有できるが、これらのキーは各パッケージタスクで異なる値を持つこともできる。
グローバルスコープ
各スコープ軸は、その軸タイプのインスタンスを代入しうるが(例えば、タスク軸はタスクを代入しうる)、軸には特別な値Globalを代入することもできる。
Globalはその名の通りの働きだ。settingの値は、その軸のすべてのインスタンスについて適用される。例えば、タスク軸がGlobalの場合、settingは全タスクに適用される。
委譲
スコープ付きキーは、そのスコープに対応する値を持たなければ未定義となる。
それぞれのスコープについて、sbtは、他のスコープからなるフォールバックサーチパスを持つ。 典型的には、キーが特化的なキーについて関連づけされた値を持たないのであれば、より一般的なスコープからそれを取得しようとする。 これにはGlobalスコープや全体ビルドスコープがある。
この機能により、一般的なスコープについての値を設定しておけば、複数のより特化したスコープにそれを継承させることができるんだ。
inpectコマンドを使用して、フォールバックサーチパスあるいは「委譲」を調べることができる。 これを以下に述べるので読み進めて欲しい。
sbt実行時のスコープ付キーの参照
コマンドラインや対話モードにおいて、sbtはスコープ付キーを以下のように表示(あるいは解析)する
{<build-uri>}<project-id>/config:key(for task-key)
{<build-uri>}<project-id> はプロジェクト軸を指定する。プロジェクト軸が全体ビルドスコープの場合には、<project-id>パートは存在しない
- configはコンフィギュレーション軸を指定する
- (for task-key)はタスク軸を指定する
- keyはスコープ付されるキーを指定する
「*」はどの軸にも指定することができ、これはGlobalスコープを表す。
もしスコープ付キーのパートを省略した場合、それらは以下のように推測される。
- プロジェクトを省略すると、「現在のプロジェクト」が使用される。
- コンフィギュレーションを省略すると、キー依存コンフィギュレーションが自動検出される。
- タスクを省略すると、Globalタスクスコープが使用される。
詳細については「Inspecting Settings」を参照のこと。
スコープ付キーの記述例
- full-classpath キーのみなのでデフォルトスコープが使用される。つまり、現在のプロジェクト、キー依存のコンフィギュレーション、グローバルタスクスコープだ。
- test:full-classpath コンフィギュレーションが指定されているので、testコンフィギュレーションにおけるfull-classpathになる。他の二つのスコープ軸はデフォルトだ。
- *:full-classpath コンフィギュレーションについてGlobalを指定している。
- full-classpath(for doc) docタスクにスコープ付されたfull-classpathを指定している。プロジェクトとコンフィギュレーションについてはデフォルトだ。
{file:/home/hp/checkout/hello/}default-aea33a/test:full-classpath specifies a project, {file:/home/hp/checkout/hello/}default-aea33a, where the project is identified with the build {file:/home/hp/checkout/hello/} and then a project id inside that build default-aea33a. Also specifies configuration test, but leaves the default task axis.
{file:/home/hp/checkout/hello/}/test:full-classpath sets the project axis to "entire build" where the build is {file:/home/hp/checkout/hello/}
{.}/test:full-classpath sets the project axis to "entire build" where the build is {.}. {.} can be written ThisBuild in Scala code.
{file:/home/hp/checkout/hello/}/compile:full-classpath(for doc) sets all three scope axes.
スコープの調査
対話モードでinspectコマンドを使うことにより、キーとそのスコープを調査することができる。 test:full-classpathをinspectしてみよう。
$ sbt > inspect test:full-classpath [info] Task: scala.collection.Seq[sbt.Attributed[java.io.File]] [info] Description: [info] The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies. [info] Provided by: [info] {file:/home/hp/checkout/hello/}default-aea33a/test:full-classpath [info] Dependencies: [info] test:exported-products [info] test:dependency-classpath [info] Reverse dependencies: [info] test:run-main [info] test:run [info] test:test-loader [info] test:console [info] Delegates: [info] test:full-classpath [info] runtime:full-classpath [info] compile:full-classpath [info] *:full-classpath [info] {.}/test:full-classpath [info] {.}/runtime:full-classpath [info] {.}/compile:full-classpath [info] {.}/*:full-classpath [info] */test:full-classpath [info] */runtime:full-classpath [info] */compile:full-classpath [info] */*:full-classpath [info] Related: [info] compile:full-classpath [info] compile:full-classpath(for doc) [info] test:full-classpath(for doc) [info] runtime:full-classpath
最初の行は、これがタスクであることを示している(「.sbt build definition」で説明したようにsettingではない)。 また、タスクの結果値が「scala.collection.Seq[sbt.Attributed[java.io.File]]」というタイプであることを示している。
"Provided by"はこの値を定義するスコープ付キーを示している。 この場合には
{file:/home/hp/checkout/hello/}default-aea33a/test:full-classpath
になるが、「test」コンフィギュレーションスコープと「{file:/home/hp/checkout/hello/}default-aea33a」というプロジェクトスコープが付いている。
"Dependencies"についてはまだ説明していない。チャンネルはそのままだよ。
委譲(delegates)も表示されている。値が定義されていなければ、sbtは以下を検索する。
- two other configurations (runtime:full-classpath, compile:full-classpath). In these scoped keys, the project is unspecified meaning "current project" and the task is unspecified meaning Global
- configuration set to Global (*:full-classpath), since project is still unspecified it's "current project" and task is still unspecified so Global
project set to {.} or ThisBuild (meaning the entire build, no specific project)
- project axis set to Global (*/test:full-classpath) (remember, an unspecified project means current, so searching Global here is new; i.e. * and "no project shown" are different for the project axis; i.e. */test:full-classpath is not the same as test:full-classpath)
- both project and configuration set to Global (*/*:full-classpath) (remember that unspecified task means Global already, so */*:full-classpath uses Global for all three axes)
(上記の例のinspect test:full-classpathとは異なる)「inspect full-classpath」を実行して、違いを確認してみよう。 コンフィギュレーションが省略されているが、compileであると自動推論される。 したがって、「inspect compile:full-classpath」と入力しても同じ結果が現れる。
さらに「inspect *:full-classpath」としてみよう。 full-classpathはデフォルトではGlobalコンフィギュレーションでは定義されていない。
さらなる詳細は「Inspecting Settings」を参照して欲しい
ビルド定義中でのスコープの参照
build.sbt中において、裸のキーでsettingを作成すると、それは現在のプロジェクトにスコープされ、コンフィギュレーション及びタスクはGlobalになる。
name := "hello"
sbtを起動してnameをinspectし、そのProvided byを見てみよう。
{file:/home/hp/checkout/hello/}default-aea33a/*:name
となるが、これはプロジェクトが「{file:/home/hp/checkout/hello/}default-aea33a」であり、コンフィギュレーションが「*」つまりGlobalであり、タスクは示されていない(これもGlobalを意味する)。
build.sbtは常に単一プロジェクトのsettingしか定義しない。したがって、「現在のプロジェクト」とは、そのbuild.sbtの存在するプロジェクトのことだ。 (マルチプロジェクトビルドでは、それぞれのプロジェクトはそれ自身のbuild.sbtを持つ)。
キーは「in」というオーバロードされたメソッドを持ち、これはスコープを設定する。 inの引数には、どのスコープ軸のインスタンスをも与えることができる。 例えば、こんなことはしないと思うけど、Compileコンフィギュレーションスコープを指定してnameを設定することができる。
name in Compile := "hello"
あるいは、package-binタスクのスコープをつけることもできる(意味が無い!ただの例だよ)。
name in packageBin := "hello"
あるいは、複数のスコープ軸をつけることができる。例えばpackageBinタスクとCompileコンフィギュレーションにするには、
name in (Compile, packageBin) := "hello"
すべての軸についてGlobalにするには、
name in Global := "hello"
(「name in Global」は暗黙的にすべての軸をGlobalにする。タスクとコンフィゆレーションは既にGlobalだからだ。 だから、「*/*:name」になる。「{file:/home/hp/checkout/hello/}default-aea33a/*:name」ではなく)。
もしScalaを使ったことが無いのなら覚えておこう。 「in」や「:=」はただのメソッドであって、マジックではないことを理解するのは重要だ。 Scalaはそれらのナイスな書き方を許しているのだが、Javaスタイルにすることも可能だ。
name.in(Compile).:=("hello")
醜い書き方をする必要はないが、しかしそれらが確かにメソッドであることはわかるだろう。
スコープをいつ指定するべきか?
キーが通常スコープ付されるものであれば、スコープを指定すべきだ。 例えば、compileタスクは通常Compile及びTestコンフィギュレーションにスコープ付されており、これらのスコープ外では存在しようがない。
compileキーの値を変更するには、「compile in Compile」あるいは「compile in Test」と記述する必要がある。 単に「compile」と記述してしまうと、現在のプロジェクトにスコープ付された新しいcompileタスクを定義してしまうことになり、 コンフィギュレーションにスコープ付された標準的なcompileタスクをオーバライドすることにはならない。
もし"Reference to undefined setting"などというエラーが発生した場合、それはたいていスコープを指定し忘れているか、あるいは間違ったスコープをし指定しているかのどちらかだ。 君のキーは何か他のスコープで定義されているのかもしれない。sbtはエラーメッセージの中で示唆しようとする。"Did you mean compile:compile?"等のメッセージをよく見てみよう。
「名前」がキーのほんの一部でしか無いことを意識しよう。 実際に、すべてのキーは名前とスコープから構成される(しかもスコープは三つの軸からなる)。 言い換えれば「packageOptions in (Compile, packageBin)」という式がキー名称なのだ。 単純には、packageOptionもキー名称なのだが、しかし異なるものだ (もしin無しで使用すれば、スコープは暗黙的に、現在のプロジェクト、Globalコンフィギュレーション、Globalタスクになる)