1. マルチプロジェクトビルド

マルチプロジェクトビルド 

このページでは、単一のビルドに複数のサブプロジェクトを導入します。

最初に「入門ガイド」の前のページを読んでください。特に、このページを読む前にbuild.sbtを理解する必要があります。

複数のサブプロジェクト 

特に、複数のサブプロジェクトが相互に依存し、一緒に変更することが多い場合は、単一のビルドで複数の関連するサブプロジェクトを保持すると便利な場合があります。

ビルド内の各サブプロジェクトには独自のソースディレクトリがあり、パッケージを実行すると独自の jar ファイルが生成され、一般的には他のプロジェクトと同じように機能します。

プロジェクトは、Project型の lazy val を宣言することで定義されます。例:

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

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

val の名前は、サブプロジェクトの ID として使用され、sbt シェルでサブプロジェクトを参照するために使用されます。

オプションで、ベースディレクトリが val の名前と同じ場合は、省略できます。

lazy val util = project

lazy val core = project

ビルド全体の設定 

複数のサブプロジェクトで共通の設定を抽出するには、ThisBuild にスコープされた設定を定義します。ThisBuild は、ビルドのデフォルト値を定義するために使用できる特別なサブプロジェクト名として機能します。1 つ以上のサブプロジェクトを定義し、サブプロジェクトが 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 を 1 か所で変更でき、ビルドをリロードするとサブプロジェクト全体に反映されます。

共通設定 

複数のプロジェクトで共通の設定を抽出する別の方法は、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
  )

依存性 

ビルド内のプロジェクトは互いに完全に独立している可能性がありますが、通常は依存関係によって相互に関連付けられます。依存関係には、集約とクラスパスの 2 種類があります。

集約 

集約とは、集約プロジェクトでタスクを実行すると、集約されたプロジェクトでもタスクが実行されることを意味します。例:

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

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

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

上記の例では、ルートプロジェクトは utilcore を集約します。例のように 2 つのサブプロジェクトで sbt を起動し、コンパイルを試してください。3 つのプロジェクトすべてがコンパイルされるはずです。

この場合、ルートプロジェクトである*集約を実行するプロジェクト*では、タスクごとに集約を制御できます。たとえば、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 のクラスを使用できるようになります。これにより、プロジェクトをコンパイルする際の順序も作成されます。core をコンパイルする前に、util を更新してコンパイルする必要があります。

複数のプロジェクトに依存するには、dependsOn(bar, baz) のように、dependsOn に複数の引数を使用します。

構成ごとのクラスパス依存性 

core dependsOn(util) は、corecompile 構成が utilcompile 構成に依存することを意味します。これを明示的に dependsOn(util % "compile->compile") と記述することもできます。

"compile->compile"-> は「依存する」を意味するため、"test->compile" は、coretest 構成が utilcompile 構成に依存することを意味します。

->config 部分を省略すると、->compile が暗示されるため、dependsOn(util % "test") は、coretest 構成が utilCompile 構成に依存することを意味します。

便利な宣言は "test->test" で、testtest に依存することを意味します。これにより、たとえば、util/src/test/scala にテスト用のユーティリティコードを配置し、core/src/test/scala でそのコードを使用できます。

依存関係に対して複数の構成を設定でき、それらはセミコロンで区切られます。たとえば、dependsOn(util % "test->test;compile->compile") のようにします。

プロジェクト間の依存関係 

ファイルとサブプロジェクトが非常に多い大規模なプロジェクトでは、sbt は変更されたファイルを継続的に監視する際に最適に動作しなくなる可能性があり、多くのディスクおよびシステム I/O を使用します。

sbt には、trackInternalDependencies および exportToInternal 設定があります。これらを使用すると、compile を呼び出すときに、依存サブプロジェクトのコンパイルをトリガーするかどうかを制御できます。どちらのキーも、3 つの値 (TrackLevel.NoTrackingTrackLevel.TrackIfMissing、および TrackLevel.TrackAlways) のいずれかを受け取ります。デフォルトでは、両方とも TrackLevel.TrackAlways に設定されています。

trackInternalDependenciesTrackLevel.TrackIfMissing に設定されている場合、出力ディレクトリに *.class ファイル (または exportJarstrue の場合は JAR ファイル) がない限り、sbt は内部 (プロジェクト間) の依存関係を自動的にコンパイルしようとしなくなります。

設定が TrackLevel.NoTracking に設定されている場合、内部依存関係のコンパイルはスキップされます。クラスパスは引き続き追加され、依存性グラフには依然として依存関係として表示されることに注意してください。動機は、開発中に多くのサブプロジェクトを含むビルドで変更をチェックする I/O オーバーヘッドを削減することです。すべてのサブプロジェクトを TrackIfMissing に設定する方法を次に示します。

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

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

exportToInternal 設定を使用すると、依存先のサブプロジェクトが内部トラッキングをオプトアウトできます。これは、いくつかのサブプロジェクトを除いてほとんどのサブプロジェクトを追跡する場合に役立ちます。trackInternalDependenciesexportToInternal の設定の交差部分を使用して、実際の追跡レベルが決定されます。1 つのプロジェクトをオプトアウトする例を次に示します。

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

デフォルトのルートプロジェクト 

ビルドのルートディレクトリに対してプロジェクトが定義されていない場合、sbt はビルド内の他のすべてのプロジェクトを集約するデフォルトのプロジェクトを作成します。

プロジェクト hello-foobase = file("foo") で定義されているため、サブディレクトリ foo に含まれます。そのソースは、foo/Foo.scala のように foo の直下、または foo/src/main/scala にあります。通常の sbt ディレクトリ構造は、ビルド定義ファイルを除き、foo の下で適用されます。

sbt インタラクティブプロンプトで、projects と入力してプロジェクトを一覧表示し、project <projectname> と入力して現在のプロジェクトを選択します。compile などのタスクを実行すると、現在のプロジェクトで実行されます。したがって、必ずしもルートプロジェクトをコンパイルする必要はなく、サブプロジェクトのみをコンパイルすることもできます。

subProjectID/compile のように、プロジェクト ID を明示的に指定することで、別のプロジェクトでタスクを実行できます。

共通コード 

.sbtファイル内の定義は、他の.sbtファイルからは参照できません。.sbtファイル間でコードを共有するには、ビルドルートのproject/ディレクトリに1つ以上のScalaファイルを定義してください。

詳細は、ビルドの構成を参照してください。

付録: サブプロジェクトのビルド定義ファイル 

例えば、fooディレクトリ内の.sbtファイル(例えば、foo/build.sbt)は、ビルド全体のビルド定義にマージされますが、hello-fooプロジェクトにスコープされます。

プロジェクト全体が hello にある場合、hello/build.sbthello/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/*:versionhello/foo/build.sbt で定義され、hello-bar/*:versionhello/bar/build.sbt で定義され、hello/*:versionhello/build.sbt で定義されました。スコープ付きキーの構文を思い出してください。各 version キーは、build.sbt の場所に基づいてプロジェクトにスコープされます。ただし、3つの build.sbt はすべて同じビルド定義の一部です。

スタイルの選択

  • 各サブプロジェクトの設定は、そのプロジェクトのベースディレクトリにある *.sbt ファイルに記述できます。一方、ルートの build.sbt は、設定なしで lazy val foo = (project in file("foo")) の形式で最小限のプロジェクト宣言のみを行います。
  • すべてのビルド定義を1つのファイルにまとめるために、ルートの build.sbt ファイルにすべてのプロジェクト宣言と設定を記述することをお勧めします。ただし、それはあなた次第です。

注意:サブプロジェクト内にプロジェクトサブディレクトリや project/*.scala ファイルを含めることはできません。foo/project/Build.scala は無視されます。