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

Unified 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, 10 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/src/codegen/js_codegen.dart ('k') | lib/src/options.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/src/dependency_graph.dart
diff --git a/lib/src/dependency_graph.dart b/lib/src/dependency_graph.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3f656df3e376feb3c040cc3ad8e8be658f0534ee
--- /dev/null
+++ b/lib/src/dependency_graph.dart
@@ -0,0 +1,333 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tracks the shape of the import/export graph and dependencies between files.
+library ddc.src.dependency_graph;
+
+import 'dart:collection' show HashSet;
+
+import 'package:analyzer/analyzer.dart' show parseDirectives;
+import 'package:analyzer/src/generated/ast.dart'
+ show
+ LibraryDirective,
+ ImportDirective,
+ ExportDirective,
+ PartDirective,
+ CompilationUnit,
+ Identifier;
+import 'package:analyzer/src/generated/engine.dart'
+ show ParseDartTask, AnalysisContext;
+import 'package:analyzer/src/generated/source.dart' show Source, SourceKind;
+import 'package:html5lib/dom.dart' show Document;
+import 'package:html5lib/parser.dart' as html;
+import 'package:logging/logging.dart' show Logger;
+import 'package:path/path.dart' as path;
+
+import 'info.dart';
+import 'options.dart';
+import 'utils.dart';
+
+/// Holds references to all source nodes in the import graph. This is mainly
+/// used as a level of indirection to ensure that each source has a canonical
+/// representation.
+class SourceGraph {
+ /// All nodes in the source graph. Used to get a canonical representation for
+ /// any node.
+ final Map<Uri, SourceNode> nodes = {};
+
+ /// Analyzer used to resolve source files.
+ final AnalysisContext _context;
+ final CompilerOptions _options;
+
+ SourceGraph(this._context, this._options);
+
+ /// Node associated with a resolved [uri].
+ SourceNode nodeFromUri(Uri uri, [bool isPart = false]) {
+ var uriString = Uri.encodeFull('$uri');
+ var kind = uriString.endsWith('.html')
+ ? SourceKind.HTML
+ : isPart ? SourceKind.PART : SourceKind.LIBRARY;
+ return nodeFor(uri, _context.sourceFactory.forUri(uriString), kind);
+ }
+
+ /// Construct the node of the given [kind] with the given [uri] and [source].
+ SourceNode nodeFor(Uri uri, Source source, SourceKind kind) {
+ // TODO(sigmund): validate canonicalization?
+ // TODO(sigmund): add support for changing a file from one kind to another
+ // (e.g. converting a file from a part to a library).
+ return nodes.putIfAbsent(uri, () {
+ if (kind == SourceKind.HTML) {
+ return new HtmlSourceNode(uri, source);
+ } else if (kind == SourceKind.LIBRARY) {
+ return new LibrarySourceNode(uri, source);
+ } else if (kind == SourceKind.PART) {
+ return new PartSourceNode(uri, source);
+ } else {
+ assert(false); // unreachable
+ }
+ });
+ }
+}
+
+/// A node in the import graph representing a source file.
+abstract class SourceNode {
+ /// Resolved URI for this node.
+ final Uri uri;
+
+ /// Resolved source from the analyzer. We let the analyzer internally track
+ /// for modifications to the source files.
+ final Source source;
+
+ /// Last stamp read from `source.modificationStamp`.
+ int _lastStamp = 0;
+
+ /// Whether we need to rebuild this source file.
+ bool needsRebuild = false;
+
+ /// Whether the structure of dependencies from this node (scripts, imports,
+ /// exports, or parts) changed after we reparsed its contents.
+ bool structureChanged = false;
+
+ /// Direct dependencies (script tags for HtmlSourceNodes; imports, exports and
+ /// parts for LibrarySourceNodes).
+ Iterable<SourceNode> get directDeps;
+
+ SourceNode(this.uri, this.source);
+
+ /// Check for whether the file has changed and, if so, mark [needsRebuild] and
+ /// [structureChanged] as necessary.
+ void update(SourceGraph graph) {
+ int newStamp = source.modificationStamp;
+ if (newStamp > _lastStamp) {
+ _lastStamp = newStamp;
+ needsRebuild = true;
+ }
+ }
+
+ String toString() {
+ var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri";
+ return '[$runtimeType: $simpleUri]';
+ }
+}
+
+/// A node representing an entry HTML source file.
+class HtmlSourceNode extends SourceNode {
+ /// Libraries referred to via script tags.
+ Set<LibrarySourceNode> scripts = new Set<LibrarySourceNode>();
+
+ @override
+ Iterable<SourceNode> get directDeps => scripts;
+
+ /// Parsed document, updated whenever [update] is invoked.
+ Document document;
+
+ HtmlSourceNode(uri, source) : super(uri, source);
+
+ void update(SourceGraph graph) {
+ super.update(graph);
+ if (needsRebuild) {
+ document = html.parse(source.contents.data, generateSpans: true);
+ var newScripts = new Set<LibrarySourceNode>();
+ var tags = document.querySelectorAll('script[type="application/dart"]');
+ for (var script in tags) {
+ var src = script.attributes['src'];
+ if (src == null) {
+ // TODO(sigmund): expose these as compile-time failures
+ _log.severe(script.sourceSpan.message(
+ 'inlined script tags not supported at this time '
+ '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).',
+ color: graph._options.useColors ? colorOf('error') : false));
+ continue;
+ }
+ var node = graph.nodeFromUri(uri.resolve(src));
+ if (!node.source.exists()) {
+ _log.severe(script.sourceSpan.message('Script file $src not found',
+ color: graph._options.useColors ? colorOf('error') : false));
+ }
+ newScripts.add(node);
+ }
+
+ if (!_same(newScripts, scripts)) {
+ structureChanged = true;
+ scripts = newScripts;
+ }
+ }
+ }
+}
+
+/// A node representing a Dart part.
+class PartSourceNode extends SourceNode {
+ final Iterable<SourceNode> directDeps = const [];
+ PartSourceNode(uri, source) : super(uri, source);
+}
+
+/// A node representing a Dart library.
+class LibrarySourceNode extends SourceNode {
+ LibrarySourceNode(uri, source) : super(uri, source);
+
+ Set<LibrarySourceNode> imports = new Set<LibrarySourceNode>();
+ Set<LibrarySourceNode> exports = new Set<LibrarySourceNode>();
+ Set<PartSourceNode> parts = new Set<PartSourceNode>();
+
+ Iterable<SourceNode> get directDeps =>
+ [imports, exports, parts].expand((e) => e);
+
+ LibraryInfo info;
+
+ void update(SourceGraph graph) {
+ super.update(graph);
+ if (needsRebuild && source.contents.data != null) {
+ // If the defining compilation-unit changed, the structure might have
+ // changed.
+ var unit = parseDirectives(source.contents.data, name: source.fullName);
+ var newImports = new Set<LibrarySourceNode>();
+ var newExports = new Set<LibrarySourceNode>();
+ var newParts = new Set<PartSourceNode>();
+ for (var d in unit.directives) {
+ if (d is LibraryDirective) continue;
+ var target =
+ ParseDartTask.resolveDirective(graph._context, source, d, null);
+ var uri = target.uri;
+ var node = graph.nodeFor(uri, target,
+ d is PartDirective ? SourceKind.PART : SourceKind.LIBRARY);
+ if (!node.source.exists()) {
+ _log.severe(spanForNode(unit, source, d).message(
+ 'File $uri not found',
+ color: graph._options.useColors ? colorOf('error') : false));
+ }
+
+ if (d is ImportDirective) {
+ newImports.add(node);
+ } else if (d is ExportDirective) {
+ newExports.add(node);
+ } else if (d is PartDirective) {
+ newParts.add(node);
+ }
+ }
+
+ if (!_same(newImports, imports)) {
+ structureChanged = true;
+ imports = newImports;
+ }
+
+ if (!_same(newExports, exports)) {
+ structureChanged = true;
+ exports = newExports;
+ }
+
+ if (!_same(newParts, parts)) {
+ structureChanged = true;
+ parts = newParts;
+ }
+ }
+
+ // The library should be marked as needing rebuild if a part changed
+ // internally:
+ for (var p in parts) {
+ p.update(graph);
+ if (p.needsRebuild) needsRebuild = true;
+ }
+ }
+}
+
+/// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable
+/// from [start].
+///
+/// That is, staring from [start], we update the graph by detecting file changes
+/// and rebuilding the structure of the graph wherever it changed (an import was
+/// added or removed, etc).
+///
+/// After calling this function a node is marked with `needsRebuild` only if it
+/// contained local changes. Rebuild decisions that derive from transitive
+/// changes (e.g. when the API of a dependency changed) are handled later in
+/// [rebuild].
+void refreshStructureAndMarks(SourceNode start, SourceGraph graph) {
+ visitInPreOrder(start, (n) => n.update(graph));
+}
+
+/// Clears all the `needsRebuild` and `structureChanged` marks in nodes
+/// reachable from [start].
+void clearMarks(SourceNode start) {
+ visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false);
+}
+
+/// Traverses from [start] with the purpose of building any source that needs to
+/// be rebuilt.
+///
+/// This function will call [build] in a post-order fashion, on a subset of the
+/// reachable nodes. There are four rules used to decide when to rebuild a node
+/// (call [build] on a node):
+///
+/// * Only rebuild Dart libraries ([LibrarySourceNode]) or HTML files
+/// ([HtmlSourceNode]), but never part files ([PartSourceNode]). That is
+/// because those are built as part of some library.
+///
+/// * Always rebuild [LibrarySourceNode]s and [HtmlSourceNode]s with local
+/// changes or changes in a part of the library. Internally this function
+/// calls [refreshStructureAndMarks] to ensure that the graph structure is
+/// up-to-date and that these nodes with local changes contain the
+/// `needsRebuild` bit.
+///
+/// * Rebuild [HtmlSourceNode]s if there were structural changes somewhere
+/// down its reachable subgraph. This is done because HTML files embed the
+/// transitive closure of the import graph in their output.
+///
+/// * Rebuild [LibrarySourceNode]s that depend on other [LibrarySourceNode]s
+/// whose API may have changed. The result of [build] is used to determine
+/// whether other nodes need to be rebuilt. The function [build] is expected
+/// to return `true` on a node `n` if it detemines other nodes that import
+/// `n` may need to be rebuilt as well.
+rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) {
+ refreshStructureAndMarks(start, graph);
+ // Hold which source nodes may have changed their public API, this includes
+ // libraries that were modified or libraries that export other modified APIs.
+ // TODO(sigmund): consider removing this special support for exports? Many
+ // cases anways require using summaries to understand what parts of the public
+ // API may be affected by transitive changes. The re-export case is just one
+ // of those transitive cases, but is not sufficient. See
+ // https://github.com/dart-lang/dev_compiler/issues/76
+ var apiChangeDetected = new HashSet<SourceNode>();
+ bool structureHasChanged = false;
+
+ bool shouldBuildNode(SourceNode n) {
+ if (n is PartSourceNode) return false;
+ if (n.needsRebuild) return true;
+ if (n is HtmlSourceNode) return structureHasChanged;
+ return (n as LibrarySourceNode).imports
+ .any((i) => apiChangeDetected.contains(i));
+ }
+
+ visitInPostOrder(start, (n) {
+ if (n.structureChanged) structureHasChanged = true;
+ if (shouldBuildNode(n)) {
+ if (build(n)) apiChangeDetected.add(n);
+ } else if (n is LibrarySourceNode &&
+ n.exports.any((e) => apiChangeDetected.contains(e))) {
+ apiChangeDetected.add(n);
+ }
+ n.needsRebuild = false;
+ n.structureChanged = false;
+ });
+}
+
+/// Helper that runs [action] on nodes reachable from [node] in pre-order.
+visitInPreOrder(SourceNode node, void action(SourceNode node),
+ {Set<SourceNode> seen}) {
+ if (seen == null) seen = new HashSet<SourceNode>();
+ if (!seen.add(node)) return;
+ action(node);
+ node.directDeps.forEach((d) => visitInPreOrder(d, action, seen: seen));
+}
+
+/// Helper that runs [action] on nodes reachable from [node] in post-order.
+visitInPostOrder(SourceNode node, void action(SourceNode node),
+ {Set<SourceNode> seen}) {
+ if (seen == null) seen = new HashSet<SourceNode>();
+ if (!seen.add(node)) return;
+ node.directDeps.forEach((d) => visitInPostOrder(d, action, seen: seen));
+ action(node);
+}
+
+bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b);
+final _log = new Logger('ddc.graph');
« no previous file with comments | « lib/src/codegen/js_codegen.dart ('k') | lib/src/options.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698