enso/project/FixInstrumentsGeneration.scala
2021-02-22 13:32:55 +00:00

141 lines
5.2 KiB
Scala

import sbt.Keys._
import sbt._
object FixInstrumentsGeneration {
/** This task detects any changes in source files of Instruments and forces
* recompilation of all instruments on any change. This is to ensure that the
* Annotation Processor registers all of the instruments.
*
* Without that fix, incremental compilation would not register unchanged
* instruments, leading to runtime errors.
*
* It should be added as a dependency of Compile / compile / compileInputs.
*/
lazy val preCompileTask = Def.task {
val log = streams.value.log
val root = baseDirectory.value
val classFilesDirectory = (Compile / classDirectory).value
val FragileFiles(fragileSources, fragileClassFiles) =
getFragileFiles(root, classFilesDirectory)
val fragileSourcesStore =
streams.value.cacheStoreFactory.make("instruments_fixer")
Tracked.diffInputs(fragileSourcesStore, FileInfo.hash)(
fragileSources.toSet
) { sourcesDiff: ChangeReport[File] =>
if (sourcesDiff.modified.nonEmpty && sourcesDiff.unmodified.nonEmpty) {
val others =
if (sourcesDiff.modified.size >= 2)
s" and ${sourcesDiff.modified.size - 1} others"
else ""
val firstInstrument = sourcesDiff.modified.head
val sourcesMessage = firstInstrument.toString + others
log.warn(
s"Instruments sources ($sourcesMessage) have been changed.\n" +
s"Forcing recompilation of all instruments to maintain " +
s"consistency of generated services files."
)
fragileClassFiles.foreach(_.delete())
}
}
}
/** This task detects if just a subset of the Instruments has been recompiled
* (right now we did not find a way of detecting this before compilation). If
* the Instrumentation state is detected to be inconsistent, current
* compilation is aborted and classfiles are deleted to ensure that when
* re-run Instrumentation will be brought back to a consistent state.
*
* Without that fix, incremental compilation would not register unchanged
* instruments, leading to runtime errors.
*
* It should replace the default `Compile / compile` task in a project.
*/
lazy val patchedCompile = Def.task {
val compilationResult = (Compile / compile).value
val log = streams.value.log
val root = baseDirectory.value
val classFilesDirectory = (Compile / classDirectory).value
val FragileFiles(_, fragileClassFiles) =
getFragileFiles(root, classFilesDirectory)
val fragileClassFilesStore =
streams.value.cacheStoreFactory.make("instruments_classfiles")
Tracked.diffInputs(fragileClassFilesStore, FileInfo.lastModified)(
fragileClassFiles.toSet
) { sourcesDiff: ChangeReport[File] =>
if (sourcesDiff.modified.nonEmpty && sourcesDiff.unmodified.nonEmpty) {
fragileClassFiles.foreach(_.delete())
val projectName = name.value
log.error(
"Truffle Instrumentation is not up to date, " +
"which will lead to runtime errors\n" +
"Fixes have been applied to ensure consistent Instrumentation state, " +
"but compilation has to be triggered again.\n" +
"Please re-run the previous command.\n" +
"(If this for some reason fails, " +
s"please do a clean build of the $projectName project)"
)
throw new RuntimeException("Please re-run last command")
}
}
compilationResult
}
/** Deletes the compiled instrumentation class files, forcing all of them to
* be recompiled.
*
* Since all instruments are recompiled at once, the service state should be
* consistent as all of them will be re-registered.
*/
def cleanInstruments = Def.task {
val log = streams.value.log
val root = baseDirectory.value
val classFilesDirectory = (Compile / classDirectory).value
val FragileFiles(_, fragileClassFiles) =
getFragileFiles(root, classFilesDirectory)
fragileClassFiles.foreach { file =>
if (file.exists()) {
log.info(s"[clean-instruments] Removing $file")
file.delete()
} else {
log.info(s"[clean-instruments] $file was already missing")
}
}
log.info(
"All fragile class files have been deleted. The next compilation " +
"should be forced to recompile all of them, preserving instrumentation " +
"configuration consistency."
)
}
private case class FragileFiles(sources: Seq[File], classFiles: Seq[File])
private def getFragileFiles(
root: File,
classFilesDirectory: File
): FragileFiles = {
val fragileSources =
(file(s"$root/src/main/java/") ** "*Instrument.java").get ++
Seq(
file(s"$root/src/main/java/org/enso/interpreter/Language.java"),
file(s"$root/src/main/java/org/enso/interpreter/epb/EpbLanguage.java")
)
val fragileClassFiles =
(classFilesDirectory ** "*Instrument.class").get ++
Seq(
file(s"$classFilesDirectory/org/enso/interpreter/Language.class"),
file(s"$classFilesDirectory/org/enso/interpreter/epb/EpbLanguage.class")
)
FragileFiles(fragileSources, fragileClassFiles)
}
}