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

Side by Side Diff: lib/devc.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.
6 library dev_compiler.devc; 5 library dev_compiler.devc;
7 6
8 import 'dart:async'; 7 export 'src/analysis_context.dart'
9 import 'dart:convert'; 8 show createAnalysisContext, createAnalysisContextWithSources;
10 import 'dart:io'; 9 export 'src/compiler.dart' show BatchCompiler, setupLogger, createErrorReporter;
11 10 export 'src/server/server.dart' show DevServer;
12 import 'package:analyzer/src/generated/engine.dart' 11 export 'strong_mode.dart' show StrongModeOptions;
13 show AnalysisContext, ChangeSet;
14 import 'package:analyzer/src/generated/error.dart'
15 show AnalysisError, AnalysisErrorListener, ErrorSeverity, ErrorType;
16 import 'package:analyzer/src/generated/source.dart' show Source;
17 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
18 import 'package:path/path.dart' as path;
19 import 'package:shelf/shelf.dart' as shelf;
20 import 'package:shelf/shelf_io.dart' as shelf;
21 import 'package:shelf_static/shelf_static.dart' as shelf_static;
22
23 import 'src/analysis_context.dart';
24 import 'src/checker/checker.dart';
25 import 'src/checker/rules.dart';
26 import 'src/codegen/code_generator.dart' show CodeGenerator;
27 import 'src/codegen/html_codegen.dart';
28 import 'src/codegen/js_codegen.dart';
29 import 'src/dependency_graph.dart';
30 import 'src/info.dart'
31 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
32 import 'src/options.dart';
33 import 'src/report.dart';
34 import 'src/utils.dart';
35
36 /// Sets up the type checker logger to print a span that highlights error
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
45 abstract class AbstractCompiler {
46 CompilerOptions get options;
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;
61 List<LibraryInfo> _libraries = <LibraryInfo>[];
62 final _generators = <CodeGenerator>[];
63 bool _hashing;
64 bool _failure = false;
65
66 factory Compiler(CompilerOptions options,
67 {AnalysisContext context, AnalysisErrorListener reporter}) {
68 var strongOpts = options.strongOptions;
69 var sourceOpts = options.sourceOptions;
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:') ||
86 inputFile.startsWith('package:')
87 ? Uri.parse(inputFile)
88 : new Uri.file(path.absolute(sourceOpts.useImplicitHtml
89 ? SourceResolverOptions.implicitHtmlFile
90 : inputFile));
91 var entryNode = graph.nodeFromUri(inputUri);
92
93 return new Compiler._(
94 options, context, reporter, rules, checker, entryNode);
95 }
96
97 Compiler._(this.options, this.context, this._reporter, this.rules,
98 this._checker, this._entryNode) {
99 if (outputDir != null) {
100 _generators.add(new JSGenerator(this));
101 }
102 // TODO(sigmund): refactor to support hashing of the dart output?
103 _hashing = options.enableHashing && _generators.length == 1;
104 }
105
106 Uri get entryPointUri => _entryNode.uri;
107 String get outputDir => options.codegenOptions.outputDir;
108
109 bool _buildSource(SourceNode node) {
110 if (node is HtmlSourceNode) {
111 _buildHtmlFile(node);
112 } else if (node is DartSourceNode) {
113 _buildDartLibrary(node);
114 } else if (node is ResourceSourceNode) {
115 _buildResourceFile(node);
116 } else {
117 assert(false); // should not get a build request on PartSourceNode
118 }
119
120 // TODO(sigmund): don't always return true. Use summarization to better
121 // determine when rebuilding is needed.
122 return true;
123 }
124
125 void _buildHtmlFile(HtmlSourceNode node) {
126 if (outputDir == null) return;
127 var output = generateEntryHtml(node, options);
128 if (output == null) {
129 _failure = true;
130 return;
131 }
132 var filename = path.basename(node.uri.path);
133 String outputFile = path.join(outputDir, filename);
134 new File(outputFile).writeAsStringSync(output);
135 }
136
137 void _buildResourceFile(ResourceSourceNode node) {
138 // ResourceSourceNodes files that just need to be copied over to the output
139 // location. These can be external dependencies or pieces of the
140 // dev_compiler runtime.
141 if (outputDir == null) return;
142 var filepath =
143 resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir);
144 assert(filepath != null);
145 filepath = path.join(outputDir, filepath);
146 var dir = path.dirname(filepath);
147 new Directory(dir).createSync(recursive: true);
148 new File.fromUri(node.source.uri).copySync(filepath);
149 if (_hashing) node.cachingHash = computeHashFromFile(filepath);
150 }
151
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) {
158 var source = node.source;
159 // TODO(sigmund): find out from analyzer team if there is a better way
160 context.applyChanges(new ChangeSet()..changedSource(source));
161 var entryUnit = context.resolveCompilationUnit2(source, source);
162 var lib = entryUnit.element.enclosingElement;
163 if (!options.checkSdk && lib.source.uri.scheme == 'dart') return;
164 var current = node.info;
165 if (current != null) {
166 assert(current.library == lib);
167 } else {
168 node.info = current = new LibraryInfo(lib, _isEntry(node));
169 }
170 _libraries.add(current);
171 rules.currentLibraryInfo = current;
172
173 var resolvedParts = node.parts
174 .map((p) => context.resolveCompilationUnit2(p.source, source))
175 .toList(growable: false);
176 var libraryUnit = new LibraryUnit(entryUnit, resolvedParts);
177 bool failureInLib = false;
178 for (var unit in libraryUnit.libraryThenParts) {
179 var unitSource = unit.element.source;
180 failureInLib = logErrors(unitSource) || failureInLib;
181 _checker.visitCompilationUnit(unit);
182 if (_checker.failure) failureInLib = true;
183 }
184 if (failureInLib) {
185 _failure = true;
186 if (!options.codegenOptions.forceCompile) return;
187 }
188
189 for (var cg in _generators) {
190 var hash = cg.generateLibrary(libraryUnit, current);
191 if (_hashing) node.cachingHash = hash;
192 }
193 }
194
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() {
238 var clock = new Stopwatch()..start();
239 _libraries = <LibraryInfo>[];
240 int changed = 0;
241
242 // TODO(sigmund): propagate failures here (see TODO in run).
243 rebuild(_entryNode, (n) {
244 changed++;
245 return _buildSource(n);
246 });
247 clock.stop();
248 if (changed > 0) _dumpInfoIfRequested();
249 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
250 _log.fine("Compiled ${changed} libraries in ${time} s\n");
251 }
252
253 _dumpInfoIfRequested() {
254 if (!options.dumpInfo || _reporter is! SummaryReporter) return;
255 var result = (_reporter as SummaryReporter).result;
256 if (!options.serverMode) print(summaryToString(result));
257 var filepath = options.serverMode
258 ? path.join(outputDir, 'messages.json')
259 : options.dumpInfoFile;
260 if (filepath == null) return;
261 new File(filepath).writeAsStringSync(JSON.encode(result.toJsonMap()));
262 }
263 }
264
265 class CompilerServer {
266 final Compiler compiler;
267 final String outDir;
268 final String host;
269 final int port;
270 final String _entryPath;
271
272 factory CompilerServer(CompilerOptions options) {
273 var entryPath = path.basename(options.sourceOptions.entryPointFile);
274 var extension = path.extension(entryPath);
275 if (extension != '.html' && !options.sourceOptions.useImplicitHtml) {
276 print('error: devc in server mode requires an HTML or Dart entry point.');
277 exit(1);
278 }
279
280 // TODO(sigmund): allow running without a dir, but keep output in memory?
281 var outDir = options.codegenOptions.outputDir;
282 if (outDir == null) {
283 print('error: devc in server mode also requires specifying and '
284 'output location for generated code.');
285 exit(1);
286 }
287 var port = options.port;
288 var host = options.host;
289 var compiler = new Compiler(options);
290 return new CompilerServer._(compiler, outDir, host, port, entryPath);
291 }
292
293 CompilerServer._(
294 Compiler compiler, this.outDir, this.host, this.port, String entryPath)
295 : this.compiler = compiler,
296 // TODO(jmesserly): this logic is duplicated in a few places
297 this._entryPath = compiler.options.sourceOptions.useImplicitHtml
298 ? SourceResolverOptions.implicitHtmlFile
299 : entryPath;
300
301 Future start() async {
302 // Create output directory if needed. shelf_static will fail otherwise.
303 var out = new Directory(outDir);
304 if (!await out.exists()) await out.create(recursive: true);
305
306 var handler = const shelf.Pipeline()
307 .addMiddleware(rebuildAndCache)
308 .addHandler(shelf_static.createStaticHandler(outDir,
309 defaultDocument: _entryPath));
310 await shelf.serve(handler, host, port);
311 print('Serving $_entryPath at http://$host:$port/');
312 compiler.run();
313 }
314
315 shelf.Handler rebuildAndCache(shelf.Handler handler) => (request) {
316 print('requested $GREEN_COLOR${request.url}$NO_COLOR');
317 // Trigger recompile only when requesting the HTML page.
318 var segments = request.url.pathSegments;
319 bool isEntryPage = segments.length == 0 || segments[0] == _entryPath;
320 if (isEntryPage) compiler._runAgain();
321
322 // To help browsers cache resources that don't change, we serve these
323 // resources by adding a query parameter containing their hash:
324 // /{path-to-file.js}?____cached={hash}
325 var hash = request.url.queryParameters['____cached'];
326 var response = handler(request);
327 var policy = hash != null ? 'max-age=${24 * 60 * 60}' : 'no-cache';
328 var headers = {'cache-control': policy};
329 if (hash != null) {
330 // Note: the cache-control header should be enough, but this doesn't hurt
331 // and can help renew the policy after it expires.
332 headers['ETag'] = hash;
333 }
334 return response.change(headers: headers);
335 };
336 }
337
338 final _log = new Logger('dev_compiler');
339 final _earlyErrorResult = new CheckerResults(const [], null, true);
OLDNEW
« no previous file with comments | « bin/devc.dart ('k') | lib/src/analysis_context.dart » ('j') | lib/src/compiler.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698