1. 插件

插件 

有一个入门页面侧重于使用现有插件,您可能想先阅读它。

插件是将外部代码用于构建定义的一种方式。插件可以是用于实现任务的库(您可能会使用Knockoff编写 markdown 处理任务)。插件可以定义一系列 sbt 设置,这些设置会自动添加到所有项目中,或者显式声明为选定项目。例如,插件可能会添加一个proguard任务和相关(可覆盖)设置。最后,插件可以定义新的命令(通过commands设置)。

sbt 0.13.5 引入了自动插件,改进了插件之间的依赖管理以及显式作用域自动导入。展望未来,我们建议您迁移到自动插件。插件最佳实践页面描述了当前正在演变的编写 sbt 插件的指南。另请参阅一般最佳实践

使用自动插件 

一个常见的情况是使用发布到存储库的二进制插件。您可以使用所有所需的 sbt 插件、任何一般依赖项和任何必要的存储库创建project/plugins.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.AutoPlugin实现名称的sbt/sbt.autoplugins描述符文件。

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,并将trigger方法覆盖为allRequirements

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 控制导入 

当自动插件提供一个稳定字段,例如名为autoImportvalobject时,该字段的内容将在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 将重建插件并将其用于该项目。

    此外,该插件将在机器上的其他项目中可用,无需再次编译。此方法跳过了使用该插件的项目的publishLocalclean插件目录的开销。

所有这些都是$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 插件,另请参阅交叉构建插件