| // Copyright (c) 2013, 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. |
| |
| /// A library for compiling Dart code and manipulating analyzer parse trees. |
| library pub.dart; |
| |
| import 'dart:async'; |
| import 'dart:isolate'; |
| |
| import 'package:analyzer_experimental/analyzer.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:stack_trace/stack_trace.dart'; |
| import '../../../compiler/compiler.dart' as compiler; |
| import '../../../compiler/implementation/source_file_provider.dart' |
| show FormattingDiagnosticHandler, CompilerSourceFileProvider; |
| import '../../../compiler/implementation/filenames.dart' |
| show appendSlash; |
| |
| import 'io.dart'; |
| import 'sdk.dart' as sdk; |
| import 'utils.dart'; |
| |
| /// Returns [entrypoint] compiled to JavaScript (or to Dart if [toDart] is |
| /// true). |
| /// |
| /// By default, the package root is assumed to be adjacent to [entrypoint], but |
| /// if [packageRoot] is passed that will be used instead. |
| /// |
| /// If [inputProvider] and [diagnosticHandler] are omitted, uses a default |
| /// [compiler.CompilerInputProvider] that loads directly from the filesystem. |
| /// If either is provided, both must be. |
| Future<String> compile(String entrypoint, {String packageRoot, |
| bool toDart: false, compiler.CompilerInputProvider inputProvider, |
| compiler.DiagnosticHandler diagnosticHandler}) { |
| return new Future.sync(() { |
| var options = <String>['--categories=Client,Server', '--minify']; |
| if (toDart) options.add('--output-type=dart'); |
| if (packageRoot == null) { |
| packageRoot = path.join(path.dirname(entrypoint), 'packages'); |
| } |
| |
| // Must either pass both of these or neither. |
| if ((inputProvider == null) != (diagnosticHandler == null)) { |
| throw new ArgumentError("If either inputProvider or diagnosticHandler " |
| "is passed, then both must be."); |
| } |
| |
| if (inputProvider == null) { |
| var provider = new CompilerSourceFileProvider(); |
| inputProvider = provider.readStringFromUri; |
| diagnosticHandler = new FormattingDiagnosticHandler(provider) |
| .diagnosticHandler; |
| } |
| |
| return compiler.compile( |
| path.toUri(entrypoint), |
| path.toUri(appendSlash(_libPath)), |
| path.toUri(appendSlash(packageRoot)), |
| inputProvider, diagnosticHandler, options); |
| }); |
| } |
| |
| /// Returns the path to the directory containing the Dart core libraries. |
| /// |
| /// This corresponds to the "sdk" directory in the repo and to the root of the |
| /// compiled SDK. |
| String get _libPath { |
| if (runningFromSdk) return sdk.rootDirectory; |
| return path.join(repoRoot, 'sdk'); |
| } |
| |
| /// Returns whether [dart] looks like an entrypoint file. |
| bool isEntrypoint(CompilationUnit dart) { |
| // TODO(nweiz): this misses the case where a Dart file doesn't contain main(), |
| // but it parts in another file that does. |
| return dart.declarations.any((node) { |
| return node is FunctionDeclaration && node.name.name == "main" && |
| node.functionExpression.parameters.parameters.isEmpty; |
| }); |
| } |
| |
| /// Runs [code] in an isolate. |
| /// |
| /// [code] should be the contents of a Dart entrypoint. It may contain imports; |
| /// they will be resolved in the same context as the host isolate. |
| /// |
| /// Returns a Future that will resolve to a [SendPort] that will communicate to |
| /// the spawned isolate once it's spawned. If the isolate fails to spawn, the |
| /// Future will complete with an error. |
| Future<SendPort> runInIsolate(String code) { |
| return withTempDir((dir) { |
| var dartPath = path.join(dir, 'runInIsolate.dart'); |
| writeTextFile(dartPath, code, dontLogContents: true); |
| var bufferPort = spawnFunction(_isolateBuffer); |
| return bufferPort.call(path.toUri(dartPath).toString()).then((response) { |
| if (response.first == 'error') { |
| return new Future.error( |
| new CrossIsolateException.deserialize(response.last)); |
| } |
| |
| return response.last; |
| }); |
| }); |
| } |
| |
| // TODO(nweiz): remove this when issue 12617 is fixed. |
| /// A function used as a buffer between the host isolate and [spawnUri]. |
| /// |
| /// [spawnUri] synchronously loads the file and its imports, which can deadlock |
| /// the host isolate if there's an HTTP import pointing at a server in the host. |
| /// Adding an additional isolate in the middle works around this. |
| void _isolateBuffer() { |
| port.receive((uri, replyTo) { |
| try { |
| replyTo.send(['success', spawnUri(uri)]); |
| } catch (e, stack) { |
| replyTo.send(['error', CrossIsolateException.serialize(e, stack)]); |
| } |
| }); |
| } |
| |
| /// An exception that was originally raised in another isolate. |
| /// |
| /// Exception objects can't cross isolate boundaries in general, so this class |
| /// wraps as much information as can be consistently serialized. |
| class CrossIsolateException implements Exception { |
| /// The name of the type of exception thrown. |
| /// |
| /// This is the return value of [error.runtimeType.toString()]. Keep in mind |
| /// that objects in different libraries may have the same type name. |
| final String type; |
| |
| /// The exception's message, or its [toString] if it didn't expose a `message` |
| /// property. |
| final String message; |
| |
| /// The exception's stack trace, or `null` if no stack trace was available. |
| final Trace stackTrace; |
| |
| /// Loads a [CrossIsolateException] from a serialized representation. |
| /// |
| /// [error] should be the result of [CrossIsolateException.serialize]. |
| factory CrossIsolateException.deserialize(Map error) { |
| var type = error['type']; |
| var message = error['message']; |
| var stackTrace = error['stack'] == null ? null : |
| new Trace.parse(error['stack']); |
| return new CrossIsolateException._(type, message, stackTrace); |
| } |
| |
| /// Loads a [CrossIsolateException] from a serialized representation. |
| /// |
| /// [error] should be the result of [CrossIsolateException.serialize]. |
| CrossIsolateException._(this.type, this.message, this.stackTrace); |
| |
| /// Serializes [error] to an object that can safely be passed across isolate |
| /// boundaries. |
| static Map serialize(error, [StackTrace stack]) { |
| if (stack == null) stack = getAttachedStackTrace(error); |
| return { |
| 'type': error.runtimeType.toString(), |
| 'message': getErrorMessage(error), |
| 'stack': stack == null ? null : stack.toString() |
| }; |
| } |
| |
| String toString() => "$message\n$stackTrace"; |
| } |