From ddc62979a9bec6c4ebab3c1936b1c6e30f4423fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Sat, 4 May 2019 12:48:08 +0200 Subject: [PATCH] Automatically restart compiler when classfiles are missing. Previously, mdoc reused the same compiler instance for all compilations. Now, we create a new compiler instance when the following situation happens: - compilation succeeds without any reported errors - compilation produced no classfiles I've consistently been able to reproduce this situation in the https://github.com/spotify/scio repo when running mdoc on all the markdown sources but I haven't been able to minimize the error to a single source file. In Metals we restart the compiler frequently to avoid cryptic errors like this. The compiler has a lot of state that tends to go bad after several repeated compile requests. Fixes #164. --- .../scala/mdoc/internal/cli/Settings.scala | 3 +- .../internal/markdown/MarkdownCompiler.scala | 42 +++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala b/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala index 0439e7c5a..2c41e84bc 100644 --- a/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala +++ b/mdoc/src/main/scala/mdoc/internal/cli/Settings.scala @@ -73,7 +73,8 @@ case class Settings( "Compiler flags such as compiler plugins '-Xplugin:kind-projector.jar' " + "or custom options '-deprecated'. Formatted as a single string with space separated values. " + "To pass multiple values: --scalac-options \"-Yrangepos -deprecated\". " + - "Defaults to the value of 'scalacOptions' in the 'mdoc.properties' resource file, if any." + "Defaults to the value of 'scalacOptions' in the 'mdoc.properties' resource file, if any. " + + "When using sbt-mdoc, update the `scalacOptions` sbt setting instead of passing --scalac-options to `mdocExtraArguments`." ) scalacOptions: String = "", @Description("Remove all files in the output directory before generating a new site.") diff --git a/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownCompiler.scala b/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownCompiler.scala index 3b2baf4eb..16835bc27 100644 --- a/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownCompiler.scala +++ b/mdoc/src/main/scala/mdoc/internal/markdown/MarkdownCompiler.scala @@ -41,9 +41,8 @@ object MarkdownCompiler { val instrumentedInput = InstrumentedInput(filename, instrumented) val compileInput = Input.VirtualFile(filename, instrumented) val edit = TokenEditDistance.fromTrees(sectionInputs.map(_.source), compileInput) - val doc = compiler.compile(compileInput, reporter, edit) match { - case Some(loader) => - val cls = loader.loadClass("repl.Session$") + val doc = compiler.compile(compileInput, reporter, edit, "repl.Session$") match { + case Some(cls) => val ctor = cls.getDeclaredConstructor() ctor.setAccessible(true) val doc = ctor.newInstance().asInstanceOf[DocumentBuilder].$doc @@ -119,7 +118,10 @@ class MarkdownCompiler( settings.processArgumentString(scalacOptions) private val sreporter = new FilterStoreReporter(settings) - val global = new Global(settings, sreporter) + var global = new Global(settings, sreporter) + private def reset(): Unit = { + global = new Global(settings, sreporter) + } private val appClasspath: Array[URL] = classpath .split(File.pathSeparator) .map(path => new File(path).toURI.toURL) @@ -140,7 +142,8 @@ class MarkdownCompiler( def fail(original: Seq[Tree], input: Input, sectionPos: Position): String = { sreporter.reset() - val run = new global.Run + val g = global + val run = new g.Run run.compileSources(List(toSource(input))) val out = new ByteArrayOutputStream() val ps = new PrintStream(out) @@ -161,15 +164,38 @@ class MarkdownCompiler( def compileSources(input: Input, vreporter: Reporter, edit: TokenEditDistance): Unit = { clearTarget() sreporter.reset() - val run = new global.Run + val g = global + val run = new g.Run run.compileSources(List(toSource(input))) report(vreporter, input, edit) } - def compile(input: Input, vreporter: Reporter, edit: TokenEditDistance): Option[ClassLoader] = { + def compile( + input: Input, + vreporter: Reporter, + edit: TokenEditDistance, + className: String, + retry: Int = 0 + ): Option[Class[_]] = { compileSources(input, vreporter, edit) if (!sreporter.hasErrors) { - Some(new AbstractFileClassLoader(target, appClassLoader)) + val loader = new AbstractFileClassLoader(target, appClassLoader) + try { + Some(loader.loadClass(className)) + } catch { + case _: ClassNotFoundException => + if (retry < 1) { + reset() + compile(input, vreporter, edit, className, retry + 1) + } else { + vreporter.error( + s"${input.syntax}: skipping file, the compiler produced no classfiles " + + "and reported no errors to explain what went wrong during compilation. " + + "Please report an issue to https://github.com/scalameta/mdoc/issues." + ) + None + } + } } else { None }