forked from sbt/sbt
-
Notifications
You must be signed in to change notification settings - Fork 1
/
CompilerInterface.scala
254 lines (234 loc) · 11.5 KB
/
CompilerInterface.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/* 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
}
/** Phase that extracts dependency information */
object sbtDependency extends {
val global: Compiler.this.type = Compiler.this
val phaseName = Dependency.name
val runsAfter = List(API.name)
override val runsBefore = List("refchecks")
// keep API and dependency close to each other
// we might want to merge them in the future and even if don't
// do that then it makes sense to run those phases next to each other
val runsRightAfter = Some(API.name)
} with SubComponent {
val dependency = new Dependency(global)
def newPhase(prev: Phase) = dependency.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 += sbtDependency
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
}
}