| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library leg_apiimpl; |
| |
| import 'dart:async'; |
| |
| import 'package:package_config/packages.dart'; |
| import 'package:package_config/packages_file.dart' as pkgs; |
| import 'package:package_config/src/packages_impl.dart' |
| show MapPackages, NonFilePackagesDirectoryPackages; |
| import 'package:package_config/src/util.dart' show checkValidPackageUri; |
| |
| import '../compiler_new.dart' as api; |
| import 'common/tasks.dart' show GenericTask, Measurer; |
| import 'common.dart'; |
| import 'compiler.dart'; |
| import 'diagnostics/messages.dart' show Message; |
| import 'elements/elements.dart' as elements; |
| import 'environment.dart'; |
| import 'library_loader.dart'; |
| import 'io/source_file.dart'; |
| import 'options.dart' show CompilerOptions; |
| import 'platform_configuration.dart' as platform_configuration; |
| import 'resolved_uri_translator.dart'; |
| import 'script.dart'; |
| |
| /// Implements the [Compiler] using a [api.CompilerInput] for supplying the |
| /// sources. |
| class CompilerImpl extends Compiler { |
| final Measurer measurer; |
| api.CompilerInput provider; |
| api.CompilerDiagnostics handler; |
| Packages packages; |
| |
| bool get mockableLibraryUsed => resolvedUriTranslator.isSet |
| ? resolvedUriTranslator.mockableLibraryUsed |
| : false; |
| |
| ForwardingResolvedUriTranslator resolvedUriTranslator; |
| |
| GenericTask userHandlerTask; |
| GenericTask userProviderTask; |
| GenericTask userPackagesDiscoveryTask; |
| |
| Uri get libraryRoot => options.platformConfigUri.resolve("."); |
| |
| CompilerImpl(this.provider, api.CompilerOutput outputProvider, this.handler, |
| CompilerOptions options, |
| {MakeReporterFunction makeReporter}) |
| // NOTE: allocating measurer is done upfront to ensure the wallclock is |
| // started before other computations. |
| : measurer = new Measurer(enableTaskMeasurements: options.verbose), |
| resolvedUriTranslator = new ForwardingResolvedUriTranslator(), |
| super( |
| options: options, |
| outputProvider: outputProvider, |
| environment: new _Environment(options.environment), |
| makeReporter: makeReporter) { |
| _Environment env = environment; |
| env.compiler = this; |
| tasks.addAll([ |
| userHandlerTask = new GenericTask('Diagnostic handler', measurer), |
| userProviderTask = new GenericTask('Input provider', measurer), |
| userPackagesDiscoveryTask = |
| new GenericTask('Package discovery', measurer), |
| ]); |
| } |
| |
| void log(message) { |
| callUserHandler( |
| null, null, null, null, message, api.Diagnostic.VERBOSE_INFO); |
| } |
| |
| /// Report [exception] reading [uri]. Use [element] and [node] to compute |
| /// the error location. |
| void _reportReadError( |
| Uri uri, elements.Element element, Spannable node, exception) { |
| if (element == null || node == null) { |
| reporter.reportErrorMessage(new SourceSpan(uri, 0, 0), |
| MessageKind.READ_SELF_ERROR, {'uri': uri, 'exception': exception}); |
| } else { |
| reporter.withCurrentElement(element, () { |
| reporter.reportErrorMessage(node, MessageKind.READ_URI_ERROR, |
| {'uri': uri, 'exception': exception}); |
| }); |
| } |
| } |
| |
| /** |
| * Reads the script designated by [readableUri]. |
| */ |
| Future<Script> readScript(Uri readableUri, [Spannable node]) { |
| if (!readableUri.isAbsolute) { |
| if (node == null) node = NO_LOCATION_SPANNABLE; |
| reporter.internalError( |
| node, 'Relative uri $readableUri provided to readScript(Uri).'); |
| } |
| |
| // We need to store the current element since we are reporting read errors |
| // asynchronously and therefore need to restore the current element for |
| // [node] to be valid. |
| elements.Element element = currentElement; |
| |
| Uri resourceUri = translateUri(node, readableUri); |
| if (resourceUri == null) return _synthesizeScript(readableUri); |
| if (resourceUri.scheme == 'dart-ext') { |
| if (!options.allowNativeExtensions) { |
| reporter.withCurrentElement(element, () { |
| reporter.reportErrorMessage(node, MessageKind.DART_EXT_NOT_SUPPORTED); |
| }); |
| } |
| return _synthesizeScript(readableUri); |
| } |
| |
| // TODO(johnniwinther): Wrap the result from [provider] in a specialized |
| // [Future] to ensure that we never execute an asynchronous action without |
| // setting up the current element of the compiler. |
| return new Future.sync( |
| () => callUserProvider(resourceUri, api.InputKind.utf8)) |
| .then((api.Input sourceFile) { |
| // We use [readableUri] as the URI for the script since need to preserve |
| // the scheme in the script because [Script.uri] is used for resolving |
| // relative URIs mentioned in the script. See the comment on |
| // [LibraryLoader] for more details. |
| return new Script(readableUri, resourceUri, sourceFile); |
| }).catchError((error) { |
| _reportReadError(readableUri, element, node, error); |
| return _synthesizeScript(readableUri); |
| }); |
| } |
| |
| Future<Script> _synthesizeScript(Uri readableUri) { |
| return new Future.value(new Script.synthetic(readableUri)); |
| } |
| |
| Future<Binary> readBinary(Uri resourceUri, [Spannable node]) { |
| if (!resourceUri.isAbsolute) { |
| if (node == null) node = NO_LOCATION_SPANNABLE; |
| reporter.internalError( |
| node, 'Relative uri $resourceUri provided to readBinary(Uri).'); |
| } |
| |
| // We need to store the current element since we are reporting read errors |
| // asynchronously and therefore need to restore the current element for |
| // [node] to be valid. |
| elements.Element element = currentElement; |
| |
| return new Future.sync( |
| () => callUserProvider(resourceUri, api.InputKind.binary)) |
| .catchError((error) { |
| _reportReadError(resourceUri, element, node, error); |
| return new Binary(resourceUri, null); |
| }); |
| } |
| |
| /** |
| * Translates a readable URI into a resource URI. |
| * |
| * See [LibraryLoader] for terminology on URIs. |
| */ |
| Uri translateUri(Spannable node, Uri uri) => |
| uri.scheme == 'package' ? translatePackageUri(node, uri) : uri; |
| |
| Uri translatePackageUri(Spannable node, Uri uri) { |
| try { |
| checkValidPackageUri(uri); |
| } on ArgumentError catch (e) { |
| reporter.reportErrorMessage(node, MessageKind.INVALID_PACKAGE_URI, |
| {'uri': uri, 'exception': e.message}); |
| return null; |
| } |
| return packages.resolve(uri, notFound: (Uri notFound) { |
| reporter.reportErrorMessage( |
| node, MessageKind.LIBRARY_NOT_FOUND, {'resolvedUri': uri}); |
| return null; |
| }); |
| } |
| |
| Future<elements.LibraryElement> analyzeUri(Uri uri, |
| {bool skipLibraryWithPartOfTag: true}) { |
| Future setupFuture = new Future.value(); |
| if (resolvedUriTranslator.isNotSet) { |
| setupFuture = setupFuture.then((_) => setupSdk()); |
| } |
| if (packages == null) { |
| setupFuture = setupFuture.then((_) => setupPackages(uri)); |
| } |
| return setupFuture.then((_) { |
| return super |
| .analyzeUri(uri, skipLibraryWithPartOfTag: skipLibraryWithPartOfTag); |
| }); |
| } |
| |
| Future setupPackages(Uri uri) { |
| if (options.packageRoot != null) { |
| // Use "non-file" packages because the file version requires a [Directory] |
| // and we can't depend on 'dart:io' classes. |
| packages = new NonFilePackagesDirectoryPackages(options.packageRoot); |
| } else if (options.packageConfig != null) { |
| Future<Binary> future = |
| callUserProvider(options.packageConfig, api.InputKind.binary); |
| return future.then((Binary binary) { |
| packages = |
| new MapPackages(pkgs.parse(binary.data, options.packageConfig)); |
| }).catchError((error) { |
| reporter.reportErrorMessage( |
| NO_LOCATION_SPANNABLE, |
| MessageKind.INVALID_PACKAGE_CONFIG, |
| {'uri': options.packageConfig, 'exception': error}); |
| packages = Packages.noPackages; |
| }); |
| } else { |
| if (options.packagesDiscoveryProvider == null) { |
| packages = Packages.noPackages; |
| } else { |
| return callUserPackagesDiscovery(uri).then((p) { |
| packages = p; |
| }); |
| } |
| } |
| return new Future.value(); |
| } |
| |
| Future<Null> setupSdk() { |
| Future future = new Future.value(null); |
| if (options.resolutionInputs != null) { |
| future = Future.forEach(options.resolutionInputs, (Uri resolutionInput) { |
| reporter.log('Reading serialized data from ${resolutionInput}'); |
| Future<SourceFile> future = |
| callUserProvider(resolutionInput, api.InputKind.utf8); |
| return future.then((SourceFile sourceFile) { |
| serialization.deserializeFromText( |
| resolutionInput, sourceFile.slowText()); |
| }); |
| }); |
| } |
| if (resolvedUriTranslator.isNotSet) { |
| future = future.then((_) { |
| return platform_configuration |
| .load(options.platformConfigUri, provider) |
| .then((Map<String, Uri> mapping) { |
| resolvedUriTranslator.resolvedUriTranslator = |
| new ResolvedUriTranslator( |
| mapping, reporter, options.platformConfigUri); |
| }); |
| }); |
| } |
| // TODO(johnniwinther): This does not apply anymore. |
| // The incremental compiler sets up the sdk before run. |
| // Therefore this will be called a second time. |
| return future; |
| } |
| |
| Future<bool> run(Uri uri) { |
| Duration setupDuration = measurer.wallClock.elapsed; |
| return selfTask.measureSubtask("CompilerImpl.run", () { |
| log('Using platform configuration at ${options.platformConfigUri}'); |
| |
| return setupSdk().then((_) => setupPackages(uri)).then((_) { |
| assert(resolvedUriTranslator.isSet); |
| assert(packages != null); |
| |
| return super.run(uri); |
| }).then((bool success) { |
| if (options.verbose) { |
| StringBuffer timings = new StringBuffer(); |
| computeTimings(setupDuration, timings); |
| log("$timings"); |
| } |
| return success; |
| }); |
| }); |
| } |
| |
| void computeTimings(Duration setupDuration, StringBuffer timings) { |
| timings.writeln("Timings:"); |
| Duration totalDuration = measurer.wallClock.elapsed; |
| Duration asyncDuration = measurer.asyncWallClock.elapsed; |
| Duration cumulatedDuration = Duration.ZERO; |
| for (final task in tasks) { |
| String running = task.isRunning ? "*" : ""; |
| Duration duration = task.duration; |
| if (duration != Duration.ZERO) { |
| cumulatedDuration += duration; |
| timings.writeln(' $running${task.name} took' |
| ' ${duration.inMilliseconds}msec'); |
| for (String subtask in task.subtasks) { |
| int subtime = task.getSubtaskTime(subtask); |
| String running = task.getSubtaskIsRunning(subtask) ? "*" : ""; |
| timings.writeln( |
| ' $running${task.name} > $subtask took ${subtime}msec'); |
| } |
| } |
| } |
| Duration unaccountedDuration = |
| totalDuration - cumulatedDuration - setupDuration - asyncDuration; |
| double percent = |
| unaccountedDuration.inMilliseconds * 100 / totalDuration.inMilliseconds; |
| timings.write(' Total compile-time ${totalDuration.inMilliseconds}msec;' |
| ' setup ${setupDuration.inMilliseconds}msec;' |
| ' async ${asyncDuration.inMilliseconds}msec;' |
| ' unaccounted ${unaccountedDuration.inMilliseconds}msec' |
| ' (${percent.toStringAsFixed(2)}%)'); |
| } |
| |
| void reportDiagnostic(DiagnosticMessage message, |
| List<DiagnosticMessage> infos, api.Diagnostic kind) { |
| _reportDiagnosticMessage(message, kind); |
| for (DiagnosticMessage info in infos) { |
| _reportDiagnosticMessage(info, api.Diagnostic.INFO); |
| } |
| } |
| |
| void _reportDiagnosticMessage( |
| DiagnosticMessage diagnosticMessage, api.Diagnostic kind) { |
| // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For |
| // instance in the [Types] constructor in typechecker.dart. |
| SourceSpan span = diagnosticMessage.sourceSpan; |
| Message message = diagnosticMessage.message; |
| if (span == null || span.uri == null) { |
| callUserHandler(message, null, null, null, '$message', kind); |
| } else { |
| callUserHandler( |
| message, span.uri, span.begin, span.end, '$message', kind); |
| } |
| } |
| |
| bool get isMockCompilation => |
| mockableLibraryUsed && options.allowMockCompilation; |
| |
| void callUserHandler(Message message, Uri uri, int begin, int end, |
| String text, api.Diagnostic kind) { |
| try { |
| userHandlerTask.measure(() { |
| handler.report(message, uri, begin, end, text, kind); |
| }); |
| } catch (ex, s) { |
| reportCrashInUserCode('Uncaught exception in diagnostic handler', ex, s); |
| rethrow; |
| } |
| } |
| |
| Future<api.Input> callUserProvider(Uri uri, api.InputKind inputKind) { |
| try { |
| return userProviderTask |
| .measureIo(() => provider.readFromUri(uri, inputKind: inputKind)); |
| } catch (ex, s) { |
| reportCrashInUserCode('Uncaught exception in input provider', ex, s); |
| rethrow; |
| } |
| } |
| |
| Future<Packages> callUserPackagesDiscovery(Uri uri) { |
| try { |
| return userPackagesDiscoveryTask |
| .measureIo(() => options.packagesDiscoveryProvider(uri)); |
| } catch (ex, s) { |
| reportCrashInUserCode('Uncaught exception in package discovery', ex, s); |
| rethrow; |
| } |
| } |
| |
| Uri resolvePatchUri(String libraryName) { |
| return LibraryLoaderTask.resolvePatchUri( |
| libraryName, options.platformConfigUri); |
| } |
| } |
| |
| class _Environment implements Environment { |
| final Map<String, String> definitions; |
| |
| // TODO(sigmund): break the circularity here: Compiler needs an environment to |
| // initialize the library loader, but the environment here needs to know about |
| // how the sdk is set up and about whether the backend supports mirrors. |
| CompilerImpl compiler; |
| |
| _Environment(this.definitions); |
| |
| String valueOf(String name) { |
| assert(compiler.resolvedUriTranslator != null, |
| failedAt(NO_LOCATION_SPANNABLE, "setupSdk() has not been run")); |
| |
| var result = definitions[name]; |
| if (result != null || definitions.containsKey(name)) return result; |
| if (!name.startsWith(_dartLibraryEnvironmentPrefix)) return null; |
| |
| String libraryName = name.substring(_dartLibraryEnvironmentPrefix.length); |
| |
| // Private libraries are not exposed to the users. |
| if (libraryName.startsWith("_")) return null; |
| |
| Uri libraryUri = compiler.resolvedUriTranslator.sdkLibraries[libraryName]; |
| if (libraryUri != null && libraryUri.scheme != "unsupported") { |
| // Dart2js always "supports" importing 'dart:mirrors' but will abort |
| // the compilation at a later point if the backend doesn't support |
| // mirrors. In this case 'mirrors' should not be in the environment. |
| if (libraryName == 'mirrors') { |
| return compiler.backend.supportsReflection ? "true" : null; |
| } |
| return "true"; |
| } |
| |
| // Note: we return null on `dart:io` here, even if we allow users to |
| // unconditionally import it. |
| // |
| // In the past it was invalid to import `dart:io` for client apps. We just |
| // made it valid to import it as a stopgap measure to support packages like |
| // `http`. This is temporary until we support config-imports in the |
| // language. |
| // |
| // Because it is meant to be temporary and because the returned `dart:io` |
| // implementation will throw on most APIs, we still preserve that |
| // when compiling client apps the `dart:io` library is technically not |
| // supported, and so `const bool.fromEnvironment(dart.library.io)` is false. |
| return null; |
| } |
| } |
| |
| /// For every 'dart:' library, a corresponding environment variable is set |
| /// to "true". The environment variable's name is the concatenation of |
| /// this prefix and the name (without the 'dart:'. |
| /// |
| /// For example 'dart:html' has the environment variable 'dart.library.html' set |
| /// to "true". |
| const String _dartLibraryEnvironmentPrefix = 'dart.library.'; |