我已经离开衣柜了!我不明白SBT。在那里,我说,现在请帮助我。
所有道路都通往罗马,对于SBT来说也是如此:要开始使用SBT
,有SBT
,SBT Launcher
,SBT-extras
等,然后有不同的方法来包含和决定存储库。 有“最佳”方式吗?
我在问,因为有时候我有点失落。 SBT文档非常全面和完整,但我发现自己不知道何时使用build.sbt
或project/build.properties
或project/Build.scala
或project/plugins.sbt
。
然后变得有趣,有Scala-IDE
和SBT
- 将它们组合在一起的正确方法是什么?首先是鸡肉还是鸡蛋?
最重要的可能是,您如何找到要包含在项目中的正确的存储库和版本?我是否只是拔出一个小砍刀并开始攻击我的前进方向?我经常发现包括所有东西和厨房水槽的项目,然后我意识到 - 我不是唯一一个迷失方向的人。
作为一个简单的例子,现在,我正在开始一个全新的项目。我想使用SLICK
和Scala
的最新功能,这可能需要最新版本的SBT。 入门的理由是什么?为什么?我应该在哪个文件中定义它以及它应该如何?我知道我可以让这个工作,但我真的想要一个专家意见,应该去哪里(为什么它应该去那里会有奖金)。
我已经将SBT
用于小型项目一年多了。我使用SBT
然后使用SBT Extras
(因为它使一些头痛神奇地消失了),但我不确定为什么我应该使用这一个或另一个。我只是因为不理解事情如何融合在一起而感到有些沮丧(SBT
和知识库),并且认为如果可以用人的方式来解释它会拯救下一个如此困难的家伙。 / p>
答案 0 :(得分:28)
最重要的可能是,您如何找到要包含在项目中的正确的存储库和版本?我是否只是拔出一把小砍刀并开始乱砍我前进的方向?我经常发现包含所有东西和厨房水槽的项目
对于基于Scala的依赖项,我会考虑作者的建议。例如:http://code.google.com/p/scalaz/#SBT表示使用:
libraryDependencies += "org.scalaz" %% "scalaz-core" % "6.0.4"
或https://github.com/typesafehub/sbteclipse/有关于添加位置的说明:
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.0-RC1")
对于基于Java的依赖项,我使用http://mvnrepository.com/查看其中的内容,然后单击SBT选项卡。例如http://mvnrepository.com/artifact/net.sf.opencsv/opencsv/2.3表示使用:
libraryDependencies += "net.sf.opencsv" % "opencsv" % "2.3"
然后拉出弯刀,开始向前砍伐。如果你很幸运,你最终不会使用依赖于某些相同罐子但具有不兼容版本的罐子。鉴于Java生态系统,您通常最终会包含所有内容和厨房接收器,并且需要花费一些精力来消除依赖关系或确保您不会错过所需的依赖关系。
作为一个简单的例子,现在,我正在开始一个全新的项目。我想使用SLICK和Scala的最新功能,这可能需要最新版本的SBT。什么是开始的理智点,为什么?
我认为理智的观点是build immunity to sbt gradually。
确保您理解:
{<build-uri>}<project-id>/config:key(for task-key)
SettingKey
,TaskKey
,InputKey
) - 请阅读http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def 始终打开这4页,以便您可以跳转并查找各种定义和示例:
最大限度地利用show
和inspect
以及标签页完成,以熟悉设置的实际值,其依赖关系,定义和相关设置。我不相信您使用inspect
发现的关系会记录在任何地方。如果有更好的方式我想知道它。
答案 1 :(得分:25)
我使用sbt的方式是:
project
文件夹,其中包含MyProject.scala
文件,用于设置sbt。我比build.sbt
方法更喜欢这个 - 它是scala并且更灵活project/plugins.sbt
文件并为IDE添加适当的插件。 sbt-eclipse,sbt-idea或ensime-sbt-cmd,以便您可以为eclipse,intellij或ensime生成项目文件。我不打算检查IDE项目文件,因为它们是由sbt生成的,但可能有理由这样做。
您可以看到像here这样的示例设置。
答案 2 :(得分:1)
使用Typesafe Activator,一种调用sbt的奇特方式,它带有项目模板和种子:https://typesafe.com/activator
Activator new
Fetching the latest list of templates...
Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
1) minimal-java
2) minimal-scala
3) play-java
4) play-scala
(hit tab to see a list of all templates)
答案 3 :(得分:0)
brew install sbt
或类似的安装sbt
当您从终端执行sbt
时,它实际上会运行sbt启动器bash脚本。就我个人而言,我不必担心这种三位一体,而只需将sbt当作一件事情就可以使用。
要为特定项目配置sbt,请将.sbtopts
文件保存在项目的根目录下。要在系统范围内配置sbt,请修改/usr/local/etc/sbtopts
。执行sbt -help
应该会告诉您确切的位置。例如,要一次性为sbt提供更多内存,请执行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
这是关键的惯用sbt概念之一。我将尝试用一个问题来解释。假设您要定义一个sbt任务,该任务将使用scalaj-http执行HTTP请求。凭直觉,我们可以在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项目的一部分。可以在project/
目录下找到此不同的Scala项目,该目录具有自己的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
只是名为$9c2192aea3f1db3c251d
的常规Scala对象的成员。显然,scalaj-http
应该是定义$9c2192aea3f1db3c251d
的项目的依赖,而不是适当项目的依赖。因此,它需要在project/build.sbt
中而不是在build.sbt
中声明,因为project
是构建定义Scala项目所在的地方。
要指出构建定义只是另一个Scala项目,请执行sbt consoleProject
。这将使用类定义路径上的构建定义项目加载Scala REPL。您应该会看到
import $9c2192aea3f1db3c251d
因此,现在我们可以通过使用Scala属性而不是build.sbt
DSL来调用它来直接与构建定义项目进行交互。例如,以下执行fooTask
$9c2192aea3f1db3c251d.fooTask.eval
根项目下的 build.sbt
是一种特殊的DSL,可帮助定义project/
下的构建定义Scala项目。
构建定义Scala项目,可以在project/project/
下拥有自己的构建定义Scala项目,依此类推。我们说sbt is recursive。
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
,而在之前先执行{strong>
println("hello")
或者因为不能保证a
b
hello
和a
的顺序
b
也许矛盾的是,在sbt中,并行比串行更容易。如果您需要序列订购,则必须使用b
a
hello
或Def.sequential
之类的特殊功能来模拟理解。
Def.taskDyn
类似于
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
的依赖关系建立性质,请考虑another在语义上令人困惑的代码段,其中而不是
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
与DAG中的关系有关,并不表示
“立即给我值”
相反,它意味着类似
“我的呼叫者首先取决于我,一旦我知道整个DAG如何组合在一起,我就能为我的呼叫者提供所请求的值”
因此,现在也许更清楚为什么无法为.value
分配值了;在建立关系阶段还没有价值。
我们可以清楚地看到x
中的Scala属性和DSL语言之间的语义差异。这是一些对我有用的经验法则
build.sbt
类型的表达式组成Setting[T]
语法,而sbt将负责在.value
之间建立关系Setting[T]
或Def.sequential
命令是脱离DAG的一种懒惰方式。使用命令很容易根据需要更改构建状态并序列化任务。代价是我们失去了DAG提供的任务的并行化和重复数据删除功能,因此应该优先选择任务。您可以将命令视为会话的一种永久记录,可以在Def.taskDyn
内部进行。例如,给定
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
当我们尝试回答以下类型的问题时,作用域就会发挥作用
sbt具有一个多轴作用域空间,例如,可以使用slash syntax进行导航,
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
}
就个人而言,我很少发现自己需要担心范围。有时我只想编译测试源
show root / Compile / compile / scalacOptions
| | | |
project configuration task key
或者从特定子项目执行特定任务,而无需首先使用Test/compile
导航到该项目
project subprojB
我认为以下经验法则有助于避免范围界定并发症
subprojB/Test/compile
文件,而根项目下只有一个主文件可以控制所有其他子项目build.sbt
中,并将其显式添加到每个子项目中代替每个子项目的多个build.sbt文件
val
拥有一个主人.
├── 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
在多项目构建中,通常使用factoring out common settings
在val中定义一系列常用设置并将其添加到每个 项目。更少的概念需要学习。
例如
.
├── 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
项目导航
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)
记住构建定义是一个正确的Scala项目,它位于projects // list all projects
project multi1 // change to particular project
下。在这里我们通过创建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
下是最小的auto 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
。
请启用以下设置(应由default启用)
build.sbt
在
下use sbt shell