| Index: lib/src/dependency_graph.dart
|
| diff --git a/lib/src/dependency_graph.dart b/lib/src/dependency_graph.dart
|
| deleted file mode 100644
|
| index c2f75d6fdf2139b81e697317f798fc5db04713f1..0000000000000000000000000000000000000000
|
| --- a/lib/src/dependency_graph.dart
|
| +++ /dev/null
|
| @@ -1,552 +0,0 @@
|
| -// 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 dev_compiler.src.dependency_graph;
|
| -
|
| -import 'dart:collection' show HashSet, HashMap;
|
| -
|
| -import 'package:analyzer/analyzer.dart' show parseDirectives;
|
| -import 'package:analyzer/src/generated/ast.dart'
|
| - show
|
| - AstNode,
|
| - CompilationUnit,
|
| - ExportDirective,
|
| - Identifier,
|
| - ImportDirective,
|
| - LibraryDirective,
|
| - PartDirective,
|
| - PartOfDirective,
|
| - UriBasedDirective;
|
| -import 'package:analyzer/src/generated/engine.dart'
|
| - show ParseDartTask, AnalysisContext;
|
| -import 'package:analyzer/src/generated/error.dart';
|
| -import 'package:analyzer/src/generated/source.dart' show Source, SourceKind;
|
| -import 'package:html/dom.dart' show Document, Node, Element;
|
| -import 'package:html/parser.dart' as html;
|
| -import 'package:logging/logging.dart' show Logger, Level;
|
| -import 'package:path/path.dart' as path;
|
| -
|
| -import 'info.dart';
|
| -import 'options.dart';
|
| -import 'report.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 = {};
|
| -
|
| - /// Resources included by default on any application.
|
| - final runtimeDeps = new Set<ResourceSourceNode>();
|
| -
|
| - /// Analyzer used to resolve source files.
|
| - final AnalysisContext _context;
|
| - final AnalysisErrorListener _reporter;
|
| - final CompilerOptions _options;
|
| -
|
| - SourceGraph(this._context, this._reporter, this._options) {
|
| - var dir = _options.runtimeDir;
|
| - if (dir == null) {
|
| - _log.severe('Runtime dir could not be determined automatically, '
|
| - 'please specify the --runtime-dir flag on the command line.');
|
| - return;
|
| - }
|
| - var prefix = path.absolute(dir);
|
| - var files = _options.serverMode
|
| - ? runtimeFilesForServerMode(_options.widget)
|
| - : defaultRuntimeFiles;
|
| - for (var file in files) {
|
| - runtimeDeps.add(nodeFromUri(path.toUri(path.join(prefix, file))));
|
| - }
|
| - }
|
| -
|
| - /// Node associated with a resolved [uri].
|
| - SourceNode nodeFromUri(Uri uri) {
|
| - var uriString = Uri.encodeFull('$uri');
|
| - return nodes.putIfAbsent(uri, () {
|
| - var source = _context.sourceFactory.forUri(uriString);
|
| - var extension = path.extension(uriString);
|
| - if (extension == '.html') {
|
| - return new HtmlSourceNode(this, uri, source);
|
| - } else if (extension == '.dart' || uriString.startsWith('dart:')) {
|
| - return new DartSourceNode(this, uri, source);
|
| - } else {
|
| - return new ResourceSourceNode(this, uri, source);
|
| - }
|
| - });
|
| - }
|
| -
|
| - List<String> get resources => _options.sourceOptions.resources;
|
| -}
|
| -
|
| -/// A node in the import graph representing a source file.
|
| -abstract class SourceNode {
|
| - final SourceGraph graph;
|
| -
|
| - /// 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.
|
| - Source _source;
|
| - Source get source => _source;
|
| -
|
| - String get contents => graph._context.getContents(_source).data;
|
| -
|
| - /// Last stamp read from `source.modificationStamp`.
|
| - /// This starts at -1, because analyzer uses that for files that don't exist.
|
| - int _lastStamp = -1;
|
| -
|
| - /// A hash used to help browsers cache the output that would be produced from
|
| - /// building this node.
|
| - String cachingHash;
|
| -
|
| - /// 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 in the [SourceGraph]. These include script tags for
|
| - /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s.
|
| - Iterable<SourceNode> get allDeps => const [];
|
| -
|
| - /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many
|
| - /// operations we mainly care about dependencies at the library level, so
|
| - /// parts are excluded from this list.
|
| - Iterable<SourceNode> get depsWithoutParts => const [];
|
| -
|
| - SourceNode(this.graph, this.uri, this._source);
|
| -
|
| - /// Check for whether the file has changed and, if so, mark [needsRebuild] and
|
| - /// [structureChanged] as necessary.
|
| - void update() {
|
| - if (_source == null) {
|
| - _source = graph._context.sourceFactory.forUri(Uri.encodeFull('$uri'));
|
| - if (_source == null) return;
|
| - }
|
| -
|
| - int newStamp = _source.exists() ? _source.modificationStamp : -1;
|
| - if (newStamp > _lastStamp || newStamp == -1 && _lastStamp != -1) {
|
| - // If the timestamp changed, read the file from disk and cache it.
|
| - // We don't want the source text to change during compilation.
|
| - saveUpdatedContents();
|
| - _lastStamp = newStamp;
|
| - needsRebuild = true;
|
| - }
|
| - }
|
| -
|
| - void saveUpdatedContents() {}
|
| -
|
| - 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 {
|
| - /// Resources included by default on any application.
|
| - final runtimeDeps;
|
| -
|
| - /// Libraries referred to via script tags.
|
| - Set<DartSourceNode> scripts = new Set<DartSourceNode>();
|
| -
|
| - /// Link-rel stylesheets, images, and other specified files.
|
| - Set<SourceNode> resources = new Set<SourceNode>();
|
| -
|
| - @override
|
| - Iterable<SourceNode> get allDeps =>
|
| - [scripts, resources, runtimeDeps].expand((e) => e);
|
| -
|
| - @override
|
| - Iterable<SourceNode> get depsWithoutParts => allDeps;
|
| -
|
| - /// Parsed document, updated whenever [update] is invoked.
|
| - Document document;
|
| -
|
| - /// Tracks resource files referenced from HTML nodes, e.g.
|
| - /// `<link rel=stylesheet href=...>` and `<img src=...>`
|
| - final htmlResourceNodes = new HashMap<Element, ResourceSourceNode>();
|
| -
|
| - HtmlSourceNode(SourceGraph graph, Uri uri, Source source)
|
| - : runtimeDeps = graph.runtimeDeps,
|
| - super(graph, uri, source);
|
| -
|
| - @override
|
| - void update() {
|
| - super.update();
|
| - if (needsRebuild) {
|
| - var reporter = graph._reporter;
|
| - if (reporter is SummaryReporter) {
|
| - reporter.clearHtml(uri);
|
| - }
|
| - document = html.parse(contents, generateSpans: true);
|
| - var newScripts = new Set<DartSourceNode>();
|
| - var tags = document.querySelectorAll('script[type="application/dart"]');
|
| - for (var script in tags) {
|
| - var src = script.attributes['src'];
|
| - if (src == null) {
|
| - _reportError(graph, 'inlined script tags not supported at this time '
|
| - '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).',
|
| - script);
|
| - continue;
|
| - }
|
| - var node = graph.nodeFromUri(uri.resolve(src));
|
| - if (node == null || !node.source.exists()) {
|
| - _reportError(graph, 'Script file $src not found', script);
|
| - }
|
| - if (node != null) newScripts.add(node);
|
| - }
|
| -
|
| - if (!_same(newScripts, scripts)) {
|
| - structureChanged = true;
|
| - scripts = newScripts;
|
| - }
|
| -
|
| - // TODO(jmesserly): simplify the design here. Ideally we wouldn't need
|
| - // to track user-defined CSS, images, etc. Also we don't have a clear
|
| - // way to distinguish runtime injected resources, like messages.css, from
|
| - // user-defined files.
|
| - htmlResourceNodes.clear();
|
| - var newResources = new Set<SourceNode>();
|
| - for (var resource in graph.resources) {
|
| - newResources.add(graph.nodeFromUri(uri.resolve(resource)));
|
| - }
|
| - for (var tag in document.querySelectorAll('link[rel="stylesheet"]')) {
|
| - var res = graph.nodeFromUri(uri.resolve(tag.attributes['href']));
|
| - htmlResourceNodes[tag] = res;
|
| - newResources.add(res);
|
| - }
|
| - for (var tag in document.querySelectorAll('img[src]')) {
|
| - var res = graph.nodeFromUri(uri.resolve(tag.attributes['src']));
|
| - htmlResourceNodes[tag] = res;
|
| - newResources.add(res);
|
| - }
|
| - if (!_same(newResources, resources)) {
|
| - structureChanged = true;
|
| - resources = newResources;
|
| - }
|
| - }
|
| - }
|
| -
|
| - void _reportError(SourceGraph graph, String message, Node node) {
|
| - var span = node.sourceSpan;
|
| -
|
| - // TODO(jmesserly): should these be errors or warnings?
|
| - var errorCode = new HtmlWarningCode('dev_compiler.$runtimeType', message);
|
| - graph._reporter.onError(
|
| - new AnalysisError(_source, span.start.offset, span.length, errorCode));
|
| - }
|
| -}
|
| -
|
| -/// A node representing a Dart library or part.
|
| -class DartSourceNode extends SourceNode {
|
| - /// Set of imported libraries (empty for part files).
|
| - Set<DartSourceNode> imports = new Set<DartSourceNode>();
|
| -
|
| - /// Set of exported libraries (empty for part files).
|
| - Set<DartSourceNode> exports = new Set<DartSourceNode>();
|
| -
|
| - /// Parts of this library (empty for part files).
|
| - Set<DartSourceNode> parts = new Set<DartSourceNode>();
|
| -
|
| - /// How many times this file is included as a part.
|
| - int includedAsPart = 0;
|
| -
|
| - DartSourceNode(graph, uri, source) : super(graph, uri, source);
|
| -
|
| - @override
|
| - Iterable<SourceNode> get allDeps =>
|
| - [imports, exports, parts].expand((e) => e);
|
| -
|
| - @override
|
| - Iterable<SourceNode> get depsWithoutParts =>
|
| - [imports, exports].expand((e) => e);
|
| -
|
| - LibraryInfo info;
|
| -
|
| - // TODO(jmesserly): it would be nice to not keep all sources in memory at
|
| - // once, but how else can we ensure a consistent view across a given
|
| - // compile? One different from dev_compiler vs analyzer is that our
|
| - // messages later in the compiler need the original source text to print
|
| - // spans. We also read source text ourselves to parse directives.
|
| - // But we could discard it after that point.
|
| - void saveUpdatedContents() {
|
| - graph._context.setContents(_source, _source.contents.data);
|
| - }
|
| -
|
| - @override
|
| - void update() {
|
| - super.update();
|
| -
|
| - if (needsRebuild) {
|
| - var reporter = graph._reporter;
|
| - if (reporter is SummaryReporter) {
|
| - reporter.clearLibrary(uri);
|
| - }
|
| -
|
| - // If the defining compilation-unit changed, the structure might have
|
| - // changed.
|
| - var unit = parseDirectives(contents, name: _source.fullName);
|
| - var newImports = new Set<DartSourceNode>();
|
| - var newExports = new Set<DartSourceNode>();
|
| - var newParts = new Set<DartSourceNode>();
|
| - for (var d in unit.directives) {
|
| - // Nothing to do for parts.
|
| - if (d is PartOfDirective) return;
|
| - if (d is LibraryDirective) continue;
|
| -
|
| - var directiveUri = (d as UriBasedDirective).uri;
|
| -
|
| - // `dart:core` and other similar URLs only contain a name, but it is
|
| - // meant to be a folder when resolving relative paths from it.
|
| - var targetUri = uri.scheme == 'dart' && uri.pathSegments.length == 1
|
| - ? Uri.parse('$uri/').resolve(directiveUri.stringValue)
|
| - : uri.resolve(directiveUri.stringValue);
|
| - var target =
|
| - ParseDartTask.resolveDirective(graph._context, _source, d, null);
|
| - var node = graph.nodes.putIfAbsent(
|
| - targetUri, () => new DartSourceNode(graph, targetUri, target));
|
| - //var node = graph.nodeFromUri(targetUri);
|
| - if (node._source == null || !node._source.exists()) {
|
| - _reportError(graph, 'File $targetUri not found', d);
|
| - }
|
| -
|
| - 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;
|
| -
|
| - // When parts are removed, it's possible they were updated to be
|
| - // imported as a library
|
| - for (var p in parts) {
|
| - if (newParts.contains(p)) continue;
|
| - if (--p.includedAsPart == 0) {
|
| - p.needsRebuild = true;
|
| - }
|
| - }
|
| -
|
| - for (var p in newParts) {
|
| - if (parts.contains(p)) continue;
|
| - p.includedAsPart++;
|
| - }
|
| - parts = newParts;
|
| - }
|
| - }
|
| -
|
| - // The library should be marked as needing rebuild if a part changed
|
| - // internally:
|
| - for (var p in parts) {
|
| - // Technically for parts we don't need to look at the contents. If they
|
| - // contain imports, exports, or parts, we'll ignore them in our crawling.
|
| - // However we do a full update to make it easier to adjust when users
|
| - // switch a file from a part to a library.
|
| - p.update();
|
| - if (p.needsRebuild) needsRebuild = true;
|
| - }
|
| - }
|
| -
|
| - void _reportError(SourceGraph graph, String message, AstNode node) {
|
| - graph._reporter.onError(new AnalysisError(_source, node.offset, node.length,
|
| - new CompileTimeErrorCode('dev_compiler.$runtimeType', message)));
|
| - }
|
| -}
|
| -
|
| -/// Represents a runtime resource from our compiler that is needed to run an
|
| -/// application.
|
| -class ResourceSourceNode extends SourceNode {
|
| - ResourceSourceNode(graph, uri, source) : super(graph, uri, source);
|
| -}
|
| -
|
| -/// 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) {
|
| - visitInPreOrder(start, (n) => n.update(), includeParts: false);
|
| -}
|
| -
|
| -/// Clears all the `needsRebuild` and `structureChanged` marks in nodes
|
| -/// reachable from [start].
|
| -void clearMarks(SourceNode start) {
|
| - visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false,
|
| - includeParts: true);
|
| -}
|
| -
|
| -/// 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 ([DartSourceNode]) or HTML files
|
| -/// ([HtmlSourceNode]), but skip part files. That is because those are
|
| -/// built as part of some library.
|
| -///
|
| -/// * Always rebuild [DartSourceNode]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 [DartSourceNode]s that depend on other [DartSourceNode]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, bool build(SourceNode node)) {
|
| - refreshStructureAndMarks(start);
|
| - // 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 htmlNeedsRebuild = false;
|
| -
|
| - bool shouldBuildNode(SourceNode n) {
|
| - if (n.needsRebuild) return true;
|
| - if (n is HtmlSourceNode) return htmlNeedsRebuild;
|
| - if (n is ResourceSourceNode) return false;
|
| - return (n as DartSourceNode).imports
|
| - .any((i) => apiChangeDetected.contains(i));
|
| - }
|
| -
|
| - visitInPostOrder(start, (n) {
|
| - if (n.structureChanged) htmlNeedsRebuild = true;
|
| - if (shouldBuildNode(n)) {
|
| - var oldHash = n.cachingHash;
|
| - if (build(n)) apiChangeDetected.add(n);
|
| - if (oldHash != n.cachingHash) htmlNeedsRebuild = true;
|
| - } else if (n is DartSourceNode &&
|
| - n.exports.any((e) => apiChangeDetected.contains(e))) {
|
| - apiChangeDetected.add(n);
|
| - }
|
| - n.needsRebuild = false;
|
| - n.structureChanged = false;
|
| - if (n is DartSourceNode) {
|
| - // Note: clearing out flags in the parts could be a problem if someone
|
| - // tries to use a file both as a part and a library at the same time.
|
| - // In that case, we might not correctly propagate changes in the places
|
| - // where it is used as a library. Technically it's not allowed to have a
|
| - // file as a part and a library at once, and the analyzer should report an
|
| - // error in that case.
|
| - n.parts.forEach((p) => p.needsRebuild = p.structureChanged = false);
|
| - }
|
| - }, includeParts: false);
|
| -}
|
| -
|
| -/// Helper that runs [action] on nodes reachable from [start] in pre-order.
|
| -visitInPreOrder(SourceNode start, void action(SourceNode node),
|
| - {bool includeParts: false}) {
|
| - var seen = new HashSet<SourceNode>();
|
| - helper(SourceNode node) {
|
| - if (!seen.add(node)) return;
|
| - action(node);
|
| - var deps = includeParts ? node.allDeps : node.depsWithoutParts;
|
| - deps.forEach(helper);
|
| - }
|
| - helper(start);
|
| -}
|
| -
|
| -/// Helper that runs [action] on nodes reachable from [start] in post-order.
|
| -visitInPostOrder(SourceNode start, void action(SourceNode node),
|
| - {bool includeParts: false}) {
|
| - var seen = new HashSet<SourceNode>();
|
| - helper(SourceNode node) {
|
| - if (!seen.add(node)) return;
|
| - var deps = includeParts ? node.allDeps : node.depsWithoutParts;
|
| - deps.forEach(helper);
|
| - action(node);
|
| - }
|
| - helper(start);
|
| -}
|
| -
|
| -bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b);
|
| -
|
| -/// Runtime files added to all applications when running the compiler in the
|
| -/// command line.
|
| -final defaultRuntimeFiles = () {
|
| - var files = [
|
| - 'harmony_feature_check.js',
|
| - 'dart_utils.js',
|
| - 'dart_library.js',
|
| - '_errors.js',
|
| - '_types.js',
|
| - '_rtti.js',
|
| - '_classes.js',
|
| - '_operations.js',
|
| - 'dart_runtime.js',
|
| - ];
|
| - files.addAll(corelibOrder.map((l) => l.replaceAll('.', '/') + '.js'));
|
| - return files;
|
| -}();
|
| -
|
| -/// Curated order to minimize lazy classes needed by dart:core and its
|
| -/// transitive SDK imports.
|
| -const corelibOrder = const [
|
| - 'dart.core',
|
| - 'dart.collection',
|
| - 'dart._internal',
|
| - 'dart.math',
|
| - 'dart._interceptors',
|
| - 'dart.async',
|
| - 'dart._foreign_helper',
|
| - 'dart._js_embedded_names',
|
| - 'dart._js_helper',
|
| - 'dart.isolate',
|
| - 'dart.typed_data',
|
| - 'dart._native_typed_data',
|
| - 'dart._isolate_helper',
|
| - 'dart._js_primitives',
|
| - 'dart.convert',
|
| - 'dart.mirrors',
|
| - 'dart._js_mirrors',
|
| - 'dart.js'
|
| - // _foreign_helper is not included, as it only defines the JS builtin that
|
| - // the compiler handles at compile time.
|
| -];
|
| -
|
| -/// Runtime files added to applications when running in server mode.
|
| -List<String> runtimeFilesForServerMode([bool includeWidget = true]) =>
|
| - new List<String>.from(defaultRuntimeFiles)
|
| - ..addAll(includeWidget ? const ['messages_widget.js', 'messages.css'] : []);
|
| -
|
| -final _log = new Logger('dev_compiler.dependency_graph');
|
|
|