如何在不同机器上重用已编译的源代码

时间:2019-01-07 03:42:20

标签: scala sbt

为了加快我们的开发工作流程,我们拆分了测试,并在多个代理上并行运行每个部分。但是,编译测试源似乎大部分时间都在测试步骤中。

为避免这种情况,我们使用sbt test:compile预编译测试,并使用已编译的目标构建docker映像。

此后,每个代理程序都使用此映像来运行测试。但是,即使已编译的类存在,它似乎仍会重新编译测试和应用程序源。

有没有办法使sbt使用现有的已编译目标?

更新:提供更多背景信息

该问题严格涉及scala和sbt(因此带有sbt标签)。

我们的CI流程分为多个阶段。它大致是这样的。

  • 阶段1:使用sbt compile使用SBT将Scala项目编译为Java bitecode,我们使用sbt test:compile在同一测试中编译测试源。将目标捆绑在docker映像中并推送到远程存储库

  • 阶段2:我们使用多个代理来拆分和并行运行测试。 测试是从构建的docker映像运行的,因此环境是 相同。但是,运行sbt test会导致项目重新编译,甚至 通过编译的位码存在。

为清楚起见,我基本上想在一台计算机上进行编译,而无需重新编译就在另一台计算机上运行已编译的测试源

更新

我不认为https://stackoverflow.com/a/37440714/8261是同一个问题,因为与它不同的是,我不装载卷也不在主机上构建。一切都在docker中进行编译和运行,但分为两个构建阶段。因此,文件的修改时间和路径保持不变。

调试输出具有类似的内容

Initial source changes: 
    removed:Set()
    added: Set()
    modified: Set()
Invalidated products: Set(/app/target/scala-2.12/classes/Class1.class, /app/target/scala-2.12/classes/graph/Class2.class, ...)
External API changes: API Changes: Set()
Modified binary dependencies: Set()
Initial directly invalidated classes: Set()

Sources indirectly invalidated by:
    product: Set(/app/Class4.scala, /app/Class5.scala, ...)
    binary dep: Set()
    external source: Set()
All initially invalidated classes: Set()
All initially invalidated sources:Set(/app/Class4.scala, /app/Class5.scala, ...)
Recompiling all 304 sources: invalidated sources (266) exceeded 50.0% of all sources
Compiling 302 Scala sources and 2 Java sources to /app/target/scala-2.12/classes ...

它没有初始源更改,但是产品无效。

更新:可复制的最小项目

我创建了一个最小的sbt项目来重现该问题。 https://github.com/pulasthibandara/sbt-docker-recomplile

如您所见,在构建阶段之间没有任何变化,除了在新步骤(新容器)的第二阶段中运行。

3 个答案:

答案 0 :(得分:2)

尽管https://stackoverflow.com/a/37440714/8261指出了正确的方向,但根本的问题和解决方案却有所不同。

问题

当SBT在docker构建的不同阶段上运行时,似乎可以重新编译所有内容。这是因为docker压缩在每个阶段创建的图像,从而从源中剥离lastModifiedDate的毫秒部分。

在确定源代码是否已更改时,SBT取决于lastModifiedDate,并且由于其不同(毫秒部分),因此生成会触发完全重新编译。

解决方案

我通过在docker文件中设置SBT_OPTS env变量来解决了这个问题,例如

ENV SBT_OPTS="${SBT_OPTS} -Dsbt.io.jdktimestamps=true"

https://github.com/sbt/sbt/issues/4168#issuecomment-417658294已使用此替代方法进行了更新。

答案 1 :(得分:1)

使用SBT:

我认为这里已经有了答案:https://stackoverflow.com/a/37440714/8261

要完全正确看起来很棘手。祝你好运!

避免SBT:

如果上述方法太困难(即让sbt test认为您的测试类不需要重新编译),则可以避免使用sbt,而可以使用{ {1}}。

如果您可以获取java来记录运行测试套件所使用的sbt命令(例如,使用调试日志记录),则可以直接在测试运行程序代理上运行该命令,这将完全排除java重新编译内容。

(如果类路径太长而无法在外壳程序中作为命令行参数传递,则可能需要将sbt命令写入脚本文件。我以前不得不在大型项目中这样做)

与上面的方法相比,这将是一种更加骇人听闻的方法,但可能更快地开始工作。

答案 2 :(得分:0)

一个可能的解决方案可能是定义自己的sbt任务而没有依赖项,或者尝试更改测试任务。例如,如果这是您的测试框架,则可以创建一个任务来运行JUnit运行器。要定义任务,请参阅《实现任务》上的this

您甚至可以编译发送代码并从所需的任何scala代码的同一任务运行遥控器。从sbt参考手册

  

您可能正在定义自己的任务,或者计划重新定义现有任务。两种方式看起来都一样。使用:=将一些代码与任务键关联起来