From 85346bd9d8fca1189be411d877d0c56f934fe016 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 3 Oct 2009 09:39:16 -0400 Subject: [PATCH] Fix compilation test, add scaladoc interface, remove structural types (#2265) --- compile/AnalyzingCompiler.scala | 35 +++++++++++++++---- compile/ComponentCompiler.scala | 4 ++- compile/RawCompiler.scala | 12 ++++--- compile/interface/CompileLogger.scala | 10 +++--- compile/interface/CompilerInterface.scala | 42 ++++++++++++++++++----- compile/src/test/scala/CheckBasic.scala | 13 +++++++ compile/src/test/scala/CompileTest.scala | 16 ++++++--- compile/src/test/scala/TestCompile.scala | 3 +- 8 files changed, 102 insertions(+), 33 deletions(-) diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index 588eb204a..443722525 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -16,18 +16,39 @@ class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager) callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger) { val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath) - // this is the instance used to compile the analysis - val componentCompiler = new ComponentCompiler(new RawCompiler(scalaInstance, log), manager) + val interfaceClass = getInterfaceClass("xsbt.CompilerInterface", log) + val interface = interfaceClass.newInstance.asInstanceOf[AnyRef] + // this is commented out because of Scala ticket #2365 + /*val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: xLogger): Unit }] + // these arguments are safe to pass across the ClassLoader boundary because the types are defined in Java + // so they will be binary compatible across all versions of Scala + runnable.run(arguments.toArray, callback, maximumErrors, log)*/ + + val method = interfaceClass.getMethod("run", classOf[Array[String]], classOf[AnalysisCallback], classOf[Int], classOf[xLogger]) + method.invoke(interface, arguments.toArray[String] : Array[String], callback, maximumErrors: java.lang.Integer, log) + } + def doc(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], maximumErrors: Int, log: CompileLogger): Unit = + doc(sources, classpath, outputDirectory, options, false, maximumErrors, log) + def doc(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], compilerOnClasspath: Boolean, + maximumErrors: Int, log: CompileLogger): Unit = + { + val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath) + val interfaceClass = getInterfaceClass("xsbt.ScaladocInterface", log) + val interface = interfaceClass.newInstance.asInstanceOf[AnyRef] + val method = interfaceClass.getMethod("run", classOf[Array[String]], classOf[Int], classOf[xLogger]) + method.invoke(interface, arguments.toArray[String] : Array[String], maximumErrors: java.lang.Integer, log) + } + private def getInterfaceClass(name: String, log: CompileLogger) = + { + // this is the instance used to compile the interface component + val componentCompiler = newComponentCompiler(log) log.debug("Getting " + ComponentCompiler.compilerInterfaceID + " from component compiler for Scala " + scalaInstance.version) val interfaceJar = componentCompiler(ComponentCompiler.compilerInterfaceID) val dual = createDualLoader(scalaInstance.loader, getClass.getClassLoader) // this goes to scalaLoader for scala classes and sbtLoader for xsbti classes val interfaceLoader = new URLClassLoader(Array(interfaceJar.toURI.toURL), dual) - val interface = Class.forName("xsbt.CompilerInterface", true, interfaceLoader).newInstance.asInstanceOf[AnyRef] - val runnable = interface.asInstanceOf[{ def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: xLogger): Unit }] - // these arguments are safe to pass across the ClassLoader boundary because the types are defined in Java - // so they will be binary compatible across all versions of Scala - runnable.run(arguments.toArray, callback, maximumErrors, log) + Class.forName(name, true, interfaceLoader) } + def newComponentCompiler(log: CompileLogger) = new ComponentCompiler(new RawCompiler(scalaInstance, log), manager) protected def createDualLoader(scalaLoader: ClassLoader, sbtLoader: ClassLoader): ClassLoader = { val xsbtiFilter = (name: String) => name.startsWith("xsbti.") diff --git a/compile/ComponentCompiler.scala b/compile/ComponentCompiler.scala index 36fea4dbc..c0cbf68d5 100644 --- a/compile/ComponentCompiler.scala +++ b/compile/ComponentCompiler.scala @@ -23,6 +23,7 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) try { manager.file(binID) } catch { case e: InvalidComponent => compileAndInstall(id, binID) } } + def clearCache(id: String): Unit = manager.clearCache(binaryID(id)) protected def binaryID(id: String) = id + binSeparator + compiler.scalaInstance.actualVersion protected def compileAndInstall(id: String, binID: String): File = { @@ -45,7 +46,8 @@ class ComponentCompiler(compiler: RawCompiler, manager: ComponentManager) val (sourceFiles, resources) = extractedSources.partition(_.getName.endsWith(".scala")) withTemporaryDirectory { outputDirectory => val xsbtiJars = manager.files(xsbtiID) - compiler(Set() ++ sourceFiles, Set() ++ xsbtiJars, outputDirectory, Nil, true) + try { compiler(Set() ++ sourceFiles, Set() ++ xsbtiJars, outputDirectory, Nil, true) } + catch { case e: xsbti.CompileFailed => throw new CompileFailed(e.arguments, "Error compiling component '" + id + "'") } copy(resources x (FileMapper.rebase(dir, outputDirectory))) zip((outputDirectory ***) x (PathMapper.relativeTo(outputDirectory)), targetJar) } diff --git a/compile/RawCompiler.scala b/compile/RawCompiler.scala index f61b9787c..17a3e4295 100644 --- a/compile/RawCompiler.scala +++ b/compile/RawCompiler.scala @@ -14,10 +14,9 @@ class RawCompiler(val scalaInstance: ScalaInstance, log: CompileLogger) // The following imports ensure there is a compile error if the identifiers change, // but should not be otherwise directly referenced import scala.tools.nsc.Main - import scala.tools.nsc.Properties val arguments = (new CompilerArguments(scalaInstance))(sources, classpath, outputDirectory, options, compilerOnClasspath) - log.debug("Vanilla interface to Scala compiler " + scalaInstance.actualVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", "")) + log.debug("Plain interface to Scala compiler " + scalaInstance.actualVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", "")) val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaInstance.loader) val process = mainClass.getMethod("process", classOf[Array[String]]) process.invoke(null, toJavaArray(arguments)) @@ -26,8 +25,10 @@ class RawCompiler(val scalaInstance: ScalaInstance, log: CompileLogger) protected def checkForFailure(mainClass: Class[_], args: Array[String]) { val reporter = mainClass.getMethod("reporter").invoke(null) - val failed = reporter.asInstanceOf[{ def hasErrors: Boolean }].hasErrors - if(failed) throw new xsbti.CompileFailed { val arguments = args; override def toString = "Vanilla compile failed" } + // this is commented out because of Scala ticket #2365 + //val failed = reporter.asInstanceOf[{ def hasErrors: Boolean }].hasErrors + val failed = reporter.getClass.getMethod("hasErrors").invoke(reporter).asInstanceOf[Boolean] + if(failed) throw new CompileFailed(args, "Plain compile failed") } protected def toJavaArray(arguments: Seq[String]): Array[String] = { @@ -35,4 +36,5 @@ class RawCompiler(val scalaInstance: ScalaInstance, log: CompileLogger) assert(realArray.getClass eq classOf[Array[String]]) realArray } -} \ No newline at end of file +} +class CompileFailed(val arguments: Array[String], override val toString: String) extends xsbti.CompileFailed \ No newline at end of file diff --git a/compile/interface/CompileLogger.scala b/compile/interface/CompileLogger.scala index caaead3fe..43c3e123e 100644 --- a/compile/interface/CompileLogger.scala +++ b/compile/interface/CompileLogger.scala @@ -12,7 +12,7 @@ private final class LoggerReporter(maximumErrors: Int, log: Logger) extends scal { import scala.tools.nsc.util.{FakePos,NoPosition,Position} private val positions = new scala.collection.mutable.HashMap[Position, Severity] - + def error(msg: String) { error(FakePos("scalac"), msg) } def printSummary() @@ -22,7 +22,7 @@ private final class LoggerReporter(maximumErrors: Int, log: Logger) extends scal if(ERROR.count > 0) log.error(Message(countElementsAsString(ERROR.count, "error") + " found")) } - + def display(pos: Position, msg: String, severity: Severity) { severity.count += 1 @@ -39,7 +39,7 @@ private final class LoggerReporter(maximumErrors: Int, log: Logger) extends scal case INFO => log.info(m) }) } - + private def print(logger: F0[String] => Unit, posIn: Position, msg: String) { def log(s: => String) = logger(Message(s)) @@ -69,7 +69,7 @@ private final class LoggerReporter(maximumErrors: Int, log: Logger) extends scal for(offset <- pos.offset; src <- pos.source) { val pointer = offset - src.lineToOffset(src.offsetToLine(offset)) - val pointerSpace = lineContent.take(pointer).map { case '\t' => '\t'; case x => ' ' } + val pointerSpace = (lineContent: Seq[Char]).take(pointer).map { case '\t' => '\t'; case x => ' ' } log(pointerSpace.mkString + "^") // pointer to the column position of the error/warning } } @@ -93,7 +93,7 @@ private final class LoggerReporter(maximumErrors: Int, log: Logger) extends scal case _ => display(pos, msg, severity) } } - + private def testAndLog(pos: Position, severity: Severity): Boolean = { if(pos == null || pos.offset.isEmpty) diff --git a/compile/interface/CompilerInterface.scala b/compile/interface/CompilerInterface.scala index e5bd4c143..cfb191c9c 100644 --- a/compile/interface/CompilerInterface.scala +++ b/compile/interface/CompilerInterface.scala @@ -11,8 +11,7 @@ class CompilerInterface def run(args: Array[String], callback: AnalysisCallback, maximumErrors: Int, log: Logger) { def debug(msg: => String) = log.debug(Message(msg)) - import scala.tools.nsc.{CompilerCommand, FatalError, Global, Settings, reporters, util} - import util.FakePos + import scala.tools.nsc.{CompilerCommand, Global, Settings} debug("Interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString) @@ -37,13 +36,12 @@ class CompilerInterface def newPhase(prev: Phase) = analyzer.newPhase(prev) def name = phaseName } - lazy val pdescriptors = // done this way for compatibility between 2.7 and 2.8 + override def computePhaseDescriptors = // done this way for compatibility between 2.7 and 2.8 { phasesSet += sbtAnalyzer - val superd = super.phaseDescriptors - if(superd.contains(sbtAnalyzer)) superd else ( super.phaseDescriptors ++ Seq(sbtAnalyzer) ).toList + val superd = super.computePhaseDescriptors + if(superd.contains(sbtAnalyzer)) superd else ( superd ++ Seq(sbtAnalyzer) ).toList } - override def phaseDescriptors = pdescriptors trait Compat27 { val runsBefore: List[String] = Nil } } if(!reporter.hasErrors) @@ -56,7 +54,35 @@ class CompilerInterface if(reporter.hasErrors) { debug("Compilation failed (CompilerInterface)") - throw new xsbti.CompileFailed { val arguments = args; override def toString = "Analyzed compilation failed" } + throw new InterfaceCompileFailed(args, "Analyzed compilation failed") } } -} \ No newline at end of file +} +class ScaladocInterface +{ + def run(args: Array[String], maximumErrors: Int, log: Logger) + { + import scala.tools.nsc.{doc, CompilerCommand, Global} + val reporter = new LoggerReporter(maximumErrors, log) + val docSettings: doc.Settings = new doc.Settings(reporter.error) + val command = new CompilerCommand(args.toList, docSettings, error, false) + object compiler extends Global(command.settings, reporter) + { + override val onlyPresentation = true + } + if(!reporter.hasErrors) + { + val run = new compiler.Run + run compile command.files + val generator = new doc.DefaultDocDriver + { + lazy val global: compiler.type = compiler + lazy val settings = docSettings + } + generator.process(run.units) + } + reporter.printSummary() + if(reporter.hasErrors) throw new InterfaceCompileFailed(args, "Scaladoc generation failed") + } +} +class InterfaceCompileFailed(val arguments: Array[String], override val toString: String) extends xsbti.CompileFailed \ No newline at end of file diff --git a/compile/src/test/scala/CheckBasic.scala b/compile/src/test/scala/CheckBasic.scala index 8ca866ac0..e69a9fd0c 100644 --- a/compile/src/test/scala/CheckBasic.scala +++ b/compile/src/test/scala/CheckBasic.scala @@ -17,6 +17,19 @@ object CheckBasic extends Specification } } } + "Scaladoc on basic file should succeed" in { + WithFiles(basicName -> basicSource){ files => + for(scalaVersion <- TestCompile.allVersions) + { + FileUtilities.withTemporaryDirectory { outputDirectory => + WithCompiler(scalaVersion) { (compiler, log) => + compiler.doc(Set() ++ files, Set.empty, outputDirectory, Nil, 5, log) + } + } + true must beTrue // don't know how to just check that previous line completes without exception + } + } + } "Analyzer plugin should send source begin and end" in { WithFiles(basicName -> basicSource) { files => for(scalaVersion <- TestCompile.allVersions) diff --git a/compile/src/test/scala/CompileTest.scala b/compile/src/test/scala/CompileTest.scala index 196b92bdc..ecae21cba 100644 --- a/compile/src/test/scala/CompileTest.scala +++ b/compile/src/test/scala/CompileTest.scala @@ -37,20 +37,26 @@ object WithCompiler boot.LaunchTest.withLauncher { launch => FileUtilities.withTemporaryDirectory { componentDirectory => val manager = new ComponentManager(new boot.ComponentProvider(componentDirectory), log) + val compiler = new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager) + compiler.newComponentCompiler(log).clearCache(ComponentCompiler.compilerInterfaceID) prepare(manager, ComponentCompiler.compilerInterfaceSrcID, "CompilerInterface.scala") prepare(manager, ComponentCompiler.xsbtiID, classOf[xsbti.AnalysisCallback]) - f(new AnalyzingCompiler(ScalaInstance(scalaVersion, launch), manager), log) + f(compiler, log) } } } } - private def prepare(manager: ComponentManager, id: String, resource: Class[_]): Unit = - manager.define(id, FileUtilities.classLocationFile(resource) :: Nil) - private def prepare(manager: ComponentManager, id: String, resource: String): Unit = + def prepare(manager: ComponentManager, id: String, resource: Class[_]): Unit = define(manager, id, FileUtilities.classLocationFile(resource) :: Nil) + def prepare(manager: ComponentManager, id: String, resource: String) { val src = getClass.getClassLoader.getResource(resource) if(src eq null) error("Resource not found: " + resource) - manager.define(id, FileUtilities.asFile(src) :: Nil) + define(manager, id, FileUtilities.asFile(src) :: Nil) + } + def define(manager: ComponentManager, id: String, files: List[File]) + { + manager.clearCache(id) + manager.define(id, files) } } \ No newline at end of file diff --git a/compile/src/test/scala/TestCompile.scala b/compile/src/test/scala/TestCompile.scala index e350a9997..5e88f5691 100644 --- a/compile/src/test/scala/TestCompile.scala +++ b/compile/src/test/scala/TestCompile.scala @@ -14,11 +14,10 @@ object TestCompile def apply[T](scalaVersion: String, sources: Set[File], outputDirectory: File, options: Seq[String], superclassNames: Seq[String])(f: (TestCallback, CompileLogger) => T): T = { val testCallback = new TestCallback(superclassNames.toArray) - val result = WithCompiler(scalaVersion) { (compiler, log) => + WithCompiler(scalaVersion) { (compiler, log) => compiler(sources, Set.empty, outputDirectory, options, testCallback, 5, log) f(testCallback, log) } - result } /** Tests running the compiler interface with the analyzer plugin. The provided function is given a ClassLoader that can * load the compiled classes..*/