插件
本页面向中级用户介绍插件细节。 插件系统的一般介绍请参阅插件基础。
描述
插件用于扩展构建定义,常用来增加内置设置和任务之外的能力。示例用途包括构建 Docker 容器、支持 Scala.JS、生成 GitHub Actions YAML 等。
插件定义一系列 sbt 设置,可自动加入所有项目,或为选定项目显式声明。插件也可定义新命令。sbt 1.x 与 2.x 中的插件扩展 sbt.AutoPlugin trait,可自动处理依赖插件之间的设置顺序。
创建插件
概念上,可将 sbt 插件视为「metabuild 上的普通库」,当前 metabuild 使用 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 中,可用 lazy val 重写 def 方法,这称为统一访问原则。
建议对 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 列为其必需插件之一。因此,若定义如下 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——或核心方法,而无需显式 import 或限定名。
示例插件
典型插件示例:
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
常见情况是使用发布到仓库的二进制插件。您可创建 project/plugins.sbt,在其中声明所需的 sbt 插件、一般依赖及必要的仓库:
addSbtPlugin("org.example" % "plugin" % "1.0")
addSbtPlugin("org.example" % "another-plugin" % "2.0")
// plain library (not an sbt plugin) for use in the build definition
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"
)
Metabuild
metabuild 是 project/ 目录下的项目。该项目的 classpath 用作 project/ 中构建定义以及项目根目录下任意 .sbt 文件的 classpath,也用于 eval 和 set 命令。
具体而言:
- metabuild 声明的托管依赖会被解析并出现在构建定义 classpath 上,与普通项目相同。
project/lib/中的非托管依赖可用于构建定义,与普通项目相同。project/项目中的源码即构建定义文件,使用由托管与非托管依赖构成的 classpath 编译。- 可在
project/plugins.sbt中声明项目依赖(方式类似普通项目中的build.sbt),这些依赖将对构建定义可用。
会在构建定义 classpath 上查找 sbt/sbt.autoplugins 描述文件,其中包含 sbt.AutoPlugin 实现类名。
reload plugins 命令将当前构建切换为(根)项目的 metabuild 定义,从而像普通子项目一样操作 metabuild。reload return 切换回原始构建。未保存的 metabuild 会话设置会被丢弃。
auto plugin 是定义可自动注入子项目的设置的模块。此外,auto plugin 还提供以下能力:
- 将选定名称自动导入
.sbt文件以及eval和set命令。 - 指定对其他 auto plugin 的依赖。
- 在全部依赖满足时自动激活自身。
- 按需指定
projectSettings、buildSettings和globalSettings。
插件依赖
在 sbt 0.13.5 引入 AutoPlugin 系统之前,若插件依赖另一插件的设置和任务,构建用户必须按正确顺序包含插件。随着应用中插件增多,这变得复杂且易错。
auto plugin 可依赖其他 auto plugin,并确保这些依赖的设置先被加载。
假设有 SbtLessPlugin 和 SbtCoffeeScriptPlugin,后者又依赖 SbtJsTaskPlugin、SbtWebPlugin 和 JvmPlugin。无需手动激活所有这些插件,项目只需启用 SbtLessPlugin 和 SbtCoffeeScriptPlugin:
lazy val root = (project in file("."))
.enablePlugins(SbtLessPlugin, SbtCoffeeScriptPlugin)
这将按正确顺序从各插件拉入正确的设置序列。
Maven 发布约定
sbt 2.x 插件发布到 Maven 布局仓库时,工件名带有 _sbt2_3 后缀。例如 sbt-ci-release 1.11.2 发布至中央仓库 https://repo1.maven.org/maven2/com/github/sbt/sbt-ci-release_sbt2_3/1.11.2/。该后缀由 sbt 自动追加,因此在 build.sbt 中 name 只需写 sbt-ci-release 等即可。