1. 外掛

外掛 

有一個入門頁面著重於使用現有的外掛,您可能需要先閱讀。

外掛是在建置定義中使用外部程式碼的方式。外掛可以是實作任務的函式庫 (您可能會使用 Knockoff 來撰寫 markdown 處理任務)。外掛可以定義一連串 sbt 設定,這些設定會自動新增到所有專案,或針對選定的專案明確宣告。例如,外掛可能會新增 proguard 任務和相關聯的 (可覆寫的) 設定。最後,外掛可以定義新的命令 (透過 commands 設定)。

sbt 0.13.5 引入了自動外掛,並改善了外掛之間的依賴管理和明確範圍的自動匯入。未來,我們的建議是遷移到自動外掛。外掛最佳實踐頁面描述了目前正在發展的撰寫 sbt 外掛指南。另請參閱一般最佳實踐

使用自動外掛 

常見的情況是使用發佈到儲存庫的二進位外掛。您可以建立 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/"

許多自動外掛會自動將設定新增到專案中,但是,有些可能需要明確啟用。以下是一個範例

lazy val util = (project in file("util"))
  .enablePlugins(FooPlugin, BarPlugin)
  .disablePlugins(plugins.IvyPlugin)
  .settings(
    name := "hello-util"
  )

請參閱入門指南中的使用外掛以取得有關使用外掛的更多詳細資料。

依描述 

外掛定義是 project/ 資料夾下的專案。此專案的類別路徑是建置定義在 project/ 和專案根目錄中任何 .sbt 檔案中使用的類別路徑。它也用於 evalset 命令。

具體來說,

  1. project/ 專案宣告的受管理依賴會被擷取,並且在建置定義類別路徑上可用,就像一般的專案一樣。
  2. project/lib/ 中的非受管理依賴可供建置定義使用,就像一般的專案一樣。
  3. project/ 專案中的來源是建置定義檔案,並使用從受管理和非受管理依賴建置的類別路徑進行編譯。
  4. 專案依賴可以在 project/plugins.sbt 中宣告 (類似於一般專案中的 build.sbt 檔案),並且可供建置定義使用。

會在建置定義類別路徑中搜尋 sbt/sbt.autoplugins 描述器檔案,其中包含 sbt.AutoPlugin 實作的名稱。

reload plugins 命令會將目前的建置變更為 (根) 專案的 project/ 建置定義。這允許像一般專案一樣操作建置定義專案。reload return 會變更回原始建置。任何尚未儲存的外掛定義專案的工作階段設定都會被捨棄。

自動外掛是一個模組,它定義自動注入到專案中的設定。此外,自動外掛還提供以下功能

  • 自動將選定名稱匯入 .sbt 檔案以及 evalset 命令。
  • 指定其他自動外掛的外掛依賴。
  • 當所有依賴都存在時,自動啟用自身。
  • 適當指定 projectSettingsbuildSettingsglobalSettings

外掛依賴 

當傳統外掛想要重複使用現有外掛中的某些功能時,它會將外掛作為函式庫依賴提取進來,然後它會

  1. 將依賴中的設定序列新增為其自身設定序列的一部分,或
  2. 告知建置使用者以正確的順序包含它們。

隨著應用程式中外掛數量的增加,這變得複雜且更容易出錯。自動外掛的主要目標是減輕這種設定依賴問題。自動外掛可以依賴其他自動外掛,並確保這些依賴設定會先載入。

假設我們有 SbtLessPluginSbtCoffeeScriptPlugin,它們又依賴 SbtJsTaskPluginSbtWebPluginJvmPlugin。專案不需要手動啟用所有這些外掛,只需像這樣啟用 SbtLessPluginSbtCoffeeScriptPlugin

lazy val root = (project in file("."))
  .enablePlugins(SbtLessPlugin, SbtCoffeeScriptPlugin)

這將會以正確的順序從外掛中提取正確的設定序列。這裡的關鍵概念是您宣告想要的外掛,sbt 可以填補空白。

但是,外掛實作並不需要產生自動外掛。它對於外掛消費者來說是一種便利,並且由於其自動性質,並不總是合適的。

全域外掛 

$HOME/.sbt/1.0/plugins/ 目錄被視為全域外掛定義專案。它是一個正常的 sbt 專案,其類別路徑可供該使用者的所有 sbt 專案定義使用,如上所述的每個專案外掛。

建立自動外掛 

最小的 sbt 外掛是一個 Scala 函式庫,它是針對 sbt 執行的 Scala 版本 (目前為 2.12.18) 或 Java 函式庫建置的。此類型的函式庫不需要執行任何特殊操作。較典型的外掛將提供 sbt 任務、命令或設定。這種外掛可能會自動提供這些設定,或讓使用者明確整合它們。

若要建立自動外掛,請建立專案並啟用 SbtPlugin

ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / homepage := Some(url("https://github.com/sbt/sbt-hello"))

lazy val root = (project in file("."))
  .enablePlugins(SbtPlugin)
  .settings(
    name := "sbt-hello",
    pluginCrossBuild / sbtVersion := {
      scalaBinaryVersion.value match {
        case "2.12" => "1.2.8" // set minimum sbt version
      }
    }
  )

一些需要注意的細節

  • sbt 外掛必須使用 sbt 本身編譯所用的 Scala 2.12.x 進行編譯。透過不指定 scalaVersion,sbt 將預設為適合外掛的 Scala 版本。
  • 依預設,sbt 外掛是使用您正在使用的 sbt 版本進行編譯的。由於 sbt 不會保持向前相容性,這通常會要求您所有的外掛使用者也升級到最新版本。pluginCrossBuild / sbtVersion 是一個可選設定,可讓您的外掛針對較舊的 sbt 版本進行編譯,這允許外掛使用者從一系列 sbt 版本中選擇。

接著,撰寫外掛程式碼並將您的專案發佈到儲存庫。該外掛程式可以如前一節所述使用。

首先,在適當的命名空間中,透過擴展 sbt.AutoPlugin 來定義您的自動外掛程式物件。

projectSettings 和 buildSettings 

使用自動外掛程式時,所有提供的設定(例如 assemblySettings)都是由外掛程式直接透過 projectSettings 方法提供。以下是一個範例外掛程式,它會為 sbt 專案新增一個名為 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")
  }

  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)
    }
  )
}

如果外掛程式需要在建置層級 (也就是在 ThisBuild 中) 追加設定,則會有一個 buildSettings 方法。無論該建置有多少專案啟用此 AutoPlugin,此處傳回的設定都保證只會新增到給定的建置範圍一次。

override def buildSettings: Seq[Setting[_]] = Nil

globalSettings 會附加到全域設定(in Global)一次。這些設定讓外掛程式可以自動提供新功能或新預設值。此功能的主要用途之一是全域新增命令,例如用於 IDE 外掛程式。

override def globalSettings: Seq[Setting[_]] = Nil

使用 globalSettings 來定義設定的預設值。

實作外掛程式相依性 

下一步是定義外掛程式相依性。

package sbtless

import sbt._
import Keys._
object SbtLessPlugin extends AutoPlugin {
  override def requires = SbtJsTaskPlugin
  override lazy val projectSettings = ...
}

requires 方法會傳回 Plugins 類型的值,這是一個用於建構相依性清單的 DSL。requires 方法通常包含以下其中一個值

  • empty(沒有外掛程式)
  • 其他自動外掛程式
  • && 運算子(用於定義多個相依性)

根外掛程式和觸發外掛程式 

有些外掛程式應該始終在專案上明確啟用。我們稱這些為根外掛程式,也就是外掛程式相依性圖中的「根」節點。預設情況下,自動外掛程式是根外掛程式。

自動外掛程式也提供了一種讓外掛程式在滿足其相依性時自動附加到專案的方式。我們稱這些為觸發外掛程式,它們是透過覆寫 trigger 方法建立的。

例如,我們可能想要建立一個觸發外掛程式,可以自動將命令附加到建置中。若要執行此操作,請將 requires 方法設定為傳回 empty,並使用 allRequirements 覆寫 trigger 方法。

package sbthello

import sbt._
import Keys._

object HelloPlugin2 extends AutoPlugin {
  override def trigger = allRequirements
  override lazy val buildSettings = Seq(commands += helloCommand)
  lazy val helloCommand =
    Command.command("hello") { (state: State) =>
      println("Hi!")
      state
    }
}

建置使用者仍然需要在 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 = ...
}

事實證明,PlayScala 外掛程式(如果您不知道,Play 框架是一個 sbt 外掛程式)將 SbtJsTaskPlugin 列為其所需的其中一個外掛程式。因此,如果我們使用以下程式碼定義 build.sbt

lazy val root = (project in file("."))
  .enablePlugins(PlayScala)

那麼來自 SbtLessPlugin 的設定序列將在來自 PlayScala 的設定之後的某處自動附加。

這讓外掛程式可以靜默且正確地使用更多功能來擴充現有的外掛程式。它還可以幫助使用者移除排序的負擔,讓外掛程式作者在為其使用者提供功能時有更大的自由度和權力。

使用 autoImport 控制匯入 

當自動外掛程式提供一個名為 autoImport 的穩定欄位 (例如 valobject) 時,該欄位的內容會以萬用字元方式匯入到 seteval.sbt 檔案中。在下一個範例中,我們將用一個任務取代我們的 hello 命令,以輕鬆取得 greeting 的值。實際上,建議優先選擇設定或任務而非命令

package sbthello

import sbt._
import Keys._

object HelloPlugin3 extends AutoPlugin {
  object autoImport {
    val greeting = settingKey[String]("greeting")
    val hello = taskKey[Unit]("say hello")
  }
  import autoImport._
  override def trigger = allRequirements
  override lazy val buildSettings = Seq(
    greeting := "Hi!",
    hello := helloTask.value)
  lazy val helloTask =
    Def.task {
      println(greeting.value)
    }
}

通常,autoImport 用於提供新的鍵 - SettingKeyTaskKeyInputKey - 或核心方法,而無需匯入或限定。

範例外掛程式 

一個典型外掛程式的範例

build.sbt:

ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / homepage := Some(url("https://github.com/sbt/sbt-obfuscate"))

lazy val root = (project in file("."))
  .enablePlugins(SbtPlugin)
  .settings(
    name := "sbt-obfuscate",
    pluginCrossBuild / sbtVersion := {
      scalaBinaryVersion.value match {
        case "2.12" => "1.2.8" // set minimum sbt version
      }
    }
  )

ObfuscatePlugin.scala:

package sbtobfuscate

import sbt._
import sbt.Keys._

object ObfuscatePlugin extends AutoPlugin {
  // by defining autoImport, the settings are automatically imported into user's `*.sbt`
  object autoImport {
    // configuration points, like the built-in `version`, `libraryDependencies`, or `compile`
    val obfuscate = taskKey[Seq[File]]("Obfuscates files.")
    val obfuscateLiterals = settingKey[Boolean]("Obfuscate literals.")

    // default values for the tasks and settings
    lazy val baseObfuscateSettings: Seq[Def.Setting[_]] = Seq(
      obfuscate := {
        Obfuscate(sources.value, (obfuscate / obfuscateLiterals).value)
      },
      obfuscate / obfuscateLiterals := false
    )
  }

  import autoImport._
  override def requires = sbt.plugins.JvmPlugin

  // This plugin is automatically enabled for projects which are JvmPlugin.
  override def trigger = allRequirements

  // a group of settings that are automatically added to projects.
  override val projectSettings =
    inConfig(Compile)(baseObfuscateSettings) ++
    inConfig(Test)(baseObfuscateSettings)
}

object Obfuscate {
  def apply(sources: Seq[File], obfuscateLiterals: Boolean): Seq[File] = {
    // TODO obfuscate stuff!
    sources
  }
}

使用範例 

使用外掛程式的建置定義可能如下所示。obfuscate.sbt

obfuscate / obfuscateLiterals := true

全域外掛程式範例 

最簡單的全域外掛程式定義是在 $HOME/.sbt/1.0/plugins/build.sbt 中宣告程式庫或外掛程式

libraryDependencies += "org.example" %% "example-plugin" % "0.1"

此外掛程式將可供目前使用者的每個 sbt 專案使用。

此外

  • Jar 可以直接放置在 $HOME/.sbt/1.0/plugins/lib/ 中,並且可供目前使用者的每個建置定義使用。
  • 對從原始碼建置的外掛程式的相依性,可以在 $HOME/.sbt/1.0/plugins/project/Build.scala 中宣告,如 .scala 建置定義中所述。
  • 外掛程式可以直接在 $HOME/.sbt/1.0/plugins/ 中的 Scala 原始碼檔案中定義,例如 $HOME/.sbt/1.0/plugins/MyPlugin.scala$HOME/.sbt/1.0/plugins//build.sbt 應該包含 sbtPlugin := true。這可用於在最初開發外掛程式時更快地進行轉換
  1. 編輯全域外掛程式碼
  2. reload 您要使用已修改外掛程式的專案
  3. sbt 將重建外掛程式並將其用於專案。

    此外,該外掛程式也將可在機器上的其他專案中使用,而無需再次重新編譯。此方法會跳過 publishLocal 和清除使用外掛程式的專案的外掛程式目錄的開銷。

這些都是 $HOME/.sbt/1.0/plugins/ 作為一個標準專案的後果,該專案的類別路徑會新增到每個 sbt 專案的建置定義中。

在建置定義中使用程式庫的範例 

舉例來說,我們會將 Grizzled Scala 程式庫新增為外掛程式。雖然這不提供 sbt 特定的功能,但它示範了如何宣告外掛程式。

1a) 手動管理 

  1. https://oss.sonatype.org/content/repositories/releases/org/clapper/grizzled-scala2.8.1/1.0.4/grizzled-scala2.8.1-1.0.4.jar 手動下載 jar
  2. 將其放在 project/lib/

1b) 自動管理:直接編輯方法 

編輯 project/plugins.sbt 以包含

libraryDependencies += "org.clapper" %% "grizzled-scala" % "1.0.4"

如果 sbt 正在執行,請執行 reload

1c) 自動管理:命令列方法 

我們可以使用 reload plugins 切換到 project/ 中的外掛程式專案。

$ sbt
> reload plugins
[info] Set current project to default (in build file:/Users/sbt/demo2/project/)
>

然後,我們可以像往常一樣新增相依性並將其儲存到 project/plugins.sbt。執行 update 來驗證相依性是否正確很有用,但並非必要。

> set libraryDependencies += "org.clapper" %% "grizzled-scala" % "1.0.4"
...
> update
...
> session save
...

若要切換回主要專案,請使用 reload return

> reload return
[info] Set current project to root (in build file:/Users/sbt/demo2/)

1d) 專案相依性 

此變體顯示如何使用 sbt 的外部專案支援來宣告外掛程式的來源相依性。這表示外掛程式將從原始碼建置並在類別路徑上使用。

編輯 project/plugins.sbt

lazy val root = (project in file(".")).dependsOn(assemblyPlugin)

lazy val assemblyPlugin = RootProject(uri("git://github.com/sbt/sbt-assembly"))

如果 sbt 正在執行,請執行 reload

請注意,此方法在開發外掛程式時非常有用。使用外掛程式的專案會在 reload 上重建外掛程式。這會節省 publishLocalupdate 的中間步驟。它也可用於使用來自其儲存庫的外掛程式的開發版本。

但是,建議透過將其附加為片段來明確指定認可或標記到儲存庫

lazy val assemblyPlugin = uri("git://github.com/sbt/sbt-assembly#0.9.1")

使用此方法的一個注意事項是,本機 sbt 會嘗試執行遠端外掛程式的建置。外掛程式自己的建置很可能使用不同的 sbt 版本,因為許多外掛程式會為多個 sbt 版本進行跨發佈。因此,建議盡可能使用二進位成品。

2) 使用程式庫 

Grizzled Scala 已準備好在建置定義中使用。這包括 evalset 命令以及 .sbtproject/*.scala 檔案。

> eval grizzled.sys.os

build.sbt 檔案中

import grizzled.sys._
import OperatingSystem._

libraryDependencies ++=
    if(os == Windows)
        Seq("org.example" % "windows-only" % "1.0")
    else
        Seq.empty

發佈外掛程式 

外掛程式可以像任何其他專案一樣發佈。當您將外掛程式發佈到 Maven 佈局儲存庫時,請使用 sbt 1.9.x 或更高版本。

但是,如果您嘗試將外掛程式發佈到遵循 Maven 佈局的儲存庫,則有一個注意事項。

如果您的成品儲存庫期望成品符合 Maven 佈局並拒絕不符合的成品,您可以: 1. (建議) 如果您和您的外掛程式的取用者使用 sbt 1.9.x 或更高版本

從 sbt 1.9 開始,它會嘗試以新的和舊有的 Maven 樣式(為了向後相容性)發佈任何外掛程式。舊有的 Maven 樣式與 Maven 佈局並不完全相容。您需要使用以下方式停用它: sbtPluginPublishLegacyMavenStyle := false 請注意,您將無法使用舊於 1.9 的 sbt 來取用此外掛程式,因為它只能解析舊有的 Maven 樣式(或者您需要使用 sbt-vspp 中所述的技巧)。3. 如果您使用 sbt < 1.9.x

您可以使用 https://github.com/esbeetee/sbt-vspp/ 5. 如果您無法使用 sbt 1.9.x 且您不能/不想使用 sbt-vspp

您的 Artifactory 設定中應該有一個類似於「抑制 POM 一致性檢查」的選項,即使成品不完全遵循 Maven 佈局,它也允許您提交成品。

您可以在以下問題中找到有關此的更多詳細資訊。

最佳做法 

如果您是外掛程式作者,請查閱外掛程式最佳做法頁面;它包含一組準則,可協助您確保您的外掛程式具有一致性,並能與其他外掛程式良好運作。

有關跨建置 sbt 外掛程式,另請參閱 跨建置外掛程式