Custom settings and tasks(自定义设置和任务)
Defining a key(定义一个关键字)
关键字已经在例子说明很多了怎样去定义个关键字。大多数关键字都是默认实现的。
关键字拥有三个类型。SettingKey 和 TaskKey 在.sbt构造定义里面描述。InputKey在任务章节有介绍
一些关键字例子
val scalaVersion =settingKey[String]("The version of Scala used forbuilding.")
val clean =taskKey[Unit]("Deletes files produced by the build, such as generated sources,compiled
关键字构造有两个字符串参数:一个关键字的名字("scalaVersion") 和一个文档描述字符串("Theversion of scala used for building.")
从 .sbt构造定义回想一下在SettingKey[T]里T参数类型,描述了存储值的类型。在TaskKey[T]里面T是描述任务结果的类型。从 .sbt构造定义回想一个setting是有当项目加载的时候,分配一个固定的值,而一个任务每次执行会重复计算。
关键字需要定义到.sbt文件,.scala文件或者是一个自动加载插件。任何vals 找到 autoImport 对象下启动自动插件会自动导入你的.sbt文件
Implementing a task(实现一个任务)
一旦你为你的任务定义一个关键字,你需要用一个任务定义来计算它。你可以定义你自己的任务,或者计划去重新定义一个已经存在的任务。无论哪种方式看起来都像一样。使用:=来联系一些代码通过任务关键字:
val sampleStringTask= taskKey[String]("A sample string task.")
val sampleIntTask =taskKey[Int]("A sample int task.")
lazy val commonSettings =Seq(
organization := "com.example",
version := "0.1.0-SNAPSHOT"
)
lazy val library =(project in file("library")).
settings(commonSettings: _*).
settings(
sampleStringTask := System.getProperty("user.home"),
sampleIntTask := {
val sum = 1 + 2
println("sum: " + sum)
sum
}
)
如果任务有依赖,你需要引用他们的值使用value方法,就像在更多类型关键字讨论一样
在实现任务最困难的部分茶花村不是sbt特有的。任务仅仅是scala代码。困难的部分就是写你任务的内容做就是你尝试去做的。例如,你可能在使用HTML库的情况下尝试格式一个HTML。
Sbt有一些使用库 和通用方法,特别是你可以常常使用API操作IO操作文件和路径
Execution semantics of tasks(执行语义任务)
当一个自定义任务依赖其他任务使用 value方法,一个重要的细节是注意任务执行的语义。通过执行语义,我们意味当这些任务被评估
如果我们用一个sampleIntTask作为例子,任务代码块每行被严格一个跟一个执行. 这就是一个语义顺序
sampleIntTask := {
val sum = 1 + 2 // first
println("sum: " + sum) // second
sum // third
}
实际上jvm可以内联 sum 到 3,但是观察任务的影响会保持相同如果每一行执行是一个接一个
现在假设我们定义了两个自定义任务 startServer 和 stopServer,和一个修改sampleIntTask如下
val startServer =taskKey[Unit]("start server")
val stopServer =taskKey[Unit]("stop server")
val sampleIntTask =taskKey[Int]("A sample int task.")
val sampleStringTask= taskKey[String]("A sample string task.")
lazy val commonSettings =Seq(
organization := "com.example",
version := "0.1.0-SNAPSHOT"
)
lazy val library =(project in file("library")).
settings(commonSettings: _*).
settings(
startServer := {
println("starting...")
Thread.sleep(500)
},
stopServer := {
println("stopping...")
Thread.sleep(500)
},
sampleIntTask := {
startServer.value
val sum = 1 + 2
println("sum: " + sum)
stopServer.value // THIS WON'TWORK
sum
},
sampleStringTask := {
startServer.value
val s = sampleIntTask.value.toString
println("s: " + s)
s
}
)
在sbt交互模式下运行sampleIntTask 结果如下:
> sampleIntTask
stopping...
starting...
sum: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:00:00PM
我们检查发生什么,看一下sampleIntTask: 图像执行符号
不像scala计划那样执行方法,在任务 执行value 方法并不会严格过程。反而,他们简单看作占位符表示sampleIntTask 依赖 startServer 和 stopServer。当通过你执行sampleIntTask,sbt任务引擎将会:
1. 在执行sampleIntTask之前 执行依赖的任务(偏序)
2. 尝试并行执行依赖任务,如果这些任务都是独立的(并行)
3. 每个依赖任务可能执行一次并且每次命令只会执行一次(两分)
Deduplication of task dependencies(任务依赖分开)
为了证明最后一个论点,我们在sbt交互模式下执行sampleStringTask
> sampleStringTask
stopping...
starting...
sum: 3
s: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:30:00PM
因为 sampleStringTask同时依赖 startServer 和 sampleIntTask,而sampleIntTask也依赖startServer,这样就会一个任务被依赖两次。如果是在scala方法调用,就会执行两次,但是因为value只会表示一个依赖任务,它就只会执行一次。
下面就是sampleStringTask’s 执行图片
如果我们不删除任务依赖,我们相会最终编译 测试代码会被执行多次 当 测试任务之被调用因为 compile in Test 出现多次作为一个依赖的任务在test in Test.
Cleanup task(清理任务)
怎样实现一个stopServer任务?清理任务的观点在任务执行模型上并不是适合,因为任务都是关于跟踪依赖的。最后操作应该成为任务内部子任务所决定的。例如stopServer 实例应该是取决于 sampleStringTask,,stopServer 应该属于 sampleStringTask.。
lazy val library =(project in file("library")).
settings(commonSettings: _*).
settings(
startServer := {
println("starting...")
Thread.sleep(500)
},
sampleIntTask := {
startServer.value
val sum = 1 + 2
println("sum: " + sum)
sum
},
sampleStringTask := {
startServer.value
val s = sampleIntTask.value.toString
println("s: " + s)
s
},
sampleStringTask := {
val old = sampleStringTask.value
println("stopping...")
Thread.sleep(500)
old
}
)
为了证明它有效,我们执行sampleStringTask
> sampleStringTask
starting...
sum: 3
s: 3
stopping...
[success] Total time: 1 s, completed Dec 22, 2014 6:00:00PM
Use plain Scala(使用简单scala)
另一个方法确保有些发生在一些其他东西发生后去使用scala.。在project/ServerUtil.scala实现一个简单功能 ,如下
sampleIntTask := {
ServerUtil.startServer
try {
val sum = 1 + 2
println("sum: " + sum)
} finally {
ServerUtil.stopServer
}
sum
}
因为这种调用是根据语言顺序的,每样发生都是顺序的,这里没有两分,所以你需要小心点。
Turn them into plugins(把他们转成插件)
如果你发现你有很多自定义代码,考虑一下把他们放到一个插件被多个构造重复使用。
是一个很容易创建一个插件,正如前面讨论的。这里执行一个快速的尝试。。
Organizing the build(构造组织)
sbt is recursive(sbt是递归)
build.sbt 隐去了sbt怎样实际工作。 Sbt 构造是通过scala 代码定义的。这些代码,自身,需要去构造。有什么比sbt更加好的方法呢?
项目的目录是在你的build的其他build,知道怎样去构造你的build。区分builds,我们有时使用正确的术语引用你的build,并且引用构造项目的元构造
hello/ # your build's root project's base directory
Hello.scala# a source file in your build's root project
# (couldbe in src/main/scala too)
build.sbt# build.sbt is part of the source code for
#meta-build's root project inside project/;
# thebuild definition for your build
project/# base directory of meta-build's root project
Build.scala# a source file in the meta-build's root project,
# thatis, a source file in the build definition
# thebuild definition for your build
build.sbt# this is part of the source code for
#meta-meta-build's root project in project/project;
# builddefinition's build definition
project/# base directory of meta-meta-build's root project;
# thebuild definition project for the build definition
Build.scala# source file in the root project of
#meta-meta-build in project/project/
不用担心!大多数时间你不需要搞成这样。但是明白这个原理可能是有用的.
顺便说一下,任何时候以 .scala和.sbt结尾的文件都是可用的,但是命名build.sbt和Build.scala
是约定一个。这样就意味着多个文件是允许的
Tracking dependencies in one place(在一个地方跟踪依赖)
一个种使用事实是 在project下的.scala文件成为构造定义的一部分,去创建
project/Dependencies.scala 在一个地方跟踪依赖
import sbt._
object Dependencies {
// Versions
lazy val akkaVersion = "2.3.8"
// Libraries
val akkaActor = "com.typesafe.akka" %% "akka-actor" % akkaVersion
val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion
val specs2core = "org.specs2" %% "specs2-core" % "2.4.14"
// Projects
val backendDeps =
Seq(akkaActor, specs2core % Test)
}
这个 Dependencies 对象可以在build.sbt使用,为了使用需要导入import Dependencies._.
import Dependencies._
lazy val commonSettings =Seq(
version := "0.1.0",
scalaVersion := "2.11.8"
)
lazy val backend =(project in file("backend")).
settings(commonSettings: _*).
settings(
libraryDependencies ++= backendDeps
)
这个技术是很有用的当你多个巨大的项目,并且你想保证子项目有相同的依赖
When to use .scalafiles(什么时候使用.scala文件)
在 .scala文件,你可以写任何scala代码,包括高级类和对象
推荐应用是去定义大部分设置在一个build.sbt文件,并且使用 project/*.scala文件对于任务实现和共享值,例如关键字。使用.scala文件也取决怎样通过scala 你和你的团队便利