| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library pub.load_transformers; | 5 library pub.load_transformers; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 import 'dart:isolate'; | 9 import 'dart:isolate'; |
| 10 | 10 |
| 11 import 'package:barback/barback.dart'; | 11 import 'package:barback/barback.dart'; |
| 12 // TODO(nweiz): don't import from "src" once issue 14966 is fixed. | 12 // TODO(nweiz): don't import from "src" once issue 14966 is fixed. |
| 13 import 'package:barback/src/internal_asset.dart'; | 13 import 'package:barback/src/internal_asset.dart'; |
| 14 import 'package:path/path.dart' as p; |
| 14 import 'package:source_maps/source_maps.dart'; | 15 import 'package:source_maps/source_maps.dart'; |
| 15 import 'package:stack_trace/stack_trace.dart'; | 16 import 'package:stack_trace/stack_trace.dart'; |
| 16 | 17 |
| 17 import '../barback.dart'; | 18 import '../barback.dart'; |
| 18 import '../dart.dart' as dart; | 19 import '../dart.dart' as dart; |
| 20 import '../io.dart'; |
| 19 import '../log.dart' as log; | 21 import '../log.dart' as log; |
| 20 import '../utils.dart'; | 22 import '../utils.dart'; |
| 21 import 'build_environment.dart'; | 23 import 'build_environment.dart'; |
| 22 import 'excluding_transformer.dart'; | 24 import 'excluding_transformer.dart'; |
| 23 import 'server.dart'; | 25 import 'server.dart'; |
| 24 | 26 |
| 25 /// A Dart script to run in an isolate. | |
| 26 /// | |
| 27 /// This script serializes one or more transformers defined in a Dart library | |
| 28 /// and marshals calls to and from them with the host isolate. | |
| 29 const _TRANSFORMER_ISOLATE = """ | |
| 30 import 'dart:async'; | |
| 31 import 'dart:isolate'; | |
| 32 import 'dart:convert'; | |
| 33 import 'dart:mirrors'; | |
| 34 | |
| 35 import '<<URL_BASE>>/packages/source_maps/span.dart'; | |
| 36 import '<<URL_BASE>>/packages/stack_trace/stack_trace.dart'; | |
| 37 import '<<URL_BASE>>/packages/barback/barback.dart'; | |
| 38 // TODO(nweiz): don't import from "src" once issue 14966 is fixed. | |
| 39 import '<<URL_BASE>>/packages/barback/src/internal_asset.dart'; | |
| 40 | |
| 41 /// Sets up the initial communication with the host isolate. | |
| 42 void main(_, SendPort replyTo) { | |
| 43 var port = new ReceivePort(); | |
| 44 replyTo.send(port.sendPort); | |
| 45 port.first.then((wrappedMessage) { | |
| 46 _respond(wrappedMessage, (message) { | |
| 47 var library = Uri.parse(message['library']); | |
| 48 var configuration = JSON.decode(message['configuration']); | |
| 49 var mode = new BarbackMode(message['mode']); | |
| 50 return initialize(library, configuration, mode). | |
| 51 map(_serializeTransformerOrGroup).toList(); | |
| 52 }); | |
| 53 }); | |
| 54 } | |
| 55 | |
| 56 /// Loads all the transformers and groups defined in [uri]. | |
| 57 /// | |
| 58 /// Loads the library, finds any Transformer or TransformerGroup subclasses in | |
| 59 /// it, instantiates them with [configuration] and [mode], and returns them. | |
| 60 Iterable initialize(Uri uri, Map configuration, BarbackMode mode) { | |
| 61 var mirrors = currentMirrorSystem(); | |
| 62 var transformerClass = reflectClass(Transformer); | |
| 63 var groupClass = reflectClass(TransformerGroup); | |
| 64 | |
| 65 // TODO(nweiz): if no valid transformers are found, throw an error message | |
| 66 // describing candidates and why they were rejected. | |
| 67 return mirrors.libraries[uri].declarations.values.map((declaration) { | |
| 68 if (declaration is! ClassMirror) return null; | |
| 69 var classMirror = declaration; | |
| 70 if (classMirror.isPrivate) return null; | |
| 71 if (classMirror.isAbstract) return null; | |
| 72 if (!classIsA(classMirror, transformerClass) && | |
| 73 !classIsA(classMirror, groupClass)) { | |
| 74 return null; | |
| 75 } | |
| 76 | |
| 77 var constructor = getConstructor(classMirror, 'asPlugin'); | |
| 78 if (constructor == null) return null; | |
| 79 if (constructor.parameters.isEmpty) { | |
| 80 if (configuration.isNotEmpty) return null; | |
| 81 return classMirror.newInstance(const Symbol('asPlugin'), []).reflectee; | |
| 82 } | |
| 83 if (constructor.parameters.length != 1) return null; | |
| 84 | |
| 85 return classMirror.newInstance(const Symbol('asPlugin'), | |
| 86 [new BarbackSettings(configuration, mode)]).reflectee; | |
| 87 }).where((classMirror) => classMirror != null); | |
| 88 } | |
| 89 | |
| 90 /// A wrapper for a [Transform] that's in the host isolate. | |
| 91 /// | |
| 92 /// This retrieves inputs from and sends outputs and logs to the host isolate. | |
| 93 class ForeignTransform implements Transform { | |
| 94 /// The port with which we communicate with the host isolate. | |
| 95 /// | |
| 96 /// This port and all messages sent across it are specific to this transform. | |
| 97 final SendPort _port; | |
| 98 | |
| 99 final Asset primaryInput; | |
| 100 | |
| 101 TransformLogger get logger => _logger; | |
| 102 TransformLogger _logger; | |
| 103 | |
| 104 /// Creates a transform from a serializable map sent from the host isolate. | |
| 105 ForeignTransform(Map transform) | |
| 106 : _port = transform['port'], | |
| 107 primaryInput = deserializeAsset(transform['primaryInput']) { | |
| 108 _logger = new TransformLogger((assetId, level, message, span) { | |
| 109 _call(_port, { | |
| 110 'type': 'log', | |
| 111 'level': level.name, | |
| 112 'message': message, | |
| 113 'assetId': assetId == null ? null : _serializeId(assetId), | |
| 114 'span': span == null ? null : _serializeSpan(span) | |
| 115 }); | |
| 116 }); | |
| 117 } | |
| 118 | |
| 119 Future<Asset> getInput(AssetId id) { | |
| 120 return _call(_port, { | |
| 121 'type': 'getInput', | |
| 122 'id': _serializeId(id) | |
| 123 }).then(deserializeAsset); | |
| 124 } | |
| 125 | |
| 126 Future<String> readInputAsString(AssetId id, {Encoding encoding}) { | |
| 127 if (encoding == null) encoding = UTF8; | |
| 128 return getInput(id).then((input) => input.readAsString(encoding: encoding)); | |
| 129 } | |
| 130 | |
| 131 Stream<List<int>> readInput(AssetId id) => | |
| 132 _futureStream(getInput(id).then((input) => input.read())); | |
| 133 | |
| 134 void addOutput(Asset output) { | |
| 135 _call(_port, { | |
| 136 'type': 'addOutput', | |
| 137 'output': serializeAsset(output) | |
| 138 }); | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 /// Returns the mirror for the root Object type. | |
| 143 ClassMirror get objectMirror => reflectClass(Object); | |
| 144 | |
| 145 // TODO(nweiz): clean this up when issue 13248 is fixed. | |
| 146 MethodMirror getConstructor(ClassMirror classMirror, String constructor) { | |
| 147 var name = new Symbol("\${MirrorSystem.getName(classMirror.simpleName)}" | |
| 148 ".\$constructor"); | |
| 149 var candidate = classMirror.declarations[name]; | |
| 150 if (candidate is MethodMirror && candidate.isConstructor) return candidate; | |
| 151 return null; | |
| 152 } | |
| 153 | |
| 154 // TODO(nweiz): get rid of this when issue 12439 is fixed. | |
| 155 /// Returns whether or not [mirror] is a subtype of [superclass]. | |
| 156 /// | |
| 157 /// This includes [superclass] being mixed in to or implemented by [mirror]. | |
| 158 bool classIsA(ClassMirror mirror, ClassMirror superclass) { | |
| 159 if (mirror == superclass) return true; | |
| 160 if (mirror == objectMirror) return false; | |
| 161 return classIsA(mirror.superclass, superclass) || | |
| 162 mirror.superinterfaces.any((int) => classIsA(int, superclass)); | |
| 163 } | |
| 164 | |
| 165 /// Converts [transformerOrGroup] into a serializable map. | |
| 166 Map _serializeTransformerOrGroup(transformerOrGroup) { | |
| 167 if (transformerOrGroup is Transformer) { | |
| 168 return _serializeTransformer(transformerOrGroup); | |
| 169 } else { | |
| 170 assert(transformerOrGroup is TransformerGroup); | |
| 171 return _serializeTransformerGroup(transformerOrGroup); | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 /// Converts [transformer] into a serializable map. | |
| 176 Map _serializeTransformer(Transformer transformer) { | |
| 177 var port = new ReceivePort(); | |
| 178 port.listen((wrappedMessage) { | |
| 179 _respond(wrappedMessage, (message) { | |
| 180 if (message['type'] == 'isPrimary') { | |
| 181 return transformer.isPrimary(deserializeAsset(message['asset'])); | |
| 182 } else { | |
| 183 assert(message['type'] == 'apply'); | |
| 184 | |
| 185 // Make sure we return null so that if the transformer's [apply] returns | |
| 186 // a non-serializable value it doesn't cause problems. | |
| 187 return transformer.apply( | |
| 188 new ForeignTransform(message['transform'])).then((_) => null); | |
| 189 } | |
| 190 }); | |
| 191 }); | |
| 192 | |
| 193 return { | |
| 194 'type': 'Transformer', | |
| 195 'toString': transformer.toString(), | |
| 196 'port': port.sendPort | |
| 197 }; | |
| 198 } | |
| 199 | |
| 200 // Converts [group] into a serializable map. | |
| 201 Map _serializeTransformerGroup(TransformerGroup group) { | |
| 202 return { | |
| 203 'type': 'TransformerGroup', | |
| 204 'toString': group.toString(), | |
| 205 'phases': group.phases.map((phase) { | |
| 206 return phase.map(_serializeTransformerOrGroup).toList(); | |
| 207 }).toList() | |
| 208 }; | |
| 209 } | |
| 210 | |
| 211 /// Converts a serializable map into an [AssetId]. | |
| 212 AssetId _deserializeId(Map id) => new AssetId(id['package'], id['path']); | |
| 213 | |
| 214 /// Converts [id] into a serializable map. | |
| 215 Map _serializeId(AssetId id) => {'package': id.package, 'path': id.path}; | |
| 216 | |
| 217 /// Converts [span] into a serializable map. | |
| 218 Map _serializeSpan(Span span) { | |
| 219 // TODO(nweiz): convert FileSpans to FileSpans. | |
| 220 return { | |
| 221 'type': 'fixed', | |
| 222 'sourceUrl': span.sourceUrl, | |
| 223 'start': _serializeLocation(span.start), | |
| 224 'text': span.text, | |
| 225 'isIdentifier': span.isIdentifier | |
| 226 }; | |
| 227 } | |
| 228 | |
| 229 /// Converts [location] into a serializable map. | |
| 230 Map _serializeLocation(Location location) { | |
| 231 // TODO(nweiz): convert FileLocations to FileLocations. | |
| 232 return { | |
| 233 'type': 'fixed', | |
| 234 'sourceUrl': location.sourceUrl, | |
| 235 'offset': location.offset, | |
| 236 'line': location.line, | |
| 237 'column': location.column | |
| 238 }; | |
| 239 } | |
| 240 | |
| 241 /// Responds to a message sent by [_call]. | |
| 242 /// | |
| 243 /// [wrappedMessage] is the raw message sent by [_call]. This unwraps it and | |
| 244 /// passes the contents of the message to [callback], then sends the return | |
| 245 /// value of [callback] back to [_call]. If [callback] returns a Future or | |
| 246 /// throws an error, that will also be sent. | |
| 247 void _respond(wrappedMessage, callback(message)) { | |
| 248 var replyTo = wrappedMessage['replyTo']; | |
| 249 new Future.sync(() => callback(wrappedMessage['message'])) | |
| 250 .then((result) => replyTo.send({'type': 'success', 'value': result})) | |
| 251 .catchError((error, stackTrace) { | |
| 252 replyTo.send({ | |
| 253 'type': 'error', | |
| 254 'error': _serializeException(error, stackTrace) | |
| 255 }); | |
| 256 }); | |
| 257 } | |
| 258 | |
| 259 /// Wraps [message] and sends it across [port], then waits for a response which | |
| 260 /// should be sent using [_respond]. | |
| 261 /// | |
| 262 /// The returned Future will complete to the value or error returned by | |
| 263 /// [_respond]. | |
| 264 Future _call(SendPort port, message) { | |
| 265 var receivePort = new ReceivePort(); | |
| 266 port.send({ | |
| 267 'message': message, | |
| 268 'replyTo': receivePort.sendPort | |
| 269 }); | |
| 270 | |
| 271 return receivePort.first.then((response) { | |
| 272 if (response['type'] == 'success') return response['value']; | |
| 273 assert(response['type'] == 'error'); | |
| 274 var exception = _deserializeException(response['error']); | |
| 275 return new Future.error(exception, exception.stackTrace); | |
| 276 }); | |
| 277 } | |
| 278 | |
| 279 /// An exception that was originally raised in another isolate. | |
| 280 /// | |
| 281 /// Exception objects can't cross isolate boundaries in general, so this class | |
| 282 /// wraps as much information as can be consistently serialized. | |
| 283 class CrossIsolateException implements Exception { | |
| 284 /// The name of the type of exception thrown. | |
| 285 /// | |
| 286 /// This is the return value of [error.runtimeType.toString()]. Keep in mind | |
| 287 /// that objects in different libraries may have the same type name. | |
| 288 final String type; | |
| 289 | |
| 290 /// The exception's message, or its [toString] if it didn't expose a `message` | |
| 291 /// property. | |
| 292 final String message; | |
| 293 | |
| 294 /// The exception's stack chain, or `null` if no stack chain was available. | |
| 295 final Chain stackTrace; | |
| 296 | |
| 297 /// Loads a [CrossIsolateException] from a serialized representation. | |
| 298 /// | |
| 299 /// [error] should be the result of [CrossIsolateException.serialize]. | |
| 300 CrossIsolateException.deserialize(Map error) | |
| 301 : type = error['type'], | |
| 302 message = error['message'], | |
| 303 stackTrace = error['stack'] == null ? null : | |
| 304 new Chain.parse(error['stack']); | |
| 305 | |
| 306 /// Serializes [error] to an object that can safely be passed across isolate | |
| 307 /// boundaries. | |
| 308 static Map serialize(error, [StackTrace stack]) { | |
| 309 if (stack == null && error is Error) stack = error.stackTrace; | |
| 310 return { | |
| 311 'type': error.runtimeType.toString(), | |
| 312 'message': getErrorMessage(error), | |
| 313 'stack': stack == null ? null : new Chain.forTrace(stack).toString() | |
| 314 }; | |
| 315 } | |
| 316 | |
| 317 String toString() => "\$message\\n\$stackTrace"; | |
| 318 } | |
| 319 | |
| 320 /// An [AssetNotFoundException] that was originally raised in another isolate. | |
| 321 class _CrossIsolateAssetNotFoundException extends CrossIsolateException | |
| 322 implements AssetNotFoundException { | |
| 323 final TransformInfo transform; | |
| 324 final AssetId id; | |
| 325 | |
| 326 String get message => "Could not find asset \$id."; | |
| 327 | |
| 328 /// Loads a [_CrossIsolateAssetNotFoundException] from a serialized | |
| 329 /// representation. | |
| 330 /// | |
| 331 /// [error] should be the result of | |
| 332 /// [_CrossIsolateAssetNotFoundException.serialize]. | |
| 333 _CrossIsolateAssetNotFoundException.deserialize(Map error) | |
| 334 : id = new AssetId(error['package'], error['path']), | |
| 335 super.deserialize(error); | |
| 336 | |
| 337 /// Serializes [error] to an object that can safely be passed across isolate | |
| 338 /// boundaries. | |
| 339 static Map serialize(AssetNotFoundException error, [StackTrace stack]) { | |
| 340 var map = CrossIsolateException.serialize(error); | |
| 341 map['package'] = error.id.package; | |
| 342 map['path'] = error.id.path; | |
| 343 return map; | |
| 344 } | |
| 345 } | |
| 346 | |
| 347 /// Serializes [error] to an object that can safely be passed across isolate | |
| 348 /// boundaries. | |
| 349 /// | |
| 350 /// This handles [AssetNotFoundException]s specially, ensuring that their | |
| 351 /// metadata is preserved. | |
| 352 Map _serializeException(error, [StackTrace stack]) { | |
| 353 if (error is AssetNotFoundException) { | |
| 354 return _CrossIsolateAssetNotFoundException.serialize(error, stack); | |
| 355 } else { | |
| 356 return CrossIsolateException.serialize(error, stack); | |
| 357 } | |
| 358 } | |
| 359 | |
| 360 /// Loads an exception from a serialized representation. | |
| 361 /// | |
| 362 /// This handles [AssetNotFoundException]s specially, ensuring that their | |
| 363 /// metadata is preserved. | |
| 364 CrossIsolateException _deserializeException(Map error) { | |
| 365 if (error['type'] == 'AssetNotFoundException') { | |
| 366 return new _CrossIsolateAssetNotFoundException.deserialize(error); | |
| 367 } else { | |
| 368 return new CrossIsolateException.deserialize(error); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 /// A regular expression to match the exception prefix that some exceptions' | |
| 373 /// [Object.toString] values contain. | |
| 374 final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): '); | |
| 375 | |
| 376 /// Get a string description of an exception. | |
| 377 /// | |
| 378 /// Many exceptions include the exception class name at the beginning of their | |
| 379 /// [toString], so we remove that if it exists. | |
| 380 String getErrorMessage(error) => | |
| 381 error.toString().replaceFirst(_exceptionPrefix, ''); | |
| 382 | |
| 383 /// Returns a buffered stream that will emit the same values as the stream | |
| 384 /// returned by [future] once [future] completes. If [future] completes to an | |
| 385 /// error, the return value will emit that error and then close. | |
| 386 Stream _futureStream(Future<Stream> future) { | |
| 387 var controller = new StreamController(sync: true); | |
| 388 future.then((stream) { | |
| 389 stream.listen( | |
| 390 controller.add, | |
| 391 onError: controller.addError, | |
| 392 onDone: controller.close); | |
| 393 }).catchError((e, stackTrace) { | |
| 394 controller.addError(e, stackTrace); | |
| 395 controller.close(); | |
| 396 }); | |
| 397 return controller.stream; | |
| 398 } | |
| 399 | |
| 400 Stream callbackStream(Stream callback()) { | |
| 401 var subscription; | |
| 402 var controller; | |
| 403 controller = new StreamController(onListen: () { | |
| 404 subscription = callback().listen(controller.add, | |
| 405 onError: controller.addError, | |
| 406 onDone: controller.close); | |
| 407 }, | |
| 408 onCancel: () => subscription.cancel(), | |
| 409 onPause: () => subscription.pause(), | |
| 410 onResume: () => subscription.resume(), | |
| 411 sync: true); | |
| 412 return controller.stream; | |
| 413 } | |
| 414 """; | |
| 415 | |
| 416 /// Load and return all transformers and groups from the library identified by | 27 /// Load and return all transformers and groups from the library identified by |
| 417 /// [id]. | 28 /// [id]. |
| 418 Future<Set> loadTransformers(BuildEnvironment environment, | 29 Future<Set> loadTransformers(BuildEnvironment environment, |
| 419 BarbackServer transformerServer, TransformerId id) { | 30 BarbackServer transformerServer, TransformerId id) { |
| 420 return id.getAssetId(environment.barback).then((assetId) { | 31 return id.getAssetId(environment.barback).then((assetId) { |
| 421 var path = assetId.path.replaceFirst('lib/', ''); | 32 var path = assetId.path.replaceFirst('lib/', ''); |
| 422 // TODO(nweiz): load from a "package:" URI when issue 12474 is fixed. | 33 // TODO(nweiz): load from a "package:" URI when issue 12474 is fixed. |
| 423 | 34 |
| 424 var baseUrl = transformerServer.url; | 35 var baseUrl = transformerServer.url; |
| 425 var uri = '$baseUrl/packages/${id.package}/$path'; | 36 var uri = '$baseUrl/packages/${id.package}/$path'; |
| 426 var code = 'import "$uri";\n' + | 37 var code = 'import "$uri";\n' + |
| 427 _TRANSFORMER_ISOLATE.replaceAll('<<URL_BASE>>', baseUrl); | 38 readResource(p.join("dart", "transformer_isolate.dart")) |
| 39 .replaceAll('<<URL_BASE>>', baseUrl); |
| 428 log.fine("Loading transformers from $assetId"); | 40 log.fine("Loading transformers from $assetId"); |
| 429 | 41 |
| 430 var port = new ReceivePort(); | 42 var port = new ReceivePort(); |
| 431 return dart.runInIsolate(code, port.sendPort) | 43 return dart.runInIsolate(code, port.sendPort) |
| 432 .then((_) => port.first) | 44 .then((_) => port.first) |
| 433 .then((sendPort) { | 45 .then((sendPort) { |
| 434 return _call(sendPort, { | 46 return _call(sendPort, { |
| 435 'library': uri, | 47 'library': uri, |
| 436 'mode': environment.mode.name, | 48 'mode': environment.mode.name, |
| 437 // TODO(nweiz): support non-JSON-encodable configuration maps. | 49 // TODO(nweiz): support non-JSON-encodable configuration maps. |
| (...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 665 /// | 277 /// |
| 666 /// This handles [AssetNotFoundException]s specially, ensuring that their | 278 /// This handles [AssetNotFoundException]s specially, ensuring that their |
| 667 /// metadata is preserved. | 279 /// metadata is preserved. |
| 668 dart.CrossIsolateException _deserializeException(Map error) { | 280 dart.CrossIsolateException _deserializeException(Map error) { |
| 669 if (error['type'] == 'AssetNotFoundException') { | 281 if (error['type'] == 'AssetNotFoundException') { |
| 670 return new _CrossIsolateAssetNotFoundException.deserialize(error); | 282 return new _CrossIsolateAssetNotFoundException.deserialize(error); |
| 671 } else { | 283 } else { |
| 672 return new dart.CrossIsolateException.deserialize(error); | 284 return new dart.CrossIsolateException.deserialize(error); |
| 673 } | 285 } |
| 674 } | 286 } |
| OLD | NEW |