一个简单的build.sbt文件内容如下:

name := "hello"      // 项目名称organization := "xxx.xxx.xxx" // 组织名称 version := "0.0.1-SNAPSHOT" // 版本号 scalaVersion := "2.9.2" // 使用的Scala版本号 // 其它build定义

其中, name和version的定义是必须的,因为如果想生成jar包的话,这两个属性的值将作为jar包名称的一部分。

build.sbt的内容其实很好理解,可以简单理解为一行代表一个键值对(Key-Value Pair),各行之间以空行相分割。

当然,实际情况要比这复杂,需要理解SBT的Settings引擎才可以完全领会, 以上原则只是为了便于读者理解build.sbt的内容。

除了定义以上项目相关信息,我们还可以在build.sbt中添加项目依赖

// 添加源代码编译或者运行期间使用的依赖
libraryDependencies += "ch.qos.logback" % "logback-core" % "1.0.0" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.0.0" // 或者 libraryDependencies ++= Seq( "ch.qos.logback" % "logback-core" % "1.0.0", "ch.qos.logback" % "logback-classic" % "1.0.0", ... ) // 添加测试代码编译或者运行期间使用的依赖 libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "1.8" % "test")

甚至于直接使用ivy的xml定义格式:

ivyXML :=<dependencies><dependency org="org.eclipse.jetty.orbit" name="javax.servlet" rev="3.0.0.v201112011016"> <artifact name="javax.servlet" type="orbit" ext="jar"/> </dependency> <exclude module="junit"/> <exclude module="activation"/> <exclude module="jmxri"/> <exclude module="jmxtools"/> <exclude module="jms"/> <exclude module="mail"/> </dependencies>

在这里,我们排除了某些不必要的依赖,并且声明了某个定制过的依赖声明。

当然, build.sbt文件中还可以定义很多东西,比如添加插件,声明额外的repository,声明各种编译参数等等,我们这里就不在一一赘述了。

project目录即相关文件介绍

project目录下的几个文件实际上都是非必须存在的,可以根据情况添加。

build.properties 文件声明使用的要使用哪个版本的SBT来编译当前项目, 最新的sbt boot launcher可以能够兼容编译所有0.10.x版本的SBT构建项目,比如如果我使用的是0.12版本的sbt,但却想用0.11.3版本的sbt来编译当前项目,则可以在build.properties文件中添加 sbt.version=0.11.3 来指定。 默认情况下,当前项目的构建采用使用的sbt boot launcher对应的版本。

plugins.sbt 文件用来声明当前项目希望使用哪些插件来增强当前项目使用的sbt的功能,比如像assembly功能,清理ivy local cache功能,都有相应的sbt插件供使用, 要使用这些插件只需要在plugins.sbt中声明即可,不用自己去再造轮子:

resolvers += Resolver.url("git://github.com/jrudolph/sbt-dependency-graph.git") resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0") addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.6.0")

在笔者的项目中, 使用sbt-idea来生成IDEA IDE对应的meta目录和文件,以便能够使用IDEA来编写项目代码; 使用sbt-dependency-graph来发现项目使用的各个依赖之间的关系;

为了能够成功加载这些sbt插件,我们将他们的查找位置添加到resolovers当中。有关resolvers的内容,我们后面将会详细介绍,这里注意一个比较有趣的地方就是,sbt支持直接将相应的github项目作为依赖或者插件依赖,而不用非得先将相应的依赖或者插件发布到maven或者ivy的repository当中才可以使用。

以上目录和文件通常是在创建项目的时候需要我们创建的,实际上, SBT还会在编译或者运行期间自动生成某些相应的目录和文件,比如SBT会在项目的根目录下和project目录下自动生成相应的target目录,并将编译结果或者某些缓存的信息置于其中, 一般情况下,我们不希望将这些目录和文件记录到版本控制系统中,所以,通常会将这些目录和文件排除在版本管理之外。

比如, 如果我们使用git来做版本控制,那么就可以在.gitignore中添加一行 "target/" 来排除项目根目录下和project目录下的target目录及其相关文件。

Managed Dependencies详解

sbt的managed dependencies采用Apache Ivy的依赖管理方式, 可以支持从Maven或者Ivy的Repository中自动下载相应的依赖。

简单来说,在SBT中, 使用managed dependencies基本上就意味着往__libraryDependencies__这个Key中添加所需要的依赖, 添加的一般格式如下:

libraryDependencies += groupID % artifactID % revision

比如:

libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3"

这种格式其实是简化的常见形式,实际上,我们还可以做更多微调, 比如:

(1) libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3" % "test" (2) libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3" exclude("org", "artifact") (3) libraryDependencies += "org.apache.derby" %% "derby" % "10.4.1.3"

(1)的形式允许我们限定依赖的范围只限于测试期间; (2)的形势允许我们排除递归依赖中某些我们需要排除的依赖; (3)的形式则会在依赖查找的时候,将当前项目使用的scala版本号追加到artifactId之后作为完整的artifactId来查找依赖,比如如果我们的项目使用scala2.9.2,那么(3)的依赖声明实际上等同于 "org.apache.derby" %% "derby_2.9.2" % "10.4.1.3" ,这种方式更多是为了简化同一依赖类库存在有多个Scala版本对应的发布的情况。

如果有一堆依赖要添加,一行一行的添加是一种方式,其实也可以一次添加多个依赖:

libraryDependencies ++= Seq("org.apache.derby" %% "derby" % "10.4.1.3", "org.scala-tools" %% "scala-stm" % "0.3", ...)

对于managed dependencies来说,虽然我们指定了依赖哪些类库,但有没有想过,SBT是如何知道到哪里去抓取这些类库和相关资料那?!

实际上,默认情况下, SBT回去默认的Maven2的Repository中抓取依赖,但如果默认的Repository中找不到我们的依赖,那我们可以通过resolver机制,追加更多的repository让SBT去查找并抓取, 比如:

resolvers += "Sonatype OSS Snapshots" at " https://oss.sonatype.org/content/repositories/snapshots "

at^[at实际上是String类型进行了隐式类型转换(Implicit conversion)后目标类型的方法名]之前是要追加的repository的标志名称(任意取),at后面则是要追加的repository的路径。

除了可远程访问的Maven Repo,我们也可以将本地的Maven Repo追加到resolver的搜索范围:

resolvers += "Local Maven Repository" at "file://"+Path.userHome.absolutePath+"/.m2/repository"

.scala形式的build定义

对于简单的项目来讲,.sbt形式的build定义文件就可以满足需要了,但如果我们想要使用SBT的一些高级特性,比如自定义Task, 多模块的项目构建, 就必须使用.scala形式的build定义了。 简单来讲,.sbt能干的事情,.scala形式的build定义都能干,反之,则不然。

要使用.scala形式的build定义,只要在当前项目根目录下的project/子目录下新建一个.scala后缀名的scala源代码文件即可,比如Build.scala(名称可以任意,一般使用Build.scala):

import sbt._
import Keys._object HelloBuild extends Build { override lazy val settings = super.settings ++ Seq(..) lazy val root = Project(id = "hello", base = file("."), settings = Project.defaultSettings ++ Seq(..)) }

build的定义只要扩展sbt.Build,然后添加相应的逻辑即可,所有代码都是标准的scala代码,在Build定义中,我们可以添加更多的settings, 添加自定义的task,添加相应的val和方法定义等等, 更多代码实例可以参考SBT Wiki( https://github.com/harrah/xsbt/wiki/Examples ),另外,我们在后面介绍SBT的更多高级特性的时候,也会引入更多.scala形式的build定义的使用。

NOTE.sbt和.scala之间不得不说的那些事儿实际上, 两种形式并不排斥,并不是说我使用了前者,就不能使用后者,对于某些单一的项目来说,我们可以在.sbt中定义常用的settings,而在.scala中定义自定义的其它内容, SBT在编译期间,会将.sbt中的settings等定义与.scala中的定义合并,作为最终的build定义使用。只有在多模块项目构建中,为了避免多个.sbt的存在引入过多的繁琐,才会只用.scala形式的build定义。.sbt和.scala二者之间的settings是可互相访问的, .scala中的内容会被import到.sbt中,而.sbt中的settings也会被添加到.scala的settings当中。默认情况下,.sbt中的settings会被纳入Project级别的Scope中,除非明确指定哪些Settings定义的Scope; .scala中则可以将settings纳入Build级别的Scope,也可以纳入Project级别的Scope。

SBT项目结构的本质

在了解了.sbt和.scala两种形式的build定义形式之后, 我们就可以来看看SBT项目构建结构的本质了。

首先, 一个SBT项目,与构建相关联的基本设施可以概况为3个部分, 即:

  1. 项目的根目录, 比如hello/, 用来界定项目构建的边界;
  2. 项目根目录下的*.sbt文件, 比如hello/build.sbt, 用来指定一般性的build定义;
  3. 项目根目录下的project/*.scala文件, 比如hello/project/Build.scala, 用来指定一些复杂的, *.sbt形式的build定义文件不太好搞的设置;

也就是说, 对于一个SBT项目来说,SBT在构建的时候,只关心两点:

  1. build文件的类型(是*.sbt还是*.scala);
  2. build文件的存放位置(*.sbt文件只有存放在项目的根目录下, SBT才会关注它或者它们, 而*.scala文件只有存放在项目根目录下的project目录下,SBT才不会无视它或者它们)^[实际上,只有那些定义了扩展自sbt.Build类的scala文件,才会被认为是build定义];

在以上基础规则的约束下,我们来引入一个推导条件, 即:

项目的根目录下的project/目录,其本身也是一个标准的SBT项目。

在这个条件下,我们再来仔细分析hello/project/目录,看它目录下的各项artifacts到底本质上应该是什么。

我们说项目根目录下的project/子目录下的*.scala文件是当前项目的build定义文件, 而根据以上的推导条件, project/目录本身又是一个SBT项目,我们还知道,SBT下面下的*.scala都是当前项目的源代码,所以project/下的*.scala文件, 其实都是project这个目录下的SBT项目的源代码,而这些源代码中,如果有人定义了sbt.Build,那么就会被用作project目录上层目录界定的SBT项目的build定义文件, right?!

那么,来想一个问题,如果project/目录下的*.scala是源代码文件,而project目录整体又是一个标准的SBT项目, 假如我们这些*.scala源码文件中需要依赖其他三方库,通常我们会怎么做?

对, 在当前项目的根目录下新建一个build.sbt文件,将依赖添加进去,所以,我们就有了如下的项目结构:

hello/*.scalabuild.sbtproject/*.scalabuild.sbt

也就是说,我们可以在书写当前项目的build定义的时候(因为build定义也是用scala来写),借用第三方依赖来完成某些工作,而不用什么都重新去写,在project/build.sbt下添加项目依赖,那么就可以在project/*.scala里面使用,进而构建出hello/项目的build定义是什么, 即hello/project/这个SBT项目,支撑了上一层hello/这个项目的构建!

现在再来想一下,如果hello/project/这个项目的构建要用到其它SBT特性,比如自定义task或者command啥的,我们该怎么办?!

既然hello/project/也是一个SBT项目,那么按照惯例,我们就可以再其下再新建一个project/目录,在这个下一层的project/目录下再添加*.scala源文件作为hello/project/这个SBT项目的build定义文件, 整个项目又变成了:

hello/*.scalabuild.sbtproject/*.scalabuild.sbt/project*.scala

而如果hello/project/project/下的源码又要依赖其他三方库那?! God, 再添加*.sbt或更深一层的project/*.scala!

也就是说, 从第一层的项目根目录开始, 其下project/目录内部再嵌套project/目录,可以无限递归,而且每一层的project/目录都界定了一个SBT项目,而每一个下层的project目录界定的SBT项目其实都是对上一层的SBT项目做支持,作为上一层SBT项目的build定义项目,这就跟俄罗斯娃娃这种玩具似的, 递归嵌套,一层又包一层:

一般情况下,我们不会搞这么多嵌套,但理解了SBT项目的这个结构上的本质,可以帮助我们更好的理解后面的内容,如果读者看一遍没能理解,那不妨多看几次,多参考其他资料,多揣摩揣摩吧!

大部分情况下,我们都是使用SBT内建的Task,比如compile, run等,实际上, 除了这些,我们还可以在build定义中添加更多自定义的Task。

自定义SBT的Task其实很简单,就跟把大象关冰箱里一样简单, 概况来说其实就是:

  1. 定义task;
  2. 将task添加到项目的settings当中;
  3. 使用自定义的task;

Task的定义分两部分,第一部分就是要定义一个TaskKey来标志Task, 第二部分则是定义Task的执行逻辑。

假设我们要定义一个简单的打印"hello, sbt~"信息的task,那第一步就是先定义它的Key,如下代码所示:

val hello = TaskKey[Unit]("hello", "just say hello")

TaskKey的类型指定了对应task的执行结果,因为我们只想打印一个字符串,不需要返回什么数据,所以定义的是TaskKey[Unit]。 定义TaskKey最主要的一点就是要指定一个名称(比如第一个参数“hello”),这个名称将是我们调用该task的标志性建筑。 另外,还可以可选择的通过第二个参数传入该task的相应描述和说明。

有了task对应的Key之后,我们就要定义task对应的执行逻辑,并通过 := 方法将相应的key和执行逻辑定义关联到一起:

hello := {println("hello, sbt~") }

完整的task定义代码如下所示:

val hello = TaskKey[Unit]("hello", "just say hello") hello := { println("hello, sbt~") }

NOTE:= 只是简单的将task的执行逻辑和key关联到一起, 如果之前已经将某一执行逻辑跟同一key关联过,则后者将覆盖前者,另外,如果我们想要服用其他的task的执行逻辑,或者依赖其他task,只有一个:=就有些力不从心了。这些情况下,可以考虑使用~=或者<<=等方法,他们可以借助之前的task来映射或者转换新的task定义。比如(摘自sbt wiki):
// These two settings are equivalent
intTask <<= intTask map { (value: Int) => value + 1 }
intTask ~= { (value: Int) => value + 1 }

将task添加到项目的settings当中

光完成了task的Key和执行逻辑定义还不够,我们要将这个task添加到项目的Settings当中才能使用它,所以,我们稍微对之前的代码做一补充:

object ProjectBuild extends Build {val hello = TaskKey[Unit]("hello", "just say hello") val helloTaskSetting = hello := { println("hello, sbt~") } lazy val root = Project(id = "", base = file(".")).settings(Defaults.defaultSettings ++ Seq(helloTaskSetting): _*) }

将Key与task的执行逻辑相关联的过程实际上是构建某个Setting的过程,虽然我们也可以将以上定义写成如下形式:

lazy val root = Project(id = "", base = file(".")).settings(Defaults.defaultSettings ++ Seq(hello := { println("hello, sbt~") }): _*)

但未免代码就太不雅观,也不好管理了(如果要添加多个自定义task,想想,用这种形式是不是会让代码丑陋不堪那?!),所以,我们引入了helloTaskSetting这个标志常量来帮助我们净化代码结构 :)

测试和运行定义的task

万事俱备之后,就可以使用我们的自定义task了,使用定义Key的时候指定的task名称来调用它即可:

$ sbt hello
hello, sbt~
// 或者
$ sbt
> hello
hello, sbt~
[success] Total time: 0 s, completed Oct 4, 2012 2:48:48 PM

怎么样? 在SBT中自定义task是不是很简单那?!

每个项目最终都要以相应的形式发布^[这里的发布更多是指特殊的发布形式,比如提供完整的下载包给用户,直接打包成部署包等。一般情况下,如果用Maven或者SBT,可以直接publish到相应的Maven或者Ivy Repository中],比如二进制包, 源码包,甚至直接可用的部署包等等, 假设我们想把当前的SBT项目打包成可直接解压部署的形式,我们可以使用刚刚介绍的自定义task来完成这一工作:

object ProjectBuild extends Build {import Tasks._ lazy val root = Project(id = "", base = file(".")).settings(Defaults.defaultSettings ++ Seq(distTask, helloTaskSetting): _*) } object Tasks { val hello = TaskKey[Unit]("hello", "just say hello") val helloTaskSetting = hello := { println("hello, sbt~") } val dist = TaskKey[Unit]("dist", "distribute current project as zip or gz packages") val distTask = dist <<= (baseDirectory, target, fullClasspath in Compile, packageBin in Compile, resources in Compile, streams) map { (baseDir, targetDir, cp, jar, res, s) => s.log.info("[dist] prepare distribution folders...") val assemblyDir = targetDir / "dist" val confDir = assemblyDir / "conf" val libDir = assemblyDir / "lib" val binDir = assemblyDir / "bin" Array(assemblyDir, confDir, libDir, binDir).foreach(IO.createDirectory) s.log.info("[dist] copy jar artifact to lib...") IO.copyFile(jar, libDir / jar.name) s.log.info("[dist] copy 3rd party dependencies to lib...") cp.files.foreach(f => if (f.isFile) IO.copyFile(f, libDir / f.name)) s.log.info("[dist] copy shell scripts to bin...") ((baseDir / "bin") ** "*.sh").get.foreach(f => IO.copyFile(f, binDir / 

.sbt文件的配置详解相关推荐

  1. Mybatis中接口和对应的mapper文件位置配置详解

    今天遇到一个问题是mybatis中接口和对应的mapper文件位置不同,而引起的操作也会不同,在网上找了好久最终找到了方法,这里就简单的解析一下: 我们知道在典型的maven工程中,目录结构有:src ...

  2. Tomcat 的 Server 文件配置详解

    转载自  Tomcat 的 Server 文件配置详解 前言 Tomcat隶属于Apache基金会,是开源的轻量级Web应用服务器,使用非常广泛.server.xml是Tomcat中最重要的配置文件, ...

  3. k8s的yaml文件配置详解(三)

    k8s的服务资源文件配置详解 注:本文章只作配置项解释,请灵活运用 --- #Service kind: Service apiVersion: v1 metadata: name: service ...

  4. 43. Systemd的Unit配置详解,unit文件位置,优先级,unit类型,unit文件字段详解,Unit/Service/Install字段,添加mysql服务等例子

    Systemd的Unit配置详解,unit文件位置和优先级,unit文件类型,unit文件字段详解,[Unit]字段,[Service]字段,[Install]字段,添加服务,创建.service 文 ...

  5. elasticsearch-.yml(中文配置详解)

    此elasticsearch-.yml配置文件,是在$ES_HOME/config/下 elasticsearch-.yml(中文配置详解) # ======================== El ...

  6. mybatis 同名方法_MyBatis(四):xml配置详解

    目录 1.我们将 数据库的配置语句写在 db.properties 文件中 2.在 mybatis-configuration.xml 中加载db.properties文件并读取 通过源码我们可以分析 ...

  7. logback节点配置详解

    logback节点配置详解 一:根节点 <configuration></configuration> 属性 : debug : 默认为false ,设置为true时,将打印出 ...

  8. 转 Log4j.properties配置详解

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

  9. Iptables防火墙配置详解

    iptables防火墙配置详解 iptables简介 iptables是基于内核的防火墙,功能非常强大,iptables内置了filter,nat和mangle三张表. (1)filter表负责过滤数 ...

最新文章

  1. java项目怎么定义异常_在Java项目中如何实现自定义异常
  2. 大学python实训总结-【实训总结】大学生python相关实训总结
  3. 实战操作主机角色转移,Active Directory系列之十
  4. 大型企业用什么orm_生产企业ERP有什么用
  5. Golang 学习笔记(08)—— 文件操作
  6. 《Python CookBook2》 第一章 文本 - 测试一个对象是否是类字符串 字符串对齐
  7. 基于BERT进行商品标题实体识别,很详细~
  8. es5 和 es6 class
  9. 侧方、s弯道、坡起相关
  10. 为什么仿宋字体打印出楷体_win7仿宋字体及楷体字体打包下载
  11. uni-app 安卓实现监听 PDA 扫码枪等设备按钮
  12. 高效能人士的七个习惯读后感与总结概括-(第四章)
  13. Eloquent ORM
  14. Hadoop生态之Mapreduce
  15. 对产品经理来说,PMP和NPDP两个证书,哪一个权威性比较大?
  16. 实战篇-OpenSSL之TripleDES加密算法-ECB模式
  17. docker 命令补全
  18. 快速切题 usaco ariprog
  19. shell之读取/etc/passwd中user及其id
  20. 电脑上onedrive打不开解决

热门文章

  1. 程序运行出现错误:返回值为 -1073741701 (0xc000007b)
  2. 品牌与Logo有什么区别?进来了解下
  3. Android 中英文切换及遇到的问题
  4. iOS OC08,09_内存管理
  5. 腾讯竟然又偷偷开源了一套Android原生UI框架!不吃透都对不起自己
  6. 电商代运营是做什么的
  7. 抖音直播聊天窗口如何关闭,抖音直播间看不到弹幕
  8. 微信清理网页缓存的办法
  9. Dreamweaver cc 2017 代码主题 代码样式 调整
  10. linux stat获取文件大小,Linux查看文件大小的几种方法示例 stat du ls awk (转)