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 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
43 | 43 |
44 SourceGraph(this._context, this._options); | 44 SourceGraph(this._context, this._options); |
45 | 45 |
46 /// Node associated with a resolved [uri]. | 46 /// Node associated with a resolved [uri]. |
47 SourceNode nodeFromUri(Uri uri) { | 47 SourceNode nodeFromUri(Uri uri) { |
48 var uriString = Uri.encodeFull('$uri'); | 48 var uriString = Uri.encodeFull('$uri'); |
49 return nodes.putIfAbsent(uri, () { | 49 return nodes.putIfAbsent(uri, () { |
50 var source = _context.sourceFactory.forUri(uriString); | 50 var source = _context.sourceFactory.forUri(uriString); |
51 var extension = path.extension(uriString); | 51 var extension = path.extension(uriString); |
52 if (extension == '.html') { | 52 if (extension == '.html') { |
53 return new HtmlSourceNode(uri, source); | 53 return new HtmlSourceNode(uri, source, this); |
54 } else if (extension == '.dart' || uriString.startsWith('dart:')) { | 54 } else if (extension == '.dart' || uriString.startsWith('dart:')) { |
55 return new DartSourceNode(uri, source); | 55 return new DartSourceNode(uri, source); |
56 } else if (extension == '.js') { | |
57 return new JavaScriptSourceNode(uri, source); | |
56 } else { | 58 } else { |
57 assert(false); // unreachable | 59 assert(false); // unreachable |
58 } | 60 } |
59 }); | 61 }); |
60 } | 62 } |
61 } | 63 } |
62 | 64 |
63 /// A node in the import graph representing a source file. | 65 /// A node in the import graph representing a source file. |
64 abstract class SourceNode { | 66 abstract class SourceNode { |
65 /// Resolved URI for this node. | 67 /// Resolved URI for this node. |
66 final Uri uri; | 68 final Uri uri; |
67 | 69 |
68 /// Resolved source from the analyzer. We let the analyzer internally track | 70 /// Resolved source from the analyzer. We let the analyzer internally track |
69 /// for modifications to the source files. | 71 /// for modifications to the source files. |
70 final Source source; | 72 final Source source; |
71 | 73 |
72 /// Last stamp read from `source.modificationStamp`. | 74 /// Last stamp read from `source.modificationStamp`. |
73 int _lastStamp = 0; | 75 int _lastStamp = 0; |
74 | 76 |
75 /// Whether we need to rebuild this source file. | 77 /// Whether we need to rebuild this source file. |
76 bool needsRebuild = false; | 78 bool needsRebuild = false; |
77 | 79 |
78 /// Whether the structure of dependencies from this node (scripts, imports, | 80 /// Whether the structure of dependencies from this node (scripts, imports, |
79 /// exports, or parts) changed after we reparsed its contents. | 81 /// exports, or parts) changed after we reparsed its contents. |
80 bool structureChanged = false; | 82 bool structureChanged = false; |
81 | 83 |
82 /// Direct dependencies in the [SourceGraph]. These include script tags for | 84 /// Direct dependencies in the [SourceGraph]. These include script tags for |
83 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. | 85 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. |
84 Iterable<SourceNode> get allDeps; | 86 Iterable<SourceNode> get allDeps => const []; |
85 | 87 |
86 /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many | 88 /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many |
87 /// operations we mainly care about dependencies at the library level, so | 89 /// operations we mainly care about dependencies at the library level, so |
88 /// parts are excluded from this list. | 90 /// parts are excluded from this list. |
89 Iterable<SourceNode> get depsWithoutParts; | 91 Iterable<SourceNode> get depsWithoutParts => const []; |
90 | 92 |
91 SourceNode(this.uri, this.source); | 93 SourceNode(this.uri, this.source); |
92 | 94 |
93 /// Check for whether the file has changed and, if so, mark [needsRebuild] and | 95 /// Check for whether the file has changed and, if so, mark [needsRebuild] and |
94 /// [structureChanged] as necessary. | 96 /// [structureChanged] as necessary. |
95 void update(SourceGraph graph) { | 97 void update(SourceGraph graph) { |
96 int newStamp = source.modificationStamp; | 98 int newStamp = source.modificationStamp; |
97 if (newStamp > _lastStamp) { | 99 if (newStamp > _lastStamp) { |
98 _lastStamp = newStamp; | 100 _lastStamp = newStamp; |
99 needsRebuild = true; | 101 needsRebuild = true; |
100 } | 102 } |
101 } | 103 } |
102 | 104 |
103 String toString() { | 105 String toString() { |
104 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; | 106 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; |
105 return '[$runtimeType: $simpleUri]'; | 107 return '[$runtimeType: $simpleUri]'; |
106 } | 108 } |
107 } | 109 } |
108 | 110 |
109 /// A node representing an entry HTML source file. | 111 /// A node representing an entry HTML source file. |
110 class HtmlSourceNode extends SourceNode { | 112 class HtmlSourceNode extends SourceNode { |
113 /// Javascript dependencies, included by default on any application. | |
114 Set<JavaScriptSourceNode> runtimeDeps = new Set<JavaScriptSourceNode>(); | |
Jennifer Messerly
2015/03/09 16:12:41
Are we moving to a style where we duplicate the fu
Siggi Cherem (dart-lang)
2015/03/09 17:31:39
Good question - I would prefer not to as well. But
Jennifer Messerly
2015/03/09 17:34:18
yeah, seems fine to me. Could also be "var" I supp
| |
115 | |
111 /// Libraries referred to via script tags. | 116 /// Libraries referred to via script tags. |
112 Set<DartSourceNode> scripts = new Set<DartSourceNode>(); | 117 Set<DartSourceNode> scripts = new Set<DartSourceNode>(); |
113 | 118 |
114 @override | 119 @override |
115 Iterable<SourceNode> get allDeps => scripts; | 120 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e); |
116 | 121 |
117 @override | 122 @override |
118 Iterable<SourceNode> get depsWithoutParts => scripts; | 123 Iterable<SourceNode> get depsWithoutParts => allDeps; |
119 | 124 |
120 /// Parsed document, updated whenever [update] is invoked. | 125 /// Parsed document, updated whenever [update] is invoked. |
121 Document document; | 126 Document document; |
122 | 127 |
123 HtmlSourceNode(uri, source) : super(uri, source); | 128 HtmlSourceNode(uri, source, graph) : super(uri, source) { |
129 var prefix = 'package:dev_compiler/runtime'; | |
130 runtimeDeps | |
131 ..add(graph.nodeFromUri(Uri.parse('$prefix/dart_runtime.js'))) | |
132 ..add(graph.nodeFromUri(Uri.parse('$prefix/harmony_feature_check.js'))); | |
133 } | |
124 | 134 |
125 void update(SourceGraph graph) { | 135 void update(SourceGraph graph) { |
126 super.update(graph); | 136 super.update(graph); |
127 if (needsRebuild) { | 137 if (needsRebuild) { |
128 document = html.parse(source.contents.data, generateSpans: true); | 138 document = html.parse(source.contents.data, generateSpans: true); |
129 var newScripts = new Set<DartSourceNode>(); | 139 var newScripts = new Set<DartSourceNode>(); |
130 var tags = document.querySelectorAll('script[type="application/dart"]'); | 140 var tags = document.querySelectorAll('script[type="application/dart"]'); |
131 for (var script in tags) { | 141 for (var script in tags) { |
132 var src = script.attributes['src']; | 142 var src = script.attributes['src']; |
133 if (src == null) { | 143 if (src == null) { |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
250 // Technically for parts we don't need to look at the contents. If they | 260 // Technically for parts we don't need to look at the contents. If they |
251 // contain imports, exports, or parts, we'll ignore them in our crawling. | 261 // contain imports, exports, or parts, we'll ignore them in our crawling. |
252 // However we do a full update to make it easier to adjust when users | 262 // However we do a full update to make it easier to adjust when users |
253 // switch a file from a part to a library. | 263 // switch a file from a part to a library. |
254 p.update(graph); | 264 p.update(graph); |
255 if (p.needsRebuild) needsRebuild = true; | 265 if (p.needsRebuild) needsRebuild = true; |
256 } | 266 } |
257 } | 267 } |
258 } | 268 } |
259 | 269 |
270 /// Represents a Javascript runtime resource from our compiler that is needed to | |
271 /// run an application. | |
272 class JavaScriptSourceNode extends SourceNode { | |
273 JavaScriptSourceNode(uri, source) : super(uri, source); | |
274 } | |
275 | |
260 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable | 276 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable |
261 /// from [start]. | 277 /// from [start]. |
262 /// | 278 /// |
263 /// That is, staring from [start], we update the graph by detecting file changes | 279 /// That is, staring from [start], we update the graph by detecting file changes |
264 /// and rebuilding the structure of the graph wherever it changed (an import was | 280 /// and rebuilding the structure of the graph wherever it changed (an import was |
265 /// added or removed, etc). | 281 /// added or removed, etc). |
266 /// | 282 /// |
267 /// After calling this function a node is marked with `needsRebuild` only if it | 283 /// After calling this function a node is marked with `needsRebuild` only if it |
268 /// contained local changes. Rebuild decisions that derive from transitive | 284 /// contained local changes. Rebuild decisions that derive from transitive |
269 /// changes (e.g. when the API of a dependency changed) are handled later in | 285 /// changes (e.g. when the API of a dependency changed) are handled later in |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
313 // cases anways require using summaries to understand what parts of the public | 329 // cases anways require using summaries to understand what parts of the public |
314 // API may be affected by transitive changes. The re-export case is just one | 330 // API may be affected by transitive changes. The re-export case is just one |
315 // of those transitive cases, but is not sufficient. See | 331 // of those transitive cases, but is not sufficient. See |
316 // https://github.com/dart-lang/dev_compiler/issues/76 | 332 // https://github.com/dart-lang/dev_compiler/issues/76 |
317 var apiChangeDetected = new HashSet<SourceNode>(); | 333 var apiChangeDetected = new HashSet<SourceNode>(); |
318 bool structureHasChanged = false; | 334 bool structureHasChanged = false; |
319 | 335 |
320 bool shouldBuildNode(SourceNode n) { | 336 bool shouldBuildNode(SourceNode n) { |
321 if (n.needsRebuild) return true; | 337 if (n.needsRebuild) return true; |
322 if (n is HtmlSourceNode) return structureHasChanged; | 338 if (n is HtmlSourceNode) return structureHasChanged; |
339 if (n is JavaScriptSourceNode) return false; | |
Jennifer Messerly
2015/03/09 16:12:41
eventually we may want to follow ES6 imports?
Siggi Cherem (dart-lang)
2015/03/09 17:31:39
we totally could if it comes to it.
| |
323 return (n as DartSourceNode).imports | 340 return (n as DartSourceNode).imports |
324 .any((i) => apiChangeDetected.contains(i)); | 341 .any((i) => apiChangeDetected.contains(i)); |
325 } | 342 } |
326 | 343 |
327 visitInPostOrder(start, (n) { | 344 visitInPostOrder(start, (n) { |
328 if (n.structureChanged) structureHasChanged = true; | 345 if (n.structureChanged) structureHasChanged = true; |
329 if (shouldBuildNode(n)) { | 346 if (shouldBuildNode(n)) { |
330 if (build(n)) apiChangeDetected.add(n); | 347 if (build(n)) apiChangeDetected.add(n); |
331 } else if (n is DartSourceNode && | 348 } else if (n is DartSourceNode && |
332 n.exports.any((e) => apiChangeDetected.contains(e))) { | 349 n.exports.any((e) => apiChangeDetected.contains(e))) { |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
367 if (!seen.add(node)) return; | 384 if (!seen.add(node)) return; |
368 var deps = includeParts ? node.allDeps : node.depsWithoutParts; | 385 var deps = includeParts ? node.allDeps : node.depsWithoutParts; |
369 deps.forEach(helper); | 386 deps.forEach(helper); |
370 action(node); | 387 action(node); |
371 } | 388 } |
372 helper(start); | 389 helper(start); |
373 } | 390 } |
374 | 391 |
375 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); | 392 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); |
376 final _log = new Logger('dev_compiler.graph'); | 393 final _log = new Logger('dev_compiler.graph'); |
OLD | NEW |