关于SBT:
- SBT是Scala的构建工具,全称Simple Build Tool,类似于 Maven 或 Gradle。
- GETTING STARTED WITH SCALA AND SBT ON THE COMMAND LINE
- sbt Reference Manual 要使用sbt,阅读完第一章Getting Started with sbt是必要的。下面的内容皆是第一章翻译。
特性:
- 简单项目零配置。
- 用Scala源码管理项目构建。
- 精确的重编译,节省时间。
- 使用Coursier的库管理器。
- 支持Scala和Java的混合项目。
- 等等等,具体就不列了,总之一个大型项目构建系统该有的东西。
安装:
- sbt依赖Java,确保已经安装了JDK1.8或以上版本。
- 下载压缩包或者安装包,这里的版本是1.5.5。
- 解压或者安装。
- 配置环境变量
SBT_HOME
,并添加%SBT_HOME%\bin
到path环境变量,安装包的话会自动配置。
通过案例入门sbt
创建一个项目hello作为例子:
- windows上没有的命令按照含义操作即可。
$ mkdir foo-build
$ cd foo-build
$ touch build.sbt
开始sbt shell:
$ sbt
[info] Updated file /tmp/foo-build/project/build.properties: set sbt.version to 1.1.4
[info] Loading project definition from /tmp/foo-build/project
[info] Loading settings from build.sbt ...
[info] Set current project to foo-build (in build file:/tmp/foo-build/)
[info] sbt server started at local:///Users/eed3si9n/.sbt/1.0/server/abc4fb6c89985a00fd95/sock
sbt:foo-build>
- 第一次初始化时间会很长。
- 退出shell:
sbt:foo-build> exit
- 编译:
sbt:foo-build> compile
- sbt shell中Tab可以补全。
对修改重新编译:
- 在
compile
命令(或其他命令同理)前加一个~
前缀,会进入等待状态,当项目发生修改是会自动重新编译。当然退出这个状态后就不会了。
sbt:foo-build> ~compile
[success] Total time: 0 s, completed May 6, 2018 3:52:08 PM
1. Waiting for source changes... (press enter to interrupt)
创建源文件:
- 执行
~compile
并保持,创建目录src/main/scala/example
新建源文件保存就能看到编译过程了。
// src/main/scala/example/Hello.scala
package example
object Hello extends App {
println("Hello")
}
sbt shell常用操作:
help
帮助。help run
具体条目的帮助。run
运行程序。- 上下箭头切换已执行命令。
scalaVersion
scala版本。
配置修改:
- 切换当前项目的scala版本:
set ThisBuild / scalaVersion := "2.13.6"
。 session save
保存配置到build.sbt
,此时其中就会多出ThisBuild / scalaVersion := "2.13.6"
。- 编辑
build.sbt
:
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
lazy val hello = (project in file("."))
.settings(
name := "Hello"
)
- 重新加载配置
reload
。
测试:
- 添加ScalaTest到依赖
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
lazy val hello = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" % Test,
)
- 执行测试:
test
。 - 后续继续运行追加的测试:
~testQuick
编写测试:src/test/scala/HelloSpec.scala
// src/test/scala/HelloSpec.scala
import org.scalatest.funsuite._
class HelloSpec extends AnyFunSuite {
test("Hello should start with H") {
assert("hello".startsWith("H"))
}
}
- 测试结果当然是失败
sbt:Hello> test
[info] HelloSpec:
[info] - Hello should start with H *** FAILED ***
[info] "hello" did not start with "H" (HelloSpec.scala:5)
[info] Run completed in 214 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed tests:
[error] HelloSpec
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 0 s, completed 2021年9月27日 下午11:58:01
- 改一下源码再测试就能通过了:
// src/test/scala/HelloSpec.scala
import org.scalatest.funsuite._
class HelloSpec extends AnyFunSuite {
test("Hello should start with H") {
assert("Hello".startsWith("H"))
}
}
添加库依赖:
// build.sbt
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
lazy val hello = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.9.2",
libraryDependencies += "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" % Test,
)
使用REPL(Read-Eval-Print Loop):
sbt:Hello> console
- 在scala的REPL环境中粘贴:
:paste
。 - 退出:
:q
修改build.sbt
创建一个子项目:
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
lazy val hello = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" % Test,
)
lazy val helloCore = (project in file("core"))
.settings(
name := "Hello Core",
)
reload
时会自动创建目录core/
。- 列出所有子项目:
projects
- 编译子项目:
helloCore/compile
- 子项目添加依赖:
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
val scalaTest = "org.scalatest" %% "scalatest" % "3.2.7"
lazy val hello = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0",
libraryDependencies += scalaTest % Test,
)
lazy val helloCore = (project in file("core"))
.settings(
name := "Hello Core",
libraryDependencies += scalaTest % Test,
)
广播命令、添加依赖:
- 设置
.aggregate(...)
,这样发送到hello
的命令都会被广播到helloCore
- 使用
.dependsOn(...)
可以设置依赖,下面的设置使hello
依赖于helloCore
- 将Gigahorse的依赖移到
helloCore
。
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
val scalaTest = "org.scalatest" %% "scalatest" % "3.2.7"
val gigahorse = "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0"
lazy val hello = (project in file("."))
.aggregate(helloCore)
.dependsOn(helloCore)
.settings(
name := "Hello",
libraryDependencies += scalaTest % Test,
)
lazy val helloCore = (project in file("core"))
.settings(
name := "Hello Core",
libraryDependencies += scalaTest % Test,
libraryDependencies += gigahorse,
)
使用Play JSON解析JSON:
- 添加依赖。
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
val scalaTest = "org.scalatest" %% "scalatest" % "3.2.7"
val gigahorse = "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0"
val playJson = "com.typesafe.play" %% "play-json" % "2.9.2"
lazy val hello = (project in file("."))
.aggregate(helloCore)
.dependsOn(helloCore)
.settings(
name := "Hello",
libraryDependencies += scalaTest % Test,
)
lazy val helloCore = (project in file("core"))
.settings(
name := "Hello Core",
libraryDependencies ++= Seq(gigahorse, playJson),
libraryDependencies += scalaTest % Test,
)
- 重载,添加文件:
core/src/main/scala/example/core/Weather.scala
// core/src/main/scala/example/core/Weather.scala package example.core import gigahorse._, support.okhttp.Gigahorse import scala.concurrent._, duration._ import play.api.libs.json._ object Weather { lazy val http = Gigahorse.http(Gigahorse.config) def weather: Future[String] = { val baseUrl = "https://www.metaweather.com/api/location" val locUrl = baseUrl + "/search/" val weatherUrl = baseUrl + "/%s/" val rLoc = Gigahorse.url(locUrl).get. addQueryString("query" -> "New York") import ExecutionContext.Implicits.global for { loc <- http.run(rLoc, parse) woeid = (loc \ 0 \ "woeid").get rWeather = Gigahorse.url(weatherUrl format woeid).get weather <- http.run(rWeather, parse) } yield (weather \\ "weather_state_name")(0).as[String].toLowerCase } private def parse = Gigahorse.asString andThen Json.parse }
- 修改
src/main/scala/example/Hello.scala
:
package example
import scala.concurrent._, duration._
import core.Weather
object Hello extends App {
val w = Await.result(Weather.weather, 10.seconds)
println(s"Hello! The weather in New York is $w.")
Weather.http.close()
}
- 运行:
run
sbt:Hello> run
[info] running example.Hello
Hello! The weather in New York is light cloud.
[success] Total time: 5 s, completed 2021年9月28日 上午10:29:57
添加sbt-native-packager插件:
- 创建
project/plugins.sbt
-
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.4")
- 修改
build.sbt
对Hello
项目添加.enablePlugins(JavaAppPackaging)
- 重载,本地没有执行成功,所以下面的
dist
命令也就不能用。
重载并创建.zip分发包:
dist
- 修改
-
sbt:Hello> dist [info] Wrote /tmp/foo-build/target/scala-2.12/hello_2.12-0.1.0-SNAPSHOT.pom [info] Wrote /tmp/foo-build/core/target/scala-2.12/hello-core_2.12-0.1.0-SNAPSHOT.pom [info] Your package is ready in /tmp/foo-build/target/universal/hello-0.1.0-SNAPSHOT.zip
应用容器化:
-
Docker/publishLocal
- 运行容器化后的应用:
docker run hello:0.1.0-SNAPSHOT
设置应用版本:
// build.sbt
ThisBuild / version := "0.1.0"
ThisBuild / scalaVersion := "2.13.6"
ThisBuild / organization := "com.example"
val scalaTest = "org.scalatest" %% "scalatest" % "3.2.7"
val gigahorse = "com.eed3si9n" %% "gigahorse-okhttp" % "0.5.0"
val playJson = "com.typesafe.play" %% "play-json" % "2.9.2"
lazy val hello = (project in file("."))
.aggregate(helloCore)
.dependsOn(helloCore)
.enablePlugins(JavaAppPackaging)
.settings(
name := "Hello",
libraryDependencies += scalaTest % Test,
)
lazy val helloCore = (project in file("core"))
.settings(
name := "Hello Core",
libraryDependencies ++= Seq(gigahorse, playJson),
libraryDependencies += scalaTest % Test,
)
临时切换Scala版本:
++2.12.14
在Bash中直接运行sbt的命令:
sbt clean "testOnly HelloSpec"
- 这样程序运行起来会慢一些。
- 连续的开发的话,推荐使用sbt shell或者连续测试比如
~testQuick
。
new
命令:
$ sbt new scala/scala-seed.g8
....
A minimal Scala project.
name [My Something Project]: hello
Template applied in ./hello
- 会创建一个简单的项目,要求输入项目名时输入
hello
,会在hello/
下创建一个新项目。
sbt使用
项目的目录结构:
- base directory是包含项目的目录,这里称为项目根目录。
- sbt使用和Maven一样的源码结构,源文件路径都是基于项目根目录的相对路径。
src/ main/ resources/ <files to include in main jar here> scala/ <main Scala sources> scala-2.12/ <main Scala 2.12 specific sources> java/ <main Java sources> test/ resources <files to include in test jar here> scala/ <test Scala sources> scala-2.12/ <test Scala 2.12 specific sources> java/ <test Java sources>
- 其他
src/
中的目录会被忽略,所有隐藏目录都会被忽略。 - 源码可以被放在根目录的
hello/app.scala
,对小项目是可行的。然而一般来说,人们倾向于将项目放在src/main/
下面来保证事情能够有条理地进行。如果你自行管理定制了项目的构建的话,自定义源码的位置也是可行的。 - sbt的构建定义文件:
build.sbt
- 除此之外,
project
目录中的.scala
文件可以定义项目帮助文件和一次性的插件。
build.sbt
project/
Dependencies.scala
- 生成文件:
.class
,生成的jar
,其他文件和文档等会被默认输出到target
目录。 - 一般生成文件应该要排除在版本控制之外,在
.gitignore
中添加:
target/
运行:
- 运行sbt shell:
sbt
无参数运行,进入sbt的提示符,有tab补全和执行历史。 - 编译:
compile
- 运行:
run
- 不进入sbt shell直接运行sbt命令:用
""
包起来表示是一个命令,相对来说会慢一些。
sbt clean compile "testOnly TestA TestB"
- 会一次执行
clean compile testOnly
,TestA TestB
是传给testOnly
的参数。 - 保存文件时自动重编译运行测试:
~testQuick
。 - 命令加上
~
后会进入循环模式,保存文件都会自动运行。回车退出。 - 常用命令:
Command | Description |
---|---|
clean | Deletes all generated files (in the target directory). |
compile | Compiles the main sources (in src/main/scala and src/main/java directories). |
test | Compiles and runs all tests. |
console | Starts the Scala interpreter with a classpath including the compiled sources and all dependencies. To return to sbt, type :quit, Ctrl+D (Unix), or Ctrl+Z (Windows). |
run argument* | Runs the main class for the project in the same virtual machine as sbt. |
package | Creates a jar file containing the files in src/main/resources and the classes compiled from src/main/scala and src/main/java. |
help command | Displays detailed help for the specified command. If no command is provided, displays brief descriptions of all commands. |
reload | Reloads the build definition (build.sbt, project/.scala, project/.sbt files). Needed if you change the build definition. |