このページは主に sbt プラグインの作成者を対象としています。 このページでは、プラグインの使用とプラグインを読んでいることを前提としています。
プラグイン開発者は、一貫性と使いやすさを目指す必要があります。具体的には
現在のプラグインのベストプラクティスを以下に示します。
**注:** ベストプラクティスは進化しているので、頻繁に確認してください。
既存の sbt キーがないため、新しいキーが必要になる場合があります。この場合は、プラグイン固有の接頭辞を使用します。
package sbtassembly
import sbt._, Keys._
object AssemblyPlugin extends AutoPlugin {
object autoImport {
val assembly = taskKey[File]("Builds a deployable fat jar.")
val assembleArtifact = settingKey[Boolean]("Enables (true) or disables (false) assembling an artifact.")
val assemblyOption = taskKey[AssemblyOption]("Configuration for making a deployable fat jar.")
val assembledMappings = taskKey[Seq[MappingSet]]("Keeps track of jar origins for each source.")
val assemblyPackageScala = taskKey[File]("Produces the scala artifact.")
val assemblyJarName = taskKey[String]("name of the fat jar")
val assemblyMergeStrategy = settingKey[String => MergeStrategy]("mapping from archive member path to merge strategy")
}
import autoImport._
....
}
このアプローチでは、すべての `val` は `assembly` で始まります。プラグインのユーザーは、`build.sbt` で次のように設定を参照します
assembly / assemblyJarName := "something.jar"
sbt シェル内では、ユーザーは設定を同じ方法で参照できます
sbt:helloworld> show assembly/assemblyJarName
[info] helloworld-assembly-0.1.0-SNAPSHOT.jar
キーの Scala 識別子とシェルがケバブケースを使用する sbt 0.12 スタイルのキー名は避けてください
`build.sbt` と sbt シェルの両方でキーに単一のネームスペースがあるため、異なるプラグインが `jarName` や `excludedFiles` のような汎用的なキー名を使用すると、名前の競合が発生します。
ライブラリとアーティファクトに名前を付けるには、`sbt-$projectname` スキームを使用します。一貫した命名規則を持つプラグインエコシステムにより、ユーザーはプロジェクトまたは依存関係が SBT プラグインかどうかを簡単に判断できます。
プロジェクトの名前が `foobar` の場合、以下が成り立ちます
プラグインが明白な「メイン」タスクを提供する場合、sbt シェルとタブ補完内でプラグインの機能をより直感的に探求できるように、`foobar` または `foobar...` という名前を付けることを検討してください。
プラグインに `FooBarPlugin` という名前を付けます。
ビルドファイルが何らかのパッケージにあるユーザーは、デフォルト (名前なし) パッケージで定義されている場合、プラグインを使用できません。
人々があなたのプラグインを見つけられるようにしてください。推奨される手順を以下に示します
sbt には多数の定義済みキーがあります。可能な場合は、プラグインでそれらを再利用してください。たとえば、以下を定義しないでください
val sourceFiles = settingKey[Seq[File]]("Some source files")
代わりに、sbt の既存の `sources` キーを再利用します。
プラグインは、sbt エコシステムの他の部分に自然に適合する必要があります。最初にできることは、コマンドの定義を避け、代わりに設定とタスク、およびタスクスコープを使用することです (タスクスコープの詳細については以下を参照)。`compile`、`test`、`publish` など、sbt のほとんどの興味深いものはタスクを使用して提供されます。タスクは、タスクエンジンによる重複削減と並列実行を活用できます。ScopeFilterなどの機能により、以前はコマンドが必要だった多くの機能がタスクを使用して可能になりました。
設定は他の設定とタスクから構成できます。タスクは他のタスクと入力タスクから構成できます。一方、コマンドは上記のいずれからも構成できません. 一般に、必要な最小限のものを使用してください。コマンドの正当な使用法の 1 つは、コードではなくビルド定義自体にアクセスするためのプラグインを使用することです。 sbt-inspectr は、`inspect tree` になる前はコマンドを使用して実装されていました.
たとえば、sbt の `package` タスクのコア機能は、sbt.Package に実装されており、その `apply` メソッドを介して呼び出すことができます。これにより、sbt-assembly などの他のプラグインから機能をより多く再利用できます。 sbt-assembly は、コア機能を実装するために `sbtassembly.Assembly` オブジェクトを実装しています
彼らの例に従って、プレーンな Scala オブジェクトでコア機能を提供します。
プラグインが新しいソースコードセットまたは独自のライブラリ依存関係のいずれかを導入する場合にのみ、独自の設定が必要になります.
設定は、プラグインのキーに名前空間を付けるために使用*しないでください*。単にタスクと設定を追加する場合は、独自の設定を定義しないでください. 代わりに、既存のものを再利用*するか*、メインタスクでスコープを設定します (以下を参照)。
package sbtwhatever
import sbt._, Keys._
object WhateverPlugin extends sbt.AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements
object autoImport {
// BAD sample
lazy val Whatever = config("whatever") extend(Compile)
lazy val specificKey = settingKey[String]("A plugin specific key")
}
import autoImport._
override lazy val projectSettings = Seq(
Whatever / specificKey := "another opinion" // DON'T DO THIS
)
}
プラグインが新しいソースコードセットまたは独自のライブラリ依存関係のいずれかを導入する場合にのみ、独自の設定が必要になります. たとえば、独自のファジングライブラリとファジングソースコードを必要とするファズテストを実行するプラグインを作成したとします。 `scalaSource` キーは `Compile` および `Test` 設定と同様に再利用できますが、`Fuzz` 設定にスコープが設定された `scalaSource` ( `scalaSource in Fuzz` と表記) は `src/fuzz/scala` を指すことができるため、他の Scala ソースとは異なりますディレクトリ。したがって、これら 3 つの定義は同じ*キー*を使用しますが、異なる*値*を表します。したがって、ユーザーの `build.sbt` には次のように表示される場合があります
Fuzz / scalaSource := baseDirectory.value / "source" / "fuzz" / "scala"
Compile / scalaSource := baseDirectory.value / "source" / "main" / "scala"
ファジングプラグインでは、これは `inConfig` 定義で実現されます
package sbtfuzz
import sbt._, Keys._
object FuzzPlugin extends sbt.AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements
object autoImport {
lazy val Fuzz = config("fuzz") extend(Compile)
}
import autoImport._
lazy val baseFuzzSettings: Seq[Def.Setting[_]] = Seq(
test := {
println("fuzz test")
}
)
override lazy val projectSettings = inConfig(Fuzz)(baseFuzzSettings)
}
新しいタイプの構成を定義する場合、たとえば
lazy val Fuzz = config("fuzz") extend(Compile)
構成を作成するために使用する必要があります。設定は実際には依存関係の解決 (Ivy を使用) に関連付けられており、生成された pom ファイルを変更できます。
設定を提供するかどうかにかかわらず、プラグインは、ビルドユーザーによって作成されたものを含め、複数の設定をサポートするように努める必要があります。特定の設定に関連付けられている一部のタスクは、他の設定で再利用できます。プラグインで必要性がすぐにわからない場合でも、一部のプロジェクトでは柔軟性を求める場合があります.
設定軸で設定を次のように分割します
package sbtobfuscate
import sbt._, Keys._
object ObfuscatePlugin extends sbt.AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements
object autoImport {
lazy val obfuscate = taskKey[Seq[File]]("obfuscate the source")
lazy val obfuscateStylesheet = settingKey[File]("obfuscate stylesheet")
}
import autoImport._
lazy val baseObfuscateSettings: Seq[Def.Setting[_]] = Seq(
obfuscate := Obfuscate((obfuscate / sources).value),
obfuscate / sources := sources.value
)
override lazy val projectSettings = inConfig(Compile)(baseObfuscateSettings)
}
// core feature implemented here
object Obfuscate {
def apply(sources: Seq[File]): Seq[File] = {
sources
}
}
`baseObfuscateSettings` 値は、プラグインのタスクの基本設定を提供します。これは、プロジェクトで必要な場合に他の設定で再利用できます。 `obfuscateSettings` 値は、プロジェクトが直接使用するためのデフォルトの `Compile` スコープ設定を提供します。これにより、プラグインによって提供される機能を使用する際に最大の柔軟性が得られます。生の設定を再利用する方法は次のとおりです
import sbtobfuscate.ObfuscatePlugin
lazy val app = (project in file("app"))
.settings(inConfig(Test)(ObfuscatePlugin.baseObfuscateSettings))
一般に、プラグインが最も広いスコープでキー (設定とタスク) を提供し、最も狭いスコープでそれらを参照する場合、ビルドユーザーに最大の柔軟性が与えられます。
設定またはタスクのデフォルト値がプロジェクトレベルの設定 (`baseDirectory`、`compile` など) に推移的に依存していない場合は、`globalSettings` で定義します.
例えば、sbt.Defaults
において、`licenses`、`developers`、`scmInfo` などの公開関連のキーはすべて `Global` スコープで定義されており、通常は `Nil` や `None` などの空の値に設定されています。
package sbtobfuscate
import sbt._, Keys._
object ObfuscatePlugin extends sbt.AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements
object autoImport {
lazy val obfuscate = taskKey[Seq[File]]("obfuscate the source")
lazy val obfuscateOption = settingKey[ObfuscateOption]("options to configure obfuscate")
}
import autoImport._
override lazy val globalSettings = Seq(
obfuscateOption := ObfuscateOption()
)
override lazy val projectSettings = inConfig(Compile)(
obfuscate := {
Obfuscate(
(obfuscate / sources).value,
(obfuscate / obfuscateOption).value
)
},
obfuscate / sources := sources.value
)
}
// core feature implemented here
object Obfuscate {
def apply(sources: Seq[File], opt: ObfuscateOption): Seq[File] = {
sources
}
}
上記では、`obfuscateOption` は `globalSettings` でデフォルトの仮の値が設定されていますが、`projectSettings` では `(obfuscate / obfuscateOption)` として使用されています。これにより、ユーザーは `obfuscate / obfuscateOption` を特定のサブプロジェクトレベルで設定することも、`ThisBuild` のスコープで設定してすべてのサブプロジェクトに影響を与えることもできます。
ThisBuild / obfuscate / obfuscateOption := ObfuscateOption().withX(true)
グローバルスコープでキーにデフォルト値を設定するには、そのキーの定義に使用されるすべてのキー(もしあれば)もグローバルスコープで定義されている必要があることを知っておく必要があります。そうでない場合、ロード時にエラーが発生します。
プラグインで特定の「メイン」タスクの設定を定義したい場合があります。この場合、タスク自体を使用して設定のスコープを設定できます。 `baseObfuscateSettings` を参照してください。
lazy val baseObfuscateSettings: Seq[Def.Setting[_]] = Seq(
obfuscate := Obfuscate((obfuscate / sources).value),
obfuscate / sources := sources.value
)
上記の例では、`obfuscate / sources` はメインタスクである `obfuscate` のスコープ下にあります。
`globalSettings` における既存のキーを再配線する必要がある場合があります。一般的なルールは、「*触れるものには注意を払う*」ことです。
他のプラグインからの以前の設定が無視されないように注意する必要があります。たとえば、新しい `onLoad` ハンドラーを作成する場合は、以前の `onLoad` ハンドラーが削除されないようにしてください。
package sbtsomething
import sbt._, Keys._
object MyPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements
override val globalSettings: Seq[Def.Setting[_]] = Seq(
Global / onLoad := (Global / onLoad).value andThen { state =>
... return new state ...
}
)
}