我有一个关于SBT看似不必要的重新编译的问题。我有以下场景:我在Docker容器中运行SBT,将带有我的应用程序源代码的卷附加到容器,并以sbt作为入口点启动容器。如果我在该容器内连续运行SBT,它不会重新编译整个应用程序,这很好。
但是,如果我在OS X上本机启动SBT,它会进行完全重新编译。如果之后我在docker中再次启动它,它会再次完成重新编译。这需要很长时间,而且非常烦人。这种行为可能是什么原因?
以下是我在容器中启动SBT的方法:
docker run --name=bla -it --net=host -v /Users/me/.ivy2:/tmp/.ivy2 \
-v /Users/me/.aws/config:/root/.aws/config \
-v /Users/me/.sbt:/root/.sbt \
-v /Users/me/projects/myapp:/src 01ac0b888527 \
/bin/sh -c 'sbt -Dsbt.ivy.home=/tmp/.ivy2 -Divy.home=/tmp/.ivy2 -jvm-debug 5005 -mem 3072'
我的Java,Scala和SBT版本在主机和容器中是相同的。具体来说:Scala 2.11.8,Java 1.8.0_77,SBT 0.13.11
答案 0 :(得分:3)
好的,经过一天的调试后,我找到了解决这个问题的方法。
SBT主要根据以下规则使编译的类失效:
也就是说,
的路径和修改日期必须完全相同前两点很容易实现,因为它只是对接器卷的映射。关键是要映射到与主机完全相同的路径。例如,如果您像我一样使用OS X,那么项目源的路径可能如下所示:/Users/<username>/projects/bla
,因此在您的docker run命令中,您必须执行以下操作:
docker run ... -v /Users/<username>/projects/bla:/Users/<username>/projects/bla ...
您不关心源和常春藤罐的时间戳,因为它们将完全相同(它们是相同的文件)。
关注时间戳的地方是JRE的东西。我使用JRE烘焙(使用sbt-docker
插件)构建了docker镜像,因此我最终读取了本地JRE库的修改日期并在图像中设置了相同的日期:
new mutable.Dockerfile {
...
val hostJreTimestamp = new Date(new File(javaHome + "/jre/lib/rt.jar").lastModified()).toString
val hostJceTimestamp = new Date(new File(javaHome + "/jre/lib/jce.jar").lastModified()).toString
runRaw(s"""touch -d "$hostJreTimestamp" $javaHome/jre/lib/rt.jar""")
runRaw(s"""touch -d "$hostJceTimestamp" $javaHome/jre/lib/jce.jar""")
...
}
当然,JRE也应该安装到与主机上完全相同的路径,例如,如果您曾经从RPM安装Java,则可能会出现问题。我最终下载了服务器JRE(分发为.tar.gz
)并手动将其解压缩到正确的路径。
所以,长话短说,它最终奏效了。没有重新编译,没有长时间等待。我能够从2个主要来源找到相关信息:SBT源代码,特别是此函数:https://github.com/sbt/sbt/blob/0.13/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala#L271,并在build.sbt
中启用SBT调试输出:
logLevel := Level.Debug
incOptions ~= { _.copy(apiDebug = true, relationsDebug = true) }
(准备大量输出)
答案 1 :(得分:0)
这只是猜测。正如rumoku所说,它可能是存储库的一个问题,但我认为这与SBT本身有关。从SBT的角度来看,您正在运行两台不同的机器,它必须在认为文件已更改时进行编译。
我不知道SBT或编译器如何识别Scala和Java的版本,但可能的情况是,即使您在两种环境中都拥有完全相同的Java和Scala版本,SBT认为它们是不同的。这是不同的操作系统。