| Index: lib/devc.dart
|
| diff --git a/lib/devc.dart b/lib/devc.dart
|
| index 6c28d6412a562de061b1d15bd4581c5f7afc4d67..1633bc1a28d561a7dee9fcd66c5a3b7546bbce65 100644
|
| --- a/lib/devc.dart
|
| +++ b/lib/devc.dart
|
| @@ -9,21 +9,25 @@ import 'dart:async';
|
| import 'dart:convert';
|
| import 'dart:io';
|
|
|
| +import 'package:analyzer/src/generated/engine.dart' show ChangeSet;
|
| import 'package:logging/logging.dart' show Level, Logger, LogRecord;
|
| import 'package:path/path.dart' as path;
|
| -import 'package:html5lib/parser.dart' show parse;
|
| +import 'package:shelf/shelf.dart' as shelf;
|
| +import 'package:shelf/shelf_io.dart' as shelf;
|
| +import 'package:shelf_static/shelf_static.dart' as shelf_static;
|
|
|
| -import 'src/checker/resolver.dart';
|
| import 'src/checker/checker.dart';
|
| +import 'src/checker/dart_sdk.dart' show mockSdkSources;
|
| +import 'src/checker/resolver.dart';
|
| import 'src/checker/rules.dart';
|
| import 'src/codegen/code_generator.dart' show CodeGenerator;
|
| import 'src/codegen/dart_codegen.dart';
|
| -import 'src/codegen/js_codegen.dart';
|
| import 'src/codegen/html_codegen.dart';
|
| +import 'src/codegen/js_codegen.dart';
|
| +import 'src/dependency_graph.dart';
|
| +import 'src/info.dart' show LibraryInfo, CheckerResults;
|
| import 'src/options.dart';
|
| import 'src/report.dart';
|
| -import 'src/info.dart' show LibraryInfo, CheckerResults;
|
| -import 'src/utils.dart' show reachableSources, partsOf, colorOf;
|
|
|
| /// Sets up the type checker logger to print a span that highlights error
|
| /// messages.
|
| @@ -34,139 +38,234 @@ StreamSubscription setupLogger(Level level, printFn) {
|
| });
|
| }
|
|
|
| -/// Compiles [inputFile] writing output as specified by the arguments.
|
| -CheckerResults compile(
|
| - String inputFile, TypeResolver resolver, CompilerOptions options,
|
| - [CheckerReporter reporter]) {
|
| - if (inputFile.endsWith('.html')) {
|
| - return _compileHtml(inputFile, resolver, options, reporter);
|
| - } else {
|
| - return _compileDart(inputFile, resolver, options, reporter);
|
| - }
|
| -}
|
| -
|
| -CheckerResults _compileHtml(
|
| - String inputFile, TypeResolver resolver, CompilerOptions options,
|
| - [CheckerReporter reporter]) {
|
| - var doc = parse(new File(inputFile).readAsStringSync(), generateSpans: true);
|
| - var scripts = doc.querySelectorAll('script[type="application/dart"]');
|
| - if (scripts.isEmpty) {
|
| - _log.severe('No <script type="application/dart"> found in $inputFile');
|
| - return _earlyErrorResult;
|
| - }
|
| - var mainScriptTag = scripts[0];
|
| - scripts.skip(1).forEach((s) {
|
| - _log.warning(s.sourceSpan.message(
|
| - 'unexpected script. Only one Dart script tag allowed '
|
| - '(see https://github.com/dart-lang/dart-dev-compiler/issues/53).',
|
| - color: options.useColors ? colorOf('warning') : false));
|
| - });
|
| +/// Encapsulates the logic to do a one-off compilation or a partial compilation
|
| +/// when the compiler is run as a development server.
|
| +class Compiler {
|
| + final CompilerOptions _options;
|
| + final TypeResolver _resolver;
|
| + final CheckerReporter _reporter;
|
| + final TypeRules _rules;
|
| + final CodeChecker _checker;
|
| + final SourceGraph _graph;
|
| + final SourceNode _entryNode;
|
| + List<LibraryInfo> _libraries = <LibraryInfo>[];
|
| + final List<CodeGenerator> _generators;
|
| + bool _failure = false;
|
| + bool _devCompilerRuntimeCopied = false;
|
|
|
| - var url = mainScriptTag.attributes['src'];
|
| - if (url == null) {
|
| - _log.severe(mainScriptTag.sourceSpan.message(
|
| - 'inlined script tags not supported at this time '
|
| - '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).',
|
| - color: options.useColors ? colorOf('error') : false));
|
| - return _earlyErrorResult;
|
| - }
|
| + factory Compiler(CompilerOptions options,
|
| + [TypeResolver resolver, CheckerReporter reporter]) {
|
| + if (resolver == null) {
|
| + resolver = options.useMockSdk
|
| + ? new TypeResolver.fromMock(mockSdkSources, options)
|
| + : new TypeResolver.fromDir(options.dartSdkPath, options);
|
| + }
|
|
|
| - var dartInputFile = path.join(path.dirname(inputFile), url);
|
| + if (reporter == null) {
|
| + reporter = options.dumpInfo
|
| + ? new SummaryReporter()
|
| + : new LogReporter(options.useColors);
|
| + }
|
| + var graph = new SourceGraph(resolver.context, options);
|
| + var rules = new RestrictedRules(resolver.context.typeProvider, reporter,
|
| + options: options);
|
| + var checker = new CodeChecker(rules, reporter, options);
|
| + var inputFile = options.entryPointFile;
|
| + var uri = inputFile.startsWith('dart:') || inputFile.startsWith('package:')
|
| + ? Uri.parse(inputFile)
|
| + : new Uri.file(path.absolute(inputFile));
|
| + var entryNode = graph.nodeFromUri(uri);
|
|
|
| - if (!new File(dartInputFile).existsSync()) {
|
| - _log.severe(mainScriptTag.sourceSpan.message(
|
| - 'Script file $dartInputFile not found',
|
| - color: options.useColors ? colorOf('error') : false));
|
| - return _earlyErrorResult;
|
| + var outputDir = options.outputDir;
|
| + var generators = <CodeGenerator>[];
|
| + if (options.dumpSrcDir != null) {
|
| + generators.add(new EmptyDartGenerator(
|
| + options.dumpSrcDir, entryNode.uri, rules, options));
|
| + }
|
| + if (outputDir != null) {
|
| + generators.add(options.outputDart
|
| + ? new DartGenerator(outputDir, entryNode.uri, rules, options)
|
| + : new JSGenerator(outputDir, entryNode.uri, rules, options));
|
| + }
|
| + return new Compiler._(options, resolver, reporter, rules, checker, graph,
|
| + entryNode, generators);
|
| }
|
|
|
| - var results = _compileDart(dartInputFile, resolver, options, reporter,
|
| - new Uri.file(path.absolute(inputFile)));
|
| - if (results.failure && !options.forceCompile) return results;
|
| + Compiler._(this._options, this._resolver, this._reporter, this._rules,
|
| + this._checker, this._graph, this._entryNode, this._generators);
|
|
|
| - if (options.outputDir != null) {
|
| - generateEntryHtml(inputFile, options, results, doc);
|
| - }
|
| - return results;
|
| -}
|
| + bool _buildSource(SourceNode node) {
|
| + if (node is HtmlSourceNode) {
|
| + _buildHtmlFile(node);
|
| + } else if (node is LibrarySourceNode) {
|
| + _buildDartLibrary(node);
|
| + } else {
|
| + assert(false); // should not get a build request on PartSourceNode
|
| + }
|
|
|
| -CheckerResults _compileDart(
|
| - String inputFile, TypeResolver resolver, CompilerOptions options,
|
| - [CheckerReporter reporter, Uri htmlUri]) {
|
| - Uri uri;
|
| - if (inputFile.startsWith('dart:') || inputFile.startsWith('package:')) {
|
| - uri = Uri.parse(inputFile);
|
| - } else {
|
| - uri = new Uri.file(path.absolute(inputFile));
|
| - }
|
| - var codegenRoot = htmlUri != null ? htmlUri : uri;
|
| - if (reporter == null) {
|
| - reporter = options.dumpInfo
|
| - ? new SummaryReporter()
|
| - : new LogReporter(options.useColors);
|
| + // TODO(sigmund): don't always return true. Use summarization to better
|
| + // determine when rebuilding is needed.
|
| + return true;
|
| }
|
|
|
| - var libraries = <LibraryInfo>[];
|
| - var rules = new RestrictedRules(resolver.context.typeProvider, reporter,
|
| - options: options);
|
| - var codeChecker = new CodeChecker(rules, reporter, options);
|
| - var generators = <CodeGenerator>[];
|
| - if (options.dumpSrcDir != null) {
|
| - generators.add(new EmptyDartGenerator(
|
| - options.dumpSrcDir, codegenRoot, rules, options));
|
| + void _buildHtmlFile(HtmlSourceNode node) {
|
| + if (_options.outputDir == null) return;
|
| + var output = generateEntryHtml(node, _options);
|
| + if (output == null) {
|
| + _failure = true;
|
| + return;
|
| + }
|
| + var filename = path.basename(node.uri.path);
|
| + String outputFile = path.join(_options.outputDir, filename);
|
| + new File(outputFile).writeAsStringSync(output);
|
| +
|
| + if (_options.outputDart || _devCompilerRuntimeCopied) return;
|
| + // Copy the dev_compiler runtime (implicit dependency for js codegen)
|
| + // TODO(sigmund): split this out as a separate node in our dependency graph
|
| + // (https://github.com/dart-lang/dev_compiler/issues/85).
|
| + var runtimeDir = path.join(
|
| + path.dirname(path.dirname(Platform.script.path)), 'lib/runtime/');
|
| + var runtimeOutput = path.join(_options.outputDir, 'dev_compiler/runtime/');
|
| + new Directory(runtimeOutput).createSync(recursive: true);
|
| + new File(path.join(runtimeDir, 'harmony_feature_check.js'))
|
| + .copy(path.join(runtimeOutput, 'harmony_feature_check.js'));
|
| + new File(path.join(runtimeDir, 'dart_runtime.js'))
|
| + .copy(path.join(runtimeOutput, 'dart_runtime.js'));
|
| + _devCompilerRuntimeCopied = true;
|
| }
|
| - var outputDir = options.outputDir;
|
| - if (outputDir != null) {
|
| - var cg = options.outputDart
|
| - ? new DartGenerator(outputDir, codegenRoot, rules, options)
|
| - : new JSGenerator(outputDir, codegenRoot, rules, options);
|
| - generators.add(cg);
|
| +
|
| + bool _isEntry(LibrarySourceNode node) {
|
| + if (_entryNode is LibrarySourceNode) return _entryNode == node;
|
| + return (_entryNode as HtmlSourceNode).scripts.contains(node);
|
| }
|
|
|
| - bool failure = false;
|
| - var rootSource = resolver.findSource(uri);
|
| - // TODO(sigmund): switch to use synchronous codegen?
|
| - for (var source in reachableSources(rootSource, resolver.context)) {
|
| - var entryUnit = resolver.context.resolveCompilationUnit2(source, source);
|
| + void _buildDartLibrary(LibrarySourceNode node) {
|
| + var source = node.source;
|
| + // TODO(sigmund): find out from analyzer team if there is a better way
|
| + _resolver.context.applyChanges(new ChangeSet()..changedSource(source));
|
| + var entryUnit = _resolver.context.resolveCompilationUnit2(source, source);
|
| var lib = entryUnit.element.enclosingElement;
|
| - if (!options.checkSdk && lib.isInSdk) continue;
|
| - var current = new LibraryInfo(lib, source.uri == uri);
|
| - reporter.enterLibrary(current);
|
| - libraries.add(current);
|
| - rules.currentLibraryInfo = current;
|
| + if (!_options.checkSdk && lib.isInSdk) return;
|
| + var current = node.info;
|
| + if (current != null) {
|
| + assert(current.library == lib);
|
| + } else {
|
| + node.info = current = new LibraryInfo(lib, _isEntry(node));
|
| + }
|
| + _reporter.enterLibrary(current);
|
| + _libraries.add(current);
|
| + _rules.currentLibraryInfo = current;
|
|
|
| var units = [entryUnit]
|
| - ..addAll(partsOf(entryUnit, resolver.context)
|
| - .map((p) => resolver.context.resolveCompilationUnit2(p, source)));
|
| + ..addAll(node.parts.map(
|
| + (p) => _resolver.context.resolveCompilationUnit2(p.source, source)));
|
| bool failureInLib = false;
|
| for (var unit in units) {
|
| var unitSource = unit.element.source;
|
| - reporter.enterSource(unitSource);
|
| + _reporter.enterSource(unitSource);
|
| // TODO(sigmund): integrate analyzer errors with static-info (issue #6).
|
| - failureInLib = resolver.logErrors(unitSource, reporter) || failureInLib;
|
| - unit.visitChildren(codeChecker);
|
| - if (codeChecker.failure) failureInLib = true;
|
| - reporter.leaveSource();
|
| + failureInLib = _resolver.logErrors(unitSource, _reporter) || failureInLib;
|
| + unit.visitChildren(_checker);
|
| + if (_checker.failure) failureInLib = true;
|
| + _reporter.leaveSource();
|
| }
|
| - reporter.leaveLibrary();
|
| -
|
| if (failureInLib) {
|
| - failure = true;
|
| - if (!options.forceCompile) continue;
|
| + _failure = true;
|
| + if (!_options.forceCompile) return;
|
| }
|
| - for (var cg in generators) {
|
| - cg.generateLibrary(units, current, reporter);
|
| + for (var cg in _generators) {
|
| + cg.generateLibrary(units, current, _reporter);
|
| }
|
| + _reporter.leaveLibrary();
|
| }
|
|
|
| - if (options.dumpInfo && reporter is SummaryReporter) {
|
| - print(summaryToString(reporter.result));
|
| - if (options.dumpInfoFile != null) {
|
| - new File(options.dumpInfoFile)
|
| - .writeAsStringSync(JSON.encode(reporter.result.toJsonMap()));
|
| + CheckerResults run() {
|
| + var clock = new Stopwatch()..start();
|
| +
|
| + // TODO(sigmund): we are missing a couple failures here. The
|
| + // dependendency_graph now detects broken imports or unsupported features
|
| + // like more than one script tag (see .severe messages in
|
| + // dependency_graph.dart). Such failures should be reported back
|
| + // here so we can mark failure=true in the CheckerResutls.
|
| + rebuild(_entryNode, _graph, _buildSource);
|
| + if (_options.dumpInfo && _reporter is SummaryReporter) {
|
| + var result = (_reporter as SummaryReporter).result;
|
| + print(summaryToString(result));
|
| + if (_options.dumpInfoFile != null) {
|
| + new File(_options.dumpInfoFile)
|
| + .writeAsStringSync(JSON.encode(result.toJsonMap()));
|
| + }
|
| + }
|
| + clock.stop();
|
| + if (_options.serverMode) {
|
| + var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
|
| + print('Compiled ${_libraries.length} libraries in ${time} s\n');
|
| }
|
| + return new CheckerResults(
|
| + _libraries, _rules, _failure || _options.forceCompile);
|
| + }
|
| +
|
| + void _runAgain() {
|
| + var clock = new Stopwatch()..start();
|
| + if (_reporter is SummaryReporter) (_reporter as SummaryReporter).clear();
|
| + _libraries = <LibraryInfo>[];
|
| + int changed = 0;
|
| +
|
| + // TODO(sigmund): propagate failures here (see TODO in run).
|
| + rebuild(_entryNode, _graph, (n) {
|
| + changed++;
|
| + return _buildSource(n);
|
| + });
|
| + if (_reporter is SummaryReporter) {
|
| + print(summaryToString((_reporter as SummaryReporter).result));
|
| + }
|
| + clock.stop();
|
| + var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
|
| + print("Compiled ${changed} libraries in ${time} s\n");
|
| + }
|
| +}
|
| +
|
| +class CompilerServer {
|
| + final Compiler compiler;
|
| + final String outDir;
|
| + final int port;
|
| + final String _entryPath;
|
| +
|
| + factory CompilerServer(CompilerOptions options) {
|
| + var entryPath = path.basename(options.entryPointFile);
|
| + if (path.extension(entryPath) != '.html') {
|
| + print('error: devc in server mode requires an HTML entry point.');
|
| + exit(1);
|
| + }
|
| +
|
| + // TODO(sigmund): allow running without a dir, but keep output in memory?
|
| + var outDir = options.outputDir;
|
| + if (outDir == null) {
|
| + print('error: devc in server mode also requires specifying and '
|
| + 'output location for generated code.');
|
| + exit(1);
|
| + }
|
| + var port = options.port;
|
| + print('[dev_compiler]: Serving $entryPath at http://0.0.0.0:$port/');
|
| + var compiler = new Compiler(options);
|
| + return new CompilerServer._(compiler, outDir, port, entryPath);
|
| + }
|
| +
|
| + CompilerServer._(this.compiler, this.outDir, this.port, this._entryPath);
|
| +
|
| + Future start() async {
|
| + var handler = const shelf.Pipeline()
|
| + .addMiddleware(shelf.createMiddleware(requestHandler: rebuildIfNeeded))
|
| + .addHandler(shelf_static.createStaticHandler(outDir,
|
| + defaultDocument: _entryPath));
|
| + await shelf.serve(handler, '0.0.0.0', port);
|
| + compiler.run();
|
| + }
|
| +
|
| + rebuildIfNeeded(shelf.Request request) {
|
| + var filepath = request.url.path;
|
| + if (filepath == '/$_entryPath' || filepath == '/') compiler._runAgain();
|
| }
|
| - return new CheckerResults(libraries, rules, failure || options.forceCompile);
|
| }
|
|
|
| final _log = new Logger('ddc');
|
|
|