1. スコープ

スコープ 

このページでは、スコープについて説明します。ここでは、前のページであるビルド定義タスクグラフを読んだことを前提とします。

キーに関する全容 

以前name のようなキーは、sbt のキーと値のペアのマップ内の 1 つのエントリに対応すると説明しました。これは簡略化した説明でした。

実際には、各キーは、スコープと呼ばれる複数のコンテキストで関連付けられた値を持つことができます。

具体的な例

  • ビルド定義に複数のプロジェクト (サブプロジェクトとも呼ばれます) がある場合、各プロジェクトでキーが異なる値を持つことができます。
  • compile キーは、メインソースとテストソースで異なる値を持ち、それらを異なる方法でコンパイルすることができます。
  • packageOptions キー (jar パッケージを作成するためのオプションを含む) は、クラスファイルをパッケージ化するとき (packageBin) とソースコードをパッケージ化するとき (packageSrc) で異なる値を持つ場合があります。

特定のキー name の単一の値はありません。これは、値がスコープによって異なる場合があるためです。

ただし、特定のスコープ付きキーには単一の値があります。

sbt がプロジェクトを記述するキーと値のマップを生成するために設定リストを処理することを考えると、前述のように、そのキーと値のマップのキーはスコープ付きキーです。ビルド定義 (たとえば build.sbt) で定義された各設定は、スコープ付きキーにも適用されます。

多くの場合、スコープは暗黙的であるか、デフォルトがありますが、デフォルトが間違っている場合は、build.sbt で必要なスコープを指定する必要があります。

スコープ軸 

スコープ軸は、Option[A] に似た型コンストラクターであり、スコープ内のコンポーネントを形成するために使用されます。

スコープ軸は 3 つあります

  • サブプロジェクト軸
  • 依存関係構成軸
  • タスク軸

の概念に慣れていない場合は、RGB カラーキューブを例として考えることができます

color cube

RGB カラーモデルでは、すべての色は、軸が赤、緑、青のコンポーネントに対応するキューブ内の点で表され、数値でエンコードされます。同様に、sbt の完全なスコープは、サブプロジェクト、構成、タスク値のタプルによって形成されます。

projA / Compile / console / scalacOptions

これは sbt 1.1 で導入されたスラッシュ構文です。

scalacOptions in (
  Select(projA: Reference),
  Select(Compile: ConfigKey),
  Select(console.key)
)

サブプロジェクト軸によるスコープ指定 

1 つのビルドに複数のプロジェクトを配置する場合、各プロジェクトには独自の設定が必要です。つまり、キーはプロジェクトに応じてスコープ指定できます。

プロジェクト軸は ThisBuild に設定することもできます。これは「ビルド全体」を意味するため、設定は単一のプロジェクトではなくビルド全体に適用されます。ビルドレベルの設定は、プロジェクトがプロジェクト固有の設定を定義していない場合のフォールバックとしてよく使用されます。ビルドレベルの設定については、このページで後ほど詳しく説明します。

構成軸によるスコープ指定 

依存関係構成 (または略して「構成」) は、独自のクラスパス、ソース、生成されたパッケージなどを持つ可能性のあるライブラリ依存関係のグラフを定義します。依存関係構成の概念は、sbt が管理された依存関係に使用していた Ivy に由来し、ライブラリ依存関係、およびMaven スコープに由来します。

sbt で見られる構成のいくつか

  • メインビルド (src/main/scala) を定義する Compile
  • テスト (src/test/scala) のビルド方法を定義する Test
  • run タスクのクラスパスを定義する Runtime

デフォルトでは、コンパイル、パッケージング、実行に関連するすべてのキーは構成にスコープ指定されるため、各構成で異なる動作をする可能性があります。最も明白な例は、タスクキーの compilepackagerun です。ただし、それらのキーに影響を与えるすべてのキー (sourceDirectoriesscalacOptionsfullClasspath など) も構成にスコープ指定されます。

構成についてもう 1 つ注意すべき点は、他の構成を拡張できるということです。次の図は、最も一般的な構成間の拡張関係を示しています。

dependency configurations

Test および IntegrationTestRuntime を拡張し、RuntimeCompile を拡張し、CompileInternalCompileOptional、および Provided を拡張します。

タスク軸によるスコープ指定 

設定は、タスクの動作に影響を与える可能性があります。たとえば、packageSrc タスクは packageOptions 設定の影響を受けます。

これをサポートするために、タスクキー (packageSrc など) は、別のキー (packageOptions など) のスコープになる可能性があります。

パッケージをビルドするさまざまなタスク (packageSrcpackageBinpackageDoc) は、artifactNamepackageOptions などのパッケージングに関連するキーを共有できます。これらのキーは、各パッケージングタスクで異なる値を持つことができます。

ゼロスコープコンポーネント 

各スコープ軸は、軸型のインスタンス (Some(_) に類似) で埋めるか、軸を特別な値 Zero で埋めることができます。したがって、ZeroNone として考えることができます。

Zero はすべてのスコープ軸のユニバーサルフォールバックですが、ほとんどの場合、その直接的な使用は sbt およびプラグイン作成者専用にする必要があります。

Global は、すべての軸に Zero を設定するスコープです: Zero / Zero / Zero。つまり、Global / someKeyZero / Zero / Zero / someKey の省略形です。

ビルド定義でのスコープの参照 

build.sbt で裸のキーを使用して設定を作成した場合、それは (現在のサブプロジェクト / 構成 Zero / タスク Zero) にスコープ指定されます

lazy val root = (project in file("."))
  .settings(
    name := "hello"
  )

sbt を実行し、inspect name を実行すると、ProjectRef(uri("file:/private/tmp/hello/"), "root") / name によって提供されていることがわかります。つまり、プロジェクトは ProjectRef(uri("file:/Users/xxx/hello/"), "root") であり、構成もタスクスコープも表示されていません (つまり、Zero)。

右側の裸のキーも (現在のサブプロジェクト / 構成 Zero / タスク Zero) にスコープ指定されます

organization := name.value

スコープ軸の型は、/演算子を持つようにメソッドが拡張されました。/への引数には、キーまたは別のスコープ軸を指定できます。したがって、例えば、これを行う正当な理由はありませんが、Compile構成にスコープされたnameキーのインスタンスを持つことができます。

Compile / name := "hello"

または、packageBinタスクにスコープされた名前を設定することもできます(無意味です!単なる例です)。

packageBin / name := "hello"

または、複数のスコープ軸を持つnameを設定することもできます。例えば、Compile構成のpackageBinタスクで設定できます。

Compile / packageBin / name := "hello"

または、Globalを使用することもできます。

// same as Zero / Zero / Zero / concurrentRestrictions
Global / concurrentRestrictions := Seq(
  Tags.limitAll(1)
)

Global / concurrentRestrictionsは、暗黙的にZero / Zero / Zero / concurrentRestrictionsに変換され、すべての軸をZeroスコープコンポーネントに設定します。タスクと構成はデフォルトですでにZeroであるため、ここではプロジェクトをZeroにする効果があります。つまり、ProjectRef(uri("file:/tmp/hello/"), "root") / Zero / Zero / concurrentRestrictionsではなく、Zero / Zero / Zero / concurrentRestrictionsを定義します。)

sbtシェルからスコープ付きキーを参照する 

コマンドラインおよびsbtシェルでは、sbtは次のようにスコープ付きキーを表示(および解析)します。

ref / Config / intask / key
  • refは、サブプロジェクト軸を識別します。これは、<project-id>ProjectRef(uri("file:..."), "id")、または「ビルド全体」スコープを示すThisBuildのいずれかです。
  • Configは、大文字のScala識別子を使用して、構成軸を識別します。
  • intaskは、タスク軸を識別します。
  • keyは、スコープ設定されるキーを識別します。

各軸にZeroが表示される可能性があります。

スコープ付きキーの一部を省略すると、次のように推論されます。

  • プロジェクトを省略すると、現在のプロジェクトが使用されます。
  • 構成またはタスクを省略すると、キーに依存する構成が自動的に検出されます。

詳細については、設定システムとの対話を参照してください。

sbtシェルでのスコープ付きキー表記の例 

  • fullClasspathはキーのみを指定しているため、デフォルトのスコープ(現在のプロジェクト、キーに依存する構成、およびZeroタスクスコープ)が使用されます。
  • Test / fullClasspathは構成を指定しているため、これはTest構成でのfullClasspathであり、他の2つのスコープ軸はデフォルトになります。
  • root / fullClasspathは、プロジェクトIDで識別されるプロジェクトrootを指定します。
  • root / Zero / fullClasspathは、プロジェクトrootを指定し、デフォルトの構成ではなく、構成にZeroを指定します。
  • doc / fullClasspathは、docタスクにスコープされたfullClasspathキーを指定し、プロジェクト軸と構成軸はデフォルトになります。
  • ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspathは、プロジェクトProjectRef(uri("file:/tmp/hello/"), "root")を指定します。また、構成Testを指定し、デフォルトのタスク軸のままにします。
  • ThisBuild / versionは、サブプロジェクト軸を「ビルド全体」に設定します。ここで、ビルドはThisBuildであり、デフォルトの構成になります。
  • Zero / fullClasspathは、サブプロジェクト軸をZeroに設定し、デフォルトの構成になります。
  • root / Compile / doc / fullClasspathは、3つのスコープ軸すべてを設定します。

スコープの検査 

sbtシェルでは、inspectコマンドを使用して、キーとそのスコープを理解できます。inspect Test/fullClasspathを試してください。

$ sbt
sbt:Hello> inspect Test / fullClasspath
[info] Task: scala.collection.Seq[sbt.internal.util.Attributed[java.io.File]]
[info] Description:
[info]  The exported classpath, consisting of build products and unmanaged and managed, internal and external dependencies.
[info] Provided by:
[info]  ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspath
[info] Defined at:
[info]  (sbt.Classpaths.classpaths) Defaults.scala:1639
[info] Dependencies:
[info]  Test / dependencyClasspath
[info]  Test / exportedProducts
[info]  Test / fullClasspath / streams
[info] Reverse dependencies:
[info]  Test / testLoader
[info] Delegates:
[info]  Test / fullClasspath
[info]  Runtime / fullClasspath
[info]  Compile / fullClasspath
[info]  fullClasspath
[info]  ThisBuild / Test / fullClasspath
[info]  ThisBuild / Runtime / fullClasspath
[info]  ThisBuild / Compile / fullClasspath
[info]  ThisBuild / fullClasspath
[info]  Zero / Test / fullClasspath
[info]  Zero / Runtime / fullClasspath
[info]  Zero / Compile / fullClasspath
[info]  Global / fullClasspath
[info] Related:
[info]  Compile / fullClasspath
[info]  Runtime / fullClasspath

最初の行には、これが(.sbtビルド定義で説明されているように、設定とは対照的に)タスクであることがわかります。タスクの結果の値の型は、scala.collection.Seq[sbt.Attributed[java.io.File]]になります。

「Provided by」は、値を定義するスコープ付きキーを指します。この場合は、ProjectRef(uri("file:/tmp/hello/"), "root") / Test / fullClasspathTest構成とProjectRef(uri("file:/tmp/hello/"), "root")プロジェクトにスコープされたfullClasspathキー)です。

「Dependencies」は、前のページで詳細に説明しました。

「Delegates」については後で説明します。

inspect fullClasspath(上記の例とは対照的に、inspect Test / fullClasspath)を試して、違いを感じてください。構成が省略されているため、Compileとして自動検出されます。したがって、inspect Compile / fullClasspathinspect fullClasspathと同じように見えるはずです。

別のコントラストとして、inspect ThisBuild / Zero / fullClasspathを試してください。fullClasspathは、デフォルトではZero構成スコープでは定義されていません。

繰り返しますが、詳細については、設定システムとの対話を参照してください。

スコープを指定するタイミング 

問題のキーが通常スコープされている場合は、スコープを指定する必要があります。例えば、compileタスクは、デフォルトではCompile構成とTest構成にスコープされており、これらのスコープの外には存在しません。

compileキーに関連付けられた値を変更するには、Compile / compileまたはTest / compileと記述する必要があります。単にcompileを使用すると、構成にスコープされた標準のコンパイルタスクを上書きするのではなく、現在のプロジェクトにスコープされた新しいコンパイルタスクが定義されます。

「未定義の設定への参照」のようなエラーが発生した場合は、スコープの指定に失敗したか、間違ったスコープを指定したことがよくあります。使用しているキーは、他のスコープで定義されている可能性があります。sbtは、エラーメッセージの一部として、何を意味したかを示唆しようとします。「Did you mean Compile / compile?」を探してください。

それを考える1つの方法は、名前はキーの一部にすぎないということです。実際には、すべてのキーは名前とスコープの両方で構成されています(スコープには3つの軸があります)。式全体Compile / packageBin / packageOptionsは、言い換えれば、キー名です。単にpackageOptionsもキー名ですが、別のものです(スラッシュのないキーの場合、スコープが暗黙的に想定されます:現在のプロジェクト、Zero構成、Zeroタスク)。

ビルドレベルの設定 

サブプロジェクト間で共通の設定をファクタリングするための高度なテクニックは、ThisBuildにスコープされた設定を定義することです。

特定のサブプロジェクトにスコープされたキーが見つからない場合、sbtはフォールバックとしてThisBuildでそれを探します。このメカニズムを使用すると、versionscalaVersionorganizationなどの頻繁に使用されるキーに対して、ビルドレベルのデフォルト設定を定義できます。

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

lazy val root = (project in file("."))
  .settings(
    name := "Hello",
    publish / skip := true
  )

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

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

便宜上、キーと設定式の本体の両方をThisBuildにスコープ設定するinThisBuild(...)関数があります。設定式をそこに入れることは、可能な限りThisBuild /を前に付けるのと同じです。

後で説明するスコープ委譲の性質上、ビルドレベルの設定は、純粋な値、またはGlobalまたはThisBuildスコープからの設定のみに設定する必要があります。

スコープ委譲 

スコープ付きキーは、そのスコープに値が関連付けられていない場合、未定義になる可能性があります。

sbtには、各スコープ軸に対して、他のスコープ値で構成されるフォールバック検索パスがあります。通常、キーがより具体的なスコープに関連付けられた値を持たない場合、sbtはThisBuildスコープなどのより一般的なスコープから値を取得しようとします。

この機能により、より一般的なスコープで値を1回設定することで、複数のより具体的なスコープが値を継承できます。後でスコープ委譲について詳しく説明します。