/* sbt -- Simple Build Tool * Copyright 2008, 2009 Mark Harrah */ package xsbt import xsbti.{AnalysisCallback,Logger,Problem,Reporter,Severity} import xsbti.compile._ import scala.tools.nsc.{backend, io, reporters, symtab, util, Phase, Global, Settings, SubComponent} import scala.tools.nsc.interactive.RangePositions import backend.JavaPlatform import scala.tools.util.PathResolver import symtab.SymbolLoaders import util.{ClassPath,DirectoryClassPath,MergedClassPath,JavaClassPath} import ClassPath.{ClassPathContext,JavaContext} import io.AbstractFile import scala.annotation.tailrec import scala.collection.mutable import Log.debug import java.io.File final class CompilerInterface { def newCompiler(options: Array[String], output: Output, initialLog: Logger, initialDelegate: Reporter, resident: Boolean): CachedCompiler = new CachedCompiler0(options, output, new WeakLog(initialLog, initialDelegate), resident) 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) } // for compatibility with Scala versions without Global.registerTopLevelSym (2.8.1 and earlier) sealed trait GlobalCompat { self: Global => def registerTopLevelSym(sym: Symbol): Unit sealed trait RunCompat { def informUnitStarting(phase: Phase, unit: CompilationUnit) {} } } sealed abstract class CallbackGlobal(settings: Settings, reporter: reporters.Reporter, output: Output) extends Global(settings, reporter) with GlobalCompat { def callback: AnalysisCallback def findClass(name: String): Option[(AbstractFile,Boolean)] lazy val outputDirs: Iterable[File] = { output match { case single: SingleOutput => List(single.outputDirectory) case multi: MultipleOutput => multi.outputGroups.toStream map (_.outputDirectory) } } // Map source files to public inherited dependencies. These dependencies are tracked as the symbol for the dealiased base class. val inheritedDependencies = new mutable.HashMap[File, mutable.Set[Symbol]] def addInheritedDependencies(file: File, deps: Iterable[Symbol]) { inheritedDependencies.getOrElseUpdate(file, new mutable.HashSet) ++= deps } } class InterfaceCompileFailed(val arguments: Array[String], val problems: Array[Problem], override val toString: String) extends xsbti.CompileFailed class InterfaceCompileCancelled(val arguments: Array[String], override val toString: String) extends xsbti.CompileCancelled private final class WeakLog(private[this] var log: Logger, private[this] var delegate: Reporter) { def apply(message: String) { assert(log ne null, "Stale reference to logger") log.error(Message(message)) } def logger: Logger = log def reporter: Reporter = delegate def clear() { log = null delegate = null } } private final class CachedCompiler0(args: Array[String], output: Output, initialLog: WeakLog, resident: Boolean) extends CachedCompiler { val settings = new Settings(s => initialLog(s)) output match { case multi: MultipleOutput => for (out <- multi.outputGroups) settings.outputDirs.add(out.sourceDirectory.getAbsolutePath, out.outputDirectory.getAbsolutePath) case single: SingleOutput => settings.outputDirs.setSingleOutput(single.outputDirectory.getAbsolutePath) } val command = Command(args.toList, settings) private[this] val dreporter = DelegatingReporter(settings, initialLog.reporter) try { if(!noErrors(dreporter)) { dreporter.printSummary() handleErrors(dreporter, initialLog.logger) } } finally initialLog.clear() def noErrors(dreporter: DelegatingReporter) = !dreporter.hasErrors && command.ok def commandArguments(sources: Array[File]): Array[String] = (command.settings.recreateArgs ++ sources.map(_.getAbsolutePath)).toArray[String] def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized { debug(log, "Running cached compiler " + hashCode.toHexString + ", interfacing (CompilerInterface) with Scala compiler " + scala.tools.nsc.Properties.versionString) val dreporter = DelegatingReporter(settings, delegate) try { run(sources.toList, changes, callback, log, dreporter, progress) } finally { dreporter.dropDelegate() } } private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, dreporter: DelegatingReporter, compileProgress: CompileProgress) { if(command.shouldStopWithInfo) { dreporter.info(null, command.getInfoMessage(compiler), true) throw new InterfaceCompileFailed(args, Array(), "Compiler option supplied that disabled actual compilation.") } if(noErrors(dreporter)) { debug(log, args.mkString("Calling Scala compiler with arguments (CompilerInterface):\n\t", "\n\t", "")) compiler.set(callback, dreporter) val run = new compiler.Run with compiler.RunCompat { override def informUnitStarting(phase: Phase, unit: compiler.CompilationUnit) { compileProgress.startUnit(phase.name, unit.source.path) } override def progress(current: Int, total: Int) { if (!compileProgress.advance(current, total)) cancel } } val sortedSourceFiles = sources.map(_.getAbsolutePath).sortWith(_ < _) run compile sortedSourceFiles processUnreportedWarnings(run) dreporter.problems foreach { p => callback.problem(p.category, p.position, p.message, p.severity, true) } } dreporter.printSummary() if(!noErrors(dreporter)) handleErrors(dreporter, log) // the case where we cancelled compilation _after_ some compilation errors got reported // will be handled by line above so errors still will be reported properly just potentially not // all of them (because we cancelled the compilation) if (dreporter.cancelled) handleCompilationCancellation(dreporter, log) } def handleErrors(dreporter: DelegatingReporter, log: Logger): Nothing = { debug(log, "Compilation failed (CompilerInterface)") throw new InterfaceCompileFailed(args, dreporter.problems, "Compilation failed") } def handleCompilationCancellation(dreporter: DelegatingReporter, log: Logger): Nothing = { assert(dreporter.cancelled, "We should get here only if when compilation got cancelled") debug(log, "Compilation cancelled (CompilerInterface)") throw new InterfaceCompileCancelled(args, "Compilation has been cancelled") } def processUnreportedWarnings(run: compiler.Run) { // allConditionalWarnings and the ConditionalWarning class are only in 2.10+ final class CondWarnCompat(val what: String, val warnings: mutable.ListBuffer[(compiler.Position, String)]) implicit def compat(run: AnyRef): Compat = new Compat final class Compat { def allConditionalWarnings = List[CondWarnCompat]() } val warnings = run.allConditionalWarnings if(!warnings.isEmpty) compiler.logUnreportedWarnings(warnings.map(cw => ("" /*cw.what*/, cw.warnings.toList))) } val compiler: Compiler = { if (command.settings.Yrangepos.value) new Compiler() with RangePositions // unnecessary in 2.11 else new Compiler() } class Compiler extends CallbackGlobal(command.settings, dreporter, output) { object dummy // temporary fix for #4426 object sbtAnalyzer extends { val global: Compiler.this.type = Compiler.this val phaseName = Analyzer.name val runsAfter = List("jvm") override val runsBefore = List("terminal") val runsRightAfter = None } with SubComponent { val analyzer = new Analyzer(global) def newPhase(prev: Phase) = analyzer.newPhase(prev) def name = phaseName } /** This phase walks trees and constructs a representation of the public API, which is used for incremental recompilation. * * We extract the api after picklers, since that way we see the same symbol information/structure * irrespective of whether we were typechecking from source / unpickling previously compiled classes. */ object apiExtractor extends { val global: Compiler.this.type = Compiler.this val phaseName = API.name val runsAfter = List("typer") override val runsBefore = List("erasure") // allow apiExtractor's phase to be overridden using the sbt.api.phase property // (in case someone would like the old timing, which was right after typer) // TODO: consider migrating to simply specifying "pickler" for `runsAfter` and "uncurry" for `runsBefore` val runsRightAfter = Option(System.getProperty("sbt.api.phase")) orElse Some("pickler") } with SubComponent { val api = new API(global) def newPhase(prev: Phase) = api.newPhase(prev) def name = phaseName } override lazy val phaseDescriptors = { phasesSet += sbtAnalyzer phasesSet += apiExtractor superComputePhaseDescriptors } // Required because computePhaseDescriptors is private in 2.8 (changed to protected sometime later). private[this] def superComputePhaseDescriptors() = superCall("computePhaseDescriptors").asInstanceOf[List[SubComponent]] private[this] def superDropRun(): Unit = try { superCall("dropRun") } catch { case e: NoSuchMethodException => () } // dropRun not in 2.8.1 private[this] def superCall(methodName: String): AnyRef = { val meth = classOf[Global].getDeclaredMethod(methodName) meth.setAccessible(true) meth.invoke(this) } def logUnreportedWarnings(seq: Seq[(String, List[(Position,String)])]): Unit = // Scala 2.10.x and later { val drep = reporter.asInstanceOf[DelegatingReporter] for( (what, warnings) <- seq; (pos, msg) <- warnings) yield callback.problem(what, drep.convert(pos), msg, Severity.Warn, false) } def set(callback: AnalysisCallback, dreporter: DelegatingReporter) { this.callback0 = callback reporter = dreporter } def clear() { callback0 = null superDropRun() reporter = null } def findClass(name: String): Option[(AbstractFile, Boolean)] = getOutputClass(name).map(f => (f,true)) orElse findOnClassPath(name).map(f =>(f, false)) def getOutputClass(name: String): Option[AbstractFile] = { // This could be improved if a hint where to look is given. val className = name.replace('.', '/') + ".class" outputDirs map (new File(_, className)) find (_.exists) map (AbstractFile.getFile(_)) } def findOnClassPath(name: String): Option[AbstractFile] = classPath.findClass(name).flatMap(_.binary.asInstanceOf[Option[AbstractFile]]) private[this] var callback0: AnalysisCallback = null def callback: AnalysisCallback = callback0 } }