有人可以解释使用SBT的正确方法吗?


100

我要从衣柜里出来!我不了解SBT。我说过了,现在请帮助我。

条条大路通罗马,那是SBT一样的:要开始SBTSBTSBT LauncherSBT-extras,等,然后有不同的方式,包括和决定库。有没有“最佳”方法?

我问是因为有时候我会迷路。SBT文档非常详尽和完整,但是我发现自己不知道何时使用build.sbtor project/build.propertiesproject/Build.scalaor project/plugins.sbt

然后,它变成乐趣,还有就是Scala-IDESBT- 什么是使用它们在一起的正确方法?首先是鸡肉还是鸡蛋?

最重要的可能是,如何找到要包含在项目中的正确存储库和版本?我是否只是拉出一个小背包,然后开始破解前进的道路?我经常发现包含所有内容和厨房水槽的项目,然后我意识到-我并不是唯一迷失一点的人。

作为一个简单的例子,现在,我正在启动一个全新的项目。我想用最新的功能SLICKScala这很可能需要最新版本的SBT的。入门的理智点是什么,为什么?我应该在哪个文件中定义它,外观如何?我知道我可以解决这个问题,但是我真的很希望专家就一切应该去哪里(为什么要去那里会有奖金)提出专家意见。

我已经在SBT小型项目中使用了一年多了。我曾经使用过SBT,然后SBT Extras(因为它使一些头痛神奇地消失了),但是我不确定为什么要使用一个或另一个。我因不了解事物如何配合(SBT以及存储库)而感到沮丧,并且认为如果可以用人的方式来解释,这将使下一个来此工作的人免于很多麻烦。


2
您到底有“ Scala-IDE和SBT”是什么意思?您用sbt定义项目,而sbt可以生成一个ide(eclipse oder intellij)项目。因此SBT排名第一...
1

2
@Jan我提到这是因为Scala-IDE使用SBT作为构建管理器。请参阅assembla.com/spaces/scala-ide/wiki/SBT-based_build_manager,然后在他们提到“无需定义您的SBT项目文件”的文章中放下。我感到困惑。
2012年

好。由于我通常使用intellij(或sublime)来编辑scala,所以我不知道。我猜生成器会生成自己的sbt配置吗?
1

2
@JacobusR的斯卡拉IDE使用SBT建立自己的项目的源代码的事实是一个实现细节,并且用户需要没有到这个担心。确实有0个含义。在Eclipse之外,用户可以使用SBT,Maven,Ant等构建项目,而这对于Scala IDE不会有任何影响。还有一件事,即使您有SBT项目,Scala IDE也不在乎,即,它不需要您Build.scala设置类路径,这就是为什么您实际上需要sbteclipse来生成Eclipse .classpath的原因。希望这可以帮助。
Mirco Dotta 2012年

1
@Jan Scala IDE加剧了混乱,是的,提供了有关建立良好的Scala开发环境和适当编程流程的坚实指导的文档,将非常有用。
2012年

Answers:


29

最重要的可能是,如何找到要包含在项目中的正确存储库和版本?我是否只是拉出一个小背包,然后开始破解前进的道路?我经常发现包含所有内容和厨房水槽的项目

对于基于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。入门的理智点是什么,为什么?

我认为理智的一点是逐步建立对sbt的免疫力

确保您了解:

  1. 范围格式 {<build-uri>}<project-id>/config:key(for task-key)
  2. 设置的3个香精(SettingKeyTaskKeyInputKey) -在阅读“任务键”一节http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def

始终保持这四个页面处于打开状态,以便您可以跳转并查找各种定义和示例:

  1. http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def
  2. http://www.scala-sbt.org/release/docs/Detailed-Topics/index
  3. http://harrah.github.com/xsbt/latest/sxr/Keys.scala.html
  4. http://harrah.github.com/xsbt/latest/sxr/Defaults.scala.html

充分利用showinspect 和制表符补全,以熟悉设置的实际值,其依赖项,定义和相关设置。我不相信inspect任何地方都会记录您会发现使用的关系。如果有更好的方法,我想知道。


25

我使用sbt的方式是:

  1. 使用sbt-extras-只需获取shell脚本并将其添加到项目的根目录
  2. 创建一个包含用于设置sbt projectMyProject.scala文件的文件夹。我比方法更喜欢build.sbt它-它是scala,而且更灵活
  3. 创建一个project/plugins.sbt文件并为您的IDE添加适当的插件。sbt-eclipse,sbt-idea或ensime-sbt-cmd都可以,以便可以为eclipse,intellij或ensime生成项目文件。
  4. 在项目的根目录中启动sbt并为IDE生成项目文件
  5. 利润

因为它们是由sbt生成的,所以我不必理会IDE项目文件,但是您可能有这样做的理由。

您可以在此处看到这样设置的示例。


谢谢您的好回答。我接受了另一个答案,因为它涵盖了更多领域,并且投票赞成您的理由,这也确实很不错。如果可以的话,我会接受的。
2012年

0

使用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)

5
我不赞成这种想法,如果有疑问,在组合中添加更多的魔力不太可能解决您的问题。
立方2014年

0

安装

brew install sbt 或从技术上讲由以下内容构成的类似安装sbt

sbt从终端执行时,它实际上运行sbt启动器bash脚本。就我个人而言,我不必担心这种三位一体,而只需将sbt当作一件事情就可以使用。

组态

要为特定项目配置sbt,请在项目.sbtopts的根目录中保存文件。要在系统范围内配置sbt,请修改/usr/local/etc/sbtopts。执行sbt -help应告诉您确切的位置。例如,给SBT多个存储器作为一次性执行sbt -mem 4096,或保存-mem 4096.sbtoptssbtopts用于存储器增加永久生效。

 项目结构

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关键概念之一。我将尝试用一个问题来解释。假设您要定义一个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只是名为的常规Scala对象的成员$9c2192aea3f1db3c251d。显然,scalaj-http应该是项目定义$9c2192aea3f1db3c251d的依赖关系,而不是适当项目的依赖关系。因此,需要在project/build.sbt而不是中声明它build.sbt,因为project构建定义Scala项目位于此位置。

要指出构建定义只是另一个Scala项目,请执行sbt consoleProject。这将使用类定义路径上的构建定义项目加载Scala REPL。您应该看到以下内容的导入:

import $9c2192aea3f1db3c251d

因此,现在我们可以通过使用Scala适当的名称而不是build.sbtDSL 来与构建定义项目直接交互。例如,以下执行fooTask

$9c2192aea3f1db3c251d.fooTask.eval

build.sbtroot项目下的是一个特别的DSL,它有助于在project/。下定义构建定义Scala项目。

而构建定义Scala项目,可以在其下拥有自己的构建定义Scala项目project/project/,依此类推。我们说sbt是递归的

sbt默认是并行的

sbt 从任务中构建DAG。这使它可以分析任务之间的依赖关系,并并行执行它们,甚至执行重复数据删除。build.sbtDSL的设计考虑了这一点,这可能会导致最初令人惊讶的语义。您认为以下代码段的执行顺序是什么?

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任务。然而,这实际上意味着执行ab并行,和之前 println("hello")使

a
b
hello

或因为不能保证a和的顺序b

b
a
hello

也许矛盾的是,在sbt中,并行比串行更容易。如果需要串行订购,则必须使用特殊的东西,例如Def.sequentialDef.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取决于并且必须等待ab

换一种说法

  • 对于应用语义,使用.value
  • 用于单子语义sequentialtaskDyn

考虑由于的依赖关系建立性质而导致的另一个语义混乱的代码段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语言中DSL语言在语义上的差异build.sbt。这是一些对我有用的经验法则

  • DAG由类型的表达式组成 Setting[T]
  • 在大多数情况下,我们只使用.value语法,而sbt将负责建立之间的关系Setting[T]
  • 有时,我们必须手动调整DAG的一部分,为此我们使用Def.sequentialDef.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

应该有效地为所有分项目的插件,而无需显式调用enablePluginbuild.sbt

IntelliJ和sbt

请启用以下设置(默认情况下应确实启用)

use sbt shell

Preferences | Build, Execution, Deployment | sbt | sbt projects

关键参考

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.