1. 通用最佳实践

通用最佳实践 

本页介绍使用 sbt 的最佳实践。

project/~/.sbt/ 

任何构建项目所需的任何东西都应该放在 project/ 中。这包括诸如 Web 插件之类的东西。~/.sbt/ 应该包含用于处理构建的本地自定义项和命令,但不是必需的。例如,IDE 插件。

本地设置 

有两种选择可以设置特定于用户的设置。例如,将本地 Maven 仓库插入解析器列表的开头

resolvers := {
  val localMaven = "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository"
  localMaven +: resolvers.value
}
  1. 将特定于用户的设置放在全局 .sbt 文件中,例如 $HOME/.sbt/1.0/global.sbt。这些设置将应用于所有项目。
  2. 在未签入版本控制的项目中将设置放在 .sbt 文件中,例如 <project>/local.sbt。sbt 会将来自多个 .sbt 文件的设置组合在一起,因此你仍然可以拥有标准的 <project>/build.sbt 并将其签入版本控制。

.sbtrc 

将要在 sbt 启动时执行的命令放在 .sbtrc 文件中,每行一个。这些命令在加载项目之前运行,并且对于定义别名很有用,例如。sbt 会执行 $HOME/.sbtrc(如果存在)中的命令,然后执行 <project>/.sbtrc(如果存在)。

生成的文件 

将任何生成的文件写入输出目录的子目录,该目录由 target 设置指定。这使得在构建后清理变得容易,并提供了一个组织生成文件的单一位置。任何特定于 Scala 版本的生成文件都应该放在 crossTarget 中,以便高效地进行交叉构建。

有关生成源代码和资源的信息,请参见 生成文件

不要硬编码 

不要硬编码常量,例如输出目录 target/。这对于插件来说尤其重要。例如,用户可能会将 target 设置更改为指向 build/,插件需要尊重这一点。相反,使用设置,例如

myDirectory := target.value / "sub-directory"

不要“改变”文件 

构建自然会涉及大量文件操作。我们如何将此与任务系统协调起来,而任务系统在其他方面帮助我们避免可变状态?一种方法(也是推荐的方法,也是 sbt 默认任务所使用的方法)是只对任何给定文件写入一次,并且只从单个任务写入。

构建产品(或副产品)应该只由一个任务写入一次。然后,该任务至少应提供作为其结果创建的文件。另一个想要使用文件的任务应该映射该任务,同时获取文件引用并确保该任务已运行(因此文件已构造)。显然,你无法对用户或其他进程修改文件做太多事情,但你可以通过在任务级别将文件内容视为不可变的,使构建控制下的 I/O 更可预测。

例如

lazy val makeFile = taskKey[File]("Creates a file with some content.")

// define a task that creates a file,
//  writes some content, and returns the File
makeFile := {
    val f: File = file("/tmp/data.txt")
    IO.write(f, "Some content")
    f
}

// The result of makeFile is the constructed File,
//   so useFile can map makeFile and simultaneously
//   get the File and declare the dependency on makeFile
useFile :=
    doSomething( makeFile.value )

这种安排并非总是可行,但它应该是规则而不是例外。

使用绝对路径 

只构造绝对文件。要么指定绝对路径

file("/home/user/A.scala")

要么从绝对基准构造文件

base / "A.scala"

这与不硬编码最佳实践有关,因为正确的方法涉及引用 baseDirectory 设置。例如,以下定义了 myPath 设置为 <base>/licenses/ 目录。

myPath := baseDirectory.value / "licenses"

在 Java(因此在 Scala 中),相对文件相对于当前工作目录。由于多种原因,工作目录并不总是与构建根目录相同。

此规则的唯一例外是指定项目的基准目录时。在这里,为了方便起见,sbt 会为你将相对文件解析到构建根目录。

解析器组合器 

  1. 在任何地方都使用 token 来明确地划分制表符补全边界。
  2. 不要重叠或嵌套令牌。这里的行为是未指定的,将来可能会导致错误。
  3. 使用 flatMap 进行一般递归。sbt 的组合器很严格,以限制生成的类的数量,因此像这样使用 flatMap
lazy val parser: Parser[Int] =
  token(IntBasic) flatMap { i =>
    if(i <= 0)
      success(i)
    else
      token(Space ~> parser)
  }

此示例定义了一个解析器,它是一个用空格分隔的整数列表,以一个负数结尾,并返回该最终的负数。