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
|
package scala.tools.nsc
package dependencies
import java.io.{InputStream, OutputStream, PrintStream, InputStreamReader, BufferedReader}
import io.{AbstractFile, PlainFile, VirtualFile}
import scala.collection._
trait Files { self : SubComponent =>
class FileDependencies(val classpath: String) {
import FileDependencies._
class Tracker extends mutable.OpenHashMap[AbstractFile, mutable.Set[AbstractFile]] {
override def default(key: AbstractFile) = {
this(key) = new mutable.HashSet[AbstractFile]
this(key)
}
}
val dependencies = new Tracker
val targets = new Tracker
def isEmpty = dependencies.isEmpty && targets.isEmpty
def emits(source: AbstractFile, result: AbstractFile) =
targets(source) += result
def depends(from: AbstractFile, on: AbstractFile) =
dependencies(from) += on
def reset(file: AbstractFile) = dependencies -= file
def cleanEmpty = {
dependencies foreach {case (_, value) =>
value retain (x => x.exists && (x ne removedFile))}
dependencies retain ((key, value) => key.exists && !value.isEmpty)
targets foreach {case (_, value) => value retain (_.exists)}
targets retain ((key, value) => key.exists && !value.isEmpty)
}
def containsFile(f: AbstractFile) = targets.contains(f.absolute)
def invalidatedFiles(maxDepth: Int) = {
val direct = new mutable.HashSet[AbstractFile]
for ((file, products) <- targets) {
// This looks a bit odd. It may seem like one should invalidate a file
// if *any* of its dependencies are older than it. The forall is there
// to deal with the fact that a) Some results might have been orphaned
// and b) Some files might not need changing.
direct(file) ||= products.forall(d => d.lastModified < file.lastModified)
}
val indirect = dependentFiles(maxDepth, direct)
for ((source, targets) <- targets
if direct(source) || indirect(source) || (source eq removedFile)) {
targets foreach (_.delete)
targets -= source
}
(direct, indirect)
}
/** Return the set of files that depend on the given changed files.
* It computes the transitive closure up to the given depth.
*/
def dependentFiles(depth: Int, changed: Set[AbstractFile]): Set[AbstractFile] = {
val indirect = new mutable.HashSet[AbstractFile]
val newInvalidations = new mutable.HashSet[AbstractFile]
def invalid(file: AbstractFile) =
indirect(file) || changed(file) || (file eq removedFile)
def go(i: Int) : Unit = if(i > 0) {
newInvalidations.clear
for((target, depends) <- dependencies if !invalid(target);
d <- depends)
newInvalidations(target) ||= invalid(d)
indirect ++= newInvalidations
if (!newInvalidations.isEmpty) go(i - 1)
}
go(depth)
indirect --= changed
}
def writeTo(file: AbstractFile, fromFile: AbstractFile => String): Unit =
writeToFile(file)(out => writeTo(new PrintStream(out), fromFile))
def writeTo(print: PrintStream, fromFile: AbstractFile => String): Unit = {
def emit(tracker: Tracker) =
for ((f, ds) <- tracker; d <- ds) print.println(fromFile(f) + arrow + fromFile(d))
cleanEmpty
print.println(classpath)
print.println(separator)
emit(dependencies)
print.println(separator)
emit(targets)
}
}
object FileDependencies {
private val separator:String = "-------"
private val arrow = " -> "
private val removedFile = new VirtualFile("removed")
private def validLine(l: String) = (l != null) && (l != separator)
def readFrom(file: AbstractFile, toFile: String => AbstractFile): Option[FileDependencies] =
readFromFile(file) { in =>
val reader = new BufferedReader(new InputStreamReader(in))
val it = new FileDependencies(reader.readLine)
def readLines(valid: Boolean)(f: (AbstractFile, AbstractFile) => Unit): Boolean = {
var continue = valid
var line: String = null
while (continue && {line = reader.readLine; validLine(line)}) {
line.split(arrow) match {
case Array(from, on) => f(toFile(from), toFile(on))
case _ =>
global.inform("Parse error: Unrecognised string " + line)
continue = false
}
}
continue
}
reader.readLine
val dResult = readLines(true)(
(_, _) match {
case (null, _) => // fromFile is removed, it's ok
case (fromFile, null) =>
// onFile is removed, should recompile fromFile
it.depends(fromFile, removedFile)
case (fromFile, onFile) => it.depends(fromFile, onFile)
})
readLines(dResult)(
(_, _) match {
case (null, null) =>
// source and target are all removed, it's ok
case (null, targetFile) =>
// source is removed, should remove relative target later
it.emits(removedFile, targetFile)
case (_, null) =>
// it may has been cleaned outside, or removed during last phase
case (sourceFile, targetFile) => it.emits(sourceFile, targetFile)
})
Some(it)
}
}
def writeToFile[T](file: AbstractFile)(f: OutputStream => T) : T = {
val out = file.bufferedOutput
try {
f(out)
} finally {
out.close
}
}
def readFromFile[T](file: AbstractFile)(f: InputStream => T) : T = {
val in = file.input
try{
f(in)
} finally {
in.close
}
}
}
|