diff options
author | Guillaume Martres <smarter@ubuntu.com> | 2016-11-20 00:02:50 +0100 |
---|---|---|
committer | Guillaume Martres <smarter@ubuntu.com> | 2016-11-22 01:35:08 +0100 |
commit | c3eb841ce8ae349d9820dbf6c18884955e74254e (patch) | |
tree | 5e82e22a6f0e8245c11a6db81cb9647106a14bde /sbt-bridge/src/xsbt | |
parent | da1bfe392c638fc03181e0d6b51eb41dbdcce548 (diff) | |
download | dotty-c3eb841ce8ae349d9820dbf6c18884955e74254e.tar.gz dotty-c3eb841ce8ae349d9820dbf6c18884955e74254e.tar.bz2 dotty-c3eb841ce8ae349d9820dbf6c18884955e74254e.zip |
Make every project use the new directory structure
Diffstat (limited to 'sbt-bridge/src/xsbt')
-rw-r--r-- | sbt-bridge/src/xsbt/CompilerClassLoader.scala | 90 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/CompilerInterface.scala | 72 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/ConsoleInterface.scala | 73 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/DelegatingReporter.scala | 48 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/Log.scala | 10 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/Message.scala | 8 | ||||
-rw-r--r-- | sbt-bridge/src/xsbt/ScaladocInterface.scala | 72 |
7 files changed, 373 insertions, 0 deletions
diff --git a/sbt-bridge/src/xsbt/CompilerClassLoader.scala b/sbt-bridge/src/xsbt/CompilerClassLoader.scala new file mode 100644 index 000000000..3cb3f344f --- /dev/null +++ b/sbt-bridge/src/xsbt/CompilerClassLoader.scala @@ -0,0 +1,90 @@ +package xsbt + +import java.net.{URL, URLClassLoader} + +/** A classloader to run the compiler + * + * A CompilerClassLoader is constructed from a list of `urls` that need to be on + * the classpath to run the compiler and the classloader used by sbt. + * + * To understand why a custom classloader is needed for the compiler, let us + * describe some alternatives that wouldn't work. + * - `new URLClassLoader(urls)`: + * The compiler contains sbt phases that callback to sbt using the `xsbti.*` + * interfaces. If `urls` does not contain the sbt interfaces we'll get a + * `ClassNotFoundException` in the compiler when we try to use them, if + * `urls` does contain the interfaces we'll get a `ClassCastException` or a + * `LinkageError` because if the same class is loaded by two different + * classloaders, they are considered distinct by the JVM. + * - `new URLClassLoader(urls, sbtLoader)`: + * Because of the JVM delegation model, this means that we will only load + * a class from `urls` if it's not present in the parent `sbtLoader`, but + * sbt uses its own version of the scala compiler and scala library which + * is not the one we need to run the compiler. + * + * Our solution is to implement a subclass of URLClassLoader with no parent, instead + * we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`. + */ +class CompilerClassLoader(urls: Array[URL], sbtLoader: ClassLoader) + extends URLClassLoader(urls, null) { + override def loadClass(className: String, resolve: Boolean): Class[_] = + if (className.startsWith("xsbti.")) { + // We can't use the loadClass overload with two arguments because it's + // protected, but we can do the same by hand (the classloader instance + // from which we call resolveClass does not matter). + val c = sbtLoader.loadClass(className) + if (resolve) + resolveClass(c) + c + } else { + super.loadClass(className, resolve) + } +} + +object CompilerClassLoader { + /** Fix the compiler bridge ClassLoader + * + * Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs + * + * The classloader that we get from sbt looks like: + * + * URLClassLoader(bridgeURLs, + * DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter)) + * + * DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and + * everything else with `scalaLoader`. Once we have loaded the dotty Main + * class using `scalaLoader`, subsequent classes in the dotty compiler will + * also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt + * compiler phases are part of dotty and still need access to the `xsbti.*` + * interfaces in `sbtLoader`, therefore DualLoader does not work for us + * (this issue is not present with scalac because the sbt phases are + * currently defined in the compiler bridge itself, not in scalac). + * + * CompilerClassLoader is a replacement for DualLoader. Until we can fix + * this in sbt proper, we need to use reflection to construct our own + * fixed classloader: + * + * URLClassLoader(bridgeURLs, + * CompilerClassLoader(scalaLoader.getURLs, sbtLoader)) + * + * @param bridgeLoader The classloader that sbt uses to load the compiler bridge + * @return A fixed classloader that works with dotty + */ + def fixBridgeLoader(bridgeLoader: ClassLoader) = bridgeLoader match { + case bridgeLoader: URLClassLoader => + val dualLoader = bridgeLoader.getParent + val dualLoaderClass = dualLoader.getClass + + // DualLoader#parentA and DualLoader#parentB are private + val parentAField = dualLoaderClass.getDeclaredField("parentA") + parentAField.setAccessible(true) + val parentBField = dualLoaderClass.getDeclaredField("parentB") + parentBField.setAccessible(true) + val scalaLoader = parentAField.get(dualLoader).asInstanceOf[URLClassLoader] + val sbtLoader = parentBField.get(dualLoader).asInstanceOf[URLClassLoader] + + val bridgeURLs = bridgeLoader.getURLs + new URLClassLoader(bridgeURLs, + new CompilerClassLoader(scalaLoader.getURLs, sbtLoader)) + } +} diff --git a/sbt-bridge/src/xsbt/CompilerInterface.scala b/sbt-bridge/src/xsbt/CompilerInterface.scala new file mode 100644 index 000000000..bf1488dad --- /dev/null +++ b/sbt-bridge/src/xsbt/CompilerInterface.scala @@ -0,0 +1,72 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import xsbti.{ AnalysisCallback, Logger, Problem, Reporter, Severity, DependencyContext } +import xsbti.api.SourceAPI +import xsbti.compile._ +import Log.debug +import java.io.File + +import dotty.tools.dotc.core.Contexts.ContextBase +import dotty.tools.dotc.{ Main => DottyMain } +import dotty.tools.dotc.interfaces._ + +import java.net.URLClassLoader + +final class CompilerInterface { + def newCompiler(options: Array[String], output: Output, initialLog: Logger, + initialDelegate: Reporter, resident: Boolean): CachedCompiler = { + // The classloader that sbt uses to load the compiler bridge is broken + // (see CompilerClassLoader#fixBridgeLoader for details). To workaround + // this we construct our own ClassLoader and then run the following code + // with it: + // new CachedCompilerImpl(options, output, resident) + + val bridgeLoader = getClass.getClassLoader + val fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader) + val cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl") + cciClass.getConstructors.head + .newInstance(options, output, resident: java.lang.Boolean) + .asInstanceOf[CachedCompiler] + } + + def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, + delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit = + cached.run(sources, changes, callback, log, delegate, progress) +} + +class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) extends CachedCompiler { + val outputArgs = + output match { + case multi: MultipleOutput => + ??? + case single: SingleOutput => + List("-d", single.outputDirectory.getAbsolutePath.toString) + } + + def commandArguments(sources: Array[File]): Array[String] = + (outputArgs ++ args.toList ++ sources.map(_.getAbsolutePath).sortWith(_ < _)).toArray[String] + + def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { + run(sources.toList, changes, callback, log, delegate, progress) + } + private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, compileProgress: CompileProgress): Unit = { + debug(log, args.mkString("Calling Dotty compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) + val ctx = (new ContextBase).initialCtx.fresh + .setSbtCallback(callback) + .setReporter(new DelegatingReporter(delegate)) + + val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader] + + val reporter = DottyMain.process(commandArguments(sources.toArray), ctx) + if (reporter.hasErrors) { + throw new InterfaceCompileFailed(args, Array()) + } + } +} + +class InterfaceCompileFailed(override val arguments: Array[String], override val problems: Array[Problem]) extends xsbti.CompileFailed { + override val toString = "Compilation failed" +} diff --git a/sbt-bridge/src/xsbt/ConsoleInterface.scala b/sbt-bridge/src/xsbt/ConsoleInterface.scala new file mode 100644 index 000000000..f56918113 --- /dev/null +++ b/sbt-bridge/src/xsbt/ConsoleInterface.scala @@ -0,0 +1,73 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import xsbti.Logger +import scala.tools.nsc.{ GenericRunnerCommand, Interpreter, InterpreterLoop, ObjectRunner, Settings } +import scala.tools.nsc.interpreter.InteractiveReader +import scala.tools.nsc.reporters.Reporter +import scala.tools.nsc.util.ClassPath + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.repl.REPL +import dotty.tools.dotc.repl.REPL.Config + +class ConsoleInterface { + def commandArguments( + args: Array[String], + bootClasspathString: String, + classpathString: String, + log: Logger + ): Array[String] = args + + def run(args: Array[String], + bootClasspathString: String, + classpathString: String, + initialCommands: String, + cleanupCommands: String, + loader: ClassLoader, + bindNames: Array[String], + bindValues: Array[Any], + log: Logger + ): Unit = { + val completeArgs = + args :+ + "-bootclasspath" :+ bootClasspathString :+ + "-classpath" :+ classpathString + + println("Starting dotty interpreter...") + val repl = ConsoleInterface.customRepl( + initialCommands :: Nil, + cleanupCommands :: Nil, + bindNames zip bindValues, + loader + ) + repl.process(completeArgs) + } +} + +object ConsoleInterface { + def customConfig( + initCmds: List[String], + cleanupCmds: List[String], + boundVals: Array[(String, Any)], + loader: ClassLoader + ) = new Config { + override val initialCommands: List[String] = initCmds + override val cleanupCommands: List[String] = cleanupCmds + override val boundValues: Array[(String, Any)] = boundVals + override val classLoader: Option[ClassLoader] = Option(loader) + } + + def customRepl(cfg: Config): REPL = new REPL { + override lazy val config = cfg + } + + def customRepl( + initCmds: List[String], + cleanupCmds: List[String], + boundVals: Array[(String, Any)], + loader: ClassLoader + ): REPL = customRepl(customConfig(initCmds, cleanupCmds, boundVals, loader)) +} diff --git a/sbt-bridge/src/xsbt/DelegatingReporter.scala b/sbt-bridge/src/xsbt/DelegatingReporter.scala new file mode 100644 index 000000000..770d6b2c7 --- /dev/null +++ b/sbt-bridge/src/xsbt/DelegatingReporter.scala @@ -0,0 +1,48 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import dotty.tools._ +import dotc._ +import reporting._ +import reporting.diagnostic.MessageContainer +import reporting.diagnostic.messages +import core.Contexts._ +import xsbti.{Maybe, Position} + +final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter + with UniqueMessagePositions + with HideNonSensicalMessages + with MessageRendering { + import MessageContainer._ + + override def printSummary(implicit ctx: Context): Unit = delegate.printSummary() + + def doReport(cont: MessageContainer)(implicit ctx: Context): Unit = { + val severity = + cont match { + case _: messages.Error => xsbti.Severity.Error + case _: messages.Warning => xsbti.Severity.Warn + case _ => xsbti.Severity.Info + } + + val position = new Position { + def line: Maybe[Integer] = Maybe.nothing() + def lineContent: String = "" + def offset: Maybe[Integer] = Maybe.nothing() + def pointer: Maybe[Integer] = Maybe.nothing() + def pointerSpace: Maybe[String] = Maybe.nothing() + def sourceFile: Maybe[java.io.File] = Maybe.nothing() + def sourcePath: Maybe[String] = Maybe.nothing() + } + + val sb = new StringBuilder() + sb.append(messageAndPos(cont.contained, cont.pos, diagnosticLevel(cont))) + if (ctx.shouldExplain(cont) && cont.contained.explanation.nonEmpty) { + sb.append(explanation(cont.contained)) + } + + delegate.log(position, sb.toString(), severity) + } +} diff --git a/sbt-bridge/src/xsbt/Log.scala b/sbt-bridge/src/xsbt/Log.scala new file mode 100644 index 000000000..e514d7abb --- /dev/null +++ b/sbt-bridge/src/xsbt/Log.scala @@ -0,0 +1,10 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +object Log { + def debug(log: xsbti.Logger, msg: => String) = log.debug(Message(msg)) + def settingsError(log: xsbti.Logger): String => Unit = + s => log.error(Message(s)) +} diff --git a/sbt-bridge/src/xsbt/Message.scala b/sbt-bridge/src/xsbt/Message.scala new file mode 100644 index 000000000..48f24f533 --- /dev/null +++ b/sbt-bridge/src/xsbt/Message.scala @@ -0,0 +1,8 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +object Message { + def apply[T](s: => T) = new xsbti.F0[T] { def apply() = s } +} diff --git a/sbt-bridge/src/xsbt/ScaladocInterface.scala b/sbt-bridge/src/xsbt/ScaladocInterface.scala new file mode 100644 index 000000000..3ad9c7941 --- /dev/null +++ b/sbt-bridge/src/xsbt/ScaladocInterface.scala @@ -0,0 +1,72 @@ +/* sbt -- Simple Build Tool + * Copyright 2008, 2009 Mark Harrah + */ +package xsbt + +import xsbti.Logger +import dotty.tools.dottydoc.api.scala.Dottydoc +import java.net.URL + +class ScaladocInterface { + def run(args: Array[String], log: Logger, delegate: xsbti.Reporter) = + (new DottydocRunner(args, log, delegate)).run() +} + +class DottydocRunner(args: Array[String], log: Logger, delegate: xsbti.Reporter) extends Dottydoc { + def run(): Unit = getOutputFolder(args).map { outputFolder => + val index = createIndex(args) + val resources = getResources(args) + val template = getTemplate(resources) + + template.fold(writeJson(index, outputFolder)) { tpl => + buildDocs(outputFolder, tpl, resources, index) + } + } getOrElse { + delegate.log( + NoPosition, + "No output folder set for API documentation (\"-d\" parameter should be passed to the documentation tool)", + xsbti.Severity.Error + ) + } + + private[this] val NoPosition = new xsbti.Position { + val line = xsbti.Maybe.nothing[Integer] + val lineContent = "" + val offset = xsbti.Maybe.nothing[Integer] + val sourcePath = xsbti.Maybe.nothing[String] + val sourceFile = xsbti.Maybe.nothing[java.io.File] + val pointer = xsbti.Maybe.nothing[Integer] + val pointerSpace = xsbti.Maybe.nothing[String] + } + + private def getStringSetting(name: String): Option[String] = + args find (_.startsWith(name)) map (_.drop(name.length)) + + private def getOutputFolder(args: Array[String]): Option[String] = + args sliding(2) find { case Array(x, _) => x == "-d" } map (_.tail.head.trim) + + private def getTemplate(resources: List[URL]): Option[URL] = + resources.find(_.getFile.endsWith("template.html")) + + private def getResources(args: Array[String]): List[URL] = { + val cp = args sliding (2) find { case Array(x, _) => x == "-classpath" } map (_.tail.head.trim) getOrElse "" + + cp.split(":").find(_.endsWith("dottydoc-client.jar")).map { resourceJar => + import java.util.jar.JarFile + val jarEntries = (new JarFile(resourceJar)).entries + var entries: List[URL] = Nil + + while (jarEntries.hasMoreElements) { + val entry = jarEntries.nextElement() + + if (!entry.isDirectory()) { + val path = s"jar:file:$resourceJar!/${entry.getName}" + val url = new URL(path) + entries = url :: entries + } + } + + entries + } getOrElse (Nil) + } +} |