| OLD | NEW |
| (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'); | |
| OLD | NEW |