Locked History Actions

sbt/Getting-Started-Scopes

スコープ

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タスクになる)

次はSettingについてもっと