build.sbt的定義格式

一個簡單的build.sbt文件內容以下:html

name := "hello" // 項目名稱 organization := "xxx.xxx.xxx" // 組織名稱 version := "0.0.1-SNAPSHOT" // 版本號 scalaVersion := "2.9.2" // 使用的Scala版本號 // 其它build定義

其中, name和version的定義是必須的,由於若是想生成jar包的話,這兩個屬性的值將做爲jar包名稱的一部分。java

build.sbt的內容其實很好理解,能夠簡單理解爲一行表明一個鍵值對(Key-Value Pair),各行之間以空行相分割。git

固然,實際狀況要比這複雜,須要理解SBT的Settings引擎才能夠徹底領會, 以上原則只是爲了便於讀者理解build.sbt的內容。github

除了定義以上項目相關信息,咱們還能夠在build.sbt中添加項目依賴web

// 添加源代碼編譯或者運行期間使用的依賴
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定義格式:shell

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>

在這裏,咱們排除了某些沒必要要的依賴,而且聲明瞭某個定製過的依賴聲明。apache

固然, 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/
    *.scala
    build.sbt
    project/
        *.scala
        build.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/
    *.scala
    build.sbt
    project/
        *.scala
        build.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 / f.name)) s.log.info("[dist] copy configuration templates to conf...") ((baseDir / "conf") * "*.xml").get.foreach(f => IO.copyFile(f, confDir / f.name)) s.log.info("[dist] copy examples chanenl deployment...") IO.copyDirectory(baseDir / "examples", assemblyDir / "examples") res.filter(_.name.startsWith("logback")).foreach(f => IO.copyFile(f, assemblyDir / f.name)) } }

這種方式好是好,可就是不夠通用,你我應該都不想每一個項目裏的Build文件裏都拷貝粘帖一把這些代碼吧?! 何況, 哪些artifacts要打包進去,打包以前哪些參數能夠調整,以這種形式來看,都不方便調整(若是你不煩每次都添加修改代碼的話), 那SBT有沒有更好的方式來支持相似的需求那?! 固然有咯, SBT的Plugins機制就是爲此而生的!

SBT Plugin機制容許咱們擴展SBT項目的build定義, 這裏的擴展基本能夠理解爲容許咱們向項目的build定義裏添加各類所需的Settings, 好比自定義Task,瞅一眼Plugin的代碼就更明瞭了:

trait Plugin { def settings: Seq[Setting[_]] = Nil }

咱們知道若是項目位於hello目錄下的話, 該項目的build定義能夠位於 hello/\*.sbt 或者 hello/project/*.scala 兩種位置,既然Plugin是對build定義的擴展,那麼, 咱們就能夠認爲項目的build定義依賴這些plugin的某些狀態或者行爲,即plugin屬於項目build定義的某種依賴,從這個層次來看,plugin的配置和使用跟library dependency的配置和使用是同樣的(具體有稍微的差別)。不過,既然plugin是對build定義的擴展(及被依賴),那麼,咱們應該在build定義對應的SBT項目的build定義中配置它(聽起來是否是有些繞? 若是讀者感受繞,看不明白的話,不妨回頭看看"SBT項目結構的本質"一節的內容),即 hello/project/\*.sbt 或者 hello/project/project/\*.scala , 大多數狀況下,咱們會直接在像 hello/project/plugins.sbt ^[plugins.sbt的名稱只是便於識別,實際上,SBT只關注.sbt的後綴,具體名稱是不關心的,由於plugins.sbt本質上也是一個SBT項目的build定義文件,除了在其中配置Plugin,咱們一樣能夠添加第三方依賴, 追加其餘Setting等,跟通常的.sbt配置文件無異]配置文件中配置和添加Plugin:

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")

由於Plugin實現通常也都是以三方包的形式發佈的, addSbtPlugin 所作的事情實際上就是根據Plugin發佈時使用的artifactId等標誌性信息^[在SBT中,使用ModuleID來抽象和標誌相應三方庫的標誌],將它們轉換成Setting添加到當前項目build定義中。

若是Plugin是發佈到SBT默認會查找的Maven或者Ivy Repository中,則只須要 addSbtPlugin 就好了, 不然, 須要將Plugin發佈到的Repository添加到resolvers以便SBT能夠發現並加載成功。

有哪些現成的Plugin能夠用嗎?

在前面的配置實例中,咱們已經看到兩個筆者經常使用的Plugin:

  1. sbt-idea
    • 筆者使用IntelliJ IDEA來開發scala應用, sbt-idea這個插件能夠幫助我從SBT的配置中生成IDEA這個IDE對應的classpath,項目信息等元數據, 這樣,我只要運行 sbt gen-idea 這一命令以後,就能夠在IDEA中直接打開當前的SBT項目了。若是讀者使用其餘的IDEA,那也可使用相似的插件,好比 sbteclipse 或者 sbt-netbeans-plugin
  2. sbt-dependency-graph
    • 項目的依賴越多, 依賴關係就越複雜, 這個插件能夠幫助咱們理清楚項目各個依賴之間的關係,完成跟Maven的dependency:tree相似的功能

除了這些, 讀者還能夠在SBT的 Plugins List 中找到更多有用的Plugin,好比:

    1. xsbt-web-plugin
      • 看名字就能猜到是幹啥的了
    2. sbt-assembly
      • 能夠將當前項目的二進制包以及依賴的全部第三方庫都打包成一個jar包發佈,即one-jar, 對於那種直接運行的應用程序很方便
    3. sbt-aether-deploy
      • 使用aether來部署當前項目, aethor是Maven中管理Repository的API,如今單獨剝離出來更通用了
    4. sbt-dirty-money

      • SBT會將項目的依賴抓取到本地的ivy cache中緩存起來,避免頻繁的update對帶寬形成的浪費,但有些時候須要對緩存裏失效的內容進行清理,使用這個插件能夠避免本身手動遍歷目錄逐一刪除相應的artifacts
        關於現成可用的Plugin就介紹這些,更多的好東西仍是有待讀者本身去發掘吧!

      TIPS

      若是某些SBT Plugin我的常常用到,那麼,能夠將這些Plugin配置爲Global plugin, 即在用戶的home目錄下的".sbt/plugins/"目錄下新建一個plugins.sbt文件(名稱無所謂,類型有所謂,你懂的哦),而後將這些經常使用的插件配置進去,以後,在任何的SBT項目下,就均可以使用這些插件了,「配置一次,處處運行」!

      不過, 筆者建議, 跟項目相關的SBT Plugin仍是應該配置到當前項目中,這樣,走版本控制,別人均可統一使用這些Plugin,只有哪些本身經常使用,而與具體項目綁定關係不是很強的Plugin才配置爲global的plugin, 道理跟git和svn處理ignore的作法差別是相似的!

想寫個本身的SBT Plugin該咋整?!

編寫一個本身的SBT Plugin其實並不複雜,一個SBT Plugin工程跟通常的SBT項目並沒有質上的差別,惟一的差異就在於咱們須要在SBT Plugin項目的build定義中指定一項Setting用來代表當前項目是一個SBT Plugin項目,而不是一個通常的SBT項目,這項Setting即:

sbtPlugin := true

有了這項Setting, SBT在編譯和發佈當前這個Plugin項目的時候就會作兩個事情:

  1. 將SBT API加入當前項目的classpath中(這樣咱們就能夠在編寫Plugin的時候使用到SBT的API);
  2. 在發佈(publish-local, publish)當前項目的時候,SBT會搜尋sbt.Plugin的實現,而後將這些實現添加到sbt/sbt.plugins這個文件中,並將這個文件與當前Plugin項目的其它artifacts一塊兒打包到jar中發佈;

Plugin項目發佈以後,就能夠在其餘項目中引用它們,怎麼用,前面詳細介紹過了,這裏再也不贅述。

有了編寫SBT Plugin理論指導,咱們就能夠着手實踐了, 咱們先把hello這個自定義task轉換爲Plugin實現以下:

// HelloSBT.scala

import sbt._ object HelloSBT extends Plugin { val helloSbt = TaskKey[Unit]("hello-sbt", "just say hello") val helloSbtSetting = helloSbt := { println("hello, sbt~") } }

而後,咱們爲其配置build定義:

// ${project.root}/build.sbt

name := "hello_sbt_plugin" organization := "com.github.fujohnwang" version := "0.0.1" sbtPlugin := true scalaVersion := "2.9.2"

編譯併發布到本地的ivy庫中(測試無誤後,能夠直接發佈到其餘共享範圍更大的repo中),執行:

sbt publish-local

以後,咱們就能夠在其餘SBT項目的build定義中使用到這個SBT Plugin了,好比咱們將其添加到某個SBT項目的${project.root}/project/plugins.sbt(名稱不重要,還記得吧? 注意路徑):

addSbtPlugin("com.github.fujohnwang" % "hello_sbt_plugin" % "0.0.1")

而且將__helloSbtSetting__手動配置到${project.root}/build.sbt中:

HelloSBT.helloSbtSetting

好啦,如今算是萬事大吉了,在當前SBT項目直接運行 sbt hello-sbt 試試吧!

理論和實踐咱們都闡明瞭,如今該說一下在編寫SBT Plugin的時候應該注意的一些事情了。

首先,回頭查看HelloSBT的代碼實現,讀者應該發現,咱們將SettingKey的名稱和變量名都作了改變,這是由於咱們在編寫Plugin的時候,要儘可能避免命名衝突,因此,經過引入當前Plugin的名稱前綴來達到這一目的;

其次,咱們沒有將helloSbtSetting加入到 Plugin.settings (這裏一樣注意添加了前綴的命名方式),這就意味着,咱們在使用這一Plugin的時候,要手動將這一Setting加到目標項目的build定義中才能使用到它,緣由在於,雖然咱們能夠override並將helloSbtSetting加入 Plugin.settings ,這樣可讓SBT自動加載到當前項目,但通常狀況下咱們不會這樣作,由於在多模塊的項目中,這一setting也會自動加載到全部項目上,除非是command類型的Plugin,不然,這種行爲是不合適的, 故此,大部分Plugin實現都是提供本身的Setting,並讓用戶決定是否加載使用;

其實,編寫一個SBT Plugin還要注意不少東西,但這裏就不一一列舉了,你們能夠參考 Best PracticesPlugins Best Practices 這兩份SBT Wiki文檔,裏面詳細說明了編寫SBT Plugin的一些最佳實踐,不過,做爲結束,我補充最基本的一點, 即"不要硬編碼Plugin實現的任何配置"! 讀者能夠嘗試將dist自定義task轉換成一個SBT Plugin,在轉換過程當中,不妨爲"dist"啦, "conf"啦, "bin"啦這些目標目錄設立相應的SettingKey並給予默認值,這樣就不會像咱們的自定義task裏似的,直接硬編碼這些目錄名稱了,並且,插件的使用者也能夠在使用插件的項目中經過override相應的Plugin的這些SettingKey標誌的Setting來提供自定義的值, 怎麼樣? 動手嘗試一把?!

NOTE

要編寫一個SBT Plugin還須要修煉一下SBT的內功,包括搞清楚SBT的Setting系統,Configuration,Command等深層次概念, 這樣,在編寫SBT Plugin的時候纔不會感受「侷促」,^_^

多模塊工程管理(Multi-Module Project)

對於Maven用戶來講, 多模塊的工程管理早就不是什麼神祕的特性了吧?! 但筆者實際上對於這種工程實踐卻一直持保留意見,由於不少時候,架構項目結構的人並無很好的理解項目中各類實體的粒度與邊界之間的合理關係, 不少明明在package層次/粒度能夠搞定的事情也每每被歸入到了子工程的粒度中去,這種不合適的粒度和邊界選擇,一方面反映了最初規劃項目結構的人對自身項目的理解不足,另外一方面也會爲後繼的開發和維護人員帶來些許的繁瑣。因此不少時候,若是某些關注點足以設立一個項目來管理,那我寧願直接爲其設立獨立的項目結構,而後讓須要依賴的項目依賴它便可以了(大部分時候,咱們要解決的就是各個項目之間的依賴關係,不是嗎?),固然, 這種作法並不是絕對,只是更多的在強調粒度和邊界選擇的合理性上。

扯多了,如今讓咱們來看看在SBT中咱們是如何來規劃和組織多模塊的項目結構的吧!

包含多個子模塊或者子項目的SBT項目跟一個標準的獨立的SBT項目相差很少,惟一的差異在於:

  1. build定義中多了對多個子模塊/工程的關係的描述;
  2. 項目的根目錄下多了多個子模塊/工程相應的目錄;

下面是一個多模塊工程的典型結構:

${project.root}
    - build.sbt
    + src/main/scala
    + project
        - Build.scala
    + module1
        - build.sbt
        + src/main/scala
    + module2
        - build.sbt
        + src/main/scala
    + module3
        - build.sbt
        + src/main/scala

咱們能夠發現,除了多了各個子模塊/工程相應的目錄,其它方面跟一個標準獨立的SBT項目無異, 這些子模塊/工程與當前項目或者其它子模塊/工程之間的關係由當前項目的build定義來「說明」, 固然這種關係的描述是如此的「糾纏」,只能在*.scala形式的build定義中聲明, 例如在${project.root}/project/Build.scala)中咱們能夠簡單的定義多個子模塊/工程之間的關係以下:

import sbt._ import Keys._ object MultipleModuleProjectBuild extends Build { lazy val root = Project(id = "root", base = file(".")) aggregate(sub1, sub2) lazy val sub1 = Project(id = "m1", base = file("module1")) lazy val sub2 = Project(id = "m2", base = file("module2")) dependsOn(sub3) lazy val sub3 = Project(id = "m3", base = file("module3")) }

在當前項目的build定義中,咱們聲明瞭多個Project實例來對應相應的子項目,並經過Porject的aggregate和dependsOn來進一步申明各個項目之間的關係。 aggregate指明的是一種並行的相互獨立的行爲,只是這種行爲是隨父項目執行相應動做而觸發的, 好比,在父項目中執行compile,則會同時觸發module1和module2兩個子模塊的編譯,只不過,兩個子模塊之間的執行順序等行爲並沒有聯繫; 而dependsOn則說明一種強依賴關係, 像module2這個子項目,由於它依賴module3,因此,編譯module2子模塊/項目的話,會首先編譯module3,而後纔是module2,固然,在module3的相應artifact也會加入到module2的classpath中。

咱們既然已經瞭解瞭如何在父項目中定義各個子模塊/項目之間的關係,下面咱們來看一下各個子模塊/項目內部的細節吧! 簡單來說, 每一個子模塊/項目也能夠看做一個標準的SBT項目,但一個很明顯的差別在於:  每一個子模塊/項目下不能夠再建立project目錄下其下相應的*.scala定義文件(實際上能夠建立,但會被SBT忽略) 。不過, 子模塊/項目本身下面仍是可使用.sbt形式的build定義的,在各自的.sbt build定義文件中能夠指定各個子模塊/項目各自的Setting或者依賴,好比version和LibrarayDependencies等。

通常狀況下, SBT下的多模塊/工程的組織結構就是如此,即由父項目來規劃組織結構和關係,而由各個子模塊/項目自治的管理各自的設置和依賴。但這也僅僅是倡導,若是讀者願意,徹底能夠在父項目的Build定義中添加任何本身想添加的東西,好比將各個子模塊/項目的Settings直接挪到父項目的.scala形式的build定義中去(只不過,可能會讓這個.scala形式的build定義看起來有些臃腫或者複雜罷了), 怎麼作? 本身查sbt.Project的scaladoc文檔去 :)

總的來講,多子模塊/工程的項目組織主體上仍是以父項目爲主體,各個子模塊/項目雖然有必定的「經濟」獨立性,但並不是徹底自治, 貌似跟未成年人在各自家庭裏的地位是類似吧?! 哈~

TIPS

在Maven的多子模塊/項目的組織結構中,咱們很喜歡將全部項目能夠重用或者共享的一些設定或者依賴放到父項目的build定義中,在SBT中也是能夠的,只要在父項目的.sbt或者.scala形式的build定義中添加便可,不過,要將這些共享的設定的scope設定爲ThisBuild, 例如:

    scalaVersion in ThisBuild := "2.9.2"

這樣,就不用在每個項目/模塊的build定義中逐一聲明要使用的scala版本了。其它的共享設定能夠依法炮製哦~
相關文章
相關標籤/搜索