Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(141)

Side by Side Diff: lib/src/dependency_graph.dart

Issue 973433003: Initial cut for a development server (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /// Tracks the shape of the import/export graph and dependencies between files.
Jennifer Messerly 2015/03/03 02:24:21 copyright?
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 Done.
2 library ddc.src.dependency_graph;
3
4 import 'package:analyzer/analyzer.dart' show parseDirectives;
5 import 'package:analyzer/src/generated/ast.dart'
6 show
7 LibraryDirective,
8 ImportDirective,
9 ExportDirective,
10 PartDirective,
11 CompilationUnit,
12 Identifier;
13 import 'package:analyzer/src/generated/engine.dart'
14 show ParseDartTask, AnalysisContext;
15 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind;
16 import 'package:html5lib/dom.dart' show Document;
17 import 'package:html5lib/parser.dart' as html;
18 import 'package:logging/logging.dart' show Logger;
19 import 'package:path/path.dart' as path;
20
21 import 'info.dart';
22 import 'options.dart';
23 import 'utils.dart';
24
25 /// Holds references to all source nodes in the import graph. This is mainly
26 /// used as a level of indirection to ensure that each source has a canonical
27 /// representation.
28 class SourceGraph {
29 /// All nodes in the source graph. Used to get a canonical representation for
30 /// any node.
31 Map<Uri, SourceNode> nodes = {};
32
33 /// Analyzer used to resolve source files.
34 AnalysisContext _context;
Jennifer Messerly 2015/03/03 02:24:22 final?
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 Done.
35 CompilerOptions _options;
36
37 SourceGraph(this._context, this._options);
38
39 /// Node associated with a resolved [uri].
40 SourceNode nodeFromUri(Uri uri, [bool isPart = false]) {
41 var uriString = Uri.encodeFull('$uri');
42 var kind = uriString.endsWith('.html')
43 ? SourceKind.HTML
44 : isPart ? SourceKind.PART : SourceKind.LIBRARY;
45 return nodeFor(uri, _context.sourceFactory.forUri(uriString), kind);
46 }
47
48 /// Construct the node of the given [kind] with the given [uri] and [source].
49 SourceNode nodeFor(Uri uri, Source source, SourceKind kind) {
50 // TODO(sigmund): validate canonicalization?
51 // TODO(sigmund): add support for changing a file from one kind to another
52 // (e.g. converting a file from a part to a library).
53 return nodes.putIfAbsent(uri, () {
54 if (kind == SourceKind.HTML) {
55 return new HtmlSourceNode(uri, source);
56 } else if (kind == SourceKind.LIBRARY) {
57 return new LibrarySourceNode(uri, source);
58 } else if (kind == SourceKind.PART) {
59 return new PartSourceNode(uri, source);
60 } else {
61 assert(false); // unreachable
62 }
63 });
64 }
65 }
66
67 /// A node in the import graph representing a source file.
68 abstract class SourceNode {
69 /// Resolved URI for this node.
70 final Uri uri;
71
72 /// Resolved source from the analyzer. We let the analyzer internally track
73 /// for modifications to the source files.
74 final Source source;
75
76 /// Last stamp read from `source.modificationStamp`.
77 int _lastStamp = 0;
78
79 /// Whether we need to rebuild this source file.
80 bool needsRebuild = false;
81
82 /// Whether the structure of dependencies from this node (scripts, imports,
83 /// exports, or parts) changed after we reparsed its contents.
84 bool structureChanged = false;
85
86 /// Direct dependencies (script tags for HtmlSourceNodes; imports, exports and
87 /// parts for LibrarySourceNodes).
88 Iterable<SourceNode> get directDeps;
89
90 SourceNode(this.uri, this.source);
91
92 /// Check for whether the file has changed and, if so, mark [needsRebuild] and
93 /// [structureChanged] as necessary.
94 void update(SourceGraph graph) {
95 int newStamp = source.modificationStamp;
96 if (newStamp > _lastStamp) {
97 _lastStamp = newStamp;
98 needsRebuild = true;
99 }
100 }
101
102 String toString() {
103 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri";
104 return '[$runtimeType: $simpleUri]';
105 }
106 }
107
108 /// A node representing an entry HTML source file.
109 class HtmlSourceNode extends SourceNode {
110 /// Libraries referred to via script tags.
111 Set<LibrarySourceNode> scripts = new Set<LibrarySourceNode>();
Jennifer Messerly 2015/03/03 02:24:21 final? also, we are officially Java :)
Siggi Cherem (dart-lang) 2015/03/04 04:44:24 In this case no, we update the entire list every t
112
113 @override
114 Iterable<SourceNode> get directDeps => scripts;
115
116 /// Parsed document, updated whenever [update] is invoked.
117 Document document;
118
119 HtmlSourceNode(uri, source) : super(uri, source);
120
121 void update(SourceGraph graph) {
122 super.update(graph);
123 if (needsRebuild) {
124 document = html.parse(source.contents.data, generateSpans: true);
125 var newScripts = new Set<LibrarySourceNode>();
126 var tags = document.querySelectorAll('script[type="application/dart"]');
127 for (var script in tags) {
128 var src = script.attributes['src'];
129 if (src == null) {
130 // TODO(sigmund): expose these as compile-time failures
131 _log.severe(script.sourceSpan.message(
132 'inlined script tags not supported at this time '
133 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).',
134 color: graph._options.useColors ? colorOf('error') : false));
135 continue;
136 }
137 var node = graph.nodeFromUri(uri.resolve(src));
138 if (!node.source.exists()) {
139 _log.severe(script.sourceSpan.message('Script file $src not found',
140 color: graph._options.useColors ? colorOf('error') : false));
141 }
142 newScripts.add(node);
143 }
144
145 if (!_same(newScripts, scripts)) {
146 structureChanged = true;
147 scripts = newScripts;
148 }
149 }
150 }
151 }
152
153 /// A node representing a Dart part.
154 class PartSourceNode extends SourceNode {
155 final Iterable<SourceNode> directDeps = const [];
156 PartSourceNode(uri, source) : super(uri, source);
157 }
158
159 /// A node representing a Dart library.
160 class LibrarySourceNode extends SourceNode {
161 LibrarySourceNode(uri, source) : super(uri, source);
162
163 Set<LibrarySourceNode> imports = new Set<LibrarySourceNode>();
164 Set<LibrarySourceNode> exports = new Set<LibrarySourceNode>();
165 Set<PartSourceNode> parts = new Set<PartSourceNode>();
166
167 Iterable<SourceNode> get directDeps =>
168 [imports, exports, parts].expand((e) => e);
169
170 LibraryInfo info;
171
172 void update(SourceGraph graph) {
173 super.update(graph);
174 if (needsRebuild && source.contents.data != null) {
175 // If the defining compilation-unit changed, the structure might have
176 // changed.
177 var unit = parseDirectives(source.contents.data, name: source.fullName);
178 var newImports = new Set<LibrarySourceNode>();
179 var newExports = new Set<LibrarySourceNode>();
180 var newParts = new Set<PartSourceNode>();
181 for (var d in unit.directives) {
182 if (d is LibraryDirective) continue;
183 var target =
184 ParseDartTask.resolveDirective(graph._context, source, d, null);
185 var uri = target.uri;
186 var node = graph.nodeFor(uri, target,
187 d is PartDirective ? SourceKind.PART : SourceKind.LIBRARY);
188 if (!node.source.exists()) {
189 _log.severe(spanFor(unit, source.uri, d).message(
190 'File $uri not found',
191 color: graph._options.useColors ? colorOf('error') : false));
192 }
193
194 if (d is ImportDirective) {
195 newImports.add(node);
196 } else if (d is ExportDirective) {
197 newExports.add(node);
198 } else if (d is PartDirective) {
199 newParts.add(node);
200 }
201 }
202
203 if (!_same(newImports, imports)) {
204 structureChanged = true;
205 imports = newImports;
206 }
207
208 if (!_same(newExports, exports)) {
209 structureChanged = true;
210 exports = newExports;
211 }
212
213 if (!_same(newParts, parts)) {
214 structureChanged = true;
215 parts = newParts;
216 }
217 }
218
219 // The library should be marked as needing rebuild if a part changed
220 // internally:
221 for (var p in parts) {
222 p.update(graph);
223 if (p.needsRebuild) needsRebuild = true;
224 }
225 }
226 }
227
228 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable
229 /// from [start].
230 ///
231 /// That is, staring from [start], we update the graph by detecting file changes
232 /// and rebuilding the structure of the graph wherever it changed (an import was
233 /// added or removed, etc).
234 ///
235 /// After calling this function a node is marked with `needsRebuild` only if it
236 /// contained local changes. Rebuild decisions that derive from transitive
237 /// changes (e.g. when the API of a dependency changed) are handled later in
238 /// [rebuild].
239 void refreshStructureAndMarks(SourceNode start, SourceGraph graph) {
240 visitInPreOrder(start, (n) => n.update(graph));
241 }
242
243 /// Clears all the `needsRebuild` and `structureChanged` marks in nodes
244 /// reachable from [start].
245 void clearMarks(SourceNode start) {
246 visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false);
247 }
248
249 /// Traverses from [start] with the purpose of building any source that needs to
250 /// be rebuilt.
251 ///
252 /// This function will call [build] in a post-order fashion, on a subset of the
253 /// reachable nodes. There are four rules used to decide when to rebuild a node
254 /// (call [build] on a node):
255 ///
256 /// * Only rebuild Dart libraries ([LibrarySourceNode]) or HTML files
257 /// ([HtmlSourceNode]), but never part files ([PartSourceNode]). That is
258 /// because those are built as part of some library.
259 ///
260 /// * Always rebuild [LibrarySourceNode]s and [HtmlSourceNode]s with local
261 /// changes or changes in a part of the library. Internally this function
262 /// calls [refreshStructureAndMarks] to ensure that the graph structure is
263 /// up-to-date and that these nodes with local changes contain the
264 /// `needsRebuild` bit.
265 ///
266 /// * Rebuild [HtmlSourceNode]s if there were structural changes somewhere
267 /// down its reachable subgraph. This is done because HTML files embed the
268 /// transitive closure of the import graph in their output.
269 ///
270 /// * Rebuild [LibrarySourceNode]s that depend on other [LibrarySourceNode]s
271 /// whose API may have changed. The result of [build] is used to determine
272 /// whether other nodes need to be rebuilt. The function [build] is expected
273 /// to return `true` on a node `n` if it detemines other nodes that import
274 /// `n` may need to be rebuilt as well.
275 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) {
276 refreshStructureAndMarks(start, graph);
277 // Hold which source nodes may have changed their public API, this includes
278 // libraries that were modified or libraries that export other modified APIs.
279 // TODO(sigmund): consider removing this special support for exports? Many
280 // cases anways require using summaries to understand what parts of the public
281 // API may be affected by transitive changes. The re-export case is just one
282 // of those transitive cases, but is not sufficient. See
283 // https://github.com/dart-lang/dev_compiler/issues/76
284 var apiChangeDetected = new Set<SourceNode>();
Jennifer Messerly 2015/03/03 02:24:22 by default Set is LinkedHashSet, which preserves i
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 I'm not depending on it, so I went ahead and did t
285 bool structureHasChanged = false;
286
287 bool shouldBuildNode(SourceNode n) {
288 if (n is PartSourceNode) return false;
289 if (n.needsRebuild) return true;
290 if (n is HtmlSourceNode) return structureHasChanged;
291 return (n as LibrarySourceNode).imports
292 .any((i) => apiChangeDetected.contains(i));
293 }
294
295 visitInPostOrder(start, (n) {
296 if (n.structureChanged) structureHasChanged = true;
297 if (shouldBuildNode(n)) {
298 if (build(n)) apiChangeDetected.add(n);
299 } else if (n is LibrarySourceNode &&
300 n.exports.any((e) => apiChangeDetected.contains(e))) {
301 apiChangeDetected.add(n);
302 }
303 n.needsRebuild = false;
304 n.structureChanged = false;
305 });
306 }
307
308 /// Helper that runs [action] on nodes reachable from [node] in pre-order.
309 visitInPreOrder(SourceNode node, void action(SourceNode node),
310 {Set<SourceNode> seen}) {
311 if (seen == null) seen = new Set<SourceNode>();
Jennifer Messerly 2015/03/03 02:24:21 same comment on HashSet vs LinkedHashSet
Siggi Cherem (dart-lang) 2015/03/04 04:44:24 Done.
312 if (seen.contains(node)) return;
Jennifer Messerly 2015/03/03 02:24:22 you can combine the `contains` and `add` in one ha
Siggi Cherem (dart-lang) 2015/03/04 04:44:24 thx, Done
313 seen.add(node);
314 action(node);
315 node.directDeps.forEach((d) => visitInPreOrder(d, action, seen: seen));
316 }
317
318 /// Helper that runs [action] on nodes reachable from [node] in post-order.
319 visitInPostOrder(SourceNode node, void action(SourceNode node),
320 {Set<SourceNode> seen}) {
321 if (seen == null) seen = new Set<SourceNode>();
322 if (seen.contains(node)) return;
323 seen.add(node);
324 node.directDeps.forEach((d) => visitInPostOrder(d, action, seen: seen));
325 action(node);
326 }
327
328 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b);
329 final _log = new Logger('ddc.graph');
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698