sbt 示例教程

本页面假设您已经安装了 sbt runner

让我们从示例开始,而不是解释 sbt 如何工作或为什么这样工作。

创建一个最小的 sbt 构建

mkdir foo-build
cd foo-build
touch build.sbt
mkdir project
echo "sbt.version=2.0.0-RC6" > project/build.properties

启动 sbt shell

$ sbt
[info] welcome to sbt 2.0.0-RC6 (Azul Systems, Inc. Java)
....
[info] started sbt server
sbt:foo-build>

退出 sbt shell

要退出 sbt shell,请输入 exit 或使用 Ctrl+D(Unix)或 Ctrl+Z(Windows)。

sbt:foo-build> exit

编译一个项目

按照惯例,我们将使用 sbt:...>> 提示符来表示我们正在 sbt 交互式 shell 中。

$ sbt
sbt:foo-build> compile
[success] elapsed time: 0 s, cache 0%, 1 onsite task

代码更改时重新编译

compile 命令(或任何其他命令)前加上 ~ 会导致该命令在项目中的源文件被修改时自动重新执行。例如:

sbt:foo-build> ~compile
[success] elapsed time: 0 s, cache 100%, 1 disk cache hit
[info] 1. Monitoring source files for foo-build/compile...
[info]    Press <enter> to interrupt or '?' for more options.

创建一个源文件

保持前一个命令运行。从另一个 shell 或在您的文件管理器中,在 foo-build 目录下创建以下嵌套目录:src/main/scala/example。然后,使用您喜欢的编辑器在 example 目录中创建 Hello.scala,内容如下:

package example

@main def main(args: String*): Unit =
  println(s"Hello ${args.mkString}")

运行中的命令应该会检测到这个新文件:

[info] Build triggered by /tmp/foo-build/src/main/scala/example/Hello.scala. Running 'compile'.
[info] compiling 1 Scala source to /tmp/foo-build/target/out/jvm/scala-3.3.3/foo/backend ...
[success] elapsed time: 1 s, cache 0%, 1 onsite task
[info] 2. Monitoring source files for foo-build/compile...
[info]    Press <enter> to interrupt or '?' for more options.

Enter 键退出 ~compile

运行先前的命令

在 sbt shell 中,按两次向上箭头键找到您在开始时执行的 compile 命令。

sbt:foo-build> compile

获取帮助

使用 help 命令获取关于可用命令的基本帮助。

sbt:foo-build> help

  <command> (; <command>)*                       Runs the provided semicolon-separated commands.
  about                                          Displays basic information about sbt and the build.
  tasks                                          Lists the tasks defined for the current project.
  settings                                       Lists the settings defined for the current project.
  reload                                         (Re)loads the current project or changes to plugins project or returns from it.
  new                                            Creates a new sbt build.
  new                                            Creates a new sbt build.
  projects                                       Lists the names of available projects or temporarily adds/removes extra builds to the session.

....

显示特定任务的描述:

sbt:foo-build> help run
Runs a main class, passing along arguments provided on the command line.

运行您的应用

sbt:foo-build> run
[info] running example.main
Hello
[success] elapsed time: 0 s, cache 50%, 1 disk cache hit, 1 onsite task

从 sbt shell 设置 scalaVersion

sbt:foo-build> set scalaVersion := "3.7.3"
[info] Defining scalaVersion
[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 51 others.
[info]  Run `last` for details.
[info] Reapplying settings...
[info] set current project to foo (in build file:/tmp/foo-build/)

检查 scalaVersion 设置:

sbt:foo-build> scalaVersion
[info] 3.7.3

将会话保存到 build.sbt

我们可以使用 session save 保存临时设置。

sbt:foo-build> session save
[info] Reapplying settings...
[info] set current project to foo-build (in build file:/tmp/foo-build/)
[warn] build source files have changed
[warn] modified files:
[warn]   /tmp/foo-build/build.sbt
[warn] Apply these changes by running `reload`.
[warn] Automatically reload the build when source changes are detected by setting `Global / onChangedBuildSource := ReloadOnSourceChanges`.
[warn] Disable this warning by setting `Global / onChangedBuildSource := IgnoreSourceChanges`.

build.sbt 文件现在应该包含:

scalaVersion := "3.7.3"

命名您的项目

使用编辑器,按如下方式更改 build.sbt

scalaVersion := "3.3.3"
organization := "com.example"
name := "Hello"

重新加载构建

使用 reload 命令重新加载构建。该命令会导致 build.sbt 文件被重新读取,并应用其设置。

sbt:foo-build> reload
[info] welcome to sbt 2.x (Azul Systems, Inc. Java)
[info] loading project definition from /tmp/foo-build/project
[info] loading settings for project hello from build.sbt ...
[info] set current project to Hello (in build file:/tmp/foo-build/)
sbt:Hello>

请注意,提示符现在已更改为 sbt:Hello>

将 toolkit-test 添加到 libraryDependencies

使用编辑器,按如下方式更改 build.sbt

scalaVersion := "3.3.3"
organization := "com.example"
name := "Hello"
libraryDependencies += "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test

使用 reload 命令使 build.sbt 中的更改生效。

sbt:Hello> reload

运行增量测试

sbt:Hello> test

持续运行增量测试

sbt:Hello> ~test

编写测试

保持前一个命令运行,使用编辑器创建一个名为 src/test/scala/example/HelloSuite.scala 的文件:

package example

class HelloSuite extends munit.FunSuite:
  test("Hello should start with H") {
    assert("hello".startsWith("H"))
  }
end HelloSuite

~test 应该会检测到更改:

example.HelloSuite:
==> X example.HelloSuite.Hello should start with H  0.012s munit.FailException: /tmp/foo-build/src/test/scala/example/HelloSuite.scala:5 assertion failed
4:  test("Hello should start with H") {
5:    assert("hello".startsWith("H"))
6:  }
    at munit.FunSuite.assert(FunSuite.scala:11)
    at example.HelloSuite.$init$$$anonfun$1(HelloSuite.scala:5)
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error]   example.HelloSuite
[error] (Test / testQuick) sbt.TestsFailedException: Tests unsuccessful
[error] elapsed time: 1 s, cache 50%, 3 disk cache hits, 3 onsite tasks

使测试通过

使用编辑器,将 src/test/scala/example/HelloSuite.scala 更改为:

package example

class HelloSuite extends munit.FunSuite:
  test("Hello should start with H") {
    assert("Hello".startsWith("H"))
  }
end HelloSuite

确认测试通过,然后按 Enter 键退出持续测试。

添加库依赖

使用编辑器,按如下方式更改 build.sbt

scalaVersion := "3.3.3"
organization := "com.example"
name := "Hello"
libraryDependencies ++= Seq(
  "org.scala-lang" %% "toolkit" % "0.1.7",
  "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test,
)

使用 reload 命令使 build.sbt 中的更改生效。

使用 Scala REPL

我们可以查询纽约的当前天气。

sbt:Hello> console
Welcome to Scala 3.3.3 (1.8.0_402, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala>
import sttp.client4.quick.*
import sttp.client4.Response

val newYorkLatitude: Double = 40.7143
val newYorkLongitude: Double = -74.006
val response: Response[String] = quickRequest
  .get(
    uri"https://api.open-meteo.com/v1/forecast?latitude=\$newYorkLatitude&longitude=\$newYorkLongitude&current_weather=true"
  )
  .send()

println(ujson.read(response.body).render(indent = 2))

// press Ctrl+D

// Exiting paste mode, now interpreting.

{
  "latitude": 40.710335,
  "longitude": -73.99307,
  "generationtime_ms": 0.36704540252685547,
  "utc_offset_seconds": 0,
  "timezone": "GMT",
  "timezone_abbreviation": "GMT",
  "elevation": 51,
  "current_weather": {
    "temperature": 21.3,
    "windspeed": 16.7,
    "winddirection": 205,
    "weathercode": 3,
    "is_day": 1,
    "time": "2023-08-04T10:00"
  }
}
import sttp.client4.quick._
import sttp.client4.Response
val newYorkLatitude: Double = 40.7143
val newYorkLongitude: Double = -74.006
val response: sttp.client4.Response[String] = Response({"latitude":40.710335,"longitude":-73.99307,"generationtime_ms":0.36704540252685547,"utc_offset_seconds":0,"timezone":"GMT","timezone_abbreviation":"GMT","elevation":51.0,"current_weather":{"temperature":21.3,"windspeed":16.7,"winddirection":205.0,"weathercode":3,"is_day":1,"time":"2023-08-04T10:00"}},200,,List(:status: 200, content-encoding: deflate, content-type: application/json; charset=utf-8, date: Fri, 04 Aug 2023 10:09:11 GMT),List(),RequestMetadata(GET,https://api.open-meteo.com/v1/forecast?latitude=40.7143&longitude...

scala> :q // to quit

创建子项目

按如下方式更改 build.sbt:

scalaVersion := "3.3.3"
organization := "com.example"

lazy val hello = project
  .in(file("."))
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test
    )
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core"
  )

使用 reload 命令使 build.sbt 中的更改生效。

列出所有子项目

sbt:Hello> projects
[info] In file:/tmp/foo-build/
[info]   * hello
[info]     helloCore

编译子项目

sbt:Hello> helloCore/compile

将 toolkit-test 添加到子项目

按如下方式更改 build.sbt:

scalaVersion := "3.3.3"
organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      toolkitTest % Test
    )
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += toolkitTest % Test
  )

广播命令

设置 aggregate 以便发送到 hello 的命令也会广播到 helloCore:

scalaVersion := "3.3.3"
organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      toolkitTest % Test
    )
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += toolkitTest % Test
  )

reload 之后,~test 现在会在两个子项目上运行:

sbt:Hello> ~test

Enter 键退出持续测试。

使 hello 依赖于 helloCore

使用 .dependsOn(...) 添加对其他子项目的依赖。同时,让我们将 toolkit 依赖移至 helloCore

scalaVersion := "3.3.3"
organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .dependsOn(helloCore)
  .settings(
    name := "Hello",
    libraryDependencies += toolkitTest % Test
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7",
    libraryDependencies += toolkitTest % Test
  )

使用 uJson 解析 JSON

让我们在 helloCore 中使用 toolkit 中的 uJson。

添加 core/src/main/scala/example/core/Weather.scala:

package example.core

import sttp.client4.quick._
import sttp.client4.Response

object Weather:
  def temp() =
    val response: Response[String] = quickRequest
      .get(
        uri"https://api.open-meteo.com/v1/forecast?latitude=40.7143&longitude=-74.006&current_weather=true"
      )
      .send()
    val json = ujson.read(response.body)
    json.obj("current_weather")("temperature").num
end Weather

接下来,按如下方式更改 src/main/scala/example/Hello.scala:

package example

import example.core.Weather

@main def main(args: String*): Unit =
  val temp = Weather.temp()
  println(s"Hello! The current temperature in New York is $temp C.")

让我们运行应用,看看它是否正常工作:

sbt:Hello> run
[info] compiling 1 Scala source to /tmp/foo-build/core/target/scala-2.13/classes ...
[info] compiling 1 Scala source to /tmp/foo-build/target/scala-2.13/classes ...
[info] running example.Hello
Hello! The current temperature in New York is 22.7 C.

临时切换 scalaVersion

sbt:Hello> ++3.3.3!
[info] Forcing Scala version to 3.3.3 on all projects.
[info] Reapplying settings...
[info] Set current project to Hello (in build file:/tmp/foo-build/)

检查 scalaVersion 设置:

sbt:Hello> scalaVersion
[info] helloCore / scalaVersion
[info]  3.3.3
[info] scalaVersion
[info]  3.3.3

此设置将在 reload 之后消失。

批处理模式

您也可以在批处理模式下运行 sbt,直接从终端传递 sbt 命令。

$ sbt clean "testOnly HelloSuite"

sbt new 命令

您可以使用 sbt new 命令快速设置一个简单的 "Hello world" 构建。

$ sbt new scala/scala-seed.g8
....
A minimal Scala project.

name [My Something Project]: hello

Template applied in ./hello

当提示输入项目名称时,请输入 hello

这将在名为 hello 的目录下创建一个新项目。

致谢

本页面基于 William "Scala William" Narmontas 编写的 Essential sbt 教程。