Locked History Actions

sbt/Getting-Started-More-About-Settings

Settingについてもっと

https://github.com/harrah/xsbt/wiki/Getting-Started-More-About-Settingsの訳(2011/10/28時点)

ここでは、単なる「:=」メソッド以外の、settingを作成する他の方法を説明する。 「.sbt build definition」と「scope」を読んであることを前提にするよ。

Settingsについてふたたび

ビルド定義がsettingのリストを作成することを思い出してくれ。 これらはsbtのビルド記述(これはキー・値ペアのマップだ)を変換するために用いられる。 Settingはsbtの以前のマップを入力として、新しいマップを出力する。 この新マップがsbtの新しい状態となる。

これとは異なるsettingはマップを異なる方法で変換する。 以前に「:=」メソッドについて教えた。

「:=」によるSettingは固定的な値を変換結果の新マップに代入する。 例えば、「name := "hello"」というsettingでマップを変換すれば、新マップはnameをキーとして"hello"を保持していることだろう。

Settingが意味を持つためには、最終的にはsettingのマスタリストに格納されなければならない (build.sbt中の全行は自動的にリスト中に格納されるが、しかし.scalaファイルでは、sbtが見つけられない場所にsettingを記述してしまう可能性もありうる)。

+= と ++= で以前の値に追加する

「:=」による置換は最も単純な変換だが、キーには他のメソッドもある。 SettingKey[T]におけるTがシーケンスであれば、つまりキー値のタイプがシーケンスであるなら、置換ではなく、そのシーケンスに追加することができる。

  • +=は一つの要素をシーケンスに追加する。
  • ++=は別のシーケンスを接続する。

例えば、Compile(コンフィギュレーション?)の中のsourceDirectoriesというキーの値はSeq[File]になっている。 デフォルトでは、この値には「src/main/scala」が含まれている。もしsourceというディレクトリにあるソースコードもコンパイルしたければ(標準に従っていない場合だな)、これを以下のように追加することができる。

sourceDirectories in Compile += new File("source")

あるいは簡単のために、sbtパッケージにあるfile()関数を使ってもいい。

sourceDirectories in Compile += file("source")

(file()は単に新しいFileを作成する)

++=を使うことによって一度に複数のディレクトリを追加することができる。

sourceDirectories in Compile ++= Seq(file("sources1"), file("sources2"))

Seq(a, b, c, ...)はシーケンスを作るためのScala標準の機能だ。

もし、全ディレクトリをまるごと置換したいのであれば、当然「:=」が使える。

sourceDirectories in Compile := Seq(file("sources1"), file("sources2"))

~=で値を変換する

sourceDirectories in Compileの値の先頭に要素を付け加えたい場合は? あるいは、デフォルトディレクトリの中の一つをフィルタリングしたい場合は?

キーの以前の値に依存するような方法でsettingを作成することもできる。

  • ~=はsettingの以前の値に関数を適用し、同じ型の新しい値を作成する。

sourceDirectories in Compileを変更したいのであれば、~=を次のように使うことができる。

// src/main/scalaを削除する
sourceDirectories in Compile ~= { srcDirs => srcDirs filter(!_.getAbsolutePath.endsWith("src/main/scala")) }

ここでsrcDirsは匿名関数のパラメータなのだが、これによりsourceDirectories in Compileの古い値が匿名関数に渡される。 この関数の返値がsourceDirectories in Compileの新しい値になる。

もっと簡単な例としては

// プロジェクト名を大文字にする
name ~= { _.toUpperCase }

キーがSettingKey[T]あるいはTaskKey[T]というタイプであるなら、~=メソッドに渡す関数は、T=>Tというタイプだ。 この関数はキーの値を同じタイプの別の値に変換する。

<<=で、別のキー値を元に計算する

~=は、キーの以前に結び付けられた値から新しい値を定義するものだった。 しかし、他のキー値をもとに値を作成したい場合は?

  • <<=によって、任意の他のキー値を使って新しい値を計算することができる。

<<=はInitialize[T]という単一の引数をとる。Initialize[T]のインスタンスは、入力されたキーの集合に関連づけられた値から何らかの計算を行い、それらの値に基づいたT型の値を返す。それがTタイプの値を初期化する。

Initialize[T]が与えられた場合、<<=は当然Setting[T]を返す(:=、+=、~=などと同様に)。

些細なInitialize[T]: <<=を使って他のキーに依存する。

すべてのキーは事前にInitializeトレイトをextendsしているので、最も簡単なInitalizeとしては一つのキーがある

// 無意味だが正当
name <<= name

Initialize[T]として使用された場合、SettingKey[T]はその現在の値を計算する。 したがって、「name <<= name」はnameが既にもっている値をnameに設定することになる。

他のキーの値を使えば、もう少しは有用になるだろう。 ただし、キーは同一のタイプである必要はある。

// プロジェクト名を組織名として使う(どちらもSettingKey[String])
organization <<= name

(これは、一つのキーに別名をつける方法)

値のタイプが同一でない場合、Initialize[T]から別のタイプに変換する必要がある。例えばInitialize[S]だ。 これはInitializeのapplyメソッドを使って行うことができる。

// nameはKey[String]であり、baseDirectoryはKey[File]である
// ディレクトリ名からプロジェクト名を取得する
name <<= baseDirectory.apply(_.getName)

applyはScalaにおいては特別な存在で、オブジェクト自体を関数のように呼び出すことができる。 だから以下のように記述してもよい。

name <<= baseDirectory(_.getName)

これはbaseDirectoryの値を_.getNameという関数を使って変換するのだが、 _.getNameはFileを引数として文字列を返す。 getNameはjava.io.Fileオブジェクトの標準的なメソッドだ。

依存付きのSetting

「name <<= baseDirectory(_.getName)」によってnameはbaseDirectoryへの依存を持ったといえる。 これをbuild.sbtに記述してsbtの対話モードを起動し、「inspect name」と入力してみよう。 以下のように出力されるはずだ(一部)

[info] Dependencies:
[info]  *:base-directory

こうやってsbtは、あるsettingが他のsettingに依存することを知る。 いくつかのsettingはタスクを記述するものであることを思い出せば、これがタスク間の依存を作成することがわかるだろう。

例えば、「inspect compile」とすれば、それが別のキーcompile-inputsに依存することがわかる。 「inspect compile-inputs」とすると、それはまた別のキーに依存している。 依存チェインをたどることによってマジックが起こる。 例えば、compileと入力すれば、sbtは自動的にupdateを行う。 これは単に、compileに必要な入力を得るにはupdateを先に行う必要があるからだ。

このようにして、明示しなくても、すべてのビルドにおける依存は自動的に解決される。 もし、あるキーを他の計算に使用するなら、その計算はそのキーに依存する。うまくいくんだ。

複雑なInitialize[T]: <<=を使って複数のキーに依存する

複数の他のキーへの依存をサポートするため、sbtはInitializeオブジェクトのタプルを適用し、識別するメソッドを持つ(訳注:?)。 Scalaではタプルを(1, "a")などと記述する(これは(Int, String)という型になる)。

三つのInitializeオブジェクトを持つタプルを作成すれば、そのタイプは(Initialize[A], Initialize[B], Initialize[C])になる。 Iitializeオブジェクトはキーでもよい、SettingKey[T]はInitialize[T]のインスタンスでもあるからだ。

以下に単純な例をあげる。このケースでは、三つのキーはStringだ。

// 三つのSettingKey[String]のタプル、これは三つのInitialize[String]のタプルでもある
(name, organization, version)

The apply method on a tuple of Initialize takes a function as its argument. Using each Initialize in the tuple, sbt computes a corresponding value (the current value of the key). These values are passed in to the function. The function then returns one value, which is wrapped up in a new Initialize. If you wrote it out with explicit types (Scala does not require this), it would look like:

val tuple: (Initialize[String], Initialize[String], Initialize[String]) = (name, organization, version)
val combined: Initialize[String] = tuple.apply({ (n, o, v) =>
    "project " + n + " from " + o + " version " + v })
val setting: Setting[String] = name <<= combined

So each key is already an Initialize; but you can combine up to nine simple Initialize (such as keys) into one composite Initialize by placing them in tuples, and invoking the apply method.

The <<= method on SettingKey[T] is expecting an Initialize[T], so you can use this technique to create an Initialize[T] with multiple dependencies on arbitrary keys.

Because function syntax in Scala just calls the apply method, you could write the code like this, omitting the explicit .apply and just treating tuple as a function:

val tuple: (Initialize[String], Initialize[String], Initialize[String]) = (name, organization, version)
val combined: Initialize[String] = tuple({ (n, o, v) =>
    "project " + n + " from " + o + " version " + v })
val setting: Setting[String] = name <<= combined

In a build.sbt, this code using intermediate val will not work, since you can only write single expressions in a .sbt file, not multiple statements.

You can use a more concise syntax in build.sbt, like this:

name <<= (name, organization, version) { (n, o, v) => "project " + n + " from " + o + " version " + v }

Here the tuple of Initialize (also a tuple of SettingKey) works as a function, taking the anonymous function delimited by {} as its argument, and returning an Initialize[T] where T is the result type of the anonymous function.

Tuples of Initialize have one other method, identity, which simply returns an Initialize with a tuple value. (a: Initialize[A], b: Initialize[B]).identity would result in a value of type Initialize[(A, B)]. identity combines two Initialize into one, without losing or modifying any of the values.

Settingが未定義となるとき

Whenever a setting uses ~= or <<= to create a dependency on itself or another key's value, the value it depends on must exist. If it does not, sbt will complain. It might say "Reference to undefined setting", for example. When this happens, be sure you're using the key in the scope that defines it.

It's possible to create cycles, which is an error; sbt will tell you if you do this. Tasks with dependencies

As noted in .sbt build definition, task keys create a Setting[Task[T]] rather than a Setting[T] when you build a setting with :=, <<=, etc. Similarly, task keys are instances of Initialize[Task[T]] rather than Initialize[T], and <<= on a task key takes an Initialize[Task[T]] parameter.

The practical importance of this is that you can't have tasks as dependencies for a non-task setting.

Take these two keys (from Keys):

val scalacOptions = TaskKey[Seq[String]]("scalac-options", "Options for the Scala compiler.") val checksums = SettingKey[Seq[String]]("checksums", "The list of checksums to generate and to verify for dependencies.")

(scalacOptions and checksums have nothing to do with each other, they are just two keys with the same value type, where one is a task.)

You cannot compile a build.sbt that tries to alias one of these to the other like this:

scalacOptions <<= checksums

checksums <<= scalacOptions

The issue is that scalacOptions.<<= expects an Initialize[Task[Seq[String]]] and checksums.<<= expects an Initialize[Seq[String]]. There is, however, a way to convert an Initialize[T] to an Initialize[Task[T]], called map:

scalacOptions <<= checksums map identity

(identity is a standard Scala function that returns its input as its result.)

There is no way to go the other direction, that is, a setting key can't depend on a task key. That's because a setting key is only computed once on project load, so the task would not be re-run every time, and tasks expect to re-run every time.

A task can depend on both settings and other tasks, though, just use map rather than apply to build an Initialize[Task[T]] rather than an Initialize[T]. Remember the usage of apply with a non-task setting looks like this:

name <<= (name, organization, version) { (n, o, v) => "project " + n + " from " + o + " version " + v }

((name, organization, version) has an apply method and is thus a function, taking the anonymous function in {} braces as a parameter.)

To create an Initialize[Task[T]] you need a map in there rather than apply:

// this WON'T compile because name (on the left of <<=) is not a task and we used map
name <<= (name, organization, version) map { (n, o, v) => "project " + n + " from " + o + " version " + v }

// this WILL compile because packageBin is a task and we used map
packageBin in Compile <<= (name, organization, version) map { (n, o, v) => file(o + "-" + n + "-" + v + ".jar") }

// this WILL compile because name is not a task and we used apply
name <<= (name, organization, version) { (n, o, v) => "project " + n + " from " + o + " version " + v }

// this WON'T compile because packageBin is a task and we used apply
packageBin in Compile <<= (name, organization, version) { (n, o, v) => file(o + "-" + n + "-" + v + ".jar") }

Bottom line: when converting a tuple of keys into an Initialize[Task[T]], use map; when converting a tuple of keys into an Initialize[T] use apply; and you need the Initialize[Task[T]] if the key on the left side of <<= is a TaskKey[T] rather than a SettingKey[T]. Remember, aliases use <<= not :=

If you want one key to be an alias for another, you might be tempted to use := to create the following nonsense alias:

// doesn't work, and not useful
packageBin in Compile := packageDoc in Compile

The problem is that :='s argument must be a value (or for tasks, a function returning a value). For packageBin which is a TaskKey[File], it must be a File or a function => File. packageDoc is not a File, it's a key.

The proper way to do this is with <<=, which takes a key (really an Initialize, but keys are instances of Initialize):

// works, still not useful
packageBin in Compile <<= packageDoc in Compile

Here, <<= expects an Initialize[Task[File]], which is a computation that will return a file later, when sbt runs the task. Which is what you want: you want to alias a task by making it run another task, not by setting it one time when sbt loads the project.

(By the way: the in Compile scope is needed to avoid "undefined" errors, because the packaging tasks like packageBin are per-configuration, not global.) Appending with dependencies: <+= and <++=

There are a couple more methods for appending to lists, which combine += and ++= with <<=. That is, they let you compute a new list element or new list to concatenate, using dependencies on other keys in order to do so.

These methods work exactly like <<=, but for <++=, the function you write to convert the dependencies' values into a new value should create a Seq[T] instead of a T.

Unlike <<= of course, <+= and <++= will append to the previous value of the key on the left, rather than replacing it.

For example, say you have a coverage report named after the project, and you want to add it to the files removed by clean:

cleanFiles <+= (name) { n => file("coverage-report-" + n + ".txt") }

Next

At this point you know how to get things done with settings, so we can move on to a specific key that comes up often: libraryDependencies. Learn about library dependencies.

次はライブラリ依存性