Skip to content

Commit c9f782c

Browse files
committed
Fix incremental compilation issue with TASTY files
We cannot delegate to the default class file manager. In some situations, when an incremental compilation fails, we need to restore tasty files from previous compilation. These tasty files must be backed up in a tmp directory which is different from the one of the default class file manager.
1 parent 35c464f commit c9f782c

File tree

2 files changed

+74
-25
lines changed

2 files changed

+74
-25
lines changed

sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -112,33 +112,20 @@ object DottyPlugin extends AutoPlugin {
112112
* corresponding .tasty or .hasTasty file is also deleted.
113113
*/
114114
def dottyPatchIncOptions(incOptions: IncOptions): IncOptions = {
115-
val inheritedNewClassFileManager = ClassFileManagerUtil.getDefaultClassFileManager(incOptions)
116-
val tastyFileManager = new ClassFileManager {
117-
private[this] val inherited = inheritedNewClassFileManager
118-
119-
def delete(classes: Array[File]): Unit = {
120-
val tastySuffixes = List(".tasty", ".hasTasty")
121-
inherited.delete(classes flatMap { classFile =>
122-
if (classFile.getPath endsWith ".class") {
123-
val prefix = classFile.getAbsolutePath.stripSuffix(".class")
124-
tastySuffixes.map(suffix => new File(prefix + suffix)).filter(_.exists)
125-
} else Nil
126-
})
127-
}
115+
val tastyFileManager = new TastyFileManager
128116

129-
def generated(classes: Array[File]): Unit = {}
130-
def complete(success: Boolean): Unit = {}
131-
}
117+
// Once sbt/zinc#562 is fixed, can be:
118+
// val newExternalHooks =
119+
// incOptions.externalHooks.withExternalClassFileManager(tastyFileManager)
132120
val inheritedHooks = incOptions.externalHooks
133-
val externalClassFileManager: Optional[ClassFileManager] = Option(inheritedHooks.getExternalClassFileManager.orElse(null)) match {
134-
case Some(prevManager) =>
135-
Optional.of(WrappedClassFileManager.of(prevManager, Optional.of(tastyFileManager)))
136-
case None =>
137-
Optional.of(tastyFileManager)
138-
}
139-
140-
val hooks = new DefaultExternalHooks(inheritedHooks.getExternalLookup, externalClassFileManager)
141-
incOptions.withExternalHooks(hooks)
121+
val external = Optional.of(tastyFileManager: ClassFileManager)
122+
val prevManager = inheritedHooks.getExternalClassFileManager
123+
val fileManager: Optional[ClassFileManager] =
124+
if (prevManager.isPresent) Optional.of(WrappedClassFileManager.of(prevManager.get, external))
125+
else external
126+
val newExternalHooks = new DefaultExternalHooks(inheritedHooks.getExternalLookup, fileManager)
127+
128+
incOptions.withExternalHooks(newExternalHooks)
142129
}
143130

144131
override val globalSettings: Seq[Def.Setting[_]] = Seq(
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package dotty.tools.sbtplugin
2+
3+
import java.io.File
4+
import java.nio.file.Files
5+
6+
import sbt.io.IO
7+
import xsbti.compile.ClassFileManager
8+
9+
import scala.collection.mutable
10+
11+
12+
/** A class file manger that prunes .tasty and .hasTasty as needed.
13+
*
14+
* This makes sure that, when a .class file must be deleted, the
15+
* corresponding .tasty or .hasTasty file is also deleted.
16+
*
17+
* This code is adapted from Zinc `TransactionalClassFileManager`.
18+
* We need to duplicate the logic since forwarding to the default class
19+
* file manager doesn't work: we need to backup tasty files in a different
20+
* temporary directory as class files.
21+
*/
22+
final class TastyFileManager extends ClassFileManager {
23+
private[this] val tempDir = Files.createTempDirectory("backup").toFile
24+
25+
private[this] val generatedTastyFiles = new mutable.HashSet[File]
26+
private[this] val movedTastyFiles = new mutable.HashMap[File, File]
27+
28+
override def delete(classes: Array[File]): Unit = {
29+
val toBeBackedUp = tastyFiles(classes)
30+
.filter(t => t.exists && !movedTastyFiles.contains(t) && !generatedTastyFiles(t))
31+
for (c <- toBeBackedUp)
32+
movedTastyFiles.put(c, move(c))
33+
IO.deleteFilesEmptyDirs(classes)
34+
}
35+
36+
override def generated(classes: Array[File]): Unit =
37+
generatedTastyFiles ++= tastyFiles(classes)
38+
39+
override def complete(success: Boolean): Unit = {
40+
if (!success) {
41+
IO.deleteFilesEmptyDirs(generatedTastyFiles)
42+
for ((orig, tmp) <- movedTastyFiles) IO.move(tmp, orig)
43+
}
44+
IO.delete(tempDir)
45+
}
46+
47+
private def tastyFiles(classes: Array[File]): Array[File] = {
48+
val tastySuffixes = List(".tasty", ".hasTasty")
49+
classes.flatMap { classFile =>
50+
if (classFile.getPath.endsWith(".class")) {
51+
val prefix = classFile.getAbsolutePath.stripSuffix(".class")
52+
tastySuffixes.map(suffix => new File(prefix + suffix)).filter(_.exists)
53+
} else Nil
54+
}
55+
}
56+
57+
private def move(c: File): File = {
58+
val target = File.createTempFile("sbt", ".tasty", tempDir)
59+
IO.move(c, target)
60+
target
61+
}
62+
}

0 commit comments

Comments
 (0)