プラグイン
このページでは中級者向けにプラグインの詳細を解説する。 プラグインシステムの概要はプラグインの基本を参照。
説明
プラグインはビルド定義を拡張する手段であり、組み込みのセッティングやタスクではできない機能を追加するのに使われる。例としては Docker コンテナの構築、Scala.JS のサポート、GitHub Actions の YAML 生成などがある。
プラグインは sbt のセッティングの列を定義し、すべてのプロジェクトに自動的に追加するか、選択したプロジェクトに明示的に宣言する。プラグインは新しいコマンドも定義できる。sbt 1.x および 2.x のプラグインは sbt.AutoPlugin トレイトを継承し、依存プラグイン間のセッティングの順序を自動化する。
プラグインの作成
概念的には、sbt プラグインは「メタビルド (現在は Scala 3.8.2) 上にある何の変哲もないライブラリ」と考えられる。しかし実際には、現代の sbt プラグインは auto plugin(sbt.AutoPlugin)を中心に構築され、正しい順序でタスクとセッティングを提供する。詳細はプラグイン依存性の節を参照。
build.sbt の設定
auto plugin を作るには、プロジェクトを作成し SbtPlugin を有効にする。
version := "0.1.0-SNAPSHOT"
organization := "com.example"
homepage := Some(url("https://github.com/sbt/sbt-hello"))
lazy val root = rootProject
.enablePlugins(SbtPlugin)
.settings(
name := "sbt-hello",
pluginCrossBuild / sbtVersion := {
scalaBinaryVersion.value match
case "3" => "2.0.0-RC10" // set minimum sbt version
}
)
- sbt 2.x のプラグインは Scala 3.x でコンパイルする必要がある。
scalaVersionを指定しない場合、sbt はプラグインに適した Scala バージョンをデフォルトとする。 pluginCrossBuild / sbtVersionは、プラグインを より古い sbt バージョン向けにコンパイルするためのセッティングであり、プラグイン利用者が sbt のバージョン範囲から選べるようにする。
projectSettings
適切な名前空間で、sbt.AutoPlugin を継承して auto plugin オブジェクトを定義する。auto plugin では、セッティングはプラグインが projectSettings メソッドを通じて直接提供する。サブプロジェクトに hello という名前のタスクを追加する例を示す:
package sbthello
import sbt.*
import Keys.*
object HelloPlugin extends AutoPlugin:
override def trigger = allRequirements
object autoImport:
val helloGreeting = settingKey[String]("greeting")
val hello = taskKey[Unit]("say hello")
end autoImport
import autoImport.*
override lazy val globalSettings: Seq[Setting[?]] = Seq(
helloGreeting := "hi",
)
override lazy val projectSettings: Seq[Setting[?]] = Seq(
hello := {
val s = streams.value
val g = helloGreeting.value
s.log.info(g)
}
)
end HelloPlugin
Scala では、def メソッドは統一アクセス原理と呼ばれる仕組みで lazy val によりオーバーライドできる。
projectSettings には lazy val を使うことを推奨する。セッティング定義は不変であることが多いためである。
globalSettings
globalSettings はグローバルなセッティングに一度だけ追加される(例: Global / helloGreeting)。これによりプラグインは新しい機能や新しいデフォルトを提供できる。主な用途の一つは、IDE プラグインのようにコマンドをグローバルに追加することである。
override lazy val globalSettings: Seq[Setting[?]] = Nil
可能な限り、プラグインは globalSettings でデフォルト値を定義すべきである。値を最も広いスコープで提供し、最も狭いスコープで使用すると、ユーザーがセッティングを変更できる自由度が最大になる。
プラグイン依存性の実装
次にプラグイン依存性を定義する。
package sbtless
import sbt.*
import Keys.*
object SbtLessPlugin extends AutoPlugin:
override def requires = SbtJsTaskPlugin
override lazy val projectSettings = ...
end SbtLessPlugin
requires メソッドは Plugins 型の値を返し、依存リストを構築する DSL である。requires メソッドには通常、次のいずれかが含まれる:
empty(プラグインなし)- 他の auto plugin
&&演算子(複数の依存を定義する)
ルートプラグインとトリガー付きプラグイン
一部のプラグインは常にサブプロジェクトで明示的に有効化される必要がある。これらをルートプラグインと呼ぶ。プラグイン依存グラフの「ルート」ノードとなるからだ。
auto plugin は、依存が満たされるサブプロジェクトに自動的に発動される方法も提供する。これらをトリガー付きプラグインと呼び、trigger メソッドをオーバーライドして作成する。
例えば、ビルドにコマンドを自動的に追加できるトリガー付きプラグインを作りたい場合がある。そうするには、requires メソッドが empty を返すようにし、trigger メソッドを allRequirements でオーバーライドする。
package sbthello
import sbt.*
import Keys.*
object HelloPlugin2 extends AutoPlugin:
override def trigger = allRequirements
....
end HelloPlugin2
ビルドユーザーは、このプラグインを project/plugins.sbt に含める必要があるが、build.sbt に含める必要はなくなる。この機能はプラグイン依存性と組み合わせると真価を発揮する。SbtLessPlugin を別のプラグインに依存するよう修正してみる:
package sbtless
import sbt.*
import Keys.*
object SbtLessPlugin extends AutoPlugin:
override def trigger = allRequirements
override def requires = SbtJsTaskPlugin
override lazy val projectSettings = ...
end SbtLessPlugin
実際、PlayScala プラグイン(Play フレームワークは sbt プラグインである)は SbtJsTaskPlugin をプラグイン依存性の 1つとして列挙している。したがって、次のような build.sbt を定義すると:
lazy val root = rootProject
.enablePlugins(PlayScala)
SbtLessPlugin からのセッティング列が、PlayScala のセッティングの後のどこかに自動的に追加される。
これによりプラグインは、既存のプラグインを透過的に、かつ正しく、機能拡張することができる。またユーザーから手動でセッティング列の順序付けをする負担を取り除くことで、プラグイン作者は安心して機能拡張を提供することができる。
autoImport によるインポートの制御
auto plugin が autoImport という名前の val、lazy val、または object を提供すると、そのフィールドの内容は set、eval、および *.sbt ファイルでワイルドカードインポートされる。
package sbthello
import sbt.*
import Keys.*
object HelloPlugin3 extends AutoPlugin:
object autoImport:
val greeting = settingKey[String]("greeting")
val hello = taskKey[Unit]("say hello")
end autoImport
import autoImport.*
override def trigger = allRequirements
....
end HelloPlugin3
通常、autoImport は新しいキー(SettingKey、TaskKey、または InputKey)や、インポートや修飾なしで使えるコアメソッドを提供するために使われる。
プラグインの例
典型的なプラグインの例:
version := "0.1.0-SNAPSHOT"
organization := "com.example"
homepage := Some(url("https://github.com/sbt/sbt-obfuscate"))
lazy val root = rootProject
.enablePlugins(SbtPlugin)
.settings(
name := "sbt-obfuscate",
pluginCrossBuild / sbtVersion := {
scalaBinaryVersion.value match
case "3" => "2.0.0-RC10" // set minimum sbt version
}
)
package sbtobfuscate
import sbt.*
import sbt.Keys.*
import sbt.CacheImplicits.given
import xsbti.HashedVirtualFileRef
object ObfuscatePlugin extends AutoPlugin:
// by defining autoImport, the settings are automatically
// imported into user's `*.sbt`
object autoImport:
val obfuscate = taskKey[Seq[HashedVirtualFileRef]]("Obfuscates files.")
// configuration points, like built-in `version` and `libraryDependencies`
val obfuscateLiterals = settingKey[Boolean]("Obfuscate literals.")
end autoImport
import autoImport.*
// This plugin is automatically enabled for projects which are JvmPlugin.
override def trigger = allRequirements
// default values for the settings
override lazy val globalSettings: Seq[Def.Setting[?]] = Seq(
obfuscateLiterals := false,
)
// default implementations for the tasks
val baseObfuscateSettings: Seq[Def.Setting[?]] = Seq(
obfuscate := {
Obfuscate(sourcesVF.value, (obfuscate / obfuscateLiterals).value)
},
)
// a group of settings that are automatically added to projects.
override lazy val projectSettings: Seq[Def.Setting[?]] =
inConfig(Compile)(baseObfuscateSettings) ++
inConfig(Test)(baseObfuscateSettings)
end ObfuscatePlugin
object Obfuscate:
def apply(
sources: Seq[HashedVirtualFileRef],
obfuscateLiterals: Boolean
): Seq[HashedVirtualFileRef] =
// TODO obfuscate stuff!
sources
end Obfuscate
使用例
プラグインを使うビルド定義は、例えば次のような build.sbt になる:
obfuscateLiterals := true
auto plugin の利用
よくあるのは、リポジトリに公開されたバイナリプラグインを使う場合である。必要な sbt プラグイン、一般的な依存、必要なリポジトリをすべて含む project/plugins.sbt を作成できる:
addSbtPlugin("org.example" % "plugin" % "1.0")
addSbtPlugin("org.example" % "another-plugin" % "2.0")
// ビルド定義で使う通常のライブラリ(sbt プラグインではない)
libraryDependencies += "org.example" % "utilities" % "1.3"
resolvers += "Example Plugin Repository" at "https://example.org/repo/"
多くの auto plugin はプロジェクトにセッティングを自動追加するが、明示的な有効化が必要なものもある。例を示す:
lazy val util = (project in file("util"))
.enablePlugins(FooPlugin, BarPlugin)
.disablePlugins(plugins.IvyPlugin)
.settings(
name := "hello-util"
)
メタビルド
メタビルドは project/ フォルダ下のプロジェクトである。このプロジェクトのクラスパスは、project/ 内のビルド定義およびプロジェクトのベースディレクトリにある .sbt ファイルに使われる。eval および set コマンドにも使われる。
具体的には、
- メタビルドが宣言したマネージ依存性は取得され、通常のプロジェクトと同様にビルド定義のクラスパスで利用できる。
project/lib/のアンマネージ依存性は、通常のプロジェクトと同様にビルド定義で利用できる。project/プロジェクト内のソースはビルド定義ファイルであり、マネージ依存性とアンマネージ依存性から構築したクラスパスでコンパイルされる。- サブプロジェクト依存は
project/plugins.sbtに宣言できる(通常プロジェクトのbuild.sbtと同様)し、ビルド定義から利用できる。
ビルド定義のクラスパスから sbt/sbt.autoplugins 記述子ファイルが検索され、sbt.AutoPlugin 実装の名前が含まれる。
reload plugins コマンドは現在のビルドを(ルート)プロジェクトのメタビルド定義に切り替える。これによりメタビルドプロジェクトを通常のサブプロジェクトのように操作できる。reload return は元のビルドに戻る。メタビルドプロジェクトのセッションで保存されていないセッティングは破棄される。
auto plugin は、サブプロジェクトに自動注入するセッティングを定義するモジュールである。さらに auto plugin は次の機能を提供する:
- 選択した名前を
.sbtファイルおよびevalとsetコマンドに自動インポートする。 - 他の auto plugin へのプラグイン依存を指定する。
- すべての依存が揃うと自動的に自身を有効化する。
- 必要に応じて
projectSettings、buildSettings、globalSettingsを指定する。
プラグイン依存性
sbt 0.13.5 で AutoPlugin システムが導入される前は、あるプラグインが別のプラグインのセッティングやタスクに依存する場合、ビルド利用者が正しい順序でプラグインを含める必要があった。アプリケーション内のプラグイン数が増えると複雑でエラーが起きやすくなった。
auto plugin は他の auto plugin に依存でき、これらの依存セッティングが先にロードされるようにできる。
SbtLessPlugin と、さらに SbtJsTaskPlugin、SbtWebPlugin、JvmPlugin に依存する SbtCoffeeScriptPlugin があるとする。これらすべてを手動で有効化する代わりに、プロジェクトは次のように SbtLessPlugin と SbtCoffeeScriptPlugin だけを有効化すればよい:
lazy val root = (project in file("."))
.enablePlugins(SbtLessPlugin, SbtCoffeeScriptPlugin)
これにより、正しい順序でプラグインから適切なセッティング列が引き込まれる。
Maven 公開の慣例
sbt 2.x のプラグインは、成果物名に _sbt2_3 サフィックスを付けて Maven レイアウトのリポジトリに公開される。例えば sbt-ci-release 1.11.2 は Central Repo に https://repo1.maven.org/maven2/com/github/sbt/sbt-ci-release_sbt2_3/1.11.2/ として公開される。このサフィックスは sbt が自動的に付加するため、build.sbt の name は sbt-ci-release などとだけ書けばよい。