1. 常见问题解答

常见问题解答 

项目信息 

“sbt” 这个名字代表什么,为什么不应该写成“SBT”? 

TL;DR sbt 这个名字不代表任何东西,它就是“sbt”,并且应该这样写。

当 Mark Harrah (@harrah) 第一次创建这个项目时,他将其命名为“Simple Build Tool”,但在他的 首次公开发布中,他已将其称为“sbt”。随着时间的推移,有些人重新定义 sbt 为“Scala Build Tool”,但我们认为这也不准确,因为它可以用于构建仅包含 Java 的项目。

如今,我们只称 sbt 为“sbt”,为了强调这个名字不再是 首字母缩略词,我们总是将其全部小写。但是,我们很乐意接受 酢豚(subuta)作为昵称。

如何获得帮助? 

如何报告 bug? 

如何提供帮助? 

用法 

我的最后一个命令没有生效,但我看不到任何解释。为什么? 

sbt 1.9.8 默认情况下会抑制大多数堆栈跟踪和调试信息。它有一个很好的副作用,就是减少了屏幕上的噪音,但对于新手来说,这可能会让你找不到解释。要查看命令上一次执行的输出,并以更高的详细程度查看,请输入 last <task>,其中 <task> 是失败的任务,或者你想要查看详细输出的任务。例如,如果你发现你的 update 无法按预期加载所有依赖项,你可以输入

> last update

它将显示 update 命令上一次运行的完整输出。

如何禁用输出中的 ANSI 代码? 

有时 sbt 无法检测到 ANSI 代码不受支持,你就会看到类似于

[0m[ [0minfo [0m]  [0mSet current project to root

这样的输出,或者 ANSI 代码受支持,但你想要禁用彩色输出。要完全禁用 ANSI 代码,请传递 -no-colors 选项

$ sbt -no-colors

如何使用 sbt 项目配置(依赖项等)启动 Scala 解释器(REPL)? 

在 sbt 的 shell 中运行 console

构建定义 

:=+=++= 方法是什么? 

这些是用于构建 SettingTask 的键上的方法。入门指南涵盖了所有这些方法,请参见 .sbt 构建定义任务图追加值 以获得示例。

% 方法是什么? 

它用于从字符串创建 ModuleID,用于指定管理依赖项。请阅读入门指南中的 库依赖项 部分。

ThisBuild / scalaVersion 是什么意思? 

ThisBuild 充当一个特殊的子项目名称,你可以使用它来定义构建的默认值。当你定义一个或多个子项目时,如果子项目没有定义 scalaVersion 键,它将查找 ThisBuild / scalaVersion

参见 构建范围设置

ModuleIDProject、… 是什么? 

要弄清楚未知类型或方法,请查看 入门指南,如果你还没有看过。还可以尝试 索引,它包含常用的方法、值和类型,以及 API 文档

如何将文件添加到 jar 包? 

工件中包含的文件默认情况下由 mappings 任务配置,该任务由相关包任务限定范围。mappings 任务返回一个映射序列 Seq[(File,String)],从要包含的文件映射到 jar 中的路径。有关创建这些映射的详细信息,请参见 映射文件

例如,要将生成的源代码添加到打包的源代码工件中

Compile / packageSrc / mappings ++= {
  import Path.{flat, relativeTo}
  val base = (Compile / sourceManaged).value
  val srcs = (Compile / managedSources).value
  srcs pair (relativeTo(base) | flat)
}

这将从 managedSources 任务中获取源代码,并将它们相对于 managedSource 基目录进行相对化,回退到扁平化映射。如果源代码生成任务没有将源代码写入 managedSource 目录,则必须调整映射函数以尝试相对于其他目录进行相对化,或采用更适合生成器的方案。

如何生成源代码或资源? 

参见 生成文件

如果输入文件没有改变,任务如何避免重复工作? 

参见 缓存

扩展 sbt 

如何添加新的依赖项配置? 

参见 如何定义自定义依赖项配置

如何添加测试配置? 

参见 测试 中的 其他测试配置 部分。

除了 run 之外,如何创建自定义运行任务? 

此答案摘自 邮件列表讨论

阅读入门指南,直到 自定义设置 部分以了解背景知识。

基本运行任务通过以下方式创建

lazy val myRunTask = taskKey[Unit]("A custom run task.")

// this can go either in a `build.sbt` or the settings member
//   of a Project in a full configuration
fullRunTask(myRunTask, Test, "foo.Foo", "arg1", "arg2")

如果你想要能够在命令行上提供参数,请将 TaskKey 替换为 InputKey,将 fullRunTask 替换为 fullRunInputTaskTest 部分可以替换为其他配置,例如 Compile,以使用该配置的类路径。

此运行任务可以通过在范围内指定任务键来单独配置。例如

myRunTask / fork := true

myRunTask / javaOptions += "-Xmx6144m"

如何表达对外部工具(如 ProGuard)的依赖? 

工具依赖项用于实现任务,而项目源代码不需要它们。这些依赖项可以在它们自己的配置和类路径中声明。以下是步骤

  1. 定义新的 配置
  2. 在该配置中声明工具 依赖项
  3. 定义一个类路径,该类路径从 update 生成的 更新报告 中提取依赖项。
  4. 使用类路径来实现任务。

作为示例,考虑一个 proguard 任务。此任务需要 ProGuard jar 文件才能运行该工具。首先,定义并添加新的配置

lazy val ProguardConfig = config("proguard").hide

ivyConfigurations += ProguardConfig

然后,

// Add proguard as a dependency in the custom configuration.
//  This keeps it separate from project dependencies.
libraryDependencies +=
   "net.sf.proguard" % "proguard" % "4.4" % ProguardConfig.name

// Extract the dependencies from the UpdateReport.
ProguardConfig / managedClasspath := {
    // these are the types of artifacts to include
    val artifactTypes: Set[String] = (ProguardConfig / classpathTypes).value
    Classpaths.managedJars(proguardConfig, artifactTypes, update.value)
}

// Use the dependencies in a task, typically by putting them
//  in a ClassLoader and reflectively calling an appropriate
//  method.
proguard := {
    val cp: Seq[File] = (ProguardConfig / managedClasspath).value
  // ... do something with , which includes proguard ...
}

定义中间类路径是可选的,但对于调试或如果多个任务需要使用它,它可能会有用。也可以在内联中指定工件类型。以下 proguard 任务替代方案将如下所示

proguard := {
   val artifactTypes = Set("jar")
    val cp =
      Classpaths.managedJars(proguardConfig, artifactTypes, update.value)
  // ... do something with , which includes proguard ...
}

如何动态更改 sbt 的类路径? 

可以注册一些额外的 jar 包,这些 jar 包将被放置在 sbt 的类路径上。通过 State,可以获取一个 xsbti.ComponentProvider,它管理应用程序组件。组件是 ~/.sbt/boot/ 目录中的一组文件,在本例中,应用程序是 sbt。除了基本类路径之外,"extra" 组件中的组件也包含在 sbt 的类路径上。

(注意:应用程序类路径上的额外组件由启动器配置文件 boot.properties[main] 部分中的 components 属性声明。)

由于这些组件被添加到 ~/.sbt/boot/ 目录中,而 ~/.sbt/boot/ 可能为只读,因此这可能会失败。在这种情况下,用户通常是故意这样设置 sbt 的,因此通常不需要错误恢复(只需一条简短的错误消息解释情况即可)。

动态类路径增强的示例 

以下代码可以在需要 State => State 的地方使用,例如在 onLoad 设置(如下所述)或在 命令 中。它将一些文件添加到 "extra" 组件中,如果这些文件尚未添加,则会重新加载 sbt。请注意,重新加载将删除用户的会话状态。

def augment(extra: Seq[File])(s: State): State = {
    // Get the component provider
  val cs: xsbti.ComponentProvider = s.configuration.provider.components()

    // Adds the files in 'extra' to the "extra" component
    //   under an exclusive machine-wide lock.
    //   The returned value is 'true' if files were actually copied and 'false'
    //   if the target files already exists (based on name only).
  val copied: Boolean = s.locked(cs.lockFile, cs.addToComponent("extra", extra.toArray))

    // If files were copied, reload so that we use the new classpath.
  if(copied) s.reload else s
}

如何在项目加载或卸载时采取行动? 

请参阅 如何在启动时采取行动

项目加载/卸载钩子的示例 

以下示例维护一个项目加载次数的计数,并打印该数字。

{
  // the key for the current count
  val key = AttributeKey[Int]("loadCount")
  // the State transformer
  val f = (s: State) => {
    val previous = s get key getOrElse 0
    println("Project load count: " + previous)
    s.put(key, previous + 1)
  }
  Global / onLoad := {
    val previous = (Global / onLoad).value
    f compose previous
  }
}

错误 

在项目加载时,"对未初始化设置的引用“ 

设置初始化器按顺序执行。如果设置的初始化依赖于尚未初始化的其他设置,sbt 将停止加载。

在本例中,我们尝试在 libraryDependencies 初始化为一个空序列之前将一个库追加到它。

libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test"

disablePlugins(plugins.IvyPlugin)

要更正此问题,请包含 IvyPlugin 插件设置,其中包括 libraryDependencies := Seq()。因此,我们只是删除了显式禁用。

libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test"

当使用 作用域设置 时,会出现这种错误的更微妙的变化。

// error: Reference to uninitialized setting
settings = Seq(
  libraryDependencies += "commons-io" % "commons-io" % "1.2" % "test",
  fullClasspath := fullClasspath.value.filterNot(_.data.name.contains("commons-io"))
)

此设置在测试和编译作用域之间有所不同。解决方案是使用作用域设置,既作为初始化器的输入,又是我们更新的设置。

Compile / fullClasspath := (Compile / fullClasspath).value.filterNot(_.data.name.contains("commons-io"))

依赖管理 

如何解决校验和错误? 

当已发布的校验和(例如 sha1 或 md5 哈希值)与为下载的工件(例如 jar 或 pom.xml)计算的校验和不同时,就会出现此错误。此类错误的一个示例是

[warn]  problem while downloading module descriptor:
https://repo1.maven.org/maven2/commons-fileupload/commons-fileupload/1.2.2/commons-fileupload-1.2.2.pom:
invalid sha1: expected=ad3fda4adc95eb0d061341228cc94845ddb9a6fe computed=0ce5d4a03b07c8b00ab60252e5cacdc708a4e6d8 (1070ms)

通常应将无效校验和报告给存储库所有者(如针对上述错误所做的那样,已完成)。同时,您可以使用以下设置暂时禁用检查

checksums in update := Nil

有关详细信息,请参阅 库管理

我添加了一个插件,现在我的交叉编译失败了! 

这个问题经常出现。插件只发布了用于 sbt 使用的 Scala 版本(目前为 2.12)。您仍然可以在交叉编译期间使用插件,因为 sbt 只会查找插件的 2.12 版本。

... 除非您在错误的位置指定插件!

一个常见的错误是将全局插件定义放在 ~/.sbt/plugins.sbt 中。这是错误的。 ~/.sbt 中的 .sbt 文件为每个构建加载,也就是说,为每个交叉编译加载。因此,如果您为 Scala 2.11.0 构建,sbt 将尝试查找为 2.11.0 编译的插件版本,而它通常不会找到。这是因为它不知道依赖项是一个插件。

要告诉 sbt 依赖项是一个 sbt 插件,请确保您在 ~/.sbt/plugins/ 中的 .sbt 文件中定义全局插件。sbt 知道 ~/.sbt/plugins 中的文件仅供 sbt 本身使用,而不是作为一般构建定义的一部分。如果您在目录下的文件中定义插件,它们不会影响您的交叉编译。任何以 .sbt 结尾的文件名都可以,但大多数人使用 ~/.sbt/plugins/build.sbt~/.sbt/plugins/plugins.sbt

其他 

我在哪里可以找到 1.9.8 的插件? 

请参阅 社区插件,获取当前可用插件的列表。