Skip to content

Commit ff57450

Browse files
committed
Fix incremental compilation not working after restarting sbt
Previously, every time sbt was restarted, `compile` would do a full recompilation. This happened because sbt uses Java serialization to persist the incremental compilation analysis, deserialization was always silently failing because we used to serialize a class from the dotty-compiler jar which is not on the classpath at deserialization time. See the added comments for more details.
1 parent e54b7e3 commit ff57450

File tree

1 file changed

+62
-3
lines changed

1 file changed

+62
-3
lines changed

compiler/src/dotty/tools/dotc/sbt/ThunkHolder.scala

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private[sbt] trait ThunkHolder {
2424
* It will be forced by the next call to `forceThunks()`
2525
*/
2626
def lzy[T <: AnyRef](t: => T): api.Lazy[T] = {
27-
val l = SafeLazy(() => t)
27+
val l = SafeLazyWrapper(() => t)
2828
thunks += l
2929
l
3030
}
@@ -37,10 +37,69 @@ private[sbt] trait ThunkHolder {
3737
* see https://github.com/sbt/zinc/issues/114
3838
*/
3939
def strict2lzy[T <: AnyRef](t: T): api.Lazy[T] =
40-
SafeLazy.strict(t)
40+
SafeLazyWrapper.strict(t)
4141
}
4242

43-
// TODO: Use xsbti.SafeLazy once https://github.com/sbt/zinc/issues/113 is fixed
43+
/** Wrapper around SafeLazy implementations.
44+
*
45+
* `xsbti.SafeLazy` is part of sbt but it is not part of the `interface` jar
46+
* that dotty depends on, therefore we can only access it by reflection,
47+
* and this will only succeed when dotty is run by sbt (otherwise
48+
* `xsbti.SafeLazy` won't be on the classpath at all).
49+
*
50+
* For testing purposes, we still want to be able to run the sbt phases outside
51+
* of sbt, using `-Yforce-sbt-phases` and `-Ydump-sbt-inc`, therefore we
52+
* provide a copy of SafeLazy in `dotty.tools.dotc.sbt.SafeLazy` that we use
53+
* when `xsbti.SafeLazy` is unavailable.
54+
*
55+
* This raises a question: why bother with `xsbti.SafeLazy` if we have our own
56+
* version anyway? Because sbt uses Java serialization to persist the output of
57+
* the incremental compilation analysis when sbt is stopped and restarted. If
58+
* we used `dotty.tools.dotc.sbt.SafeLazy` with sbt, deserialization would fail
59+
* and every restart of sbt would require a full recompilation.
60+
*
61+
* Note: this won't be needed once we switch to zinc 1.0 where `SafeLazy` becomes
62+
* part of the `interface` jar, see https://github.com/sbt/zinc/issues/113
63+
*/
64+
private object SafeLazyWrapper {
65+
66+
private[this] val safeLazy =
67+
try {
68+
Class.forName("xsbti.SafeLazy")
69+
} catch {
70+
case e: ClassNotFoundException =>
71+
null
72+
}
73+
74+
private[this] val safeLazyApply =
75+
if (safeLazy != null)
76+
safeLazy.getMethod("apply", classOf[xsbti.F0[_]])
77+
else
78+
null
79+
private[this] val safeLazyStrict =
80+
if (safeLazy != null)
81+
safeLazy.getMethod("strict", classOf[Object])
82+
else
83+
null
84+
85+
def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] =
86+
if (safeLazyApply != null)
87+
safeLazyApply
88+
.invoke(null, new xsbti.F0[T] { def apply() = eval() })
89+
.asInstanceOf[xsbti.api.Lazy[T]]
90+
else
91+
SafeLazy(eval)
92+
93+
def strict[T <: AnyRef](value: T): xsbti.api.Lazy[T] =
94+
if (safeLazyStrict != null)
95+
safeLazyStrict
96+
.invoke(null, value)
97+
.asInstanceOf[xsbti.api.Lazy[T]]
98+
else
99+
SafeLazy.strict(value)
100+
}
101+
102+
// Adapted from https://github.com/sbt/sbt/blob/0.13/compile/api/src/main/scala/xsbti/SafeLazy.scala
44103
private object SafeLazy {
45104
def apply[T <: AnyRef](eval: () => T): xsbti.api.Lazy[T] =
46105
new Impl(eval)

0 commit comments

Comments
 (0)