1. 多專案建置

多專案建置 

此頁面介紹單一建置中的多個子專案。

請先閱讀入門指南中的先前頁面,尤其是您需要了解build.sbt,然後再閱讀此頁面。

多個子專案 

將多個相關子專案保留在單一建置中可能很有用,尤其是在它們彼此依賴並且您傾向於一起修改它們的情況下。

建置中的每個子專案都有自己的來源目錄,在您執行封裝時會產生自己的 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/*: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 將會被忽略。