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

Side by Side Diff: lib/devc.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
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 /// Command line tool to run the checker on a Dart program. 5 /// Command line tool to run the checker on a Dart program.
6 library ddc.devc; 6 library ddc.devc;
Jennifer Messerly 2015/03/03 02:24:21 I wonder if this should be in "src"? just noticed
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 good point - now that I made most things private,
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:convert'; 9 import 'dart:convert';
10 import 'dart:io'; 10 import 'dart:io';
11 11
12 import 'package:analyzer/src/generated/engine.dart' show ChangeSet;
12 import 'package:logging/logging.dart' show Level, Logger, LogRecord; 13 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
13 import 'package:path/path.dart' as path; 14 import 'package:path/path.dart' as path;
14 import 'package:html5lib/parser.dart' show parse; 15 import 'package:shelf/shelf.dart' as shelf;
16 import 'package:shelf/shelf_io.dart' as shelf;
17 import 'package:shelf_static/shelf_static.dart' as shelf_static;
15 18
19 import 'src/checker/checker.dart';
20 import 'src/checker/dart_sdk.dart' show mockSdkSources;
16 import 'src/checker/resolver.dart'; 21 import 'src/checker/resolver.dart';
17 import 'src/checker/checker.dart';
18 import 'src/checker/rules.dart'; 22 import 'src/checker/rules.dart';
19 import 'src/codegen/code_generator.dart' show CodeGenerator; 23 import 'src/codegen/code_generator.dart' show CodeGenerator;
20 import 'src/codegen/dart_codegen.dart'; 24 import 'src/codegen/dart_codegen.dart';
25 import 'src/codegen/html_codegen.dart';
21 import 'src/codegen/js_codegen.dart'; 26 import 'src/codegen/js_codegen.dart';
22 import 'src/codegen/html_codegen.dart'; 27 import 'src/dependency_graph.dart';
28 import 'src/info.dart' show LibraryInfo, CheckerResults;
23 import 'src/options.dart'; 29 import 'src/options.dart';
24 import 'src/report.dart'; 30 import 'src/report.dart';
25 import 'src/info.dart' show LibraryInfo, CheckerResults;
26 import 'src/utils.dart' show reachableSources, partsOf, colorOf;
27 31
28 /// Sets up the type checker logger to print a span that highlights error 32 /// Sets up the type checker logger to print a span that highlights error
29 /// messages. 33 /// messages.
30 StreamSubscription setupLogger(Level level, printFn) { 34 StreamSubscription setupLogger(Level level, printFn) {
31 Logger.root.level = level; 35 Logger.root.level = level;
32 return Logger.root.onRecord.listen((LogRecord rec) { 36 return Logger.root.onRecord.listen((LogRecord rec) {
33 printFn('${rec.level.name.toLowerCase()}: ${rec.message}'); 37 printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
34 }); 38 });
35 } 39 }
36 40
37 /// Compiles [inputFile] writing output as specified by the arguments. 41 /// Encapsulates the logic to do a one-off compilation or a partial compilation
38 CheckerResults compile( 42 /// when the compiler is run as a development server.
39 String inputFile, TypeResolver resolver, CompilerOptions options, 43 class Compiler {
40 [CheckerReporter reporter]) { 44 final CompilerOptions options;
41 if (inputFile.endsWith('.html')) { 45 final TypeResolver resolver;
42 return _compileHtml(inputFile, resolver, options, reporter); 46 final CheckerReporter reporter;
43 } else { 47 final TypeRules rules;
44 return _compileDart(inputFile, resolver, options, reporter); 48 final CodeChecker checker;
45 } 49 final SourceGraph graph;
46 } 50 final SourceNode entryNode;
47 51 List<LibraryInfo> libraries = <LibraryInfo>[];
Jennifer Messerly 2015/03/03 02:24:21 since this is in a public library, do we want to p
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 Good point - actually all of the properties here s
48 CheckerResults _compileHtml( 52 final List<CodeGenerator> generators;
49 String inputFile, TypeResolver resolver, CompilerOptions options,
50 [CheckerReporter reporter]) {
51 var doc = parse(new File(inputFile).readAsStringSync(), generateSpans: true);
52 var scripts = doc.querySelectorAll('script[type="application/dart"]');
53 if (scripts.isEmpty) {
54 _log.severe('No <script type="application/dart"> found in $inputFile');
55 return _earlyErrorResult;
56 }
57 var mainScriptTag = scripts[0];
58 scripts.skip(1).forEach((s) {
59 _log.warning(s.sourceSpan.message(
60 'unexpected script. Only one Dart script tag allowed '
61 '(see https://github.com/dart-lang/dart-dev-compiler/issues/53).',
62 color: options.useColors ? colorOf('warning') : false));
63 });
64
65 var url = mainScriptTag.attributes['src'];
66 if (url == null) {
67 _log.severe(mainScriptTag.sourceSpan.message(
68 'inlined script tags not supported at this time '
69 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).',
70 color: options.useColors ? colorOf('error') : false));
71 return _earlyErrorResult;
72 }
73
74 var dartInputFile = path.join(path.dirname(inputFile), url);
75
76 if (!new File(dartInputFile).existsSync()) {
77 _log.severe(mainScriptTag.sourceSpan.message(
78 'Script file $dartInputFile not found',
79 color: options.useColors ? colorOf('error') : false));
80 return _earlyErrorResult;
81 }
82
83 var results = _compileDart(dartInputFile, resolver, options, reporter);
84 if (results.failure && !options.forceCompile) return results;
85
86 if (options.outputDir != null) {
87 generateEntryHtml(inputFile, options, results, doc);
88 }
89 return results;
90 }
91
92 CheckerResults _compileDart(
93 String inputFile, TypeResolver resolver, CompilerOptions options,
94 [CheckerReporter reporter]) {
95 Uri uri;
96 if (inputFile.startsWith('dart:') || inputFile.startsWith('package:')) {
97 uri = Uri.parse(inputFile);
98 } else {
99 uri = new Uri.file(path.absolute(inputFile));
100 }
101
102 if (reporter == null) {
103 reporter = options.dumpInfo
104 ? new SummaryReporter()
105 : new LogReporter(options.useColors);
106 }
107
108 var libraries = <LibraryInfo>[];
109 var rules = new RestrictedRules(resolver.context.typeProvider, reporter,
110 options: options);
111 var codeChecker = new CodeChecker(rules, reporter, options);
112 var generators = <CodeGenerator>[];
113 if (options.dumpSrcDir != null) {
114 generators
115 .add(new EmptyDartGenerator(options.dumpSrcDir, uri, rules, options));
116 }
117 var outputDir = options.outputDir;
118 if (outputDir != null) {
119 var cg = options.outputDart
120 ? new DartGenerator(outputDir, uri, rules, options)
121 : new JSGenerator(outputDir, uri, rules, options);
122 generators.add(cg);
123 }
124
125 bool failure = false; 53 bool failure = false;
126 var rootSource = resolver.findSource(uri); 54
127 // TODO(sigmund): switch to use synchronous codegen? 55 factory Compiler(CompilerOptions options,
128 for (var source in reachableSources(rootSource, resolver.context)) { 56 [TypeResolver resolver, CheckerReporter reporter]) {
57 if (resolver == null) {
58 resolver = options.useMockSdk
59 ? new TypeResolver.fromMock(mockSdkSources, options)
60 : new TypeResolver.fromDir(options.dartSdkPath, options);
61 }
62
63 if (reporter == null) {
64 reporter = options.dumpInfo
65 ? new SummaryReporter()
66 : new LogReporter(options.useColors);
67 }
68 var graph = new SourceGraph(resolver.context, options);
69 var rules = new RestrictedRules(resolver.context.typeProvider, reporter,
70 options: options);
71 var checker = new CodeChecker(rules, reporter, options);
72 var inputFile = options.entryPointFile;
73 var uri = inputFile.startsWith('dart:') || inputFile.startsWith('package:')
74 ? Uri.parse(inputFile)
75 : new Uri.file(path.absolute(inputFile));
76 var entryNode = graph.nodeFromUri(uri);
77
78 var outputDir = options.outputDir;
79 var generators = <CodeGenerator>[];
80 if (options.dumpSrcDir != null) {
81 generators.add(new EmptyDartGenerator(
82 options.dumpSrcDir, entryNode.uri, rules, options));
83 }
84 if (outputDir != null) {
85 generators.add(options.outputDart
86 ? new DartGenerator(outputDir, entryNode.uri, rules, options)
87 : new JSGenerator(outputDir, entryNode.uri, rules, options));
88 }
89 return new Compiler._(options, resolver, reporter, rules, checker, graph,
90 entryNode, generators);
91 }
92
93 Compiler._(this.options, this.resolver, this.reporter, this.rules,
94 this.checker, this.graph, this.entryNode, this.generators);
95
96 bool _buildSource(SourceNode node) {
97 if (node is HtmlSourceNode) {
98 _buildHtmlFile(node);
99 } else if (node is LibrarySourceNode) {
100 _buildDartLibrary(node);
101 }
Jennifer Messerly 2015/03/03 02:24:21 what other kinds of nodes are possible?
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 True, at this point no other kind of node should r
102
103 // TODO(sigmund): don't always return true. Use summarization to better
104 // determine when rebuilding is needed.
105 return true;
106 }
107
108 bool _devCompilerRuntimeCopied = false;
109
110 void _buildHtmlFile(HtmlSourceNode node) {
111 if (options.outputDir == null) return;
112 var output = generateEntryHtml(node, options);
113 if (output == null) {
114 failure = true;
115 return;
116 }
117 var filename = path.basename(node.uri.path);
118 String outputFile = path.join(options.outputDir, filename);
119 new File(outputFile).writeAsStringSync(output);
120
121 if (options.outputDart || _devCompilerRuntimeCopied) return;
122 // Copy the dev_compiler runtime (implicit dependency for js codegen)
123 // TODO(sigmund): split this out as a separate node in our dependency graph?
Jennifer Messerly 2015/03/03 02:24:21 +1 .. also that way it could have mtime tracking
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 Good point - I've opened https://github.com/dart-l
124 var runtimeDir = path.join(
125 path.dirname(path.dirname(Platform.script.path)), 'lib/runtime/');
126 var runtimeOutput = path.join(options.outputDir, 'dev_compiler/runtime/');
127 new Directory(runtimeOutput).createSync(recursive: true);
128 new File(path.join(runtimeDir, 'harmony_feature_check.js'))
129 .copy(path.join(runtimeOutput, 'harmony_feature_check.js'));
130 new File(path.join(runtimeDir, 'dart_runtime.js'))
131 .copy(path.join(runtimeOutput, 'dart_runtime.js'));
132 _devCompilerRuntimeCopied = true;
133 }
134
135 bool _isEntry(LibrarySourceNode node) {
136 if (entryNode is LibrarySourceNode) return entryNode == node;
137 return (entryNode as HtmlSourceNode).scripts.contains(node);
138 }
139
140 void _buildDartLibrary(LibrarySourceNode node) {
141 var source = node.source;
142 // TODO(sigmund): find out from analyzer team if there is a better way
143 resolver.context.applyChanges(new ChangeSet()..changedSource(source));
129 var entryUnit = resolver.context.resolveCompilationUnit2(source, source); 144 var entryUnit = resolver.context.resolveCompilationUnit2(source, source);
130 var lib = entryUnit.element.enclosingElement; 145 var lib = entryUnit.element.enclosingElement;
131 if (!options.checkSdk && lib.isInSdk) continue; 146 if (!options.checkSdk && lib.isInSdk) return;
132 var current = new LibraryInfo(lib, source.uri == uri); 147 var current = node.info;
148 if (current != null) {
149 assert(current.library == lib);
150 } else {
151 node.info = current = new LibraryInfo(lib, _isEntry(node));
152 }
133 reporter.enterLibrary(current); 153 reporter.enterLibrary(current);
134 libraries.add(current); 154 libraries.add(current);
135 rules.currentLibraryInfo = current; 155 rules.currentLibraryInfo = current;
136 156
137 var units = [entryUnit] 157 var units = [entryUnit]
138 ..addAll(partsOf(entryUnit, resolver.context) 158 ..addAll(node.parts.map(
139 .map((p) => resolver.context.resolveCompilationUnit2(p, source))); 159 (p) => resolver.context.resolveCompilationUnit2(p.source, source)));
140 bool failureInLib = false; 160 bool failureInLib = false;
141 for (var unit in units) { 161 for (var unit in units) {
142 var unitSource = unit.element.source; 162 var unitSource = unit.element.source;
143 reporter.enterSource(unitSource); 163 reporter.enterSource(unitSource);
144 // TODO(sigmund): integrate analyzer errors with static-info (issue #6). 164 // TODO(sigmund): integrate analyzer errors with static-info (issue #6).
145 failureInLib = resolver.logErrors(unitSource, reporter) || failureInLib; 165 failureInLib = resolver.logErrors(unitSource, reporter) || failureInLib;
146 unit.visitChildren(codeChecker); 166 unit.visitChildren(checker);
147 if (codeChecker.failure) failureInLib = true; 167 if (checker.failure) failureInLib = true;
148 reporter.leaveSource(); 168 reporter.leaveSource();
149 } 169 }
150 reporter.leaveLibrary();
151
152 if (failureInLib) { 170 if (failureInLib) {
153 failure = true; 171 failure = true;
154 if (!options.forceCompile) continue; 172 if (!options.forceCompile) return;
155 } 173 }
156 for (var cg in generators) { 174 for (var cg in generators) {
157 cg.generateLibrary(units, current, reporter); 175 cg.generateLibrary(units, current, reporter);
158 } 176 }
159 } 177 reporter.leaveLibrary();
160 178 }
161 if (options.dumpInfo && reporter is SummaryReporter) { 179
162 print(summaryToString(reporter.result)); 180 CheckerResults runOnce() {
Jennifer Messerly 2015/03/03 02:24:21 made this comment elsewhere, but I think "run" wou
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 Done.
163 if (options.dumpInfoFile != null) { 181 var clock = new Stopwatch()..start();
164 new File(options.dumpInfoFile) 182
165 .writeAsStringSync(JSON.encode(reporter.result.toJsonMap())); 183 // TODO(sigmund): we are missing a couple failures here. The
166 } 184 // dependendency_graph now detects broken imports or unsupported features
167 } 185 // like more than one script tag (see .severe messages in
168 return new CheckerResults(libraries, rules, failure || options.forceCompile); 186 // dependency_graph.dart). Such failures should be reported back
187 // here so we can mark failure=true in the CheckerResutls.
188 rebuild(entryNode, graph, _buildSource);
189 if (options.dumpInfo && reporter is SummaryReporter) {
190 var result = (reporter as SummaryReporter).result;
191 print(summaryToString(result));
192 if (options.dumpInfoFile != null) {
193 new File(options.dumpInfoFile)
194 .writeAsStringSync(JSON.encode(result.toJsonMap()));
195 }
196 }
197 clock.stop();
198 if (options.serverMode) {
199 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
200 print('Compiled ${libraries.length} libraries in ${time} s\n');
201 }
202 return new CheckerResults(
203 libraries, rules, failure || options.forceCompile);
204 }
205
206 void _runAgain() {
207 var clock = new Stopwatch()..start();
208 if (reporter is SummaryReporter) (reporter as SummaryReporter).clear();
Jennifer Messerly 2015/03/03 02:24:21 WAT :) can we file this somewhere? another idea
Siggi Cherem (dart-lang) 2015/03/04 04:44:23 It's sad, the promotion rules only apply to local
209 libraries = <LibraryInfo>[];
210 int changed = 0;
211
212 // TODO(sigmund): propagate failures here (see TODO in runOnce).
213 rebuild(entryNode, graph, (n) {
214 changed++;
215 return _buildSource(n);
216 });
217 if (reporter is SummaryReporter) {
218 print(summaryToString((reporter as SummaryReporter).result));
219 }
220 clock.stop();
221 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
222 print("Compiled ${changed} libraries in ${time} s\n");
223 }
169 } 224 }
170 225
226 class CompilerServer {
227 final Compiler compiler;
228 final String outDir;
229 final int port;
230 final String _entryPath;
231
232 factory CompilerServer(CompilerOptions options) {
233 var entryPath = path.basename(options.entryPointFile);
234 if (path.extension(entryPath) != '.html') {
235 print('error: devc in server mode requires an HTML entry point.');
236 exit(1);
237 }
238
239 // TODO(sigmund): allow running without a dir, but keep output in memory?
240 var outDir = options.outputDir;
241 if (outDir == null) {
242 print('error: devc in server mode also requires specifying and '
243 'output location for generated code.');
244 exit(1);
245 }
246 var port = options.port;
247 print('[dev_compiler]: Serving $entryPath at http://0.0.0.0:$port/');
248 var compiler = new Compiler(options);
249 return new CompilerServer._(compiler, outDir, port, entryPath);
250 }
251
252 CompilerServer._(this.compiler, this.outDir, this.port, this._entryPath);
253
254 Future start() async {
255 var handler = const shelf.Pipeline()
256 .addMiddleware(shelf.createMiddleware(requestHandler: rebuildIfNeeded))
257 .addHandler(shelf_static.createStaticHandler(outDir,
258 defaultDocument: _entryPath));
259 await shelf.serve(handler, '0.0.0.0', port);
260 compiler.runOnce();
261 }
262
263 rebuildIfNeeded(shelf.Request request) {
264 var filepath = request.url.path;
265 if (filepath == '/$_entryPath' || filepath == '/') compiler._runAgain();
266 }
267 }
268
171 final _log = new Logger('ddc'); 269 final _log = new Logger('ddc');
172 final _earlyErrorResult = new CheckerResults(const [], null, true); 270 final _earlyErrorResult = new CheckerResults(const [], null, true);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698