说明:参考sbt官方文档。
跟着这篇官方文档学完sbt的基本使用后,深感学习的最佳途径是官方的tutorial,详细而且准确。
1. sbt项目目录管理
sbt管理项目的目录结构如下(假设基础目录或者说项目目录为./)
build.sbt
src/
main/
scala/
<main scala source>
java/
<main java source>
resources/
<files to include in main jar here>
test/
scala/
<test scala source>
java/
<test java source>
resources/
<files to include in test jar here>
project/
<该文件夹下的.scala文件和build.sbt文件一起组成build support files>
target/
<编译生成的文件,或打包的jar文件>
2. 常用sbt命令
命令 | 含义 |
---|---|
clean | 删除已经生成的文件(target/ 路径下) |
compile | 编译源文件(src/main/scala 及 src/main/java路径下 ) |
test | 编译并运行所有测试文件(test/路径下的源文件) |
console | 运行scala interpreter 其classpath包括所有已编译的源文件及依赖包(在build.sbt中可以指定),:quit, Ctrl+D (Unix), or Ctrl+Z (Windows)回到sbt交互界面 |
run | 运行项目中的主类 |
package | 将src/main/resources 和 从src/main/scala and src/main/java编译出来的类打包成jar文件 |
reload | 如果对构建定义文件(build.sbt, project/.scala, project/.sbt files)做过更改,需要reload这些更改使其生效 |
dist | 生成可执行的发行版本(target/universal/路径下的zip文件 |
3. 构建定义(build-definition)
在 project/build.properties文件中可以指定sbt的版本:
sbt.version=1.2.6
构建定义在build.sbt中指定,,由一系列的子项目(subproject)组成。如下,指定一个名为Hello的子项目,其scala版本为2.12.7,其路径在基础目录(./)下:
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7")
其实build.sbt也遵循scala语法,这里使用的lazy关键字申明变量,使其初始化延迟,节省整个程序的初始化时间。当首次使用到该变量时才对其进行初始化:
When a val is declared as lazy, its initialization is deferred until it is accessed for the first time.
在build.sbt中利用以下语法来定义setting或者task:
左边(name, name, version, and scalaVersion等)是key,都是SettingKey[T], TaskKey[T], or InputKey[T] 的实例,其中[T]为泛型。比如SettingKey[String] 赋值为整数就会编译不通过:name := 12
-
有以下三种类型的key:
- SettingKey[T]: a key for a value computed once (the value is computed when loading the subproject, and kept around). 在第一次load或者利用reload命令时,对应的值才会从新计算一遍
- TaskKey[T]: a key for a value, called a task, that has to be recomputed each time, potentially with side effects.
- InputKey[T]: a key for a task that has command line arguments as input
-
内置的key
The built-in keys are just fields in an object called Keys. build.sbt隐式的import 了sbt.Keys._ -
自定义key
自定义key值需要利用SettingKey[T], TaskKey[T], or InputKey[T]的构造方法。每种构造方法都需要key值对应的类型以及描述,key的name即为赋值的变量名。比如利用以下语法定义一个名为hello的task key:lazy val hello = taskKey[Unit]("An example task")
-
定义Task 和 Settings
TaskKey[T] 是用来定义task(任务)的,task是一些操作,比如compile(编译)、package(将项目打包)等。这些task可能返回的是Unit(仅仅执行一些动作),也可能具有返回值,如果包含package的TaskKey[File],其中File为其打包好的jar文件,故返回值为File类型。
每次执行task时,比如在sbt终端输入compile来进行编译时,所有包含compile的task都会从新执行一次。其实compile以及run等就是定义在sbt._包中的task,所以在sbt终端输入run命令就可以执行run任务了。可以自定义task,比如:lazy val hi = taskKey[Unit]("An example task") lazy val hello = (project in file(".")) .settings( hi := {println("Hello, world") name = "Hello" )
reload后,在sbt终端输入hi命令后,就会执行hi task,打印Hello,world。在定义后加了description(“An example task”),在终端输入inspect hi 时,可以查看hi task的相关信息。其中name为内置的settingKey,设置subproject的name,由于是内置的,故不用像hi一样利用构造方法来创建,可直接对该变量赋值。
利用 := 能够对setting赋值,也可以对task赋一个计算(computation)。对于setting来说,赋给setting的值会在每次load时计算一次;对与task来说,让task对应的命令执行时,其对应的computation会从新执行一次。 -
bare build definition
settints可以直接在build.sbt中定义,不用写在.settings()中,这种定义称为bare style:ThisBuild / version := "1.0" ThisBuild / scalaVersion := "2.12.7"
-
添加库依赖(library dependencies)
有两种方法可以添加第三方库。第一种方法是将外部jar包放在lib/ 文件夹中(非托管包);第二种方法是在build.sbt中添加托管包。例如,用以下方法能够添加 Apache Derby 库, 版本是 10.4.1.3。val derby = "org.apache.derby" % "derby" % "10.4.1.3" ThisBuild / organization := "com.example" ThisBuild / scalaVersion := "2.12.7" ThisBuild / version := "0.1.0-SNAPSHOT" lazy val root = (project in file(".")) .settings( name := "Hello", libraryDependencies += derby )
4. Multi-project builds
当多个子项目相互关联时,在同一个build中管理他们会很方便。每一个子项目都有各自的源文件夹,当使用package命令时生成各自的jar文件。project在build.sbt中利用project类型的lazy val定义:
lazy val util = (project in file("util"))
lazy val core = (project in file("core"))
上述两个subProject的ID分别是 util和core,没有在.settings()中定义name时,name与ID相同。在sbt终端中利用与subProject对应的name来管理该subProject。
当文件夹名与 lazy val名相同时,在定义时其对应的文件夹可省略:
lazy val util = project
lazy val core = project
要想将common设置与每个subProject的设置区分开,可以像下面这样:
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.7"
// common settings
lazy val core = (project in file("core"))
.settings(
// 只与core subproject相关的settings
)
lazy val util = (project in file("util"))
.settings(
// 只与util有关的settings
)
同一个build下的不同subproject可以完全独立,也可以通过以下两种方式建立相互依赖的关系:aggregate and classpath.
-
Aggregation(聚合)
Aggregation means that running a task on the aggregate project will also run it on the aggregated projects
意思就是将不同subproject(称为aggregated projects)聚合成一个Project(称为aggregate project),如:
lazy val root = (project in file(".")) .aggregate(util, core) lazy val util = (project in file("util")) lazy val core = (project in file("core"))
-
Classpath dependencies(类路径依赖)
当一个subproject依赖于另外一个subproject的代码时,可以调用project类的dependsOn方法实现这种依赖性。lazy val core = project.dependsOn(util)
如此,在core中就可以使用util中的类了。不过由于core依赖util,因此必须先compile util中的类,然后才开始compile core中的代码
5. Library dependencies
库文件依赖可以分为以下两种类型:
- 非托管库(unmanaged dependencies):放在lib/路径下的jar包
- 托管库(managed dependencies):在构建定义中(build definition)中配置的,由sbt自动从资料库下载的库。
-
非托管库
大多数人喜欢用托管库,不过实际上非托管库更方便,只需要将jar包放在lib/路径下,它们就会被加到项目类路径(classpass)中。也可以将测试包(如 ScalaCheck, Specs2, and ScalaTest)放进lib/路径下。 -
托管库
sbt使用 Apache Ivy (是一种比较流行的包管理器)来实现包依赖的托管管理。libraryDependencies 关键字
一般情况下只需要在project setting中将libraryDependencies 列出来即可;也可以自己另外写一个maven或者Ivy配置文件,然后利用sbt来使用这些外部文件。
用以下方式声明包依赖:其中groupId, artifactId, and revision需要指定libraryDependencies += groupID % artifactID % revision libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3"