OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// Tracks the shape of the import/export graph and dependencies between files. | 5 /// Tracks the shape of the import/export graph and dependencies between files. |
6 library dev_compiler.src.dependency_graph; | 6 library dev_compiler.src.dependency_graph; |
7 | 7 |
8 import 'dart:collection' show HashSet; | 8 import 'dart:collection' show HashSet; |
9 | 9 |
10 import 'package:analyzer/analyzer.dart' show parseDirectives; | 10 import 'package:analyzer/analyzer.dart' show parseDirectives; |
(...skipping 25 matching lines...) Expand all Loading... | |
36 /// any node. | 36 /// any node. |
37 final Map<Uri, SourceNode> nodes = {}; | 37 final Map<Uri, SourceNode> nodes = {}; |
38 | 38 |
39 /// Analyzer used to resolve source files. | 39 /// Analyzer used to resolve source files. |
40 final AnalysisContext _context; | 40 final AnalysisContext _context; |
41 final CompilerOptions _options; | 41 final CompilerOptions _options; |
42 | 42 |
43 SourceGraph(this._context, this._options); | 43 SourceGraph(this._context, this._options); |
44 | 44 |
45 /// Node associated with a resolved [uri]. | 45 /// Node associated with a resolved [uri]. |
46 SourceNode nodeFromUri(Uri uri, [bool isPart = false]) { | 46 SourceNode nodeFromUri(Uri uri) { |
47 var uriString = Uri.encodeFull('$uri'); | 47 var uriString = Uri.encodeFull('$uri'); |
48 var kind = uriString.endsWith('.html') | |
49 ? SourceKind.HTML | |
50 : isPart ? SourceKind.PART : SourceKind.LIBRARY; | |
51 return nodeFor(uri, _context.sourceFactory.forUri(uriString), kind); | |
52 } | |
53 | |
54 /// Construct the node of the given [kind] with the given [uri] and [source]. | |
55 SourceNode nodeFor(Uri uri, Source source, SourceKind kind) { | |
56 // TODO(sigmund): validate canonicalization? | |
57 // TODO(sigmund): add support for changing a file from one kind to another | |
58 // (e.g. converting a file from a part to a library). | |
59 return nodes.putIfAbsent(uri, () { | 48 return nodes.putIfAbsent(uri, () { |
60 if (kind == SourceKind.HTML) { | 49 var source = _context.sourceFactory.forUri(uriString); |
50 var extension = path.extension(uriString); | |
51 if (extension == '.html') { | |
61 return new HtmlSourceNode(uri, source); | 52 return new HtmlSourceNode(uri, source); |
62 } else if (kind == SourceKind.LIBRARY) { | 53 } else if (extension == '.dart') { |
63 return new LibrarySourceNode(uri, source); | 54 return new DartSourceNode(uri, source); |
64 } else if (kind == SourceKind.PART) { | |
65 return new PartSourceNode(uri, source); | |
66 } else { | 55 } else { |
67 assert(false); // unreachable | 56 assert(false); // unreachable |
68 } | 57 } |
69 }); | 58 }); |
70 } | 59 } |
71 } | 60 } |
72 | 61 |
73 /// A node in the import graph representing a source file. | 62 /// A node in the import graph representing a source file. |
74 abstract class SourceNode { | 63 abstract class SourceNode { |
75 /// Resolved URI for this node. | 64 /// Resolved URI for this node. |
76 final Uri uri; | 65 final Uri uri; |
77 | 66 |
78 /// Resolved source from the analyzer. We let the analyzer internally track | 67 /// Resolved source from the analyzer. We let the analyzer internally track |
79 /// for modifications to the source files. | 68 /// for modifications to the source files. |
80 final Source source; | 69 final Source source; |
81 | 70 |
82 /// Last stamp read from `source.modificationStamp`. | 71 /// Last stamp read from `source.modificationStamp`. |
83 int _lastStamp = 0; | 72 int _lastStamp = 0; |
84 | 73 |
85 /// Whether we need to rebuild this source file. | 74 /// Whether we need to rebuild this source file. |
86 bool needsRebuild = false; | 75 bool needsRebuild = false; |
87 | 76 |
88 /// Whether the structure of dependencies from this node (scripts, imports, | 77 /// Whether the structure of dependencies from this node (scripts, imports, |
89 /// exports, or parts) changed after we reparsed its contents. | 78 /// exports, or parts) changed after we reparsed its contents. |
90 bool structureChanged = false; | 79 bool structureChanged = false; |
91 | 80 |
92 /// Direct dependencies (script tags for HtmlSourceNodes; imports, exports and | 81 /// Direct dependencies in the [SourceGraph]. These include script tags for |
93 /// parts for LibrarySourceNodes). | 82 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. |
94 Iterable<SourceNode> get directDeps; | 83 Iterable<SourceNode> get allDeps; |
84 | |
85 /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many | |
86 /// operations we mainly care about dependencies at the library level, so | |
87 /// parts are excluded from this list. | |
88 Iterable<SourceNode> get depsWithoutParts; | |
95 | 89 |
96 SourceNode(this.uri, this.source); | 90 SourceNode(this.uri, this.source); |
97 | 91 |
98 /// Check for whether the file has changed and, if so, mark [needsRebuild] and | 92 /// Check for whether the file has changed and, if so, mark [needsRebuild] and |
99 /// [structureChanged] as necessary. | 93 /// [structureChanged] as necessary. |
100 void update(SourceGraph graph) { | 94 void update(SourceGraph graph) { |
101 int newStamp = source.modificationStamp; | 95 int newStamp = source.modificationStamp; |
102 if (newStamp > _lastStamp) { | 96 if (newStamp > _lastStamp) { |
103 _lastStamp = newStamp; | 97 _lastStamp = newStamp; |
104 needsRebuild = true; | 98 needsRebuild = true; |
105 } | 99 } |
106 } | 100 } |
107 | 101 |
108 String toString() { | 102 String toString() { |
109 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; | 103 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; |
110 return '[$runtimeType: $simpleUri]'; | 104 return '[$runtimeType: $simpleUri]'; |
111 } | 105 } |
112 } | 106 } |
113 | 107 |
114 /// A node representing an entry HTML source file. | 108 /// A node representing an entry HTML source file. |
115 class HtmlSourceNode extends SourceNode { | 109 class HtmlSourceNode extends SourceNode { |
116 /// Libraries referred to via script tags. | 110 /// Libraries referred to via script tags. |
117 Set<LibrarySourceNode> scripts = new Set<LibrarySourceNode>(); | 111 Set<LibrarySourceNode> scripts = new Set<LibrarySourceNode>(); |
118 | 112 |
119 @override | 113 @override |
120 Iterable<SourceNode> get directDeps => scripts; | 114 Iterable<SourceNode> get allDeps => scripts; |
115 | |
116 @override | |
117 Iterable<SourceNode> get depsWithoutParts => scripts; | |
121 | 118 |
122 /// Parsed document, updated whenever [update] is invoked. | 119 /// Parsed document, updated whenever [update] is invoked. |
123 Document document; | 120 Document document; |
124 | 121 |
125 HtmlSourceNode(uri, source) : super(uri, source); | 122 HtmlSourceNode(uri, source) : super(uri, source); |
126 | 123 |
127 void update(SourceGraph graph) { | 124 void update(SourceGraph graph) { |
128 super.update(graph); | 125 super.update(graph); |
129 if (needsRebuild) { | 126 if (needsRebuild) { |
130 document = html.parse(source.contents.data, generateSpans: true); | 127 document = html.parse(source.contents.data, generateSpans: true); |
(...skipping 18 matching lines...) Expand all Loading... | |
149 } | 146 } |
150 | 147 |
151 if (!_same(newScripts, scripts)) { | 148 if (!_same(newScripts, scripts)) { |
152 structureChanged = true; | 149 structureChanged = true; |
153 scripts = newScripts; | 150 scripts = newScripts; |
154 } | 151 } |
155 } | 152 } |
156 } | 153 } |
157 } | 154 } |
158 | 155 |
159 /// A node representing a Dart part. | 156 /// A node representing a Dart library or part. |
160 class PartSourceNode extends SourceNode { | 157 class DartSourceNode extends SourceNode { |
161 final Iterable<SourceNode> directDeps = const []; | 158 /// Set of imported libraries (empty for part files). |
162 PartSourceNode(uri, source) : super(uri, source); | 159 Set<DartSourceNode> imports = new Set<DartSourceNode>(); |
163 } | |
164 | 160 |
165 /// A node representing a Dart library. | 161 /// Set of exported libraries (empty for part files). |
166 class LibrarySourceNode extends SourceNode { | 162 Set<DartSourceNode> exports = new Set<DartSourceNode>(); |
167 LibrarySourceNode(uri, source) : super(uri, source); | |
168 | 163 |
169 Set<LibrarySourceNode> imports = new Set<LibrarySourceNode>(); | 164 /// Parts of this library (empty for part files). |
170 Set<LibrarySourceNode> exports = new Set<LibrarySourceNode>(); | 165 Set<DartSourceNode> parts = new Set<DartSourceNode>(); |
171 Set<PartSourceNode> parts = new Set<PartSourceNode>(); | |
172 | 166 |
173 Iterable<SourceNode> get directDeps => | 167 DartSourceNode(uri, source) : super(uri, source); |
168 | |
169 @override | |
170 Iterable<SourceNode> get allDeps => | |
174 [imports, exports, parts].expand((e) => e); | 171 [imports, exports, parts].expand((e) => e); |
175 | 172 |
173 @override | |
174 Iterable<SourceNode> get depsWithoutParts => | |
175 [imports, exports].expand((e) => e); | |
176 | |
176 LibraryInfo info; | 177 LibraryInfo info; |
177 | 178 |
178 void update(SourceGraph graph) { | 179 void update(SourceGraph graph) { |
179 super.update(graph); | 180 super.update(graph); |
181 | |
180 if (needsRebuild && source.contents.data != null) { | 182 if (needsRebuild && source.contents.data != null) { |
181 // If the defining compilation-unit changed, the structure might have | 183 // If the defining compilation-unit changed, the structure might have |
182 // changed. | 184 // changed. |
183 var unit = parseDirectives(source.contents.data, name: source.fullName); | 185 var unit = parseDirectives(source.contents.data, name: source.fullName); |
184 var newImports = new Set<LibrarySourceNode>(); | 186 var newImports = new Set<DartSourceNode>(); |
185 var newExports = new Set<LibrarySourceNode>(); | 187 var newExports = new Set<DartSourceNode>(); |
186 var newParts = new Set<PartSourceNode>(); | 188 var newParts = new Set<DartSourceNode>(); |
187 for (var d in unit.directives) { | 189 for (var d in unit.directives) { |
188 if (d is LibraryDirective) continue; | 190 if (d is LibraryDirective) continue; |
189 var target = | 191 var target = |
190 ParseDartTask.resolveDirective(graph._context, source, d, null); | 192 ParseDartTask.resolveDirective(graph._context, source, d, null); |
191 var uri = target.uri; | 193 var uri = target.uri; |
192 var node = graph.nodeFor(uri, target, | 194 var node = graph.nodes.putIfAbsent(uri, |
193 d is PartDirective ? SourceKind.PART : SourceKind.LIBRARY); | 195 () => new DartSourceNode(uri, target)); |
194 if (!node.source.exists()) { | 196 if (!node.source.exists()) { |
195 _log.severe(spanForNode(unit, source, d).message( | 197 _log.severe(spanForNode(unit, source, d).message( |
196 'File $uri not found', | 198 'File $uri not found', |
197 color: graph._options.useColors ? colorOf('error') : false)); | 199 color: graph._options.useColors ? colorOf('error') : false)); |
198 } | 200 } |
199 | 201 |
200 if (d is ImportDirective) { | 202 if (d is ImportDirective) { |
201 newImports.add(node); | 203 newImports.add(node); |
202 } else if (d is ExportDirective) { | 204 } else if (d is ExportDirective) { |
203 newExports.add(node); | 205 newExports.add(node); |
(...skipping 14 matching lines...) Expand all Loading... | |
218 | 220 |
219 if (!_same(newParts, parts)) { | 221 if (!_same(newParts, parts)) { |
220 structureChanged = true; | 222 structureChanged = true; |
221 parts = newParts; | 223 parts = newParts; |
222 } | 224 } |
223 } | 225 } |
224 | 226 |
225 // The library should be marked as needing rebuild if a part changed | 227 // The library should be marked as needing rebuild if a part changed |
226 // internally: | 228 // internally: |
227 for (var p in parts) { | 229 for (var p in parts) { |
228 p.update(graph); | 230 p._updateAsPart(graph); |
229 if (p.needsRebuild) needsRebuild = true; | 231 if (p.needsRebuild) needsRebuild = true; |
230 } | 232 } |
231 } | 233 } |
234 | |
235 void _updateAsPart(SourceGraph graph) { | |
236 // For parts we don't need to look at the contents, we don't expect any | |
237 // structural changes, we simply clear dependencies in case this part was | |
238 // previously a library file, and look just for file changes. | |
Jennifer Messerly
2015/03/05 01:37:01
just curious: how does the other direction work? p
Siggi Cherem (dart-lang)
2015/03/06 18:52:06
Good question - initially I was leaving the import
| |
239 imports.clear(); | |
240 exports.clear(); | |
241 parts.clear(); | |
242 super.update(graph); | |
243 } | |
244 | |
232 } | 245 } |
233 | 246 |
234 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable | 247 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable |
235 /// from [start]. | 248 /// from [start]. |
236 /// | 249 /// |
237 /// That is, staring from [start], we update the graph by detecting file changes | 250 /// That is, staring from [start], we update the graph by detecting file changes |
238 /// and rebuilding the structure of the graph wherever it changed (an import was | 251 /// and rebuilding the structure of the graph wherever it changed (an import was |
239 /// added or removed, etc). | 252 /// added or removed, etc). |
240 /// | 253 /// |
241 /// After calling this function a node is marked with `needsRebuild` only if it | 254 /// After calling this function a node is marked with `needsRebuild` only if it |
242 /// contained local changes. Rebuild decisions that derive from transitive | 255 /// contained local changes. Rebuild decisions that derive from transitive |
243 /// changes (e.g. when the API of a dependency changed) are handled later in | 256 /// changes (e.g. when the API of a dependency changed) are handled later in |
244 /// [rebuild]. | 257 /// [rebuild]. |
245 void refreshStructureAndMarks(SourceNode start, SourceGraph graph) { | 258 void refreshStructureAndMarks(SourceNode start, SourceGraph graph) { |
246 visitInPreOrder(start, (n) => n.update(graph)); | 259 visitInPreOrder(start, (n) => n.update(graph), includeParts: false); |
247 } | 260 } |
248 | 261 |
249 /// Clears all the `needsRebuild` and `structureChanged` marks in nodes | 262 /// Clears all the `needsRebuild` and `structureChanged` marks in nodes |
250 /// reachable from [start]. | 263 /// reachable from [start]. |
251 void clearMarks(SourceNode start) { | 264 void clearMarks(SourceNode start) { |
252 visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false); | 265 visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false, |
266 includeParts: true); | |
253 } | 267 } |
254 | 268 |
255 /// Traverses from [start] with the purpose of building any source that needs to | 269 /// Traverses from [start] with the purpose of building any source that needs to |
256 /// be rebuilt. | 270 /// be rebuilt. |
257 /// | 271 /// |
258 /// This function will call [build] in a post-order fashion, on a subset of the | 272 /// This function will call [build] in a post-order fashion, on a subset of the |
259 /// reachable nodes. There are four rules used to decide when to rebuild a node | 273 /// reachable nodes. There are four rules used to decide when to rebuild a node |
260 /// (call [build] on a node): | 274 /// (call [build] on a node): |
261 /// | 275 /// |
262 /// * Only rebuild Dart libraries ([LibrarySourceNode]) or HTML files | 276 /// * Only rebuild Dart libraries ([DartSourceNode]) or HTML files |
263 /// ([HtmlSourceNode]), but never part files ([PartSourceNode]). That is | 277 /// ([HtmlSourceNode]), but skip part files. That is because those are |
264 /// because those are built as part of some library. | 278 /// built as part of some library. |
265 /// | 279 /// |
266 /// * Always rebuild [LibrarySourceNode]s and [HtmlSourceNode]s with local | 280 /// * Always rebuild [DartSourceNode]s and [HtmlSourceNode]s with local |
267 /// changes or changes in a part of the library. Internally this function | 281 /// changes or changes in a part of the library. Internally this function |
268 /// calls [refreshStructureAndMarks] to ensure that the graph structure is | 282 /// calls [refreshStructureAndMarks] to ensure that the graph structure is |
269 /// up-to-date and that these nodes with local changes contain the | 283 /// up-to-date and that these nodes with local changes contain the |
270 /// `needsRebuild` bit. | 284 /// `needsRebuild` bit. |
271 /// | 285 /// |
272 /// * Rebuild [HtmlSourceNode]s if there were structural changes somewhere | 286 /// * Rebuild [HtmlSourceNode]s if there were structural changes somewhere |
273 /// down its reachable subgraph. This is done because HTML files embed the | 287 /// down its reachable subgraph. This is done because HTML files embed the |
274 /// transitive closure of the import graph in their output. | 288 /// transitive closure of the import graph in their output. |
275 /// | 289 /// |
276 /// * Rebuild [LibrarySourceNode]s that depend on other [LibrarySourceNode]s | 290 /// * Rebuild [DartSourceNode]s that depend on other [DartSourceNode]s |
277 /// whose API may have changed. The result of [build] is used to determine | 291 /// whose API may have changed. The result of [build] is used to determine |
278 /// whether other nodes need to be rebuilt. The function [build] is expected | 292 /// whether other nodes need to be rebuilt. The function [build] is expected |
279 /// to return `true` on a node `n` if it detemines other nodes that import | 293 /// to return `true` on a node `n` if it detemines other nodes that import |
280 /// `n` may need to be rebuilt as well. | 294 /// `n` may need to be rebuilt as well. |
281 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) { | 295 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) { |
282 refreshStructureAndMarks(start, graph); | 296 refreshStructureAndMarks(start, graph); |
283 // Hold which source nodes may have changed their public API, this includes | 297 // Hold which source nodes may have changed their public API, this includes |
284 // libraries that were modified or libraries that export other modified APIs. | 298 // libraries that were modified or libraries that export other modified APIs. |
285 // TODO(sigmund): consider removing this special support for exports? Many | 299 // TODO(sigmund): consider removing this special support for exports? Many |
286 // cases anways require using summaries to understand what parts of the public | 300 // cases anways require using summaries to understand what parts of the public |
287 // API may be affected by transitive changes. The re-export case is just one | 301 // API may be affected by transitive changes. The re-export case is just one |
288 // of those transitive cases, but is not sufficient. See | 302 // of those transitive cases, but is not sufficient. See |
289 // https://github.com/dart-lang/dev_compiler/issues/76 | 303 // https://github.com/dart-lang/dev_compiler/issues/76 |
290 var apiChangeDetected = new HashSet<SourceNode>(); | 304 var apiChangeDetected = new HashSet<SourceNode>(); |
291 bool structureHasChanged = false; | 305 bool structureHasChanged = false; |
292 | 306 |
293 bool shouldBuildNode(SourceNode n) { | 307 bool shouldBuildNode(SourceNode n) { |
294 if (n is PartSourceNode) return false; | |
295 if (n.needsRebuild) return true; | 308 if (n.needsRebuild) return true; |
296 if (n is HtmlSourceNode) return structureHasChanged; | 309 if (n is HtmlSourceNode) return structureHasChanged; |
297 return (n as LibrarySourceNode).imports | 310 return (n as DartSourceNode).imports |
298 .any((i) => apiChangeDetected.contains(i)); | 311 .any((i) => apiChangeDetected.contains(i)); |
299 } | 312 } |
300 | 313 |
301 visitInPostOrder(start, (n) { | 314 visitInPostOrder(start, (n) { |
302 if (n.structureChanged) structureHasChanged = true; | 315 if (n.structureChanged) structureHasChanged = true; |
303 if (shouldBuildNode(n)) { | 316 if (shouldBuildNode(n)) { |
304 if (build(n)) apiChangeDetected.add(n); | 317 if (build(n)) apiChangeDetected.add(n); |
305 } else if (n is LibrarySourceNode && | 318 } else if (n is DartSourceNode && |
306 n.exports.any((e) => apiChangeDetected.contains(e))) { | 319 n.exports.any((e) => apiChangeDetected.contains(e))) { |
307 apiChangeDetected.add(n); | 320 apiChangeDetected.add(n); |
308 } | 321 } |
309 n.needsRebuild = false; | 322 n.needsRebuild = false; |
310 n.structureChanged = false; | 323 n.structureChanged = false; |
311 }); | 324 if (n is DartSourceNode) { |
325 n.parts.forEach((p) => p.needsRebuild = false); | |
326 } | |
327 }, includeParts: false); | |
312 } | 328 } |
313 | 329 |
314 /// Helper that runs [action] on nodes reachable from [node] in pre-order. | 330 /// Helper that runs [action] on nodes reachable from [start] in pre-order. |
315 visitInPreOrder(SourceNode node, void action(SourceNode node), | 331 visitInPreOrder(SourceNode start, void action(SourceNode node), |
316 {Set<SourceNode> seen}) { | 332 {bool includeParts: false}) { |
317 if (seen == null) seen = new HashSet<SourceNode>(); | 333 var seen = new HashSet<SourceNode>(); |
318 if (!seen.add(node)) return; | 334 helper(SourceNode node) { |
319 action(node); | 335 if (!seen.add(node)) return; |
320 node.directDeps.forEach((d) => visitInPreOrder(d, action, seen: seen)); | 336 action(node); |
337 var deps = includeParts ? node.allDeps : node.depsWithoutParts; | |
338 deps.forEach(helper); | |
339 } | |
340 helper(start); | |
321 } | 341 } |
322 | 342 |
323 /// Helper that runs [action] on nodes reachable from [node] in post-order. | 343 /// Helper that runs [action] on nodes reachable from [start] in post-order. |
324 visitInPostOrder(SourceNode node, void action(SourceNode node), | 344 visitInPostOrder(SourceNode start, void action(SourceNode node), |
325 {Set<SourceNode> seen}) { | 345 {bool includeParts: false}) { |
326 if (seen == null) seen = new HashSet<SourceNode>(); | 346 var seen = new HashSet<SourceNode>(); |
327 if (!seen.add(node)) return; | 347 helper(SourceNode node) { |
328 node.directDeps.forEach((d) => visitInPostOrder(d, action, seen: seen)); | 348 if (!seen.add(node)) return; |
329 action(node); | 349 var deps = includeParts ? node.allDeps : node.depsWithoutParts; |
350 deps.forEach(helper); | |
351 action(node); | |
352 } | |
353 helper(start); | |
330 } | 354 } |
331 | 355 |
332 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); | 356 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); |
333 final _log = new Logger('dev_compiler.graph'); | 357 final _log = new Logger('dev_compiler.graph'); |
OLD | NEW |