SBT - scala构建工具

关于

sbt是scala的交互式构建工具,类似于java的maven。

构建项目

从单个文件构建

最简单的方式是从只包含一个scala源文件的目录构建项目。
例如在目录helloword创建一个HW.scala文件

object Hi {
  def main(args: Array[String]) = println("Hi!")
}

然后运行sbt,和run,或者直接运行sbt run命令。

sbt自动寻找下列目录

构建文件build.sbt

位于项目根目录,一个简单的构建文件如下

lazy val root = (project in file(".")).
  settings(
    name := "hello",
    version := "1.0",
    scalaVersion := "2.11.7"
  )

如果是要打包到jar文件,name和version是必须的。

添加依赖

val derby = "org.apache.derby" % "derby" % "10.4.1.3"

lazy val commonSettings = Seq(
  organization := "com.example",
  version := "0.1.0",
  scalaVersion := "2.11.7"
)

lazy val root = (project in file(".")).
  settings(commonSettings: _*).
  settings(
    name := "hello",
    libraryDependencies += derby
  )

依赖库的写法是:

groupID % artifactID % revision

如果将依赖的库放在lib目录下,就不需要添加该依赖。

目录结构

src/
  main/
    resources/
       <files to include in main jar here>
    scala/
       <main Scala sources>
    java/
       <main Java sources>
  test/
    resources
       <files to include in test jar here>
    scala/
       <test Scala sources>
    java/
       <test Java sources>

构建定义

一个构建定义是一个Project,拥有一个类型为 Setting[T] 的列表,Setting[T] 是会影响到 sbt 保存键值对的 map 的一种转换,T 是每一个 value 的类型。
参考前面的构建定义示例代码。

每一项 Setting 都定义为一个 Scala 表达式。在 settings 中的表达式是相互独立的,而且它们仅仅是表达式,不是完整的 Scala 语句。????WHAT
这些表达式可以用 val,lazy val,def 声明。 build.sbt 不允许使用顶层的 object 和 class。它们必须写到 project/ 目录下作为完整的 Scala 源文件。

有三种类型的 key:

键的类型

lazy val hello = taskKey[Unit]("一个 task 示例")

Tasks 任务

一个简单的hello任务如下,在build.sbt文件中加入下列代码

lazy val hello = taskKey[Unit]("Prints 'Hello World'")

hello := println("hello world!")

然后执行 sbt hello 就可以看到结果了。

一个任务首先需要定义一个taskKey[T],在这个例子中返回空类型,每一个任务是一个scala函数,可以
返回一个结果。可以在其他任务中通过.value属性访问另一个task的结果。

两种构建模式

clean   删除所有生成的文件 (在 target 目录下)。
compile 编译源文件(在 src/main/scala 和 src/main/java 目录下)。
test    编译和运行所有测试。
console 进入到一个包含所有编译的文件和所有依赖的 classpath 的 Scala 解析器。输入 :quit, Ctrl+D (Unix),或者 Ctrl+Z (Windows) 返回到 sbt。
run <参数>*   在和 sbt 所处的同一个虚拟机上执行项目的 main class。
package 将 src/main/resources 下的文件和 src/main/scala 以及 src/main/java 中编译出来的 class 文件打包成一个 jar 文件。
help <命令>   显示指定的命令的详细帮助信息。如果没有指定命令,会显示所有命令的简介。
reload  重新加载构建定义(build.sbt, project/*.scala, project/*.sbt 这些文件中定义的内容)。在修改了构建定义文件之后需要重新加载。

dependencies 依赖管理

手动管理

手动将库的jar包复制到lib目录下就可以了。如果要更改默认路径,需要修改unmanagedBase
例如修改到custom_lib/目录可以用下述命令

unmanagedBase := baseDirectory.value / "custom_lib"

更多的控制可以通过重载unmanagedJars这个task,默认的实现是

unmanagedJars in Compile := (baseDirectory.value ** "*.jar").classpath

如果要添加多个路径到默认路径,可以这样写

unmanagedJars in Compile ++= {
    val base = baseDirectory.value
    val baseDirectories = (base / "libA") +++ (base / "b" / "lib") +++ (base / "libC")
    val customJars = (baseDirectories ** "*.jar") +++ (base / "d" / "my.jar")
    customJars.classpath
}

这里对路径的语法,参考后面的路径

自动管理

sbt支持三种自动管理方式,都是通过Apache ivy来实现的。

可以通过下述语句声明依赖,其中configuration是可选的。
多个依赖可以通过Seq将每一个依赖作为一个元素进行添加,注意链接操作符号的区别,
libraryDependencies是一个 Seq ?

libraryDependencies += groupID % artifactID % revision % configuration
libraryDependencies ++= Seq(
  groupID %% artifactID % revision,
  groupID %% otherID % otherRevision
)

If you are using a dependency that was built with sbt, double the first % to be %%

sbt uses the standard Maven2 repository by default.

revision除了可以使用常规的完整版本号外,还可以使用 "latest.integration", "2.9.+", or "[1.0,)"这种形式。

resolvers

可以通过设置resolvers来添加依赖库获取的位置,格式是
resolvers += name at location,location可以是合法的URI,例如

resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
resolvers += "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository"
externalResolvers := Resolver.withDefaultResolvers(resolvers.value, mavenCentral = false)

configuration

libraryDependencies += "slinky" % "slinky" % "2.1" from "https://slinky2.googlecode.com/svn/artifacts/2.1/slinky.jar"
libraryDependencies += "org.apache.felix" % "org.apache.felix.framework" % "1.8.0" intransitive()
libraryDependencies += "org.testng" % "testng" % "5.7" classifier "jdk15"
libraryDependencies +=
  "org.lwjgl.lwjgl" % "lwjgl-platform" % lwjglVersion classifier "natives-windows" classifier "natives-linux" classifier "natives-osx"
libraryDependencies +=
    "log4j" % "log4j" % "1.2.15" exclude("javax.jms", "jms")
libraryDependencies +=
      "org.apache.felix" % "org.apache.felix.framework" % "1.8.0" withSources() withJavadoc()

-

使用pom xml文件添加依赖

externalPom()
externalPom(Def.setting(baseDirectory.value / "custom-name.xml"))

路径

创建文件和路径

sbt 0.10+ 使用java.io.File 文件类型。
创建文件方法

val source: File = file("/home/user/code/A.scala")
def readme(base: File): File = base / "README"

sbt 添加了/方法,对应于两参数构造函数。

baseDirectory task 返回bese目录绝对路径。

路径finder

一个路径finder返回一个Seq[File]。例如

def scalaSources(base: File): Seq[File] = {
  val finder: PathFinder = (base / "src") ** "*.scala"
  finder.get
}

The ** method accepts a java.io.FileFilter ,筛选目录及子目录下所有文件。
如果只访问该目录可以使用 * 函数。
惰性求值使得需要调用.get才能计算结果。

name filter 使用 *表示0个或多个字符。用||表示多个filter的或,用--表示排除。

val base = baseDirectory.value
(base / "src") * "*Test*.scala"
(base / "src") ** ("*.scala" || "*.java")
(base/"src"/"main"/"resources") * ("*.png" -- "logo.png")

组合多个finder +++, 排除结果可以用 ---
finder有一个filter方法,用于进一步筛选

(base / "lib" +++ base / "target") * "*.jar"
( (base / "src") ** "*.scala") --- ( (base / "src") ** ".svn" ** "*.scala")
( (base / "src") ** "*") filter { _.isDirectory }
base filter ClasspathUtilities.isArchive
 ```

 - to string 转换
    - toString
    - absString ,
    - getPaths , 返回Seq[String]

## 插件
插件用来扩展构建定义,可能是一个新的task。

添加插件申明,在项目根目录下的 `/project` 目录添加 `.sbt` 文件,然后在其中添加语句

```scala
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.7.0")
assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false)

val jarStartWith = Seq("pmml-","guava-","jpmml-", "json4s-")
assemblyExcludedJars in assembly := {
    val cp = (fullClasspath in assembly).value

    val filtered = cp filterNot {f =>
        jarStartWith.map(s => f.data.getName.startsWith(s)).foldLeft(false)((a,b) => a || b)
    }
    cp.foreach{c =>
        val tag = if(filtered.contains(c)) "  Excluded" else "+ Included"
        println(s"$tag : ${c.data.getName}")
    }
    filtered
}

val shadedRootPackage = "com.tracholar"
assemblyShadeRules in assembly := Seq(
    ShadeRule.rename("com.google.common.**" -> s"$shadedRootPackage.@0").inAll,
    ShadeRule.rename("org.jpmml.**" -> s"$shadedRootPackage.@0").inAll,
    ShadeRule.rename("org.dmg.pmml.**" -> s"$shadedRootPackage.@0").inAll
)

assemblyMergeStrategy in assembly := {
    case PathList("org","dmg", "pmml", xs @ _*) => MergeStrategy.first
    case PathList("org","jpmml",  xs @ _*) => MergeStrategy.first
    case PathList("com","google", xs @ _*) => MergeStrategy.first
    case PathList("com","tracholar", "spark", xs @ _*) => MergeStrategy.first
    case x => (assemblyMergeStrategy in assembly).value(x)
}
logLevel in assembly := Level.Debug

调试

传入 -jvm-debug <port> Turn on JVM debugging, open at the given port.参数即可远程调试,例如 sbt -jvm-debug 5005 run