blob: c951790dcb67c7c178c15d721f4cf79d256caf29 [file] [log] [blame]
// 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.';