此頁面主要適用於 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 Shell 中,使用者可以相同方式參考設定
sbt:helloworld> show assembly/assemblyJarName
[info] helloworld-assembly-0.1.0-SNAPSHOT.jar
避免 sbt 0.12 風格的金鑰名稱,其中金鑰的 Scala 識別碼和 Shell 使用烤肉串式命名法
val jarName = SettingKey[String]("assembly-jar-name")
val jarName = SettingKey[String]("jar-name")
val assemblyJarName = taskKey[String]("fat jar 的名稱")
因為 build.sbt
和 sbt Shell 中都有單一金鑰命名空間,如果不同的外掛使用類似 jarName
和 excludedFiles
的通用金鑰名稱,將會造成名稱衝突。
使用 sbt-$專案名稱
方案來命名您的函式庫和成品。具有一致命名慣例的外掛生態系統讓使用者更容易判斷專案或依賴是否為 SBT 外掛。
如果專案名稱為 foobar
,則以下成立
foobar
foobar-sbt
sbt-foobar-plugin
sbt-foobar
如果您的外掛提供明顯的「主要」任務,請考慮將其命名為 foobar
或 foobar...
,讓使用者更容易在 sbt Shell 和 Tab 鍵自動完成中探索您外掛的功能。
將您的外掛命名為 FooBarPlugin
。
如果使用者將其建置檔案放在某些套件中,則如果您的外掛在預設(無名稱)套件中定義,他們將無法使用您的外掛。
請確保人們可以找到您的外掛。以下是一些建議的步驟
sbt 有許多預定義金鑰。在可能的情況下,請在您的外掛中重複使用它們。例如,不要定義
val sourceFiles = settingKey[Seq[File]]("Some source files")
而是重複使用 sbt 現有的 sources
金鑰。
您的外掛應與 sbt 生態系統的其他部分自然地配合。您可以做的第一件事是避免定義命令,而改為使用設定和任務以及任務範圍設定(請參閱下文以取得有關任務範圍設定的更多資訊)。sbt 中大多數有趣的事情,例如 compile
、test
和 publish
,都是使用任務提供的。任務可以利用任務引擎的重複縮減和平行執行。透過 ScopeFilter 等功能,許多先前需要命令的功能現在可以使用任務來實現。
設定可以由其他設定和任務組成。任務可以由其他任務和輸入任務組成。另一方面,命令無法由上述任何一項組成。一般來說,請使用您需要的最小事物。命令的一個合法用途可能是使用外掛來存取建置定義本身,而不是程式碼。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 in Fuzz
)的 scalaSource
可以指向 src/fuzz/scala
,使其與其他 Scala 來源目錄區分開來。因此,這三個定義使用相同的金鑰,但它們代表不同的值。因此,在使用者的 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))
一般而言,如果外掛程式提供具有最廣泛作用域的鍵(設定和任務),並以最窄的作用域引用它們,這將為建置使用者提供最大的彈性。
globalSettings
中提供預設值 如果您的設定或任務的預設值不以遞移的方式依賴於專案級設定(例如 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
中重新連接現有鍵 有時您可能需要在 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 ...
}
)
}