取り付け
brew install sbt
または技術的に言えば構成されているsbtをインストールします
実行すると sbt
ターミナルから、実際にはsbtランチャーbashスクリプトれます。個人的には、この三位一体について心配する必要はなく、sbtを1つのものであるかのように使用しました。
構成
特定のプロジェクトのsbtを構成するに.sbtopts
は、プロジェクトのルートにあるファイルを保存します。システム全体でsbtを変更するには、を変更し/usr/local/etc/sbtopts
ます。実行sbt -help
すると、正確な場所がわかります。たとえば、sbtに1回限りの実行としてより多くのメモリを与えるsbt -mem 4096
か-mem 4096
、.sbtopts
またはsbtopts
恒久的に有効にするには、メモリの増加のために。
プロジェクトの構造
sbt new scala/scala-seed.g8
最小限のHello World sbtプロジェクト構造を作成します
.
├── README.md // most important part of any software project
├── build.sbt // build definition of the project
├── project // build definition of the build (sbt is recursive - explained below)
├── src // test and main source code
└── target // compiled classes, deployment package
頻繁なコマンド
test // run all test
testOnly // run only failed tests
testOnly -- -z "The Hello object should say hello" // run one specific test
run // run default main
runMain example.Hello // run specific main
clean // delete target/
package // package skinny jar
assembly // package fat jar
publishLocal // library to local cache
release // library to remote repository
reload // after each change to build definition
無数の貝殻
scala // Scala REPL that executes Scala language (nothing to do with sbt)
sbt // sbt REPL that executes special sbt shell language (not Scala REPL)
sbt console // Scala REPL with dependencies loaded as per build.sbt
sbt consoleProject // Scala REPL with project definition and sbt loaded for exploration with plain Scala langauage
ビルド定義は適切なScalaプロジェクトです
これは、慣用的なsbtの重要な概念の1つです。質問で説明してみます。scalaj-httpでHTTPリクエストを実行するsbtタスクを定義したいとします。直感的に私たちは次のことを試すかもしれませんbuild.sbt
libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
val fooTask = taskKey[Unit]("Fetch meaning of life")
fooTask := {
import scalaj.http._ // error: cannot resolve symbol
val response = Http("http://example.com").asString
...
}
ただし、これは見つからないというエラーになりimport scalaj.http._
ます。すぐ上に追加scalaj-http
したとき、これはどのようにして可能libraryDependencies
ですか?さらに、代わりに依存関係を追加すると、なぜ機能するのproject/build.sbt
ですか?
// project/build.sbt
libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
答えは、fooTask
実際にはメインプロジェクトとは別のScalaプロジェクトの一部です。この異なるScalaプロジェクトは、コンパイルされたクラスが常駐するproject/
独自のtarget/
ディレクトリを持つディレクトリの下にあります。実際には、以下のproject/target/config-classes
ようなものに逆コンパイルするクラスがあるはずです
object $9c2192aea3f1db3c251d extends scala.AnyRef {
lazy val fooTask : sbt.TaskKey[scala.Unit] = { /* compiled code */ }
lazy val root : sbt.Project = { /* compiled code */ }
}
これfooTask
は、という名前の通常のScalaオブジェクトの単なるメンバーであることがわかります$9c2192aea3f1db3c251d
。明らかに、適切なプロジェクトの依存関係ではなく、scalaj-http
プロジェクト定義の依存関係であるべき$9c2192aea3f1db3c251d
です。ビルド定義のScalaプロジェクトが存在する場所なので、project/build.sbt
ではなくで宣言する必要があります。build.sbt
project
ビルド定義が単なる別のScalaプロジェクトであることを強調するには、を実行しsbt consoleProject
ます。これにより、クラスパス上のビルド定義プロジェクトを含むScala REPLがロードされます。次の行に沿ってインポートが表示されるはずです
import $9c2192aea3f1db3c251d
これで、build.sbt
DSL ではなくScala本体を使用してビルド定義プロジェクトを直接操作できるようになりました。たとえば、次のように実行しますfooTask
$9c2192aea3f1db3c251d.fooTask.eval
build.sbt
ルートプロジェクトの下には、ビルド定義Scalaプロジェクトを定義するのに役立つ特別なDSLがありますproject/
。
ビルド定義のScalaプロジェクトには、独自のビルド定義のScalaプロジェクトを含めることができますproject/project/
。sbtは再帰的であると言います。
sbtはデフォルトで並列です
sbt はタスクからDAGを構築します。これにより、タスク間の依存関係を分析し、それらを並行して実行し、重複排除を実行することもできます。build.sbt
DSLはこれを念頭に置いて設計されているため、最初は驚くべきセマンティクスにつながる可能性があります。次のスニペットの実行順序はどうなっていると思いますか?
def a = Def.task { println("a") }
def b = Def.task { println("b") }
lazy val c = taskKey[Unit]("sbt is parallel by-default")
c := {
println("hello")
a.value
b.value
}
ここでの流れは、最初に印刷してhello
からを実行しa
、次にb
タスクを実行することであると直感的に理解できます。しかし、これは実際に実行を意味a
し、b
中に並行して、前に println("hello")
そう
a
b
hello
またはの順理由a
とb
保証するものではありません
b
a
hello
おそらく逆説的に、sbtではシリアルよりパラレルの方が簡単です。シリアル注文が必要なDef.sequential
場合はDef.taskDyn
、for-comprehensionなどの特別なものを使用するか、エミュレートする必要があります。
def a = Def.task { println("a") }
def b = Def.task { println("b") }
lazy val c = taskKey[Unit]("")
c := Def.sequential(
Def.task(println("hello")),
a,
b
).value
と類似しています
for {
h <- Future(println("hello"))
a <- Future(println("a"))
b <- Future(println("b"))
} yield ()
コンポーネント間に依存関係がないことがわかりますが、
def a = Def.task { println("a"); 1 }
def b(v: Int) = Def.task { println("b"); v + 40 }
def sum(x: Int, y: Int) = Def.task[Int] { println("sum"); x + y }
lazy val c = taskKey[Int]("")
c := (Def.taskDyn {
val x = a.value
val y = Def.task(b(x).value)
Def.taskDyn(sum(x, y.value))
}).value
と類似しています
def a = Future { println("a"); 1 }
def b(v: Int) = Future { println("b"); v + 40 }
def sum(x: Int, y: Int) = Future { x + y }
for {
x <- a
y <- b(x)
c <- sum(x, y)
} yield { c }
私たちが見る場所は、にsum
依存し、を待つ必要がa
ありb
ます。
言い換えると
- 以下のための応用的意味論、使用
.value
- 以下のためのモナドのセマンティクスの使用
sequential
またはtaskDyn
考えてみましょう、別の依存関係の建物の性質の結果として、意味的混乱スニペットをvalue
、代わりに
`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
val x = version.value
^
私たちは書く必要があります
val x = settingKey[String]("")
x := version.value
構文.value
はDAGの関係に関するものであり、
「今すぐ価値を与えて」
代わりにそれはのようなものを意味します
「私の発信者は最初に私に依存します。DAG全体がどのように適合するかがわかれば、要求された値を発信者に提供することができます。」
だから今はx
まだ値を割り当てることができない理由が少しはっきりしているかもしれません。関係構築の段階ではまだ価値はありません。
では、Scala本体とDSL言語のセマンティクスの違いを明確に見ることができbuild.sbt
ます。ここに私に役立ついくつかの経験則があります
- DAGはタイプの式から作成されます
Setting[T]
- ほとんどの場合、
.value
構文を使用するだけで、sbtは、Setting[T]
- 時々、DAGの一部を手動で微調整する必要があり、そのために使用する
Def.sequential
か、Def.taskDyn
- これらの順序付け/関係のシンタティックな奇妙さが処理されると、タスクのビジネスロジックの残りの部分を構築するために、通常のScalaセマンティクスに依存できます。
コマンドとタスク
コマンドはDAGからの遅延方法です。コマンドを使用すると、ビルドの状態を簡単に変更し、必要に応じてタスクをシリアル化できます。コストは、DAGによって提供されるタスクの並列化と重複排除が緩んでいることです。この方法では、タスクを優先的に選択する必要があります。コマンドは、内部で行う可能性のあるセッションの永続的な記録の一種と考えることができますsbt shell
。たとえば、
vval x = settingKey[Int]("")
x := 13
lazy val f = taskKey[Int]("")
f := 1 + x.value
次のセッションの出力を検討してください
sbt:root> x
[info] 13
sbt:root> show f
[info] 14
sbt:root> set x := 41
[info] Defining x
[info] The new value will be used by f
[info] Reapplying settings...
sbt:root> show f
[info] 42
特に、ビルド状態をでどのように変更するかではありませんset x := 41
。コマンドを使用すると、上記のセッションを永続的に記録できます。たとえば、
commands += Command.command("cmd") { state =>
"x" :: "show f" :: "set x := 41" :: "show f" :: state
}
我々はまた、使用してコマンドタイプセーフを作ることができますProject.extract
し、runTask
commands += Command.command("cmd") { state =>
val log = state.log
import Project._
log.info(x.value.toString)
val (_, resultBefore) = extract(state).runTask(f, state)
log.info(resultBefore.toString)
val mutatedState = extract(state).appendWithSession(Seq(x := 41), state)
val (_, resultAfter) = extract(mutatedState).runTask(f, mutatedState)
log.info(resultAfter.toString)
mutatedState
}
スコープ
次の種類の質問に答えようとすると、スコープが機能します
- タスクを一度定義して、マルチプロジェクトビルドのすべてのサブプロジェクトで利用できるようにする方法は?
- メインのクラスパスに依存関係をテストしないようにする方法は?
sbtには、スラッシュ構文を使用してナビゲートできる多軸スコープスペースがあります。たとえば、
show root / Compile / compile / scalacOptions
| | | |
project configuration task key
個人的には、スコープについて心配する必要はほとんどありません。時々私はテストソースだけをコンパイルしたい
Test/compile
または、特定のサブプロジェクトから特定のタスクを実行します。最初にそのプロジェクトに移動する必要はありません。 project subprojB
subprojB/Test/compile
次の経験則は、合併症のスコープを避けるのに役立つと思います
- 複数の
build.sbt
ファイルはなく、他のすべてのサブプロジェクトを制御するルートプロジェクトの下の単一のマスターファイルのみ
- 自動プラグインを介してタスクを共有する
- 共通設定をプレーンなScalaに分解し
val
、それを各サブプロジェクトに明示的に追加する
マルチプロジェクトビルド
各サブプロジェクトの複数のbuild.sbtファイルの代わりに
.
├── README.md
├── build.sbt // OK
├── multi1
│ ├── build.sbt // NOK
│ ├── src
│ └── target
├── multi2
│ ├── build.sbt // NOK
│ ├── src
│ └── target
├── project // this is the meta-project
│ ├── FooPlugin.scala // custom auto plugin
│ ├── build.properties // version of sbt and hence Scala for meta-project
│ ├── build.sbt // OK - this is actually for meta-project
│ ├── plugins.sbt // OK
│ ├── project
│ └── target
└── target
build.sbt
それらをすべて支配する単一のマスターを持っている
.
├── README.md
├── build.sbt // single build.sbt to rule theme all
├── common
│ ├── src
│ └── target
├── multi1
│ ├── src
│ └── target
├── multi2
│ ├── src
│ └── target
├── project
│ ├── FooPlugin.scala
│ ├── build.properties
│ ├── build.sbt
│ ├── plugins.sbt
│ ├── project
│ └── target
└── target
マルチプロジェクトのビルドで共通の設定を除外する一般的な方法があります
valの共通設定のシーケンスを定義し、それらを各プロジェクトに追加します。その方法を学ぶための概念が少なくなります。
例えば
lazy val commonSettings = Seq(
scalacOptions := Seq(
"-Xfatal-warnings",
...
),
publishArtifact := true,
...
)
lazy val root = project
.in(file("."))
.settings(settings)
.aggregate(
multi1,
multi2
)
lazy val multi1 = (project in file("multi1")).settings(commonSettings)
lazy val multi2 = (project in file("multi2")).settings(commonSettings)
プロジェクトのナビゲーション
projects // list all projects
project multi1 // change to particular project
プラグイン
ビルド定義はにある適切なScalaプロジェクトであることを覚えておいてくださいproject/
。ここで、.scala
ファイルを作成してプラグインを定義します
. // directory of the (main) proper project
├── project
│ ├── FooPlugin.scala // auto plugin
│ ├── build.properties // version of sbt library and indirectly Scala used for the plugin
│ ├── build.sbt // build definition of the plugin
│ ├── plugins.sbt // these are plugins for the main (proper) project, not the meta project
│ ├── project // the turtle supporting this turtle
│ └── target // compiled binaries of the plugin
ここでは最小限で自動プラグインの下では、project/FooPlugin.scala
object FooPlugin extends AutoPlugin {
object autoImport {
val barTask = taskKey[Unit]("")
}
import autoImport._
override def requires = plugins.JvmPlugin // avoids having to call enablePlugin explicitly
override def trigger = allRequirements
override lazy val projectSettings = Seq(
scalacOptions ++= Seq("-Xfatal-warnings"),
barTask := { println("hello task") },
commands += Command.command("cmd") { state =>
"""eval println("hello command")""" :: state
}
)
}
オーバーライド
override def requires = plugins.JvmPlugin
で明示的enablePlugin
に呼び出すことなく、すべてのサブプロジェクトのプラグインを効果的に有効にする必要がありますbuild.sbt
。
IntelliJとsbt
次の設定を有効にしてください(デフォルトで実際に有効になっているはずです)
use sbt shell
下
Preferences | Build, Execution, Deployment | sbt | sbt projects
主な参考文献