一工腋、sbt啟動機制理解與啟動緩慢的原因分析
眾所周知姨丈,不加修改,直接使用sbt擅腰,那么sbt“啟動(launch)”會非常慢蟋恬,甚至會失敗,尤其當初次運行時趁冈,本地尚無緩存歼争,需要大量加載自身依賴文件的情況下更是如此。
主要原因是:不加修改的情況下渗勘,sbt在啟動時會使用啟動器(sbt-launch.jar)內(nèi)置sbt.boot.properties文件(在bt-launch.jar內(nèi)的sbt/目錄下)中所指定的構(gòu)建成果物庫(build Artifact Repository)沐绒。這些缺省構(gòu)建成果物庫都是國外網(wǎng)站,在國際網(wǎng)絡(luò)通道日益擁堵的今天旺坠,初次下載速度就會格外緩慢乔遮。
又因為這些配置是在sbt啟動時使用和加載,對于所有sbt項目而言取刃,它們又是全局(global)配置蹋肮,會影響到所有的sbt 項目。因此璧疗,我們需要了解這些配置及其作用括尸,sbt-launch.jar中內(nèi)置sbt.boot.properties文件中構(gòu)建成果物庫的配置片段如下:
[repositories]
local
local-preloaded-ivy: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]
local-preloaded:file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}
maven-central
sbt-maven-releases: https://repo.scala-sbt.org/scalasbt/maven-releases/, bootOnly
sbt-maven-snapshots:https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly
typesafe-ivy-releases:https://repo.typesafe.com/typesafe/ivy-releases/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly
?sbt-ivy-snapshots:https://repo.scala-sbt.org/scalasbt/ivy-snapshots/, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext], bootOnly
其中:
[repositories]表明它是構(gòu)建成果物庫所在的“配置段”,其下內(nèi)容都是關(guān)于“repository”的配置信息病毡,直到遇到下一個“配置段”濒翻,比如[boot]段。
每個repository配置條目都由倆個部分組成(冒號分割)即:
庫名: 庫配置項
各部分如何配置如下:
庫名,是為了跟蹤調(diào)試時給使用者查看所用,使用者可以根據(jù)方便自己的記憶來進行設(shè)置,但庫名不能重復厦凤。
庫配置糊秆,給出了庫所在的位置(Url),成果物文件所在的路徑布局(Layout)格式,以及這個庫在什么階段使用。格式為:
? ? ? ?位置Url, 庫文件路徑布局(Layout)格式, 使用階段指示符
其中:
? ?位置Url必須給出。
? 庫文件路徑布局(Layout)格式的用途是給Sbt將想要下載的庫文件描述定義(比如具體依賴的成果物的組織涯塔、名字、版本等)格式 化成滿足Maven或Ivy成果物庫中的路徑布局(layout)的路徑(path)信息清蚀,并與與庫的Url地址結(jié)合匕荸,生成具體的依賴相關(guān)文件(比如pom\ivy文件和Jar文件)的完整下載地址。所謂庫文件路徑布局(Layout)枷邪,對于庫服務(wù)器而言榛搔,就是存儲成果物文件的存儲路徑安排。對于訪問者而言东揣,就對成果物文件在庫服務(wù)器URL下的訪問路徑規(guī)約践惑。Sbt缺省的布局格式用于格式化maven2庫的路徑,缺省路徑布局格式如下:
[organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]
需要注意的是嘶卧,有一些scala寫的sbt插件成果物的版本號中包括了scala版本號和sbt版本號尔觉,sbt需要用scala版本變量、sbt版本變量并與成果物自身的版本號一起來生成完整的成果物版本號芥吟,其[revision]格式會略有不同侦铜,完整布局(layout)格式如下:
[organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
使用階段指示符用來指出該庫僅在“sbt啟動期間”使用,還是后續(xù)加載項目(project)所用依賴時一直使用运沦,該指示符用bootOnly來指示泵额。bootOnly表明該庫只在sbt啟動過程中使用配深,用來下載sbt本身(或所嵌入IDE)所需的插件携添。在sbt成功啟動后,我們可以用sbt命令查看sbt當前所使用的庫篓叶。其中烈掠,show? fullResolvers 命令能夠查看包括在項目(project)的build.sbt文件中用resolvers配置的項目級構(gòu)建成果物庫,和全局級(global)的構(gòu)建成果物庫(比如缸托,上面所提到的sbt.boot.properties文件所配置的)左敌;而show resolvers命令則用來列出項目級構(gòu)建成果物庫。sbt在加載依賴(dependency)時俐镐,會按照配置文件中出現(xiàn)的順序依次地嘗試從所配置的庫來加載依賴文件 矫限。如果某個比較慢的遠程庫在排在前面,就會使得整個加載速度變慢,如果遠程庫出現(xiàn)了超時叼风,則會從下一個遠程庫中加載取董,如果這個庫加載速度較快,那么整個加載過程就會變快无宿。在網(wǎng)絡(luò)不穩(wěn)定時(比如國外網(wǎng)站超時機率高)的時候茵汰,就會出現(xiàn)某次啟動加載速度快,某次啟動加載速度慢的情況孽鸡。不明白這種機制蹂午,會導致我們的配置某次好像很成功,啟動加載速度很快彬碱,但給別人用的時候就又變慢了的情況豆胸。
上面我們介紹了sbt構(gòu)建成果物庫在啟動時(也是全局)的配置,值得注意的是堡妒,sbt在該配置段中設(shè)置了兩個“內(nèi)置的”配置條目配乱,就是在上面配置片段中的local和maven-central,通過show? fullResolvers 命令可以看到:
maven-central配置條目所代表的完整配置是:
public: https://repo1.maven.org/maven2/
它指向了Maven中央倉皮迟,它不是bootOnly搬泥,因此在sbt啟動和后續(xù)所有項目中都會用到,因此伏尼,因為前面所說的原因忿檩,忘記刪除這個配置條目,會導致國內(nèi)開發(fā)者sbt啟動和后續(xù)加載依賴出現(xiàn)極為緩慢的可能爆阶。
Local配置條目所代表的整配置是:
FileRepository(local, Patterns(ivyPatterns=Vector(${ivy.home}/local/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]), artifactPatterns=Vector(${ivy.home}/local/[organisation]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)([branch]/)[revision]/[type]s/[artifact](-[classifier]).[ext]), isMavenCompatible=false, descriptorOptional=false, skipConsistencyCheck=false), FileConfiguration(true, None))
看起來比較嚇人燥透,實際上不復雜,它表明該庫名為local辨图,其所指的庫位置位于${ivy.home}配置變量所表示的ivy home目錄下的local子目錄班套,如果不修改ivy home配置,缺省ivy home位置就是操作系統(tǒng)登錄賬號的home目錄下的 .ivy2目錄故河,如果不存在吱韭,sbt會自動創(chuàng)建,對于linux和mac os來說鱼的,ivy home路徑就是 $HOME/.ivy2/理盆,而local庫的位置則是:$HOME/.ivy2/local,這個local子目錄需要使用者自己創(chuàng)建凑阶,再在其中放入所有本地依賴庫猿规。sbt與intellij IDEA進行配合使用時,我們會用到本地依賴庫宙橱。
如果您對下載和解析依賴文件感興趣姨俩,請從以下官方文檔獲得更多信息:
https://www.scala-sbt.org/1.x/docs/Resolvers.html
https://www.scala-sbt.org/1.x/docs/Proxy-Repositories.html
二蘸拔、啟動緩慢的解決辦法
1.總體思路
讓sbt在“啟動(launch)時”只使用國內(nèi)的構(gòu)建成果物庫(build Artifact Repository)鏡像服務(wù)器,比如:
阿里云:https://maven.aliyun.com/repository/public
或華為云:https://mirrors.huaweicloud.com/repository/maven/
或者环葵,讓SBT在啟動時使用開發(fā)團隊自己的搭建的構(gòu)建成果物庫都伪,即所謂的“私服”。用來搭建“私服”的軟件產(chǎn)品包括收費的商業(yè)產(chǎn)品sonatype nexus或JFrog Artifactory积担,以及開源免費的apache Archiva等陨晶,如何搭建私服,本文不再詳述帝璧。
2.實現(xiàn)方法
上述思路的實現(xiàn)先誉,需要更改sbt的啟動設(shè)置,如果前面所述的烁,這些設(shè)置對所有sbt項目都有作用褐耳,也就是修改全局設(shè)置,sbt官方文檔:
https://www.scala-sbt.org/1.x/docs/Launcher-Configuration.html
給出了三種修改sbt 啟動設(shè)置的方式渴庆,分別是:
修改sbt-launch.jar中內(nèi)置的sbt.boot.properties文件铃芦。
另外創(chuàng)建一個sbt.boot.properties文件,并放在系統(tǒng)所配置的java? classpath之中襟雷。
創(chuàng)建專項的配置文件刃滓,比如專門用于存放repositorys配置文件,并在sbt命令行中用-Dsbt.override.build.repos參數(shù)來指定覆蓋系統(tǒng)默認的庫配置耸弄,并用-Dsbt.repository.config參數(shù)指來定自定義庫配置文件所在位置咧虎。完整的sbt 命令示例如下:
sbt? -Dsbt.override.build.repos=true? -Dsbt.repository.config=/.repositories
這三種方式的優(yōu)先級依次升高,對我而言计呈,比較喜歡第三種方式砰诵,主要是它的優(yōu)先級高,而且比較靈活捌显。第一種方式的缺點是需要修改sbt-launch.jar文件茁彭,一旦sbt 本身升級,就需要重新制作sbt-launch.jar文件扶歪,比較麻煩理肺。第二種方式的缺點是sbt.boot.properties不能設(shè)定所有需要的全局配置,比如击罪,repository服務(wù)器的身份認證信息就無法用sbt.boot.properties文件來設(shè)置哲嘲。
那么使用第三種方式是不是意味著每次使用sbt都要在命令行中輸入一串長長的配置參數(shù)呢贪薪?答案是樂觀的媳禁。我們可以在sbt安裝目錄下的conf/目錄中的“sbt運行參數(shù)配置文件”中給出這些自定義的配置參數(shù),而且還可以給出更多的運行配置選項画切。這樣竣稽,在命令行中輸入sbt時,就會自動按照這些設(shè)置運行。在windows系統(tǒng)中毫别,這個配置文件是的名稱是sbtconfig.txt娃弓,在linux和mac os系統(tǒng)中,這個配置文件的名稱是sbtopts岛宦。
順便說一下台丛,可以在sbt運行工作目錄的日志中查看sbt的啟動運行日志文件來查看這些運行配置是否生效,缺省情況下砾肺,這個啟動運行日志文件的全路徑為:
/${操作系統(tǒng)登錄用戶Home目錄}/.sbt/boot/update.log
下面給出的是sbtopts中部分sbt運行參數(shù)配置示例:
#允許覆蓋內(nèi)置的repositories
-Dsbt.override.build.repos=true
#給出自定義repositories配置文件
-Dsbt.repository.config=/Users/Shared/App4Deve/sbt/conf/.repositories
如果理解了本文第一部分中的內(nèi)容挽霉,剩下的工作很簡單,就是創(chuàng)建一個僅存放[repositories]配置段的文件变汪,文件名沒有要求侠坎,下面給出該文件的一個示例:
[repositories]
#國內(nèi)maven庫鏡像的本地“私服”的代理
local-mavenRepo-server: http://localhost:8088/artifactory/aliyun/
#國內(nèi)maven庫鏡像
maven-china: https://maven.aliyun.com/repository/public
如果你所使用的“私服”需要身份認證实胸,那么將私服的身份認證信息存放在一個文件中,“身份認證信息文件”的名稱沒有要求番官,只要在“sbt運行參數(shù)配置文件(sbtopts或sbtconfig.txt文件)”中指定該身份認證信息文件位置的全路徑即可,此時的“sbt運行參數(shù)配置文件”內(nèi)容如下:
#私服的身份認證配置文件位置
-Dsbt.boot.credentials=/Users/Shared/App4Deve/sbt/conf/credentials.sbt
#允許覆蓋內(nèi)置的repositories
-Dsbt.override.build.repos=true
#給出自定義repositories配置文件
-Dsbt.repository.config=/Users/Shared/App4Deve/sbt/conf/.repositories
而“身份認證信息文件”的內(nèi)容則很簡單假褪,示例如下:
realm=Artifactory Realm
host=localhost
user=developer
password=123#@!
此時,sbt 已經(jīng)可以訪問“私服”生音,但還需要注意以下幾點:
不同服務(wù)器的realm不同,范例所示的realm是JFrog Artifatory的realm窒升,nexus的realm則是Sonatype Nexus Repository Manager缀遍,其他服務(wù)器的realm需要自己查找。
host可以是服務(wù)器域名饱须,也可以是IP地址域醇,但不能帶端口號。
如果sbt訪問的私服是nexus蓉媳,并且還要向該私服發(fā)布(publish)自己所開發(fā)的成果物譬挚,那么在sbt向私服發(fā)布文件時,可能會出現(xiàn)Broken pipe錯誤酪呻,提示如下:
java.net.SocketException: Broken pipe (Write failed)
其主要原因是减宣,如果SBT對私服進行訪問時,采用了并行請求的訪問機制玩荠,此時nexus的身份認證尚未完成漆腌,就處理上傳成果物文件請求贼邓,會導致并行上傳失敗,出現(xiàn)上述的 Broken pipe錯誤闷尿。問題及解決方法塑径,詳見:https://support.sonatype.com/hc/en-us/articles/360000228868-Artifact-uploads-fail-with-broken-pipe-errors
#關(guān)閉gigahorse,防止nexus 的Broken pipe錯誤
-Dsbt.gigahorse=false
#私服的身份認證配置文件位置
-Dsbt.boot.credentials=/Users/Shared/App4Deve/sbt/conf/credentials.sbt
#允許覆蓋內(nèi)置的repositories
-Dsbt.override.build.repos=true
#給出自定義repositories配置文件
-Dsbt.repository.config=/Users/Shared/App4Deve/sbt/conf/.repositories
但這里還有一個簡單粗暴的解決方法填具,就是關(guān)閉sbt的并行上傳機制统舀。就是“sbt運行參數(shù)配置文件”中設(shè)置sbt 的gigahorse參數(shù)為false。此時的“sbt運行參數(shù)配置文件”如下所示:
以上設(shè)置完成后劳景,sbt 的啟動運行就會很快绑咱,尤其在使用私服的情況下會更快。簡單總結(jié)為兩步:
通過-Dsbt.override.build.repos=true的參數(shù)設(shè)置枢泰,允許開發(fā)者用自定義repositories配置覆蓋系統(tǒng)缺省配置描融。
用-Dsbt.repository.config參數(shù)來指定自定義repositories配置在哪個文件中,也就是全路徑名所表示的文件位置衡蚂。
三窿克、Intellj IDEA中sbt的使用設(shè)置
如果我們學會了如何更改sbt的設(shè)置,并在控制臺(console)的命令行下驗證了sbt的設(shè)置的成功毛甲。那么在IDEA對sbt的設(shè)置思路也是一樣的年叮,主要是找到如何設(shè)置這些參數(shù)的地方玻募,但是還存在其他的一些問題,導致sbt 在IDEA中無法運行跃惫。
首先艾栋,我們要知道,IDEA中已經(jīng)打包了(bundled)某個版本的sbt先较,不同IDEA的版本打包了不同版本的sbt闲勺,目前我所使用的IDEA版本是2019.3扣猫,該版本IDEA所打包是sbt1.2.8,而我下載sbt則是較新的sbt1.3.8债朵。為了在操作系統(tǒng)控制臺(console)中所使用sbt與IDEA中所使用的sbt能夠利用同一套配置好的構(gòu)建成果物庫(repositores)的配置文件和“私服”的認證(credentials)信息文件序芦,我們需要更改IDEA的配置1粤咪。具體操作菜單路徑如下:
Preferences|Build,Execution,Deployment|Build Tools|sbt
此時看到的IDEA設(shè)置界面如下:
1.IEDA更改配置有兩個級別寥枝,一個是全局(global)級別配置,一個是項目(project)級別設(shè)置某筐。全局級別配置用來設(shè)置所有項目都會用到的通用配置項(General Settings)冠跷,全局級別配置相當于這些通用配置項在項目級別配置中的缺省值蜜托。項目級別配置中這些通用配置項的配置值會覆蓋全局級別配置,項目級別配置中還有項目專有的配置項幔托。無論全局級別配置還是項目級別配置重挑,二者都是通過設(shè)置Preferencs開始棠涮,區(qū)別在于設(shè)置Preferences的時機,全局配置是未打開任何工程所做的Preferences配置玻粪,而項目級別Preferences則是打開具體項目后所的Preferences配置劲室。
在General Settings|Lancher(sbt-lanuch.jar)下结窘,選擇Custom,給定所要使用的sbt的sbt-launch.jar文件隧枫,這就完成了對sbt自定義選擇設(shè)置谓苟。
其次涝焙,我們需要了解的是孕暇,“sbt運行參數(shù)配置文件”——前面所說的sbtconfig.txt文件(windows)或sbtopts文件(linux\mac os)中所配置的sbt 運行參數(shù)對于在IDEA中運行sbt并不起作用妖滔,因為在操作系統(tǒng)控制臺(console)中運行sbt 命令實際上是一個shell腳本,現(xiàn)在把它叫做sbt 命令腳本它調(diào)用了操作系統(tǒng)JVAV_HOME中的JRE來運行sbt-launch.jar沮翔,即:java -jar sbt-launch.jar? [運行參數(shù)] 鉴竭,而[運行參數(shù)]則是sbt 命令腳本從“sbt運行參數(shù)配置文件”中讀取出來的岸浑。在IDEA中,是由IDEA使用配置的JRE來運行sbt-launch.jar文件璧眠,因此需要在VM參數(shù)中指定先前在“sbt運行參數(shù)配置文件”所指定的運行參數(shù)责静。具體操做菜單路徑還是:
Preferences|Build,Execution,Deployment|Build Tools|sbt
界面如下:
?
點擊黃色小圈部分灾螃,可以展開一個更大的輸入窗口來輸入運行參數(shù)腰鬼,示例如下:
?
當配置到這一步塑荒,你可能會認為,IDEA中使用sbt與在操作系統(tǒng)控制臺中使用sbt是一樣的效果齿税。但現(xiàn)實沒有那么豐滿,你會發(fā)現(xiàn)有些版本的IDEA中(目前為止拧篮,IDEA 2019.1.3以后版本均會出現(xiàn)問題)仍然使用不了sbt,仔細閱讀錯誤提示信息會提示org.jetbrains組織所開發(fā)三個sbt插件(plugin)下載失敗缺虐。這三個插件分別是:
插件作用
sbt-idea-compiler-indices幫助IDEA對.calss文件建立索引志笼,提高性能
sbt-idea-shell在IDEA中提供sbt操作的命令行控制臺
sbt-structure-extractor幫助IDEA導入sbt項目時進行目錄結(jié)構(gòu)轉(zhuǎn)換
其中把篓,后兩個是IDEA所必須的韧掩。
Jetbrains目前還沒有把這三個插件適應不同版本scala和sbt的所有版本成的插件果物都放在中央倉中窖铡,所以無論如何都無法下載到這些插件的合適版本(適應你所使用的scala與sbt 版本)费彼。因此,Jetbrains把這些插件IDEA打包在一起雇卷,安裝在本地磁盤上关划,IDEA在運行sbt前翘瓮,通過其他方式設(shè)定了sbt repositories條目(比如,在classpath中配置sbt啟動設(shè)置调榄,這一點我沒有深入研究)振峻,指向了這些插件所在的本地磁盤目錄择份,這樣,IDEA運行sbt時就能夠找到適合IDEA所打包(bundled)的sbt版本所使用的插件凤价。但是,由于我們通過VM參數(shù) -Dsbt.override.build.repos=true 的設(shè)置富蓄,覆蓋了sbt原有的repositories設(shè)置慢逾,此時,IDEA運行sbt就會找不到這些本地插件所在的repository口注,又無法遠程下載寝志,故而會出現(xiàn)上述的錯誤策添。這是IDEA已知的問題(issue)唯竹,感興趣可以看Jetbrains官方文檔:https://youtrack.jetbrains.com/issue/SCL-15261。
知道了這個原因后物臂,我們就很好解決這個問題鹦聪,那我們就有多種方式來解決這個問題蒂秘,包括但不限于:
修改我們的repositories配置文件,添加一個repository配置項规丽,重新指向IDEA所安裝的本地插件庫2撇贺。
如果repositories配置文件中有local這個內(nèi)置配置條目松嘶,則不需要修改repositories配置文件,將插件庫直接拷貝到ivy HOME路徑下的local目錄中即可巢音。
2.不同操作系統(tǒng)中官撼,安裝位置可能不同,這里不在一一列舉掠哥。這個插件庫的目錄名稱為:org.jetbrains,通過搜索找到該目錄即可续搀。如果需要拷貝顷链,必須將org.jetbrains目錄整體拷貝屈梁,不能只拷貝其下的子目錄在讶,否則將無法匹配layout格式,因為丟失了成果物開發(fā)組織的路徑信息(這里是org.jetbrains)革答。
將插件庫拷貝到一個新目錄中曙强,并修改repositories配置文件碟嘴,增加一個repository配置項,指向這個新目錄错沃。
[repositories]
#idea所需的org.jetbrains本地插件庫
local-ideaSbtPluginRepo-path: file:////Users/learn/.sbtlocal/local/,[organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
#國內(nèi)maven庫鏡像的本地“私服”的代理
local-mavenRepo-server: http://localhost:8088/artifactory/aliyun/
#國內(nèi)maven庫鏡像
maven-china: https://maven.aliyun.com/repository/public
為展示知識點枢析,我采用最后一種方式醒叁,此時repositories配置文件示例如下:
在這個示例中,我把org.jetbrains插件庫拷貝到了如下目錄:
/Users/learn/.sbtlocal/local/
到了這一步断傲,在IDEA中就可以啟動運行sbt了嗎智政?答案可沒那么樂觀续捂,intellj IDEA作為sbt的集成者,其步伐總會落后一步劫拗,最大的可能就是因為IDEA所帶的sbt-idea-compiler-indices插件與你當前所使用的scala與sbt 版本不匹配而導致sbt在intellj中無法工作矾克。好在這個插件的功能不是必須的胁附,可以在IDEA中關(guān)閉。關(guān)閉的操作路徑及界面如下:
Preferences|Build,Execution,Deployment|Compiler|Scala Compiler|ByteCode Indices
?
將上述界面中紅圈所示的index ?.class files選項關(guān)閉即可州袒。
至此郎哭,我解決了我目前所遇到的sbt的所有棘手問題
-----------全文完菇存。