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

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: Created 7 years, 4 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..383fac08b9ca833b1079925c177df19fe8bd1c7b
--- /dev/null
+++ b/sdk/lib/_internal/pub/lib/src/barback/load_transformers.dart
@@ -0,0 +1,368 @@
+// 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.wrap_transformer;
+
+import 'dart:async';
+import 'dart:isolate';
+
+import 'package:barback/barback.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+import '../dart.dart' as dart;
+
+/// A Dart script to run in an isolate.
+///
+/// This script wraps 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(_wrapTransformer).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((mirror) {
Bob Nystrom 2013/08/27 22:12:30 "mirror" -> "classMirror".
nweiz 2013/08/28 20:45:23 Done.
+ if (mirror.isPrivate) return false;
Bob Nystrom 2013/08/27 22:12:30 Might want to check and see if the class is abstra
nweiz 2013/08/28 20:45:23 Done.
+ if (!classIsA(mirror, transformerClass)) return false;
+ var constructor = mirror.constructors[mirror.simpleName];
+ if (constructor == null) return false;
+ if (!constructor.parameters.isEmpty) return false;
Bob Nystrom 2013/08/27 22:12:30 Add a TODO to report this error to the user.
nweiz 2013/08/28 20:45:23 I don't think this should be an error. Users shoul
+ return true;
+ }).map((mirror) => mirror.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 AssetId primaryId;
+
+ Future<Asset> get primaryInput => getInput(primaryId);
+
+ // 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'],
+ primaryId = _unwrapId(transform['primaryId']);
+
+ Future<Asset> getInput(AssetId id) {
+ return _receiveFuture(_port.call({'type': 'getInput', 'id': _wrapId(id)}))
+ .then(_unwrapAsset);
+ }
+
+ void addOutput(Asset output) {
+ _port.send({
+ 'type': 'addOutput',
+ 'output': _wrapAsset(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;
+
+/// Returns whether or not [superclass] is a superclass of [mirror].
Bob Nystrom 2013/08/27 22:12:30 Clearer would be "[mirror] is an instance of [supe
nweiz 2013/08/28 20:45:23 A class isn't an instance of its superclasses.
Bob Nystrom 2013/08/29 00:12:01 I guess that depends on your definition of "instan
nweiz 2013/09/04 00:21:58 Done.
+///
+/// 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));
+}
+
+/// Converts [transformer] into a serializable map.
+Map _wrapTransformer(Transformer transformer) {
Bob Nystrom 2013/08/27 22:12:30 "wrap" -> "serialize", "unwrap" -> "deserialize" h
nweiz 2013/08/28 20:45:23 Done.
+ var port = new ReceivePort();
+ port.receive((message, replyTo) {
+ _sendFuture(replyTo, new Future.sync(() {
+ if (message['type'] == 'isPrimary') {
+ return transformer.isPrimary(_unwrapAsset(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 _unwrapAsset(Map asset) {
+ var box = new MessageBox();
+ asset['sink'].add(box.sink);
+ return new Asset.fromStream(_unwrapId(asset['id']), box.stream);
Bob Nystrom 2013/08/27 22:12:30 I really think we should serialize assets directly
nweiz 2013/08/28 20:45:23 I'll add a TODO for this.
+}
+
+/// Converts a serializable map into an [AssetId].
+AssetId _unwrapId(Map id) => new AssetId(id['package'], id['path']);
Bob Nystrom 2013/08/27 22:12:30 I'd prefer adding a serialize() method directly on
nweiz 2013/08/28 20:45:23 I don't like the idea of just manually adding a se
Bob Nystrom 2013/08/29 00:12:01 True, but the nice thing is that third-party users
+
+/// Converts [asset] into a serializable map.
+Map _wrapAsset(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': _wrapId(asset.id),
+ 'sink': box.sink
+ };
+}
+
+/// Converts [id] into a serializable map.
+Map _wrapId(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)});
+ });
+}
+
+/// Sends the result of [_sendFuture] from [portCall], which should be the
Bob Nystrom 2013/08/27 22:12:30 "Sends" -> "Receives".
nweiz 2013/08/28 20:45:23 Done.
+/// 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 uri = 'http://localhost:${server.port}/packages/${library.package}/'
+ '${library.path.replaceFirst('lib/', '')}';
+ var code = 'import "$uri";' +
+ _TRANSFORMER_ISOLATE.replaceAll('<<PORT>>', server.port.toString());
Bob Nystrom 2013/08/27 22:12:30 Add a TODO to do something less unholy here...
nweiz 2013/08/28 20:45:23 Done.
+ return dart.runInIsolate(code).then((sendPort) {
+ return _receiveFuture(sendPort.call(uri)).then((transformers) {
+ return transformers
+ .map((transformer) => new _ForeignTransformer(transformer))
+ .toSet();
+ });
+ });
+}
+
+/// A wrapper for a transformer that's in a different isolate.
+class _ForeignTransformer implements Transformer {
+ /// The port with which we communicate with the host isolate.
Bob Nystrom 2013/08/27 22:12:30 "host" -> "child"?
nweiz 2013/08/28 20:45:23 Done.
+ ///
+ /// 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': _wrapAsset(asset)
+ }));
+ }
+
+ Future apply(Transform transform) {
+ return _receiveFuture(_port.call({
+ 'type': 'apply',
+ 'transform': _wrapTransform(transform)
+ }));
+ }
+
+ String toString() => _toString;
+}
+
+/// Converts [transform] into a serializable map.
+Map _wrapTransform(Transform transform) {
+ var receivePort = new ReceivePort();
+ receivePort.receive((message, replyTo) {
+ if (message['type'] == 'getInput') {
+ _sendFuture(replyTo,
+ transform.getInput(_unwrapId(message['id'])).then(_wrapAsset));
+ } else {
+ assert(message['type'] == 'addOutput');
+ transform.addOutput(_unwrapAsset(message['output']));
+ }
+ });
+
+ return {
+ 'port': receivePort.toSendPort(),
+ 'primaryId': _wrapId(transform.primaryId)
+ };
+}
+
+/// Converts a serializable map into an [Asset].
+Asset _unwrapAsset(Map asset) {
+ var box = new MessageBox();
+ asset['sink'].add(box.sink);
+ return new Asset.fromStream(_unwrapId(asset['id']), box.stream);
+}
+
+/// Converts a serializable map into an [AssetId].
+AssetId _unwrapId(Map id) => new AssetId(id['package'], id['path']);
+
+/// Converts [asset] into a serializable map.
+Map _wrapAsset(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': _wrapId(asset.id),
+ 'sink': box.sink
+ };
+}
+
+/// Converts [id] into a serializable map.
+Map _wrapId(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)});
+ });
+}
+
+/// Sends 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']));
+ });
+}

Powered by Google App Engine
This is Rietveld 408576698