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

Side by Side Diff: lib/src/compiler.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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Command line tool to run the checker on a Dart program.
6 library dev_compiler.src.compiler;
7
8 import 'dart:async';
9 import 'dart:collection';
10 import 'dart:math' as math;
11 import 'dart:io';
12
13 import 'package:analyzer/src/generated/ast.dart' show CompilationUnit;
14 import 'package:analyzer/src/generated/element.dart';
15 import 'package:analyzer/src/generated/engine.dart'
16 show AnalysisEngine, AnalysisContext, ChangeSet, ParseDartTask;
17 import 'package:analyzer/src/generated/error.dart'
18 show AnalysisError, ErrorSeverity, ErrorType;
19 import 'package:analyzer/src/generated/error.dart';
20 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
21 import 'package:analyzer/src/generated/source.dart' show Source;
22 import 'package:analyzer/src/task/html.dart';
23 import 'package:html/dom.dart' as html;
24 import 'package:html/parser.dart' as html;
25 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
26 import 'package:path/path.dart' as path;
27
28 import 'package:dev_compiler/strong_mode.dart' show StrongModeOptions;
29
30 import 'analysis_context.dart';
31 import 'checker/checker.dart';
32 import 'checker/rules.dart';
33 import 'codegen/html_codegen.dart' as html_codegen;
34 import 'codegen/js_codegen.dart';
35 import 'info.dart'
36 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
37 import 'options.dart';
38 import 'report.dart';
39
40 /// Sets up the type checker logger to print a span that highlights error
41 /// messages.
42 StreamSubscription setupLogger(Level level, printFn) {
43 Logger.root.level = level;
44 return Logger.root.onRecord.listen((LogRecord rec) {
45 printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
46 });
47 }
48
49 class BatchCompiler extends AbstractCompiler {
50 JSGenerator _jsGen;
51
52 /// Already compiled sources, so we don't compile them again.
53 final _compiled = new HashSet<LibraryElement>();
54
55 bool _failure = false;
56 bool get failure => _failure;
57
58 BatchCompiler(AnalysisContext context, CompilerOptions options,
59 {AnalysisErrorListener reporter})
60 : super(context, options, reporter) {
61 if (outputDir != null) {
62 _jsGen = new JSGenerator(this);
63 }
64 }
65
66 void reset() {
67 _compiled.clear();
68 }
69
70 /// Compiles every file in [options.inputs].
71 /// Returns true on successful compile.
72 bool run() {
73 var clock = new Stopwatch()..start();
74 options.inputs.forEach(compileFromUriString);
75 clock.stop();
76 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
77 _log.fine('Compiled ${_compiled.length} libraries in ${time} s\n');
78
79 return !_failure;
80 }
81
82 void compileFromUriString(String uriString) {
83 compileFromUri(stringToUri(uriString));
84 }
85
86 void compileFromUri(Uri uri) {
87 var source = context.sourceFactory.forUri(Uri.encodeFull('$uri'));
88 if (source == null) throw new ArgumentError.value(
89 uri.toString(), 'uri', 'could not find source for');
90 compileSource(source);
91 }
92
93 void compileSource(Source source) {
94 if (AnalysisEngine.isHtmlFileName(source.uri.path)) {
95 compileHtml(source);
96 return;
97 }
98
99 compileLibrary(context.computeLibraryElement(source));
100 }
101
102 void compileLibrary(LibraryElement library) {
103 if (!_compiled.add(library)) return;
104 if (!options.checkSdk && library.source.uri.scheme == 'dart') return;
105
106 // TODO(jmesserly): in incremental mode, we can skip the transitive
107 // compile of imports/exports.
108 library.importedLibraries.forEach(compileLibrary);
109 library.exportedLibraries.forEach(compileLibrary);
110
111 var unitElements = [library.definingCompilationUnit]..addAll(library.parts);
112 var units = <CompilationUnit>[];
113
114 bool failureInLib = false;
115 for (var element in unitElements) {
116 var unit = context.resolveCompilationUnit(element.source, library);
117 units.add(unit);
118 failureInLib = logErrors(element.source) || failureInLib;
119 checker.visitCompilationUnit(unit);
120 if (checker.failure) failureInLib = true;
121 }
122
123 if (failureInLib) {
124 _failure = true;
125 if (!options.codegenOptions.forceCompile) return;
126 }
127
128 if (_jsGen != null) {
129 var unit = units.first;
130 var parts = units.skip(1).toList();
131
132 // TODO(jmesserly): this hack is to avoid compiling the same compilation
133 // unit to JS twice. We mutate the AST, so it's not safe to run more than
134 // once on the same unit.
135 if (unit.getProperty(_propertyName) == true) return;
136 unit.setProperty(_propertyName, true);
137
138 _jsGen.generateLibrary(new LibraryUnit(unit, parts));
139 }
140 }
141
142 static const String _propertyName = 'dev_compiler.BatchCompiler.isCompiled';
143
144 void compileHtml(Source source) {
145 // TODO(jmesserly): reuse DartScriptsTask instead of copy/paste.
146 var contents = context.getContents(source);
147 var document = html.parse(contents.data, generateSpans: true);
148 var scripts = document.querySelectorAll('script[type="application/dart"]');
149
150 var loadedLibs = new LinkedHashSet<Uri>();
151
152 for (var script in scripts) {
153 Source scriptSource = null;
154 var srcAttr = script.attributes['src'];
155 if (srcAttr == null) {
156 if (script.hasContent()) {
157 var fragments = <ScriptFragment>[];
158 for (var node in script.nodes) {
159 if (node is html.Text) {
160 var start = node.sourceSpan.start;
161 fragments.add(new ScriptFragment(
162 start.offset, start.line, start.column, node.data));
163 }
164 }
165 scriptSource = new DartScript(source, fragments);
166 }
167 } else if (AnalysisEngine.isDartFileName(srcAttr)) {
168 scriptSource = context.sourceFactory.resolveUri(source, srcAttr);
169 }
170
171 if (scriptSource != null) {
172 var lib = context.computeLibraryElement(scriptSource);
173 compileLibrary(lib);
174 script.replaceWith(_linkLibraries(lib, loadedLibs));
175 }
176 }
177
178 // TODO(jmesserly): we need to clean this up so we aren't treating these
179 // as a special case.
180 for (var file in defaultRuntimeFiles) {
181 var input = path.join(options.runtimeDir, file);
182 var output = path.join(outputDir, runtimeFileOutput(file));
183 new Directory(path.dirname(output)).createSync(recursive: true);
184 new File(input).copySync(output);
185 }
186
187 new File(getOutputPath(source.uri)).openSync(mode: FileMode.WRITE)
188 ..writeStringSync(document.outerHtml)
189 ..writeStringSync('\n')
190 ..closeSync();
191 }
192
193 html.DocumentFragment _linkLibraries(
194 LibraryElement mainLib, LinkedHashSet<Uri> loaded) {
195 var alreadyLoaded = loaded.length;
196 _collectLibraries(mainLib, loaded);
197
198 var newLibs = loaded.skip(alreadyLoaded);
199 var df = new html.DocumentFragment();
200 for (var path in defaultRuntimeFiles) {
201 df.append(html_codegen.libraryInclude(runtimeFileOutput(path)));
202 }
203 for (var uri in newLibs) {
204 if (uri.scheme == 'dart') continue;
205 df.append(html_codegen.libraryInclude(getModulePath(uri)));
206 }
207 df.append(html_codegen.invokeMain(getModuleName(mainLib.source.uri)));
208 return df;
209 }
210
211 void _collectLibraries(LibraryElement lib, LinkedHashSet<Uri> loaded) {
212 var uri = lib.source.uri;
213 if (!loaded.add(uri)) return;
214 for (var l in lib.importedLibraries) _collectLibraries(l, loaded);
215 for (var l in lib.exportedLibraries) _collectLibraries(l, loaded);
216 // Move the item to the end of the list.
217 loaded.remove(uri);
218 loaded.add(uri);
219 }
220
221 String runtimeFileOutput(String file) =>
222 path.join('dev_compiler', 'runtime', file);
223 }
224
225 abstract class AbstractCompiler {
226 final CompilerOptions options;
227 final AnalysisContext context;
228 final CodeChecker checker;
229
230 AbstractCompiler(AnalysisContext context, CompilerOptions options,
231 [AnalysisErrorListener reporter])
232 : context = context,
233 options = options,
234 checker = createChecker(context.typeProvider, options.strongOptions,
235 reporter == null ? AnalysisErrorListener.NULL_LISTENER : reporter) {
236 enableDevCompilerInference(context, options.strongOptions);
237 }
238
239 static CodeChecker createChecker(TypeProvider typeProvider,
240 StrongModeOptions options, AnalysisErrorListener reporter) {
241 return new CodeChecker(
242 new RestrictedRules(typeProvider, options: options), reporter, options);
243 }
244
245 String get outputDir => options.codegenOptions.outputDir;
246 TypeRules get rules => checker.rules;
247 AnalysisErrorListener get reporter => checker.reporter;
248
249 Uri stringToUri(String uriString) {
250 var uri = uriString.startsWith('dart:') || uriString.startsWith('package:')
251 ? Uri.parse(uriString)
252 : new Uri.file(uriString);
253 return uri;
254 }
255
256 /// Directory presumed to be the common prefix for all input file:// URIs.
257 /// Used when computing output paths.
258 ///
259 /// For example:
260 /// dartdevc -o out foo/a.dart bar/b.dart
261 ///
262 /// Will produce:
263 /// out/foo/a.dart
264 /// out/bar/b.dart
265 ///
266 /// This is only used if at least one of [options.codegenOptions.inputs] is
267 /// a file URI.
268 // TODO(jmesserly): do we need an option for this?
vsm 2015/07/16 23:56:39 Might be nice at some point, but easy enough to ad
269 // Other ideas: we could look up and see what package the file is in, treat
270 // that as a base path. We could also use the current working directory as
271 // the base.
272 String get inputBaseDir {
273 if (_fileUriCommonBaseDir == null) {
274 List<String> common = null;
275 for (var uri in options.inputs.map(stringToUri)) {
276 if (uri.scheme != 'file') continue;
277
278 var segments = path.split(path.dirname(uri.path));
279 if (common == null) {
280 common = segments;
281 } else {
282 int len = math.min(common.length, segments.length);
283 while (len > 0 && common[len - 1] != segments[len - 1]) {
284 len--;
285 }
286 common.length = len;
287 }
288 }
289 _fileUriCommonBaseDir = common == null ? '' : path.joinAll(common);
290 }
291 return _fileUriCommonBaseDir;
292 }
293 String _fileUriCommonBaseDir;
294
295 String getOutputPath(Uri uri) => path.join(outputDir, getModulePath(uri));
296
297 /// Like [getModuleName] but includes the file extension, either .js or .html.
298 String getModulePath(Uri uri) {
299 var ext = path.extension(uri.path);
300 if (ext == '.dart' || ext == '' && uri.scheme == 'dart') ext = '.js';
301 return getModuleName(uri) + ext;
302 }
303
304 /// Gets the module name, without extension. For example:
305 ///
306 /// * dart:core -> dart/core
307 /// * file:foo/bar/baz.dart -> foo/bar/baz
308 /// * package:qux/qux.dart -> qux/qux
vsm 2015/07/16 23:56:39 at some point, we might want to prefix the package
309 ///
310 /// For file: URLs this will also make them relative to [inputBaseDir].
311 String getModuleName(Uri uri) {
312 var filepath = path.withoutExtension(uri.path);
313 if (uri.scheme == 'dart') {
314 filepath = 'dart/$filepath';
315 } else if (uri.scheme == 'file') {
316 filepath = path.relative(filepath, from: inputBaseDir);
317 } else {
318 assert(uri.scheme == 'package');
319 // filepath is good here, we want the output to start with a directory
320 // matching the package name.
321 }
322 return filepath;
323 }
324
325 /// Log any errors encountered when resolving [source] and return whether any
326 /// errors were found.
327 bool logErrors(Source source) {
328 List<AnalysisError> errors = context.computeErrors(source);
329 bool failure = false;
330 if (errors.isNotEmpty) {
331 for (var error in errors) {
332 // Always skip TODOs.
333 if (error.errorCode.type == ErrorType.TODO) continue;
334
335 // Skip hints for now. In the future these could be turned on via flags.
336 if (error.errorCode.errorSeverity.ordinal <
337 ErrorSeverity.WARNING.ordinal) {
338 continue;
339 }
340
341 // All analyzer warnings or errors are errors for DDC.
342 failure = true;
343 reporter.onError(error);
344 }
345 }
346 return failure;
347 }
348 }
349
350 AnalysisErrorListener createErrorReporter(
351 AnalysisContext context, CompilerOptions options) {
352 return options.dumpInfo
353 ? new SummaryReporter(context, options.logLevel)
354 : new LogReporter(context, useColors: options.useColors);
355 }
356
357 // TODO(jmesserly): find a better home for these.
358 /// Curated order to minimize lazy classes needed by dart:core and its
359 /// transitive SDK imports.
360 const corelibOrder = const [
361 'dart.core',
362 'dart.collection',
363 'dart._internal',
364 'dart.math',
365 'dart._interceptors',
366 'dart.async',
367 'dart._foreign_helper',
368 'dart._js_embedded_names',
369 'dart._js_helper',
370 'dart.isolate',
371 'dart.typed_data',
372 'dart._native_typed_data',
373 'dart._isolate_helper',
374 'dart._js_primitives',
375 'dart.convert',
376 'dart.mirrors',
377 'dart._js_mirrors',
378 'dart.js'
379 // _foreign_helper is not included, as it only defines the JS builtin that
380 // the compiler handles at compile time.
381 ];
382
383 /// Runtime files added to all applications when running the compiler in the
384 /// command line.
385 final defaultRuntimeFiles = () {
386 var files = [
387 'harmony_feature_check.js',
388 'dart_utils.js',
389 'dart_library.js',
390 '_errors.js',
391 '_types.js',
392 '_rtti.js',
393 '_classes.js',
394 '_operations.js',
395 'dart_runtime.js',
396 ];
397 files.addAll(corelibOrder.map((l) => l.replaceAll('.', '/') + '.js'));
398 return files;
399 }();
400
401 final _log = new Logger('dev_compiler.src.compiler');
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698