-
Notifications
You must be signed in to change notification settings - Fork 122
/
Copy pathAPI.scala
216 lines (187 loc) · 8 KB
/
API.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
/*
* Zinc - The incremental compiler for Scala.
* Copyright Scala Center, Lightbend, and Mark Harrah
*
* Licensed under Apache License 2.0
* SPDX-License-Identifier: Apache-2.0
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package xsbt
import scala.tools.nsc.Phase
import scala.tools.nsc.symtab.Flags
import xsbti.api._
import xsbti.VirtualFile
object API {
val name = "xsbt-api"
}
final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers with ClassName {
import global._
import scala.collection.mutable
private val nonLocalClassSymbolsInCurrentUnits = new mutable.HashSet[Symbol]()
def newPhase(prev: Phase) = new ApiPhase(prev)
class ApiPhase(prev: Phase) extends GlobalPhase(prev) {
override def description = "Extracts the public API from source files."
def name = API.name
override def run(): Unit = {
val start = System.currentTimeMillis
super.run()
// After processing all units, register generated classes
registerGeneratedClasses(nonLocalClassSymbolsInCurrentUnits.iterator)
nonLocalClassSymbolsInCurrentUnits.clear()
callback.apiPhaseCompleted()
val stop = System.currentTimeMillis
debuglog("API phase took : " + ((stop - start) / 1000.0) + " s")
}
// TODO In 2.13, shouldSkipThisPhaseForJava should be overridden instead of cancelled
// override def shouldSkipThisPhaseForJava = !global.callback.isPickleJava
override def cancelled(unit: CompilationUnit) = {
if (Thread.interrupted()) reporter.cancelled = true
reporter.cancelled || unit.isJava && !global.callback.isPickleJava
}
def apply(unit: global.CompilationUnit): Unit = processUnit(unit)
private def processUnit(unit: CompilationUnit): Unit = {
if (!unit.isJava || global.callback.isPickleJava) {
processScalaUnit(unit)
}
}
private def processScalaUnit(unit: CompilationUnit): Unit = {
val sourceFile: VirtualFile = unit.source.file match { case AbstractZincFile(vf) => vf }
debuglog("Traversing " + sourceFile)
callback.startSource(sourceFile)
val extractApi = new ExtractAPI[global.type](global, sourceFile)
val traverser = new TopLevelHandler(extractApi)
traverser.apply(unit.body)
val extractUsedNames = new ExtractUsedNames[global.type](global)
extractUsedNames.extractAndReport(unit)
val classApis = traverser.allNonLocalClasses
val mainClasses = traverser.mainClasses
// Use of iterators make this code easier to profile
val classApisIt = classApis.iterator
while (classApisIt.hasNext) {
callback.api(sourceFile, classApisIt.next())
}
val mainClassesIt = mainClasses.iterator
while (mainClassesIt.hasNext) {
callback.mainClass(sourceFile, mainClassesIt.next())
}
extractApi.allExtractedNonLocalSymbols.foreach { cs =>
// Only add the class symbols defined in this compilation unit
if (cs.sourceFile != null) nonLocalClassSymbolsInCurrentUnits.+=(cs)
}
}
}
private case class FlattenedNames(binaryName: String, className: String)
/**
* Registers only non-local generated classes in the callback by extracting
* information about its names and using the names to generate class file paths.
*
* Mimics the previous logic that was present in `Analyzer`, despite the fact
* that now we construct the names that the compiler will give to every non-local
* class independently of genbcode.
*
* Why do we do this? The motivation is that we want to run the incremental algorithm
* independently of the compiler pipeline. This independence enables us to:
*
* 1. Offload the incremental compiler logic out of the primary pipeline and
* run the incremental phases concurrently.
* 2. Know before the compilation is completed whether another compilation will or
* will not be required. This is important to make incremental compilation work
* with pipelining and enables further optimizations; for example, we can start
* subsequent incremental compilations before (!) the initial compilation is done.
* This can buy us ~30-40% faster incremental compiler iterations.
*
* This method only takes care of non-local classes because local classes have no
* relevance in the correctness of the algorithm and can be registered after genbcode.
* Local classes are only used to construct the relations of products and to produce
* the list of generated files + stamps, but names referring to local classes **never**
* show up in the name hashes of classes' APIs, hence never considered for name hashing.
*
* As local class files are owned by other classes that change whenever they change,
* we could most likely live without adding their class files to the products relation
* and registering their stamps. However, to be on the safe side, we will continue to
* register the local products in `Analyzer`.
*
* @param allClassSymbols The class symbols found in all the compilation units.
*/
def registerGeneratedClasses(classSymbols: Iterator[Symbol]): Unit = {
// Guard against a local class in case it surreptitiously leaks here
classSymbols.filter(!_.isLocalClass).foreach { symbol =>
val sourceFile = symbol.sourceFile
val sourceVF0 =
if (sourceFile == null) symbol.enclosingTopLevelClass.sourceFile
else sourceFile
val sourceVF: Option[VirtualFile] = sourceVF0 match {
case AbstractZincFile(vf) => Some(vf)
// This could be scala.reflect.io.FileZipArchive$LeakyEntry
case _ => None
}
def registerProductNames(names: FlattenedNames): Unit = {
val pathToClassFile = s"${names.binaryName}.class"
val classFile = {
JarUtils.outputJar match {
case Some(outputJar) =>
new java.io.File(JarUtils.classNameInJar(outputJar, pathToClassFile))
case None =>
val outputDir = global.settings.outputDirs.outputDirFor(sourceFile).file
new java.io.File(outputDir, pathToClassFile)
}
}
val zincClassName = names.className
val srcClassName = classNameAsString(symbol)
sourceVF foreach { source =>
callback.generatedNonLocalClass(
source,
classFile.toPath,
zincClassName,
srcClassName
)
}
}
val names = FlattenedNames(
fullName(symbol, java.io.File.separatorChar, symbol.moduleSuffix, true),
fullName(symbol, '.', symbol.moduleSuffix, false)
)
registerProductNames(names)
// Register the names of top-level module symbols that emit two class files
val isTopLevelUniqueModule =
symbol.owner.isPackageClass && symbol.isModuleClass && symbol.companionClass == NoSymbol
if (isTopLevelUniqueModule || symbol.isPackageObject) {
val names = FlattenedNames(
fullName(symbol, java.io.File.separatorChar, "", true),
fullName(symbol, '.', "", false)
)
registerProductNames(names)
}
}
}
private final class TopLevelHandler(extractApi: ExtractAPI[global.type])
extends TopLevelTraverser {
def allNonLocalClasses: Set[ClassLike] = {
extractApi.allExtractedNonLocalClasses
}
def mainClasses: Set[String] = extractApi.mainClasses
def `class`(c: Symbol): Unit = {
extractApi.extractAllClassesOf(c.owner, c)
}
}
private abstract class TopLevelTraverser extends Traverser {
def `class`(s: Symbol): Unit
override def traverse(tree: Tree): Unit = {
tree match {
case (_: ClassDef | _: ModuleDef) if isTopLevel(tree.symbol) => `class`(tree.symbol)
case _: PackageDef =>
super.traverse(tree)
case _ =>
}
}
def isTopLevel(sym: Symbol): Boolean = {
!ignoredSymbol(sym) &&
sym.isStatic &&
!sym.isImplClass &&
(!sym.hasFlag(Flags.JAVA) || global.callback.isPickleJava) &&
!sym.isNestedClass
}
}
}