本页介绍了单个构建中的多个子项目。
请先阅读入门指南中的早期页面,特别是您需要了解 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
要将多个子项目之间的通用设置分解出来,请将设置范围定义为 ThisBuild
。ThisBuild
充当一个特殊的子项目名称,您可以使用它来定义构建的默认值。当您定义一个或多个子项目时,以及当子项目未定义 scalaVersion
键时,它将查找 ThisBuild / scalaVersion
。
限制是右侧需要是一个纯值或范围定义为 Global
或 ThisBuild
的设置,并且没有范围定义为子项目的默认设置。(参见 范围)
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"))
在上面的示例中,根项目聚合了 util
和 core
。使用示例中的两个子项目启动 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 具有 trackInternalDependencies
和 exportToInternal
设置。这些可用于控制在调用 compile
时是否触发依赖子项目的编译。这两个键都将采用以下三种值之一:TrackLevel.NoTracking
、TrackLevel.TrackIfMissing
和 TrackLevel.TrackAlways
。默认情况下,它们都设置为 TrackLevel.TrackAlways
。
当 trackInternalDependencies
设置为 TrackLevel.TrackIfMissing
时,sbt 将不再尝试自动编译内部(项目间)依赖项,除非输出目录中没有 *.class
文件(或当 exportJars
为 true
时为 JAR 文件)。
当设置设置为 TrackLevel.NoTracking
时,将跳过内部依赖项的编译。请注意,类路径仍将被追加,并且依赖项图仍将显示它们作为依赖项。其动机是在开发期间节省在具有许多子项目的构建中检查更改的 I/O 开销。以下是将所有子项目设置为 TrackIfMissing
的方法。
ThisBuild / trackInternalDependencies := TrackLevel.TrackIfMissing
ThisBuild / exportJars := true
lazy val root = (project in file("."))
.aggregate(....)
exportToInternal
设置允许依赖项子项目选择退出内部跟踪,如果您希望跟踪大多数子项目,但少数子项目除外,这可能很有用。trackInternalDependencies
和 exportToInternal
设置的交集将用于确定实际跟踪级别。以下是一个选择退出一个项目的示例
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.sbt
、hello/foo/build.sbt
和 hello/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/*:version
在 hello/foo/build.sbt
中定义,hello-bar/*:version
在 hello/bar/build.sbt
中定义,hello/*:version
在 hello/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
会被忽略。