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

Unified Diff: sdk/lib/_internal/pub/lib/src/barback/load_transformers.dart

Issue 23625002: Support loading transformer plugins from pub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes. Created 7 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/pub/lib/src/barback/load_transformers.dart
diff --git a/sdk/lib/_internal/pub/lib/src/barback/load_transformers.dart b/sdk/lib/_internal/pub/lib/src/barback/load_transformers.dart
new file mode 100644
index 0000000000000000000000000000000000000000..72634bc250a9b84c84b1d1cbae2cfbdbd684c3e9
--- /dev/null
+++ b/sdk/lib/_internal/pub/lib/src/barback/load_transformers.dart
@@ -0,0 +1,398 @@
+// 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.
+
+library pub.serialize_transformer;
+
+import 'dart:async';
+import 'dart:isolate';
+
+import 'package:barback/barback.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+import '../dart.dart' as dart;
+import '../log.dart' as log;
+import '../utils.dart';
+
+/// A Dart script to run in an isolate.
+///
+/// This script serializes one or more transformers defined in a Dart library and
+/// marhsals calls to and from them with the host isolate.
+const _TRANSFORMER_ISOLATE = """
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:mirrors';
+
+import 'http://localhost:<<PORT>>/packages/barback/barback.dart';
+
+/// Sets up the initial communication with the host isolate.
+void main() {
+ port.receive((uri, replyTo) {
+ _sendFuture(replyTo, new Future.sync(() {
+ return initialize(Uri.parse(uri)).map(_serializeTransformer).toList();
+ }));
+ });
+}
+
+/// Loads all the transformers defined in [uri] and adds them to [transformers].
+///
+/// We then load the library, find any Transformer subclasses in it, instantiate
+/// them, and return them.
+Iterable<Transformer> initialize(Uri uri) {
+ var mirrors = currentMirrorSystem();
+ // TODO(nweiz): look this up by name once issue 5897 is fixed.
+ var transformerUri = Uri.parse(
+ 'http://localhost:<<PORT>>/packages/barback/src/transformer.dart');
+ var transformerClass = mirrors.libraries[transformerUri]
+ .classes[const Symbol('Transformer')];
+
+ return mirrors.libraries[uri].classes.values.where((classMirror) {
+ if (classMirror.isPrivate) return false;
+ if (isAbstract(classMirror)) return false;
+ if (!classIsA(classMirror, transformerClass)) return false;
+ var constructor = classMirror.constructors[classMirror.simpleName];
+ if (constructor == null) return false;
+ if (!constructor.parameters.isEmpty) return false;
+ return true;
+ }).map((classMirror) {
+ return classMirror.newInstance(const Symbol(''), []).reflectee;
+ });
+}
+
+/// A wrapper for a [Transform] that's in the host isolate.
+///
+/// This retrieves inputs from and sends outputs and logs to the host isolate.
+class ForeignTransform implements Transform {
+ /// The port with which we communicate with the host isolate.
+ ///
+ /// This port and all messages sent across it are specific to this transform.
+ final SendPort _port;
+
+ final Asset primaryInput;
+
+ // TODO(nweiz): implement this
+ TransformLogger get logger {
+ throw new UnimplementedError('ForeignTranform.logger is not yet '
+ 'implemented.');
+ }
+
+ /// Creates a transform from a serializable map sent from the host isolate.
+ ForeignTransform(Map transform)
+ : _port = transform['port'],
+ primaryInput = _deserializeAsset(transform['primaryInput']);
+
+ Future<Asset> getInput(AssetId id) {
+ return _receiveFuture(_port.call({
+ 'type': 'getInput',
+ 'id': _serializeId(id)
+ })).then(_deserializeAsset);
+ }
+
+ void addOutput(Asset output) {
+ _port.send({
+ 'type': 'addOutput',
+ 'output': _serializeAsset(output)
+ });
+ }
+}
+
+/// Returns the mirror for the root Object type.
+ClassMirror get objectMirror {
+ if (_objectMirror == null) {
+ _objectMirror = currentMirrorSystem()
+ .libraries[Uri.parse('dart:core')]
+ .classes[const Symbol('Object')];
+ }
+ return _objectMirror;
+}
+ClassMirror _objectMirror;
+
+// TODO(nweiz): get rid of this when issue 12439 is fixed.
+/// Returns whether or not [mirror] is a subtype of [superclass].
+///
+/// This includes [superclass] being mixed in to or implemented by [mirror].
+bool classIsA(ClassMirror mirror, ClassMirror superclass) {
+ if (mirror == superclass) return true;
+ if (mirror == objectMirror) return false;
+ return classIsA(mirror.superclass, superclass) ||
+ mirror.superinterfaces.any((int) => classIsA(int, superclass));
+}
+
+// TODO(nweiz): get rid of this when issue 12826 is fixed.
+/// Returns whether or not [mirror] is an abstract class.
+bool isAbstract(ClassMirror mirror) => mirror.members.values
+ .any((member) => member is MethodMirror && member.isAbstract);
+
+/// Converts [transformer] into a serializable map.
+Map _serializeTransformer(Transformer transformer) {
+ var port = new ReceivePort();
+ port.receive((message, replyTo) {
+ _sendFuture(replyTo, new Future.sync(() {
+ if (message['type'] == 'isPrimary') {
+ return transformer.isPrimary(_deserializeAsset(message['asset']));
+ } else {
+ assert(message['type'] == 'apply');
+ return transformer.apply(
+ new ForeignTransform(message['transform']));
+ }
+ }));
+ });
+
+ return {
+ 'toString': transformer.toString(),
+ 'port': port.toSendPort()
+ };
+}
+
+/// Converts a serializable map into an [Asset].
+Asset _deserializeAsset(Map asset) {
+ var box = new MessageBox();
+ asset['sink'].add(box.sink);
+ return new Asset.fromStream(_deserializeId(asset['id']), box.stream);
+}
+
+/// Converts a serializable map into an [AssetId].
+AssetId _deserializeId(Map id) => new AssetId(id['package'], id['path']);
+
+/// Converts [asset] into a serializable map.
+Map _serializeAsset(Asset asset) {
+ // We can't send IsolateStreams (issue 12437), so instead we send a sink and
+ // get the isolate to send us back another sink.
+ var box = new MessageBox();
+ box.stream.first.then((sink) {
+ asset.read().listen(sink.add,
+ onError: sink.addError,
+ onDone: sink.close);
+ });
+
+ return {
+ 'id': _serializeId(asset.id),
+ 'sink': box.sink
+ };
+}
+
+/// Converts [id] into a serializable map.
+Map _serializeId(AssetId id) => {'package': id.package, 'path': id.path};
+
+/// Sends the result of [future] through [port].
+///
+/// This should be received on the other end using [_receiveFuture]. It
+/// re-raises any exceptions on the other side as [CrossIsolateException]s.
+void _sendFuture(SendPort port, Future future) {
+ future.then((result) {
+ port.send({'success': result});
+ }).catchError((error) {
+ // TODO(nweiz): at least MissingInputException should be preserved here.
+ port.send({'error': CrossIsolateException.serialize(error)});
+ });
+}
+
+/// Receives the result of [_sendFuture] from [portCall], which should be the
+/// return value of [SendPort.call].
+Future _receiveFuture(Future portCall) {
+ return portCall.then((response) {
+ if (response.containsKey('success')) return response['success'];
+ return new Future.error(
+ new CrossIsolateException.deserialize(response['error']));
+ });
+}
+
+/// 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 map.
+ ///
+ /// [error] should be the result of [CrossIsolateException.serialize].
+ CrossIsolateException.deserialize(Map error)
+ : type = error['type'],
+ message = error['message'],
+ stackTrace = error['stack'] == null ? null :
+ new Trace.parse(error['stack']);
+
+ /// Serializes [error] to a map 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";
+}
+
+// Get a string description of an exception.
+//
+// Most exception types have a "message" property. We prefer this since
+// it skips the "Exception:", "HttpException:", etc. prefix that calling
+// toString() adds. But, alas, "message" isn't actually defined in the
+// base Exception type so there's no easy way to know if it's available
+// short of a giant pile of type tests for each known exception type.
+//
+// So just try it. If it throws, default to toString().
+String getErrorMessage(error) {
+ try {
+ return error.message;
+ } on NoSuchMethodError catch (_) {
+ return error.toString();
+ }
+}
+""";
+
+/// Load and return all transformers from the library identified by [library].
+///
+/// [server] is used to serve [library] and any Dart files it imports.
+Future<Set<Transformer>> loadTransformers(BarbackServer server,
+ AssetId library) {
+ var path = library.path.replaceFirst('lib/', '');
+ // TODO(nweiz): load from a "package:" URI when issue 12474 is fixed.
+ var uri = 'http://localhost:${server.port}/packages/${library.package}/$path';
+ var code = 'import "$uri";' +
+ _TRANSFORMER_ISOLATE.replaceAll('<<PORT>>', server.port.toString());
+ log.fine("Loading transformers from $library");
+ return dart.runInIsolate(code).then((sendPort) {
+ return _receiveFuture(sendPort.call(uri)).then((transformers) {
+ transformers = transformers
+ .map((transformer) => new _ForeignTransformer(transformer))
+ .toSet();
+ log.fine("Transformers from $library: $transformers");
+ return transformers;
+ });
+ }).catchError((error) {
+ if (error is! CrossIsolateException) throw error;
+ if (error.type != 'IsolateSpawnException') throw error;
+ // TODO(nweiz): don't parse this as a string once issues 12617 and 12689 are
+ // fixed.
+ if (!error.message.split('\n')[1].startsWith('import "$uri";')) {
+ throw error;
+ }
+
+ // If there was an IsolateSpawnException and the import that actually failed
+ // was the one we were loading transformers from, throw an application
+ // exception with a more user-friendly message.
+ fail('Transformer library "package:${library.package}/$path" not found.');
+ });
+}
+
+/// A wrapper for a transformer that's in a different isolate.
+class _ForeignTransformer implements Transformer {
+ /// The port with which we communicate with the child isolate.
+ ///
+ /// This port and all messages sent across it are specific to this
+ /// transformer.
+ final SendPort _port;
+
+ /// The result of calling [toString] on the transformer in the isolate.
+ final String _toString;
+
+ _ForeignTransformer(Map map)
+ : _port = map['port'],
+ _toString = map['toString'];
+
+ Future<bool> isPrimary(Asset asset) {
+ return _receiveFuture(_port.call({
+ 'type': 'isPrimary',
+ 'asset': _serializeAsset(asset)
+ }));
+ }
+
+ Future apply(Transform transform) {
+ return _receiveFuture(_port.call({
+ 'type': 'apply',
+ 'transform': _serializeTransform(transform)
+ }));
+ }
+
+ String toString() => _toString;
+}
+
+/// Converts [transform] into a serializable map.
+Map _serializeTransform(Transform transform) {
+ var receivePort = new ReceivePort();
+ receivePort.receive((message, replyTo) {
+ if (message['type'] == 'getInput') {
+ _sendFuture(replyTo, transform.getInput(_deserializeId(message['id']))
+ .then(_serializeAsset));
+ } else {
+ assert(message['type'] == 'addOutput');
+ transform.addOutput(_deserializeAsset(message['output']));
+ }
+ });
+
+ return {
+ 'port': receivePort.toSendPort(),
+ 'primaryInput': _serializeAsset(transform.primaryInput)
+ };
+}
+
+/// Converts a serializable map into an [Asset].
+Asset _deserializeAsset(Map asset) {
+ var box = new MessageBox();
+ asset['sink'].add(box.sink);
+ return new Asset.fromStream(_deserializeId(asset['id']), box.stream);
+}
+
+/// Converts a serializable map into an [AssetId].
+AssetId _deserializeId(Map id) => new AssetId(id['package'], id['path']);
+
+// TODO(nweiz): add custom serialization code for assets that can be more
+// efficiently serialized.
+/// Converts [asset] into a serializable map.
+Map _serializeAsset(Asset asset) {
+ // We can't send IsolateStreams (issue 12437), so instead we send a sink and
+ // get the isolate to send us back another sink.
+ var box = new MessageBox();
+ box.stream.first.then((sink) {
+ asset.read().listen(sink.add,
+ onError: sink.addError,
+ onDone: sink.close);
+ });
+
+ return {
+ 'id': _serializeId(asset.id),
+ 'sink': box.sink
+ };
+}
+
+/// Converts [id] into a serializable map.
+Map _serializeId(AssetId id) => {'package': id.package, 'path': id.path};
+
+/// Sends the result of [future] through [port].
+///
+/// This should be received on the other end using [_receiveFuture]. It
+/// re-raises any exceptions on the other side as [CrossIsolateException]s.
+void _sendFuture(SendPort port, Future future) {
+ future.then((result) {
+ port.send({'success': result});
+ }).catchError((error) {
+ // TODO(nweiz): at least MissingInputException should be preserved here.
+ port.send({'error': CrossIsolateException.serialize(error)});
+ });
+}
+
+/// Receives the result of [_sendFuture] from [portCall], which should be the
+/// return value of [SendPort.call].
+Future _receiveFuture(Future portCall) {
+ return portCall.then((response) {
+ if (response.containsKey('success')) return response['success'];
+ return new Future.error(
+ new dart.CrossIsolateException.deserialize(response['error']));
+ });
+}
« no previous file with comments | « sdk/lib/_internal/pub/lib/src/barback.dart ('k') | sdk/lib/_internal/pub/lib/src/barback/pub_package_provider.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698