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

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

Issue 1235503010: fixes #219, able to compile multiple entry points (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 5 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 /// Development server that compiles Dart to JS on the fly.
6 library dev_compiler.devc; 6 library dev_compiler.src.server;
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/file_system/file_system.dart' show ResourceUriResolver;
13 import 'package:analyzer/file_system/memory_file_system.dart';
12 import 'package:analyzer/src/generated/engine.dart' 14 import 'package:analyzer/src/generated/engine.dart'
13 show AnalysisContext, ChangeSet; 15 show AnalysisContext, ChangeSet;
14 import 'package:analyzer/src/generated/error.dart' 16 import 'package:analyzer/src/generated/error.dart';
15 show AnalysisError, AnalysisErrorListener, ErrorSeverity, ErrorType; 17 import 'package:analyzer/src/generated/source.dart';
16 import 'package:analyzer/src/generated/source.dart' show Source;
17 import 'package:logging/logging.dart' show Level, Logger, LogRecord; 18 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
18 import 'package:path/path.dart' as path; 19 import 'package:path/path.dart' as path;
19 import 'package:shelf/shelf.dart' as shelf; 20 import 'package:shelf/shelf.dart' as shelf;
20 import 'package:shelf/shelf_io.dart' as shelf; 21 import 'package:shelf/shelf_io.dart' as shelf;
21 import 'package:shelf_static/shelf_static.dart' as shelf_static; 22 import 'package:shelf_static/shelf_static.dart' as shelf_static;
22 23
23 import 'src/analysis_context.dart'; 24 import 'package:dev_compiler/src/codegen/code_generator.dart'
24 import 'src/checker/checker.dart'; 25 show CodeGenerator;
25 import 'src/checker/rules.dart'; 26 import 'package:dev_compiler/src/codegen/html_codegen.dart'
26 import 'src/codegen/code_generator.dart' show CodeGenerator; 27 show generateEntryHtml;
27 import 'src/codegen/html_codegen.dart'; 28 import 'package:dev_compiler/src/codegen/js_codegen.dart';
28 import 'src/codegen/js_codegen.dart'; 29 import 'package:dev_compiler/src/analysis_context.dart';
29 import 'src/dependency_graph.dart'; 30 import 'package:dev_compiler/src/compiler.dart' show AbstractCompiler;
30 import 'src/info.dart' 31 import 'package:dev_compiler/src/info.dart'
31 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit; 32 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
32 import 'src/options.dart'; 33 import 'package:dev_compiler/src/options.dart';
33 import 'src/report.dart'; 34 import 'package:dev_compiler/src/report.dart';
34 import 'src/utils.dart'; 35 import 'package:dev_compiler/src/utils.dart';
35 36
36 /// Sets up the type checker logger to print a span that highlights error 37 import 'dependency_graph.dart';
37 /// messages.
38 StreamSubscription setupLogger(Level level, printFn) {
39 Logger.root.level = level;
40 return Logger.root.onRecord.listen((LogRecord rec) {
41 printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
42 });
43 }
44 38
45 abstract class AbstractCompiler { 39 /// Encapsulates the logic when the compiler is run as a development server.
46 CompilerOptions get options; 40 class ServerCompiler extends AbstractCompiler {
47 AnalysisContext get context;
48 TypeRules get rules;
49 Uri get entryPointUri;
50 }
51
52 /// Encapsulates the logic to do a one-off compilation or a partial compilation
53 /// when the compiler is run as a development server.
54 class Compiler implements AbstractCompiler {
55 final CompilerOptions options;
56 final AnalysisContext context;
57 final AnalysisErrorListener _reporter;
58 final TypeRules rules;
59 final CodeChecker _checker;
60 final SourceNode _entryNode; 41 final SourceNode _entryNode;
61 List<LibraryInfo> _libraries = <LibraryInfo>[]; 42 List<LibraryInfo> _libraries = <LibraryInfo>[];
62 final _generators = <CodeGenerator>[]; 43 final _generators = <CodeGenerator>[];
63 bool _hashing; 44 bool _hashing;
64 bool _failure = false; 45 bool _failure = false;
65 46
66 factory Compiler(CompilerOptions options, 47 factory ServerCompiler(AnalysisContext context, CompilerOptions options,
67 {AnalysisContext context, AnalysisErrorListener reporter}) { 48 {AnalysisErrorListener reporter}) {
68 var strongOpts = options.strongOptions; 49 var srcOpts = options.sourceOptions;
69 var sourceOpts = options.sourceOptions; 50 var inputFile = options.inputs[0];
70 if (context == null) {
71 context = createAnalysisContextWithSources(strongOpts, sourceOpts);
72 }
73
74 if (reporter == null) {
75 reporter = options.dumpInfo
76 ? new SummaryReporter(context, options.logLevel)
77 : new LogReporter(context, useColors: options.useColors);
78 }
79 var graph = new SourceGraph(context, reporter, options);
80 var rules = new RestrictedRules(context.typeProvider,
81 options: options.strongOptions);
82 var checker = new CodeChecker(rules, reporter, strongOpts);
83
84 var inputFile = sourceOpts.entryPointFile;
85 var inputUri = inputFile.startsWith('dart:') || 51 var inputUri = inputFile.startsWith('dart:') ||
86 inputFile.startsWith('package:') 52 inputFile.startsWith('package:')
87 ? Uri.parse(inputFile) 53 ? Uri.parse(inputFile)
88 : new Uri.file(path.absolute(sourceOpts.useImplicitHtml 54 : new Uri.file(path.absolute(srcOpts.useImplicitHtml
89 ? SourceResolverOptions.implicitHtmlFile 55 ? SourceResolverOptions.implicitHtmlFile
90 : inputFile)); 56 : inputFile));
57 var graph = new SourceGraph(context, reporter, options);
91 var entryNode = graph.nodeFromUri(inputUri); 58 var entryNode = graph.nodeFromUri(inputUri);
92 59
93 return new Compiler._( 60 return new ServerCompiler._(options, context, reporter, entryNode);
94 options, context, reporter, rules, checker, entryNode);
95 } 61 }
96 62
97 Compiler._(this.options, this.context, this._reporter, this.rules, 63 ServerCompiler._(context, options, reporter, this._entryNode)
98 this._checker, this._entryNode) { 64 : super(options, context, reporter) {
99 if (outputDir != null) { 65 if (outputDir != null) {
100 _generators.add(new JSGenerator(this)); 66 _generators.add(new JSGenerator(this));
101 } 67 }
102 // TODO(sigmund): refactor to support hashing of the dart output? 68 // TODO(sigmund): refactor to support hashing of the dart output?
103 _hashing = options.enableHashing && _generators.length == 1; 69 _hashing = options.enableHashing && _generators.length == 1;
104 } 70 }
105 71
106 Uri get entryPointUri => _entryNode.uri; 72 Uri get entryPointUri => _entryNode.uri;
107 String get outputDir => options.codegenOptions.outputDir; 73
74 CheckerResults run() {
75 var clock = new Stopwatch()..start();
76
77 // TODO(sigmund): we are missing a couple failures here. The
78 // dependency_graph now detects broken imports or unsupported features
79 // like more than one script tag (see .severe messages in
80 // dependency_graph.dart). Such failures should be reported back
81 // here so we can mark failure=true in the CheckerResults.
82 rebuild(_entryNode, _buildSource);
83 _dumpInfoIfRequested();
84 clock.stop();
85 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
86 _log.fine('Compiled ${_libraries.length} libraries in ${time} s\n');
87 return new CheckerResults(
88 _libraries, rules, _failure || options.codegenOptions.forceCompile);
89 }
108 90
109 bool _buildSource(SourceNode node) { 91 bool _buildSource(SourceNode node) {
110 if (node is HtmlSourceNode) { 92 if (node is HtmlSourceNode) {
111 _buildHtmlFile(node); 93 _buildHtmlFile(node);
112 } else if (node is DartSourceNode) { 94 } else if (node is DartSourceNode) {
113 _buildDartLibrary(node); 95 _buildDartLibrary(node);
114 } else if (node is ResourceSourceNode) { 96 } else if (node is ResourceSourceNode) {
115 _buildResourceFile(node); 97 _buildResourceFile(node);
116 } else { 98 } else {
117 assert(false); // should not get a build request on PartSourceNode 99 assert(false); // should not get a build request on PartSourceNode
118 } 100 }
119 101
120 // TODO(sigmund): don't always return true. Use summarization to better 102 // TODO(sigmund): don't always return true.
121 // determine when rebuilding is needed. 103 // Use summaries to determine when rebuilding is needed.
122 return true; 104 return true;
123 } 105 }
124 106
125 void _buildHtmlFile(HtmlSourceNode node) { 107 void _buildHtmlFile(HtmlSourceNode node) {
126 if (outputDir == null) return; 108 if (outputDir == null) return;
127 var output = generateEntryHtml(node, options); 109 var output = generateEntryHtml(node, this);
128 if (output == null) { 110 if (output == null) {
129 _failure = true; 111 _failure = true;
130 return; 112 return;
131 } 113 }
114
132 var filename = path.basename(node.uri.path); 115 var filename = path.basename(node.uri.path);
133 String outputFile = path.join(outputDir, filename); 116 String outputFile = path.join(outputDir, filename);
134 new File(outputFile).writeAsStringSync(output); 117 new File(outputFile).writeAsStringSync(output);
135 } 118 }
136 119
137 void _buildResourceFile(ResourceSourceNode node) { 120 void _buildResourceFile(ResourceSourceNode node) {
138 // ResourceSourceNodes files that just need to be copied over to the output 121 // ResourceSourceNodes files that just need to be copied over to the output
139 // location. These can be external dependencies or pieces of the 122 // location. These can be external dependencies or pieces of the
140 // dev_compiler runtime. 123 // dev_compiler runtime.
141 if (outputDir == null) return; 124 if (outputDir == null) return;
142 var filepath = 125 var filepath =
143 resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir); 126 resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir);
144 assert(filepath != null); 127 assert(filepath != null);
145 filepath = path.join(outputDir, filepath); 128 filepath = path.join(outputDir, filepath);
146 var dir = path.dirname(filepath); 129 var dir = path.dirname(filepath);
147 new Directory(dir).createSync(recursive: true); 130 new Directory(dir).createSync(recursive: true);
148 new File.fromUri(node.source.uri).copySync(filepath); 131 new File.fromUri(node.source.uri).copySync(filepath);
149 if (_hashing) node.cachingHash = computeHashFromFile(filepath); 132 if (_hashing) node.cachingHash = computeHashFromFile(filepath);
150 } 133 }
151 134
152 bool _isEntry(DartSourceNode node) {
153 if (_entryNode is DartSourceNode) return _entryNode == node;
154 return (_entryNode as HtmlSourceNode).scripts.contains(node);
155 }
156
157 void _buildDartLibrary(DartSourceNode node) { 135 void _buildDartLibrary(DartSourceNode node) {
158 var source = node.source; 136 var source = node.source;
159 // TODO(sigmund): find out from analyzer team if there is a better way 137 // TODO(sigmund): find out from analyzer team if there is a better way
160 context.applyChanges(new ChangeSet()..changedSource(source)); 138 context.applyChanges(new ChangeSet()..changedSource(source));
161 var entryUnit = context.resolveCompilationUnit2(source, source); 139 var entryUnit = context.resolveCompilationUnit2(source, source);
162 var lib = entryUnit.element.enclosingElement; 140 var lib = entryUnit.element.enclosingElement;
163 if (!options.checkSdk && lib.source.uri.scheme == 'dart') return; 141 if (!options.checkSdk && lib.source.uri.scheme == 'dart') return;
164 var current = node.info; 142 var current = node.info;
165 if (current != null) { 143 if (current != null) {
166 assert(current.library == lib); 144 assert(current.library == lib);
167 } else { 145 } else {
168 node.info = current = new LibraryInfo(lib, _isEntry(node)); 146 node.info = current = new LibraryInfo(lib);
169 } 147 }
170 _libraries.add(current); 148 _libraries.add(current);
171 rules.currentLibraryInfo = current;
172 149
173 var resolvedParts = node.parts 150 var resolvedParts = node.parts
174 .map((p) => context.resolveCompilationUnit2(p.source, source)) 151 .map((p) => context.resolveCompilationUnit2(p.source, source))
175 .toList(growable: false); 152 .toList(growable: false);
176 var libraryUnit = new LibraryUnit(entryUnit, resolvedParts); 153 var libraryUnit = new LibraryUnit(entryUnit, resolvedParts);
177 bool failureInLib = false; 154 bool failureInLib = false;
178 for (var unit in libraryUnit.libraryThenParts) { 155 for (var unit in libraryUnit.libraryThenParts) {
179 var unitSource = unit.element.source; 156 var unitSource = unit.element.source;
157 // TODO(sigmund): integrate analyzer errors with static-info (issue #6).
180 failureInLib = logErrors(unitSource) || failureInLib; 158 failureInLib = logErrors(unitSource) || failureInLib;
181 _checker.visitCompilationUnit(unit); 159 checker.visitCompilationUnit(unit);
182 if (_checker.failure) failureInLib = true; 160 if (checker.failure) failureInLib = true;
183 } 161 }
184 if (failureInLib) { 162 if (failureInLib) {
185 _failure = true; 163 _failure = true;
186 if (!options.codegenOptions.forceCompile) return; 164 if (!options.codegenOptions.forceCompile) return;
187 } 165 }
188 166
189 for (var cg in _generators) { 167 for (var cg in _generators) {
190 var hash = cg.generateLibrary(libraryUnit, current); 168 var hash = cg.generateLibrary(libraryUnit);
191 if (_hashing) node.cachingHash = hash; 169 if (_hashing) node.cachingHash = hash;
192 } 170 }
193 } 171 }
194 172
195 /// Log any errors encountered when resolving [source] and return whether any
196 /// errors were found.
197 bool logErrors(Source source) {
198 context.computeErrors(source);
199 List<AnalysisError> errors = context.getErrors(source).errors;
200 bool failure = false;
201 if (errors.isNotEmpty) {
202 for (var error in errors) {
203 // Always skip TODOs.
204 if (error.errorCode.type == ErrorType.TODO) continue;
205
206 // Skip hints for now. In the future these could be turned on via flags.
207 if (error.errorCode.errorSeverity.ordinal <
208 ErrorSeverity.WARNING.ordinal) {
209 continue;
210 }
211
212 // All analyzer warnings or errors are errors for DDC.
213 failure = true;
214 _reporter.onError(error);
215 }
216 }
217 return failure;
218 }
219
220 CheckerResults run() {
221 var clock = new Stopwatch()..start();
222
223 // TODO(sigmund): we are missing a couple failures here. The
224 // dependency_graph now detects broken imports or unsupported features
225 // like more than one script tag (see .severe messages in
226 // dependency_graph.dart). Such failures should be reported back
227 // here so we can mark failure=true in the CheckerResutls.
228 rebuild(_entryNode, _buildSource);
229 _dumpInfoIfRequested();
230 clock.stop();
231 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
232 _log.fine('Compiled ${_libraries.length} libraries in ${time} s\n');
233 return new CheckerResults(
234 _libraries, rules, _failure || options.codegenOptions.forceCompile);
235 }
236
237 void _runAgain() { 173 void _runAgain() {
238 var clock = new Stopwatch()..start(); 174 var clock = new Stopwatch()..start();
239 _libraries = <LibraryInfo>[]; 175 _libraries = <LibraryInfo>[];
240 int changed = 0; 176 int changed = 0;
241 177
242 // TODO(sigmund): propagate failures here (see TODO in run). 178 // TODO(sigmund): propagate failures here (see TODO in run).
243 rebuild(_entryNode, (n) { 179 rebuild(_entryNode, (n) {
244 changed++; 180 changed++;
245 return _buildSource(n); 181 return _buildSource(n);
246 }); 182 });
247 clock.stop(); 183 clock.stop();
248 if (changed > 0) _dumpInfoIfRequested(); 184 if (changed > 0) _dumpInfoIfRequested();
249 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2); 185 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
250 _log.fine("Compiled ${changed} libraries in ${time} s\n"); 186 _log.fine("Compiled ${changed} libraries in ${time} s\n");
251 } 187 }
252 188
253 _dumpInfoIfRequested() { 189 _dumpInfoIfRequested() {
254 if (!options.dumpInfo || _reporter is! SummaryReporter) return; 190 if (!options.dumpInfo || reporter is! SummaryReporter) return;
255 var result = (_reporter as SummaryReporter).result; 191 var result = (reporter as SummaryReporter).result;
256 if (!options.serverMode) print(summaryToString(result)); 192 if (!options.serverMode) print(summaryToString(result));
257 var filepath = options.serverMode 193 var filepath = options.serverMode
258 ? path.join(outputDir, 'messages.json') 194 ? path.join(outputDir, 'messages.json')
259 : options.dumpInfoFile; 195 : options.dumpInfoFile;
260 if (filepath == null) return; 196 if (filepath == null) return;
261 new File(filepath).writeAsStringSync(JSON.encode(result.toJsonMap())); 197 new File(filepath).writeAsStringSync(JSON.encode(result.toJsonMap()));
262 } 198 }
263 } 199 }
264 200
265 class CompilerServer { 201 class DevServer {
266 final Compiler compiler; 202 final ServerCompiler compiler;
267 final String outDir; 203 final String outDir;
268 final String host; 204 final String host;
269 final int port; 205 final int port;
270 final String _entryPath; 206 final String _entryPath;
271 207
272 factory CompilerServer(CompilerOptions options) { 208 factory DevServer(CompilerOptions options) {
273 var entryPath = path.basename(options.sourceOptions.entryPointFile); 209 assert(options.inputs.length == 1);
210
211 var fileResolvers = createFileResolvers(options.sourceOptions);
212 if (options.sourceOptions.useImplicitHtml) {
213 fileResolvers.insert(0, _createImplicitEntryResolver(options.inputs[0]));
214 }
215
216 var context = createAnalysisContextWithSources(
217 options.strongOptions, options.sourceOptions,
218 fileResolvers: fileResolvers);
219
220 var entryPath = path.basename(options.inputs[0]);
274 var extension = path.extension(entryPath); 221 var extension = path.extension(entryPath);
275 if (extension != '.html' && !options.sourceOptions.useImplicitHtml) { 222 if (extension != '.html' && !options.sourceOptions.useImplicitHtml) {
276 print('error: devc in server mode requires an HTML or Dart entry point.'); 223 print('error: devc in server mode requires an HTML or Dart entry point.');
277 exit(1); 224 exit(1);
278 } 225 }
279 226
280 // TODO(sigmund): allow running without a dir, but keep output in memory? 227 // TODO(sigmund): allow running without a dir, but keep output in memory?
281 var outDir = options.codegenOptions.outputDir; 228 var outDir = options.codegenOptions.outputDir;
282 if (outDir == null) { 229 if (outDir == null) {
283 print('error: devc in server mode also requires specifying and ' 230 print('error: devc in server mode also requires specifying and '
284 'output location for generated code.'); 231 'output location for generated code.');
285 exit(1); 232 exit(1);
286 } 233 }
287 var port = options.port; 234 var port = options.port;
288 var host = options.host; 235 var host = options.host;
289 var compiler = new Compiler(options); 236 var compiler = new ServerCompiler(context, options);
290 return new CompilerServer._(compiler, outDir, host, port, entryPath); 237 return new DevServer._(compiler, outDir, host, port, entryPath);
291 } 238 }
292 239
293 CompilerServer._( 240 DevServer._(ServerCompiler compiler, this.outDir, this.host, this.port,
294 Compiler compiler, this.outDir, this.host, this.port, String entryPath) 241 String entryPath)
295 : this.compiler = compiler, 242 : this.compiler = compiler,
296 // TODO(jmesserly): this logic is duplicated in a few places 243 // TODO(jmesserly): this logic is duplicated in a few places
297 this._entryPath = compiler.options.sourceOptions.useImplicitHtml 244 this._entryPath = compiler.options.sourceOptions.useImplicitHtml
298 ? SourceResolverOptions.implicitHtmlFile 245 ? SourceResolverOptions.implicitHtmlFile
299 : entryPath; 246 : entryPath;
300 247
301 Future start() async { 248 Future start() async {
302 // Create output directory if needed. shelf_static will fail otherwise. 249 // Create output directory if needed. shelf_static will fail otherwise.
303 var out = new Directory(outDir); 250 var out = new Directory(outDir);
304 if (!await out.exists()) await out.create(recursive: true); 251 if (!await out.exists()) await out.create(recursive: true);
(...skipping 23 matching lines...) Expand all
328 var headers = {'cache-control': policy}; 275 var headers = {'cache-control': policy};
329 if (hash != null) { 276 if (hash != null) {
330 // Note: the cache-control header should be enough, but this doesn't hurt 277 // Note: the cache-control header should be enough, but this doesn't hurt
331 // and can help renew the policy after it expires. 278 // and can help renew the policy after it expires.
332 headers['ETag'] = hash; 279 headers['ETag'] = hash;
333 } 280 }
334 return response.change(headers: headers); 281 return response.change(headers: headers);
335 }; 282 };
336 } 283 }
337 284
338 final _log = new Logger('dev_compiler'); 285 UriResolver _createImplicitEntryResolver(String entryPath) {
286 var entry = path.absolute(SourceResolverOptions.implicitHtmlFile);
287 var src = path.absolute(entryPath);
288 var provider = new MemoryResourceProvider();
289 provider.newFile(
290 entry, '<body><script type="application/dart" src="$src"></script>');
291 return new _ExistingSourceUriResolver(new ResourceUriResolver(provider));
292 }
293
294 /// A UriResolver that continues to the next one if it fails to find an existing
295 /// source file. This is unlike normal URI resolvers, that always return
296 /// something, even if it is a non-existing file.
297 class _ExistingSourceUriResolver implements UriResolver {
298 final UriResolver resolver;
299 _ExistingSourceUriResolver(this.resolver);
300
301 Source resolveAbsolute(Uri uri) {
302 var src = resolver.resolveAbsolute(uri);
303 return src.exists() ? src : null;
304 }
305 Uri restoreAbsolute(Source source) => resolver.restoreAbsolute(source);
306 }
307
308 final _log = new Logger('dev_compiler.src.server');
339 final _earlyErrorResult = new CheckerResults(const [], null, true); 309 final _earlyErrorResult = new CheckerResults(const [], null, true);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698