sbt启动机制、配置优化及与Intellij IDEA的集成

一、sbt启动机制理解与启动缓慢的原因分析

众所周知,不加修改,直接使用sbt,那么sbt“启动(launch)”会非常慢,甚至会失败,尤其当初次运行时,本地尚无缓存,需要大量加载自身依赖文件的情况下更是如此。

主要原因是:不加修改的情况下,sbt在启动时会使用启动器(sbt-launch.jar)内置sbt.boot.properties文件(在bt-launch.jar内的sbt/目录下)中所指定的构建成果物库(build Artifact Repository)。这些缺省构建成果物库都是国外网站,在国际网络通道日益拥堵的今天,初次下载速度就会格外缓慢。

又因为这些配置是在sbt启动时使用和加载,对于所有sbt项目而言,它们又是全局(global)配置,会影响到所有的sbt 项目。因此,我们需要了解这些配置及其作用,sbt-launch.jar中内置sbt.boot.properties文件中构建成果物库的配置片段如下:

[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]表明它是构建成果物库所在的“配置段”,其下内容都是关于“repository”的配置信息,直到遇到下一个“配置段”,比如[boot]段。

每个repository配置条目都由俩个部分组成(冒号分割)即:

库名: 库配置项

各部分如何配置如下:

库名,是为了跟踪调试时给使用者查看所用,使用者可以根据方便自己的记忆来进行设置,但库名不能重复

库配置,给出了库所在的位置(Url),成果物文件所在的路径布局(Layout)格式,以及这个库在什么阶段使用。格式为:

       位置Url, 库文件路径布局(Layout)格式, 使用阶段指示符

其中:

   位置Url必须给出。

  库文件路径布局(Layout)格式的用途是给Sbt将想要下载的库文件描述定义(比如具体依赖的成果物的组织、名字、版本等)格式 化成满足Maven或Ivy成果物库中的路径布局(layout)的路径(path)信息,并与与库的Url地址结合,生成具体的依赖相关文件(比如pom\ivy文件和Jar文件)的完整下载地址。所谓库文件路径布局(Layout),对于库服务器而言,就是存储成果物文件的存储路径安排。对于访问者而言,就对成果物文件在库服务器URL下的访问路径规约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启动期间”使用,还是后续加载项目(project)所用依赖时一直使用,该指示符用bootOnly来指示。bootOnly表明该库只在sbt启动过程中使用,用来下载sbt本身(或所嵌入IDE)所需的插件。在sbt成功启动后,我们可以用sbt命令查看sbt当前所使用的库。其中,show  fullResolvers 命令能够查看包括在项目(project)的build.sbt文件中用resolvers配置的项目级构建成果物库,和全局级(global)的构建成果物库(比如,上面所提到的sbt.boot.properties文件所配置的);而show resolvers命令则用来列出项目级构建成果物库。sbt在加载依赖(dependency)时,会按照配置文件中出现的顺序依次地尝试从所配置的库来加载依赖文件 。如果某个比较慢的远程库在排在前面,就会使得整个加载速度变慢,如果远程库出现了超时,则会从下一个远程库中加载,如果这个库加载速度较快,那么整个加载过程就会变快。在网络不稳定时(比如国外网站超时机率高)的时候,就会出现某次启动加载速度快,某次启动加载速度慢的情况。不明白这种机制,会导致我们的配置某次好像很成功,启动加载速度很快,但给别人用的时候就又变慢了的情况。

上面我们介绍了sbt构建成果物库在启动时(也是全局)的配置,值得注意的是,sbt在该配置段中设置了两个“内置的”配置条目,就是在上面配置片段中的local和maven-central,通过show  fullResolvers 命令可以看到:

maven-central配置条目所代表的完整配置是:

public: https://repo1.maven.org/maven2/

它指向了Maven中央仓,它不是bootOnly因此在sbt启动和后续所有项目中都会用到,因此,因为前面所说的原因,忘记删除这个配置条目,会导致国内开发者sbt启动和后续加载依赖出现极为缓慢的可能。

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位置就是操作系统登录账号的home目录下的 .ivy2目录,如果不存在,sbt会自动创建,对于linux和mac os来说,ivy home路径就是 $HOME/.ivy2/,而local库的位置则是:$HOME/.ivy2/local,这个local子目录需要使用者自己创建,再在其中放入所有本地依赖库。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)时只使用国内的构建成果物库(build Artifact Repository)镜像服务器,比如:

阿里云:https://maven.aliyun.com/repository/public

或华为云:https://mirrors.huaweicloud.com/repository/maven/

或者,让SBT在启动时使用开发团队自己的搭建的构建成果物库,即所谓的“私服”。用来搭建“私服”的软件产品包括收费的商业产品sonatype nexus或JFrog Artifactory,以及开源免费的apache Archiva等,如何搭建私服,本文不再详述。

2.实现方法

上述思路的实现,需要更改sbt的启动设置,如果前面所述,这些设置对所有sbt项目都有作用,也就是修改全局设置,sbt官方文档:

https://www.scala-sbt.org/1.x/docs/Launcher-Configuration.html

给出了三种修改sbt 启动设置的方式,分别是:

修改sbt-launch.jar中内置的sbt.boot.properties文件。

另外创建一个sbt.boot.properties文件,并放在系统所配置的java  classpath之中。

创建专项的配置文件,比如专门用于存放repositorys配置文件,并在sbt命令行中用-Dsbt.override.build.repos参数来指定覆盖系统默认的库配置,并用-Dsbt.repository.config参数指来定自定义库配置文件所在位置。完整的sbt 命令示例如下:

sbt  -Dsbt.override.build.repos=true  -Dsbt.repository.config=/.repositories

这三种方式的优先级依次升高,对我而言,比较喜欢第三种方式,主要是它的优先级高,而且比较灵活。第一种方式的缺点是需要修改sbt-launch.jar文件,一旦sbt 本身升级,就需要重新制作sbt-launch.jar文件,比较麻烦。第二种方式的缺点是sbt.boot.properties不能设定所有需要的全局配置,比如,repository服务器的身份认证信息就无法用sbt.boot.properties文件来设置。

那么使用第三种方式是不是意味着每次使用sbt都要在命令行中输入一串长长的配置参数呢?答案是乐观的。我们可以在sbt安装目录下的conf/目录中的“sbt运行参数配置文件”中给出这些自定义的配置参数,而且还可以给出更多的运行配置选项。这样,在命令行中输入sbt时,就会自动按照这些设置运行。在windows系统中,这个配置文件是的名称是sbtconfig.txt,在linux和mac os系统中,这个配置文件的名称是sbtopts

顺便说一下,可以在sbt运行工作目录的日志中查看sbt的启动运行日志文件来查看这些运行配置是否生效,缺省情况下,这个启动运行日志文件的全路径为:

/${操作系统登录用户Home目录}/.sbt/boot/update.log

下面给出的是sbtopts中部分sbt运行参数配置示例:

#允许覆盖内置的repositories

-Dsbt.override.build.repos=true

#给出自定义repositories配置文件

-Dsbt.repository.config=/Users/Shared/App4Deve/sbt/conf/.repositories

如果理解了本文第一部分中的内容,剩下的工作很简单,就是创建一个仅存放[repositories]配置段的文件,文件名没有要求,下面给出该文件的一个示例:

[repositories]

#国内maven库镜像的本地“私服”的代理

local-mavenRepo-server: http://localhost:8088/artifactory/aliyun/

#国内maven库镜像

maven-china: https://maven.aliyun.com/repository/public

如果你所使用的“私服”需要身份认证,那么将私服的身份认证信息存放在一个文件中,“身份认证信息文件”的名称没有要求,只要在“sbt运行参数配置文件(sbtopts或sbtconfig.txt文件)”中指定该身份认证信息文件位置的全路径即可,此时的“sbt运行参数配置文件”内容如下:

#私服的身份认证配置文件位置

-Dsbt.boot.credentials=/Users/Shared/App4Deve/sbt/conf/credentials.sbt

#允许覆盖内置的repositories

-Dsbt.override.build.repos=true

#给出自定义repositories配置文件

-Dsbt.repository.config=/Users/Shared/App4Deve/sbt/conf/.repositories

而“身份认证信息文件”的内容则很简单,示例如下:

realm=Artifactory Realm

host=localhost

user=developer

password=123#@!

此时,sbt 已经可以访问“私服”,但还需要注意以下几点:

不同服务器的realm不同,范例所示的realm是JFrog Artifatory的realm,nexus的realm则是Sonatype Nexus Repository Manager,其他服务器的realm需要自己查找。

host可以是服务器域名,也可以是IP地址,但不能带端口号

如果sbt访问的私服是nexus,并且还要向该私服发布(publish)自己所开发的成果物,那么在sbt向私服发布文件时,可能会出现Broken pipe错误,提示如下:

java.net.SocketException: Broken pipe (Write failed)

其主要原因是,如果SBT对私服进行访问时,采用了并行请求的访问机制,此时nexus的身份认证尚未完成,就处理上传成果物文件请求,会导致并行上传失败,出现上述的 Broken pipe错误。问题及解决方法,详见:https://support.sonatype.com/hc/en-us/articles/360000228868-Artifact-uploads-fail-with-broken-pipe-errors

#关闭gigahorse,防止nexus 的Broken pipe错误

-Dsbt.gigahorse=false

#私服的身份认证配置文件位置

-Dsbt.boot.credentials=/Users/Shared/App4Deve/sbt/conf/credentials.sbt

#允许覆盖内置的repositories

-Dsbt.override.build.repos=true

#给出自定义repositories配置文件

-Dsbt.repository.config=/Users/Shared/App4Deve/sbt/conf/.repositories

但这里还有一个简单粗暴的解决方法,就是关闭sbt的并行上传机制。就是“sbt运行参数配置文件”中设置sbt 的gigahorse参数为false。此时的“sbt运行参数配置文件”如下所示:

以上设置完成后,sbt 的启动运行就会很快,尤其在使用私服的情况下会更快。简单总结为两步:

过-Dsbt.override.build.repos=true的参数设置,允许开发者用自定义repositories配置覆盖系统缺省配置。

-Dsbt.repository.config参数来指定自定义repositories配置在哪个文件中,也就是全路径名所表示的文件位置。

三、Intellj IDEA中sbt的使用设置

如果我们学会了如何更改sbt的设置,并在控制台(console)的命令行下验证了sbt的设置的成功。那么在IDEA对sbt的设置思路也是一样的,主要是找到如何设置这些参数的地方,但是还存在其他的一些问题,导致sbt 在IDEA中无法运行。

首先,我们要知道,IDEA中已经打包了(bundled)某个版本的sbt,不同IDEA的版本打包了不同版本的sbt,目前我所使用的IDEA版本是2019.3,该版本IDEA所打包是sbt1.2.8,而我下载sbt则是较新的sbt1.3.8。为了在操作系统控制台(console)中所使用sbt与IDEA中所使用的sbt能够利用同一套配置好的构建成果物库(repositores)的配置文件和“私服”的认证(credentials)信息文件,我们需要更改IDEA的配置1。具体操作菜单路径如下:

Preferences|Build,Execution,Deployment|Build Tools|sbt

此时看到的IDEA设置界面如下:


1.IEDA更改配置有两个级别,一个是全局(global)级别配置,一个是项目(project)级别设置。全局级别配置用来设置所有项目都会用到的通用配置项(General Settings),全局级别配置相当于这些通用配置项在项目级别配置中的缺省值。项目级别配置中这些通用配置项的配置值会覆盖全局级别配置,项目级别配置中还有项目专有的配置项。无论全局级别配置还是项目级别配置,二者都是通过设置Preferencs开始,区别在于设置Preferences的时机,全局配置是未打开任何工程所做的Preferences配置,而项目级别Preferences则是打开具体项目后所的Preferences配置。

在General Settings|Lancher(sbt-lanuch.jar)下,选择Custom,给定所要使用的sbt的sbt-launch.jar文件,这就完成了对sbt自定义选择设置。

其次,我们需要了解的是,“sbt运行参数配置文件”——前面所说的sbtconfig.txt文件(windows)或sbtopts文件(linux\mac os)中所配置的sbt 运行参数对于在IDEA中运行sbt并不起作用,因为在操作系统控制台(console)中运行sbt 命令实际上是一个shell脚本,现在把它叫做sbt 命令脚本它调用了操作系统JVAV_HOME中的JRE来运行sbt-launch.jar,即:java -jar sbt-launch.jar  [运行参数] ,而[运行参数]则是sbt 命令脚本从“sbt运行参数配置文件”中读取出来的。在IDEA中,是由IDEA使用配置的JRE来运行sbt-launch.jar文件,因此需要在VM参数中指定先前在“sbt运行参数配置文件”所指定的运行参数。具体操做菜单路径还是:

Preferences|Build,Execution,Deployment|Build Tools|sbt

界面如下:

点击黄色小圈部分,可以展开一个更大的输入窗口来输入运行参数,示例如下:

当配置到这一步,你可能会认为,IDEA中使用sbt与在操作系统控制台中使用sbt是一样的效果。但现实没有那么丰满,你会发现有些版本的IDEA中(目前为止,IDEA 2019.1.3以后版本均会出现问题)仍然使用不了sbt,仔细阅读错误提示信息会提示org.jetbrains组织所开发三个sbt插件(plugin)下载失败。这三个插件分别是:

插件作用

sbt-idea-compiler-indices帮助IDEA对.calss文件建立索引,提高性能

sbt-idea-shell在IDEA中提供sbt操作的命令行控制台

sbt-structure-extractor帮助IDEA导入sbt项目时进行目录结构转换

其中,后两个是IDEA所必须的。

Jetbrains目前还没有把这三个插件适应不同版本scala和sbt的所有版本成的插件果物都放在中央仓中,所以无论如何都无法下载到这些插件的合适版本(适应你所使用的scala与sbt 版本)。因此,Jetbrains把这些插件IDEA打包在一起,安装在本地磁盘上,IDEA在运行sbt前,通过其他方式设定了sbt repositories条目(比如,在classpath中配置sbt启动设置,这一点我没有深入研究),指向了这些插件所在的本地磁盘目录,这样,IDEA运行sbt时就能够找到适合IDEA所打包(bundled)的sbt版本所使用的插件。但是,由于我们通过VM参数 -Dsbt.override.build.repos=true 的设置,覆盖了sbt原有的repositories设置,此时,IDEA运行sbt就会找不到这些本地插件所在的repository,又无法远程下载,故而会出现上述的错误。这是IDEA已知的问题(issue),感兴趣可以看Jetbrains官方文档:https://youtrack.jetbrains.com/issue/SCL-15261。

知道了这个原因后,我们就很好解决这个问题,那我们就有多种方式来解决这个问题,包括但不限于:

修改我们的repositories配置文件,添加一个repository配置项,重新指向IDEA所安装的本地插件库2。

如果repositories配置文件中有local这个内置配置条目,则不需要修改repositories配置文件,将插件库直接拷贝到ivy HOME路径下的local目录中即可。

2.不同操作系统中,安装位置可能不同,这里不在一一列举。这个插件库的目录名称为:org.jetbrains,通过搜索找到该目录即可。如果需要拷贝,必须将org.jetbrains目录整体拷贝,不能只拷贝其下的子目录,否则将无法匹配layout格式,因为丢失了成果物开发组织的路径信息(这里是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]

#国内maven库镜像的本地“私服”的代理

local-mavenRepo-server: http://localhost:8088/artifactory/aliyun/

#国内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中关闭。关闭的操作路径及界面如下:

Preferences|Build,Execution,Deployment|Compiler|Scala Compiler|ByteCode Indices

将上述界面中红圈所示的index  .class files选项关闭即可。

至此,我解决了我目前所遇到的sbt的所有棘手问题

-----------全文完。

你可能感兴趣的:(sbt启动机制、配置优化及与Intellij IDEA的集成)