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

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

Issue 1879373004: Implement modular compilation (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 8 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
« no previous file with comments | « lib/src/codegen/side_effect_analysis.dart ('k') | lib/src/compiler/ast_builder.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
7 import 'dart:async';
8 import 'dart:collection';
9 import 'dart:math' as math;
10 import 'dart:io';
11
12 import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit;
13 import 'package:analyzer/dart/element/element.dart';
14 import 'package:analyzer/src/generated/engine.dart'
15 show AnalysisEngine, AnalysisContext, ChangeSet, ParseDartTask;
16 import 'package:analyzer/src/generated/error.dart'
17 show AnalysisError, ErrorSeverity, ErrorType;
18 import 'package:analyzer/src/generated/error.dart';
19 import 'package:analyzer/src/generated/source.dart' show Source;
20 import 'package:analyzer/src/task/html.dart';
21 import 'package:html/dom.dart' as html;
22 import 'package:html/parser.dart' as html;
23 import 'package:logging/logging.dart' show Level, Logger, LogRecord;
24 import 'package:path/path.dart' as path;
25
26 import 'analysis_context.dart';
27 import 'codegen/html_codegen.dart' as html_codegen;
28 import 'codegen/js_codegen.dart';
29 import 'options.dart';
30 import 'report.dart';
31 import 'utils.dart' show FileSystem, isStrongModeError;
32
33 /// Sets up the type checker logger to print a span that highlights error
34 /// messages.
35 StreamSubscription setupLogger(Level level, printFn) {
36 Logger.root.level = level;
37 return Logger.root.onRecord.listen((LogRecord rec) {
38 printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
39 });
40 }
41
42 CompilerOptions validateOptions(List<String> args, {bool forceOutDir: false}) {
43 var options = parseOptions(args, forceOutDir: forceOutDir);
44 if (!options.help && !options.version) {
45 var srcOpts = options.sourceOptions;
46 if (!srcOpts.useMockSdk && srcOpts.dartSdkPath == null) {
47 print('Could not automatically find dart sdk path.');
48 print('Please pass in explicitly: --dart-sdk <path>');
49 exit(1);
50 }
51 if (options.inputs.length == 0) {
52 print('Expected filename.');
53 return null;
54 }
55 }
56 return options;
57 }
58
59 /// Compile with the given options and return success or failure.
60 bool compile(CompilerOptions options) {
61 var context = createAnalysisContextWithSources(options.sourceOptions);
62 var reporter = new LogReporter(context, useColors: options.useColors);
63 return new BatchCompiler(context, options, reporter: reporter).run();
64 }
65
66 // Callback on each individual compiled library
67 typedef void CompilationNotifier(String path);
68
69 class BatchCompiler extends AbstractCompiler {
70 JSGenerator _jsGen;
71 LibraryElement _dartCore;
72 String _runtimeOutputDir;
73
74 /// Already compiled sources, so we don't check or compile them again.
75 final _compilationRecord = <LibraryElement, bool>{};
76 bool _sdkCopied = false;
77
78 bool _failure = false;
79 bool get failure => _failure;
80
81 final _pendingLibraries = <List<CompilationUnit>>[];
82
83 BatchCompiler(AnalysisContext context, CompilerOptions options,
84 {AnalysisErrorListener reporter,
85 FileSystem fileSystem: const FileSystem()})
86 : super(
87 context,
88 options,
89 new ErrorCollector(
90 context, reporter ?? AnalysisErrorListener.NULL_LISTENER),
91 fileSystem) {
92 _inputBaseDir = options.inputBaseDir;
93 if (outputDir != null) {
94 _jsGen = new JSGenerator(this);
95 _runtimeOutputDir = path.join(outputDir, 'dev_compiler', 'runtime');
96 }
97 _dartCore = context.typeProvider.objectType.element.library;
98 }
99
100 ErrorCollector get reporter => super.reporter;
101
102 /// Compiles every file in [options.inputs].
103 /// Returns true on successful compile.
104 bool run() {
105 var clock = new Stopwatch()..start();
106 options.inputs.forEach(compileFromUriString);
107 clock.stop();
108 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
109 _log.fine('Compiled ${_compilationRecord.length} libraries in ${time} s\n');
110
111 return !_failure;
112 }
113
114 void compileFromUriString(String uriString, [CompilationNotifier notifier]) {
115 _compileFromUri(stringToUri(uriString), notifier);
116 }
117
118 void _compileFromUri(Uri uri, CompilationNotifier notifier) {
119 _failure = false;
120 if (!uri.isAbsolute) {
121 throw new ArgumentError.value('$uri', 'uri', 'must be absolute');
122 }
123 var source = context.sourceFactory.forUri(Uri.encodeFull('$uri'));
124 if (source == null) {
125 throw new ArgumentError.value('$uri', 'uri', 'could not find source for');
126 }
127 _compileSource(source, notifier);
128 }
129
130 void _compileSource(Source source, CompilationNotifier notifier) {
131 if (AnalysisEngine.isHtmlFileName(source.uri.path)) {
132 _compileHtml(source, notifier);
133 } else {
134 _compileLibrary(context.computeLibraryElement(source), notifier);
135 }
136 _processPending();
137 reporter.flush();
138 }
139
140 void _processPending() {
141 // _pendingLibraries was recorded in post-order. Process from the end
142 // to ensure reverse post-order. This will ensure that we handle back
143 // edges from the original depth-first search correctly.
144
145 while (_pendingLibraries.isNotEmpty) {
146 var unit = _pendingLibraries.removeLast();
147 var library = unit.first.element.library;
148 assert(_compilationRecord[library] == true ||
149 options.codegenOptions.forceCompile);
150
151 // Process dependencies one more time to propagate failure from cycles
152 for (var import in library.imports) {
153 if (!_compilationRecord[import.importedLibrary]) {
154 _compilationRecord[library] = false;
155 }
156 }
157 for (var export in library.exports) {
158 if (!_compilationRecord[export.exportedLibrary]) {
159 _compilationRecord[library] = false;
160 }
161 }
162
163 // Generate code if still valid
164 if (_jsGen != null &&
165 (_compilationRecord[library] ||
166 options.codegenOptions.forceCompile)) {
167 _jsGen.generateLibrary(unit);
168 }
169 }
170 }
171
172 bool _compileLibrary(LibraryElement library, CompilationNotifier notifier) {
173 var success = _compilationRecord[library];
174 if (success != null) {
175 if (!success) _failure = true;
176 return success;
177 }
178
179 // Optimistically mark a library valid until proven otherwise
180 _compilationRecord[library] = true;
181
182 if (!options.checkSdk && library.source.isInSystemLibrary) {
183 // We assume the Dart SDK is always valid
184 if (_jsGen != null) _copyDartRuntime();
185 return true;
186 }
187
188 // Check dependences to determine if this library type checks
189 // TODO(jmesserly): in incremental mode, we can skip the transitive
190 // compile of imports/exports.
191 _compileLibrary(_dartCore, notifier); // implicit dart:core dependency
192 for (var import in library.imports) {
193 if (!_compileLibrary(import.importedLibrary, notifier)) {
194 _compilationRecord[library] = false;
195 }
196 }
197 for (var export in library.exports) {
198 if (!_compileLibrary(export.exportedLibrary, notifier)) {
199 _compilationRecord[library] = false;
200 }
201 }
202
203 // Check this library's own code
204 var unitElements = new List.from(library.parts)
205 ..add(library.definingCompilationUnit);
206 var units = <CompilationUnit>[];
207
208 bool failureInLib = false;
209 for (var element in unitElements) {
210 var unit = context.resolveCompilationUnit(element.source, library);
211 units.add(unit);
212 failureInLib = computeErrors(element.source) || failureInLib;
213 }
214 if (failureInLib) _compilationRecord[library] = false;
215
216 // Notifier framework if requested
217 if (notifier != null) {
218 reporter.flush();
219 notifier(getOutputPath(library.source.uri));
220 }
221
222 // Record valid libraries for further dependence checking (cycles) and
223 // codegen.
224
225 // TODO(vsm): Restructure this to not delay code generation more than
226 // necessary. We'd like to process the AST before there is any chance
227 // it's cached out. We should refactor common logic in
228 // server/dependency_graph and perhaps the analyzer itself.
229 success = _compilationRecord[library];
230 if (success || options.codegenOptions.forceCompile) {
231 _pendingLibraries.add(units);
232 }
233
234 // Return tentative success status.
235 if (!success) _failure = true;
236 return success;
237 }
238
239 void _copyDartRuntime() {
240 if (_sdkCopied) return;
241 _sdkCopied = true;
242 for (var file in defaultRuntimeFiles) {
243 var input = new File(path.join(options.runtimeDir, file));
244 var output = new File(path.join(_runtimeOutputDir, file));
245 if (output.existsSync() &&
246 output.lastModifiedSync() == input.lastModifiedSync()) {
247 continue;
248 }
249 fileSystem.copySync(input.path, output.path);
250 }
251 }
252
253 void _compileHtml(Source source, CompilationNotifier notifier) {
254 // TODO(jmesserly): reuse DartScriptsTask instead of copy/paste.
255 var contents = context.getContents(source);
256 var document = html.parse(contents.data, generateSpans: true);
257 var scripts = document.querySelectorAll('script[type="application/dart"]');
258
259 var loadedLibs = new LinkedHashSet<Uri>();
260
261 // If we're generating code, convert the HTML file as well.
262 // Otherwise, just search for Dart sources to analyze.
263 var htmlOutDir =
264 _jsGen != null ? path.dirname(getOutputPath(source.uri)) : null;
265 for (var script in scripts) {
266 Source scriptSource = null;
267 var srcAttr = script.attributes['src'];
268 if (srcAttr == null) {
269 if (script.hasContent()) {
270 var fragments = <ScriptFragment>[];
271 for (var node in script.nodes) {
272 if (node is html.Text) {
273 var start = node.sourceSpan.start;
274 fragments.add(new ScriptFragment(
275 start.offset, start.line, start.column, node.data));
276 }
277 }
278 scriptSource = new DartScript(source, fragments);
279 }
280 } else if (AnalysisEngine.isDartFileName(srcAttr)) {
281 scriptSource = context.sourceFactory.resolveUri(source, srcAttr);
282 }
283
284 if (scriptSource != null) {
285 var lib = context.computeLibraryElement(scriptSource);
286 _compileLibrary(lib, notifier);
287 if (htmlOutDir != null) {
288 script.replaceWith(_linkLibraries(lib, loadedLibs, from: htmlOutDir));
289 }
290 }
291 }
292
293 if (htmlOutDir != null) {
294 fileSystem.writeAsStringSync(
295 getOutputPath(source.uri), document.outerHtml + '\n');
296 }
297 }
298
299 html.DocumentFragment _linkLibraries(
300 LibraryElement mainLib, LinkedHashSet<Uri> loaded,
301 {String from}) {
302 assert(from != null);
303 var alreadyLoaded = loaded.length;
304 _collectLibraries(mainLib, loaded);
305
306 var newLibs = loaded.skip(alreadyLoaded);
307 var df = new html.DocumentFragment();
308
309 for (var uri in newLibs) {
310 if (uri.scheme == 'dart') {
311 if (uri.path == 'core') {
312 // TODO(jmesserly): it would be nice to not special case these.
313 for (var file in defaultRuntimeFiles) {
314 file = path.join(_runtimeOutputDir, file);
315 df.append(
316 html_codegen.libraryInclude(path.relative(file, from: from)));
317 }
318 }
319 } else {
320 var file = path.join(outputDir, getModulePath(uri));
321 df.append(html_codegen.libraryInclude(path.relative(file, from: from)));
322 }
323 }
324
325 df.append(html_codegen.invokeMain(getModuleName(mainLib.source.uri)));
326 return df;
327 }
328
329 void _collectLibraries(LibraryElement lib, LinkedHashSet<Uri> loaded) {
330 var uri = lib.source.uri;
331 if (!loaded.add(uri)) return;
332 _collectLibraries(_dartCore, loaded);
333
334 for (var l in lib.imports) _collectLibraries(l.importedLibrary, loaded);
335 for (var l in lib.exports) _collectLibraries(l.exportedLibrary, loaded);
336 // Move the item to the end of the list.
337 loaded.remove(uri);
338 loaded.add(uri);
339 }
340 }
341
342 abstract class AbstractCompiler {
343 final CompilerOptions options;
344 final AnalysisContext context;
345 final AnalysisErrorListener reporter;
346 final FileSystem fileSystem;
347
348 AbstractCompiler(this.context, this.options,
349 [AnalysisErrorListener listener, this.fileSystem = const FileSystem()])
350 : reporter = listener ?? AnalysisErrorListener.NULL_LISTENER;
351
352 String get outputDir => options.codegenOptions.outputDir;
353
354 Uri stringToUri(String uriString) {
355 var uri = uriString.startsWith('dart:') || uriString.startsWith('package:')
356 ? Uri.parse(uriString)
357 : new Uri.file(path.absolute(uriString));
358 return uri;
359 }
360
361 /// Directory presumed to be the common prefix for all input file:// URIs.
362 /// Used when computing output paths.
363 ///
364 /// For example:
365 /// dartdevc -o out foo/a.dart bar/b.dart
366 ///
367 /// Will produce:
368 /// out/foo/a.dart
369 /// out/bar/b.dart
370 ///
371 /// This is only used if at least one of [options.codegenOptions.inputs] is
372 /// a file URI.
373 // TODO(jmesserly): do we need an option for this?
374 // Other ideas: we could look up and see what package the file is in, treat
375 // that as a base path. We could also use the current working directory as
376 // the base.
377 String get inputBaseDir {
378 if (_inputBaseDir == null) {
379 List<String> common = null;
380 for (var uri in options.inputs.map(stringToUri)) {
381 if (uri.scheme != 'file') continue;
382
383 var segments = path.split(path.dirname(uri.path));
384 if (common == null) {
385 common = segments;
386 } else {
387 int len = math.min(common.length, segments.length);
388 while (len > 0 && common[len - 1] != segments[len - 1]) {
389 len--;
390 }
391 common.length = len;
392 }
393 }
394 _inputBaseDir = common == null ? '' : path.joinAll(common);
395 }
396 return _inputBaseDir;
397 }
398
399 String _inputBaseDir;
400
401 String getOutputPath(Uri uri) => path.join(outputDir, getModulePath(uri));
402
403 /// Like [getModuleName] but includes the file extension, either .js or .html.
404 String getModulePath(Uri uri) {
405 var ext = path.extension(uri.path);
406 if (ext == '.dart' || ext == '' && uri.scheme == 'dart') ext = '.js';
407 return getModuleName(uri) + ext;
408 }
409
410 /// Gets the module name, without extension. For example:
411 ///
412 /// * dart:core -> dart/core
413 /// * file:foo/bar/baz.dart -> foo/bar/baz
414 /// * package:qux/qux.dart -> qux/qux
415 ///
416 /// For file: URLs this will also make them relative to [inputBaseDir].
417 // TODO(jmesserly): we need to figure out a way to keep package and file URLs
418 // from conflicting.
419 String getModuleName(Uri uri) {
420 var filepath = path.withoutExtension(uri.path);
421 if (uri.scheme == 'dart') {
422 return 'dart/$filepath';
423 } else if (uri.scheme == 'file') {
424 return path.relative(filepath, from: inputBaseDir);
425 } else {
426 assert(uri.scheme == 'package');
427 // filepath is good here, we want the output to start with a directory
428 // matching the package name.
429 return filepath;
430 }
431 }
432
433 /// Log any errors encountered when resolving [source] and return whether any
434 /// errors were found.
435 bool computeErrors(Source source) {
436 AnalysisContext errorContext = context;
437 // TODO(jmesserly): should this be a fix somewhere in analyzer?
438 // otherwise we fail to find the parts.
439 if (source.isInSystemLibrary) {
440 errorContext = context.sourceFactory.dartSdk.context;
441 }
442 List<AnalysisError> errors = errorContext.computeErrors(source);
443 bool failure = false;
444 for (var error in errors) {
445 // TODO(jmesserly): this is a very expensive lookup, and it has to be
446 // repeated every time we want to query error severity.
447 var severity = errorSeverity(errorContext, error);
448 if (severity == ErrorSeverity.ERROR) {
449 reporter.onError(error);
450 failure = true;
451 } else if (severity == ErrorSeverity.WARNING) {
452 reporter.onError(error);
453 }
454 }
455 return failure;
456 }
457 }
458
459 // TODO(jmesserly): find a better home for these.
460 /// Curated order to minimize lazy classes needed by dart:core and its
461 /// transitive SDK imports.
462 final corelibOrder = [
463 'dart:core',
464 'dart:collection',
465 'dart:_internal',
466 'dart:math',
467 'dart:_interceptors',
468 'dart:async',
469 'dart:_foreign_helper',
470 'dart:_js_embedded_names',
471 'dart:_js_helper',
472 'dart:isolate',
473 'dart:typed_data',
474 'dart:_native_typed_data',
475 'dart:_isolate_helper',
476 'dart:_js_primitives',
477 'dart:convert',
478 // TODO(jmesserly): these are not part of corelib library cycle, and shouldn't
479 // be listed here. Instead, their source should be copied on demand if they
480 // are actually used by the application.
481 'dart:mirrors',
482 'dart:_js_mirrors',
483 'dart:js',
484 'dart:_metadata',
485 'dart:html',
486 'dart:html_common',
487 'dart:indexed_db',
488 'dart:svg',
489 'dart:web_audio',
490 'dart:web_gl',
491 'dart:web_sql',
492 'dart:_debugger'
493
494 // _foreign_helper is not included, as it only defines the JS builtin that
495 // the compiler handles at compile time.
496 ].map(Uri.parse).toList();
497
498 /// Runtime files added to all applications when running the compiler in the
499 /// command line.
500 final defaultRuntimeFiles = () {
501 String coreToFile(Uri uri) => uri.toString().replaceAll(':', '/') + '.js';
502
503 var files = [
504 'harmony_feature_check.js',
505 'dart_library.js',
506 'dart/_runtime.js',
507 ];
508 files.addAll(corelibOrder.map(coreToFile));
509 return files;
510 }();
511
512 final _log = new Logger('dev_compiler.src.compiler');
OLDNEW
« no previous file with comments | « lib/src/codegen/side_effect_analysis.dart ('k') | lib/src/compiler/ast_builder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698