本页介绍使用 sbt 的最佳实践。
project/
与 ~/.sbt/
任何构建项目所需的任何东西都应该放在 project/
中。这包括诸如 Web 插件之类的东西。~/.sbt/
应该包含用于处理构建的本地自定义项和命令,但不是必需的。例如,IDE 插件。
有两种选择可以设置特定于用户的设置。例如,将本地 Maven 仓库插入解析器列表的开头
resolvers := {
val localMaven = "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository"
localMaven +: resolvers.value
}
.sbt
文件中,例如 $HOME/.sbt/1.0/global.sbt
。这些设置将应用于所有项目。.sbt
文件中,例如 <project>/local.sbt
。sbt 会将来自多个 .sbt 文件的设置组合在一起,因此你仍然可以拥有标准的 <project>/build.sbt
并将其签入版本控制。将要在 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 会为你将相对文件解析到构建根目录。
token
来明确地划分制表符补全边界。flatMap
进行一般递归。sbt 的组合器很严格,以限制生成的类的数量,因此像这样使用 flatMaplazy val parser: Parser[Int] =
token(IntBasic) flatMap { i =>
if(i <= 0)
success(i)
else
token(Space ~> parser)
}
此示例定义了一个解析器,它是一个用空格分隔的整数列表,以一个负数结尾,并返回该最终的负数。