1. 多项目构建

多项目构建 

本页介绍了单个构建中的多个子项目。

请先阅读入门指南中的早期页面,特别是您需要了解 build.sbt 才能阅读本页。

多个子项目 

将多个相关子项目保存在单个构建中非常有用,尤其是在它们相互依赖且您倾向于一起修改它们时。

构建中的每个子项目都有自己的源代码目录,在您运行 package 时会生成自己的 jar 文件,并且通常像任何其他项目一样工作。

项目通过声明类型为 Project 的 lazy val 来定义。例如,

lazy val util = (project in file("util"))

lazy val core = (project in file("core"))

val 的名称用作子项目的 ID,用于在 sbt shell 中引用子项目。

如果基本目录与 val 的名称相同,则可以选择省略基本目录。

lazy val util = project

lazy val core = project

构建范围设置 

要将多个子项目之间的通用设置分解出来,请将设置范围定义为 ThisBuildThisBuild 充当一个特殊的子项目名称,您可以使用它来定义构建的默认值。当您定义一个或多个子项目时,以及当子项目未定义 scalaVersion 键时,它将查找 ThisBuild / scalaVersion

限制是右侧需要是一个纯值或范围定义为 GlobalThisBuild 的设置,并且没有范围定义为子项目的默认设置。(参见 范围

ThisBuild / organization := "com.example"
ThisBuild / version      := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"

lazy val core = (project in file("core"))
  .settings(
    // other settings
  )

lazy val util = (project in file("util"))
  .settings(
    // other settings
  )

现在我们可以在一个地方增加 version,并且当您重新加载构建时,它将反映到所有子项目中。

通用设置 

分解多个项目之间通用设置的另一种方法是创建一个名为 commonSettings 的序列,并在每个项目上调用 settings 方法。

lazy val commonSettings = Seq(
  target := { baseDirectory.value / "target2" }
)

lazy val core = (project in file("core"))
  .settings(
    commonSettings,
    // other settings
  )

lazy val util = (project in file("util"))
  .settings(
    commonSettings,
    // other settings
  )

依赖项 

构建中的项目可以完全独立,但通常它们会通过某种依赖关系相互关联。依赖关系有两种类型:聚合和类路径。

聚合 

聚合意味着在聚合项目上运行任务也会在聚合的项目上运行。例如,

lazy val root = (project in file("."))
  .aggregate(util, core)

lazy val util = (project in file("util"))

lazy val core = (project in file("core"))

在上面的示例中,根项目聚合了 utilcore。使用示例中的两个子项目启动 sbt,然后尝试编译。您应该看到所有三个项目都被编译了。

在进行聚合的项目中,在本例中是根项目,您可以控制每个任务的聚合。例如,要避免聚合 update 任务

lazy val root = (project in file("."))
  .aggregate(util, core)
  .settings(
    update / aggregate := false
  )

[...]

update / aggregate 是范围定义为 update 任务的聚合键。(参见 范围。)

注意:聚合将在多个项目中并行运行聚合的任务,并且它们之间没有定义的顺序。

类路径依赖项 

项目可能依赖于另一个项目中的代码。这是通过添加 dependsOn 方法调用来完成的。例如,如果 core 需要 util 在其类路径上,您将 core 定义为

lazy val core = project.dependsOn(util)

现在 core 中的代码可以使用 util 中的类。这也创建了在编译项目时的顺序;util 必须先更新和编译,然后才能编译 core。

要依赖多个项目,请对 dependsOn 使用多个参数,例如 dependsOn(bar, baz)

每个配置的类路径依赖项 

core dependsOn(util) 意味着 core 中的 compile 配置依赖于 util 中的 compile 配置。您可以将其明确地写为 dependsOn(util % "compile->compile")

"compile->compile" 中的 -> 表示“依赖于”,因此 "test->compile" 意味着 core 中的 test 配置将依赖于 util 中的 compile 配置。

省略 ->config 部分意味着 ->compile,因此 dependsOn(util % "test") 意味着 core 中的 test 配置依赖于 util 中的 Compile 配置。

一个有用的声明是 "test->test",这意味着 test 依赖于 test。这允许您将用于测试的实用程序代码放在 util/src/test/scala 中,然后在 core/src/test/scala 中使用该代码,例如。

您可以为依赖项设置多个配置,用分号分隔。例如,dependsOn(util % "test->test;compile->compile")

项目间依赖项 

在拥有大量文件和许多子项目的大型项目中,sbt 在持续监视已更改的文件和使用大量磁盘和系统 I/O 方面可能表现不佳。

sbt 具有 trackInternalDependenciesexportToInternal 设置。这些可用于控制在调用 compile 时是否触发依赖子项目的编译。这两个键都将采用以下三种值之一:TrackLevel.NoTrackingTrackLevel.TrackIfMissingTrackLevel.TrackAlways。默认情况下,它们都设置为 TrackLevel.TrackAlways

trackInternalDependencies 设置为 TrackLevel.TrackIfMissing 时,sbt 将不再尝试自动编译内部(项目间)依赖项,除非输出目录中没有 *.class 文件(或当 exportJarstrue 时为 JAR 文件)。

当设置设置为 TrackLevel.NoTracking 时,将跳过内部依赖项的编译。请注意,类路径仍将被追加,并且依赖项图仍将显示它们作为依赖项。其动机是在开发期间节省在具有许多子项目的构建中检查更改的 I/O 开销。以下是将所有子项目设置为 TrackIfMissing 的方法。

ThisBuild / trackInternalDependencies := TrackLevel.TrackIfMissing
ThisBuild / exportJars := true

lazy val root = (project in file("."))
  .aggregate(....)

exportToInternal 设置允许依赖项子项目选择退出内部跟踪,如果您希望跟踪大多数子项目,但少数子项目除外,这可能很有用。trackInternalDependenciesexportToInternal 设置的交集将用于确定实际跟踪级别。以下是一个选择退出一个项目的示例

lazy val dontTrackMe = (project in file("dontTrackMe"))
  .settings(
    exportToInternal := TrackLevel.NoTracking
  )

默认根项目 

如果构建中的根目录未定义项目,sbt 将创建一个默认项目,该项目将聚合构建中的所有其他项目。

因为项目 hello-foo 是使用 base = file("foo") 定义的,所以它将包含在子目录 foo 中。它的源代码可以直接位于 foo 下,例如 foo/Foo.scala,或者位于 foo/src/main/scala 中。通常的 sbt 目录结构 适用于 foo 下,但构建定义文件除外。

在 sbt 交互式提示符下,键入 projects 列出您的项目,键入 project <projectname> 选择当前项目。当您运行 compile 之类的任务时,它将在当前项目上运行。因此,您不必一定编译根项目,您可以只编译子项目。

您可以通过明确指定项目 ID 来运行另一个项目中的任务,例如 subProjectID/compile

通用代码 

.sbt 文件中的定义在其他 .sbt 文件中不可见。为了在 .sbt 文件之间共享代码,请在构建根目录的 project/ 目录中定义一个或多个 Scala 文件。

有关详细信息,请参见 组织构建

附录:子项目构建定义文件 

foo 中的任何 .sbt 文件(例如 foo/build.sbt)都将与整个构建的构建定义合并,但范围限定为 hello-foo 项目。

如果您的整个项目都在 hello 中,请尝试在 hello/build.sbthello/foo/build.sbthello/bar/build.sbt 中定义不同的版本(version := "0.6")。现在在 sbt 交互式提示符下运行 show version。您应该得到类似于以下内容(包含您定义的任何版本):

> show version
[info] hello-foo/*:version
[info]  0.7
[info] hello-bar/*:version
[info]  0.9
[info] hello/*:version
[info]  0.5

hello-foo/*:versionhello/foo/build.sbt 中定义,hello-bar/*:versionhello/bar/build.sbt 中定义,hello/*:versionhello/build.sbt 中定义。请记住 作用域键的语法。每个 version 键的作用域都限定在一个项目中,基于 build.sbt 的位置。但所有三个 build.sbt 都属于同一个构建定义。

样式选择

  • 每个子项目的设置可以放到该项目根目录下的 *.sbt 文件中,而根 build.sbt 只声明最小限度的项目声明,形式为 lazy val foo = (project in file("foo")),不包含设置。
  • 我们建议将所有项目声明和设置都放在根 build.sbt 文件中,以将所有构建定义保持在一个文件中。但是,这取决于你。

注意:你不能在子项目中拥有项目子目录或 project/*.scala 文件。foo/project/Build.scala 会被忽略。