Locked History Actions

Diff for "sbt/Getting-Started-More-About-Settings"

Differences between revisions 7 and 8
Deletions are marked like this. Additions are marked like this.
Line 22: Line 22:

Settings must end up in the master list of settings to do any good (all lines in a build.sbt automatically end up in the list, but in a .scala file you can get it wrong by creating a Setting without putting it where sbt will find it).
Settingが意味を持つためには、最終的にはsettingのマスタリストに格納されなければならない
(build.sbt中の全行は自動的にリスト中に格納されるが、しかし.scalaファイルでは、sbtが見つけられない場所にsettingを記述してしまう可能性もありうる)。
Line 100: Line 100:
<<=はInitialize[T]という単一の引数をとる。
 An Initialize[T] instance is a computation which takes the values associated with a set of keys as input, and returns a value of type T based on those other values. It initializes a value of type T.

Given an Initialize[T], <<= returns a Setting[T], of course (just like :=, +=, ~=, etc.).
Trivial Initialize[T]: depending on one other key with <<=

All keys extend the Initialize trait already. So the simplest Initialize is just a key:
<<=はInitialize[T]という単一の引数をとる。Initialize[T]のインスタンスは、入力されたキーの集合に関連づけられた値から何らかの計算を行い、それらの値に基づいたT型の値を返す。それがTタイプの値を初期化する。

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


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

すべてのキーは事前にInitializeトレイトをextendsしているので、最も簡単なInitalizeとしては一つのキーがある
Line 112: Line 113:
When treated as an Initialize[T], a SettingKey[T] computes its current value. So name <<= name sets the value of name to the value that name already had.

It gets a little more useful if you set a key to a different key. The keys must have identical value types, though.

{{{
// name our organization after our project (both are SettingKey[String])
Initialize[T]として使用された場合、SettingKey[T]はその現在の値を計算する。
したがって、「name <<= name」はnameが既にもっている値をnameに設定することになる。

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

{{{
// プロジェクト名を組織名として使う(どちらもSettingKey[String])
Line 121: Line 124:
(Note: this is how you alias one key to another.)

If the value types are not identical, you'll need to convert from Initialize[T] to another type, like Initialize[S]. This is done with the apply method on Initialize, like this:

{{{
// name is a Key[String], baseDirectory is a Key[File]
// name the project after the directory it's inside
(これは、一つのキーに別名をつける方法)

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


{{{
// nameはKey[String]であり、baseDirectoryはKey[File]である
// ディレクトリ名からプロジェクト名を取得する
Line 131: Line 136:
apply is special in Scala and means you can invoke the object with function syntax; so you could also write this: applyはScalaにおいては特別な存在で、オブジェクト自体を関数のように呼び出すことができる。
だから以下のように記述してもよい。
Line 137: Line 144:
That transforms the value of baseDirectory using the function _.getName, where the function _.getName takes a File and returns a String. getName is a method on the standard java.io.File object. これはbaseDirectoryの値を_.getNameという関数を使って変換するのだが、
_.getNameはFileを引数として文字列を返す。
getNameはjava.io.Fileオブジェクトの標準的なメソッドだ。

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

In the setting name <<= baseDirectory(_.getName), name will have a dependency on baseDirectory. If you place the above in build.sbt and run the sbt interactive console, then type inspect name, you should see (in part):

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

This is how sbt knows which settings depend on which other settings. Remember that some settings describe tasks, so this approach also creates dependencies between tasks.

For example, if you inspect compile you'll see it depends on another key compile-inputs, and if you inspect compile-inputs it in turn depends on other keys. Keep following the dependency chains and magic happens. When you type compile sbt automatically performs an update, for example. It Just Works because the values required as inputs to the compile computation require sbt to do the update computation first.

In this way, all build dependencies in sbt are automatic rather than explicitly declared. If you use a key's value in another computation, then the computation depends on that key. It just works! Complex Initialize[T]: depending on multiple keys with <<=

To support dependencies on multiple other keys, sbt adds apply and identity methods to tuples of Initialize objects. In Scala, you write a tuple like (1, "a") (that one has type (Int, String)).

So say you have a tuple of three Initialize objects; its type would be (Initialize[A], Initialize[B], Initialize[C]). The Initialize objects could be keys, since all SettingKey[T] are also instances of Initialize[T].

Here's a simple example, in this case all three keys are strings:

// a tuple of three SettingKey[String], also a tuple of three 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.

次はライブラリ依存性