Skip to content

Fix incremental compilation not working after restarting sbt #2134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package dotty.tools.dotc
package dotty.tools
package dotc
package sbt

import scala.annotation.tailrec
Expand All @@ -24,7 +25,7 @@ private[sbt] trait ThunkHolder {
* It will be forced by the next call to `forceThunks()`
*/
def lzy[T <: AnyRef](t: => T): api.Lazy[T] = {
val l = SafeLazy(() => t)
val l = SafeLazyWrapper(() => t)
thunks += l
l
}
Expand All @@ -37,10 +38,69 @@ private[sbt] trait ThunkHolder {
* see https://github.com/sbt/zinc/issues/114
*/
def strict2lzy[T <: AnyRef](t: T): api.Lazy[T] =
SafeLazy.strict(t)
SafeLazyWrapper.strict(t)
}

// TODO: Use xsbti.SafeLazy once https://github.com/sbt/zinc/issues/113 is fixed
/** Wrapper around SafeLazy implementations.
*
* `xsbti.SafeLazy` is part of sbt but it is not part of the `interface` jar
* that dotty depends on, therefore we can only access it by reflection,
* and this will only succeed when dotty is run by sbt (otherwise
* `xsbti.SafeLazy` won't be on the classpath at all).
*
* For testing purposes, we still want to be able to run the sbt phases outside
* of sbt, using `-Yforce-sbt-phases` and `-Ydump-sbt-inc`, therefore we
* provide a copy of SafeLazy in `dotty.tools.dotc.sbt.SafeLazy` that we use
* when `xsbti.SafeLazy` is unavailable.
*
* This raises a question: why bother with `xsbti.SafeLazy` if we have our own
* version anyway? Because sbt uses Java serialization to persist the output of
* the incremental compilation analysis when sbt is stopped and restarted. If
* we used `dotty.tools.dotc.sbt.SafeLazy` with sbt, deserialization would fail
* and every restart of sbt would require a full recompilation.
*
* Note: this won't be needed once we switch to zinc 1.0 where `SafeLazy` becomes
* part of the `interface` jar, see https://github.com/sbt/zinc/issues/113
*/
private object SafeLazyWrapper {

@sharable private[this] val safeLazy =
try {
Class.forName("xsbti.SafeLazy")
} catch {
case e: ClassNotFoundException =>
null
}

@sharable private[this] val safeLazyApply =
if (safeLazy != null)
safeLazy.getMethod("apply", classOf[xsbti.F0[_]])
else
null
@sharable private[this] val safeLazyStrict =
if (safeLazy != null)
safeLazy.getMethod("strict", classOf[Object])
else
null

def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] =
if (safeLazyApply != null)
safeLazyApply
.invoke(null, new xsbti.F0[T] { def apply() = eval() })
.asInstanceOf[xsbti.api.Lazy[T]]
else
SafeLazy(eval)

def strict[T <: AnyRef](value: T): xsbti.api.Lazy[T] =
if (safeLazyStrict != null)
safeLazyStrict
.invoke(null, value)
.asInstanceOf[xsbti.api.Lazy[T]]
else
SafeLazy.strict(value)
}

// Adapted from https://github.com/sbt/sbt/blob/0.13/compile/api/src/main/scala/xsbti/SafeLazy.scala
private object SafeLazy {
def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] =
new Impl(eval)
Expand Down