OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library pub.transformer_isolate; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:convert'; | |
9 import 'dart:isolate'; | |
10 | |
11 import 'package:barback/barback.dart'; | |
12 import 'package:source_span/source_span.dart'; | |
13 import 'package:stack_trace/stack_trace.dart'; | |
14 | |
15 import '../../../asset/dart/serialize.dart'; | |
16 import '../barback.dart'; | |
17 import '../exceptions.dart'; | |
18 import '../dart.dart' as dart; | |
19 import '../log.dart' as log; | |
20 import '../utils.dart'; | |
21 import 'asset_environment.dart'; | |
22 import 'barback_server.dart'; | |
23 import 'foreign_transformer.dart'; | |
24 import 'transformer_config.dart'; | |
25 import 'transformer_id.dart'; | |
26 | |
27 /// A wrapper for an isolate from which transformer plugins can be instantiated. | |
28 class TransformerIsolate { | |
29 /// The port used to communicate with the wrapped isolate. | |
30 final SendPort _port; | |
31 | |
32 /// A map indicating the barback server URLs for each [TransformerId] that's | |
33 /// loaded in the wrapped isolate. | |
34 /// | |
35 /// A barback server URL is the URL for the library that the given id | |
36 /// identifies. For example, the URL for "polymer/src/mirrors_remover" might | |
37 /// be "http://localhost:56234/packages/polymer/src/mirrors_remover.dart". | |
38 final Map<TransformerId, Uri> _idsToUrls; | |
39 | |
40 /// The barback mode for this run of pub. | |
41 final BarbackMode _mode; | |
42 | |
43 /// Spawns an isolate that loads all transformer libraries defined by [ids]. | |
44 /// | |
45 /// This doesn't actually instantiate any transformers, since a | |
46 /// [TransformerId] doesn't define the transformers' configuration. The | |
47 /// transformers can be constructed using [create]. | |
48 /// | |
49 /// If [snapshot] is passed, the isolate will be loaded from that path if it | |
50 /// exists. Otherwise, a snapshot of the isolate's code will be saved to that | |
51 /// path once the isolate is loaded. | |
52 static Future<TransformerIsolate> spawn(AssetEnvironment environment, | |
53 BarbackServer transformerServer, List<TransformerId> ids, {String snapshot
}) { | |
54 return mapFromIterableAsync(ids, value: (id) { | |
55 return id.getAssetId(environment.barback); | |
56 }).then((idsToAssetIds) { | |
57 var baseUrl = transformerServer.url; | |
58 var idsToUrls = mapMap(idsToAssetIds, value: (id, assetId) { | |
59 var path = assetId.path.replaceFirst('lib/', ''); | |
60 return Uri.parse('package:${id.package}/$path'); | |
61 }); | |
62 | |
63 var code = new StringBuffer(); | |
64 code.writeln("import 'dart:isolate';"); | |
65 | |
66 for (var url in idsToUrls.values) { | |
67 code.writeln("import '$url';"); | |
68 } | |
69 | |
70 code.writeln("import r'package:\$pub/transformer_isolate.dart';"); | |
71 code.writeln( | |
72 "void main(_, SendPort replyTo) => loadTransformers(replyTo);"); | |
73 | |
74 log.fine("Loading transformers from $ids"); | |
75 | |
76 var port = new ReceivePort(); | |
77 return dart.runInIsolate( | |
78 code.toString(), | |
79 port.sendPort, | |
80 packageRoot: baseUrl.resolve('packages'), | |
81 snapshot: snapshot).then((_) => port.first).then((sendPort) { | |
82 return new TransformerIsolate._(sendPort, environment.mode, idsToUrls); | |
83 }).catchError((error, stackTrace) { | |
84 if (error is! CrossIsolateException) throw error; | |
85 if (error.type != 'IsolateSpawnException') throw error; | |
86 | |
87 // TODO(nweiz): don't parse this as a string once issues 12617 and 12689 | |
88 // are fixed. | |
89 var firstErrorLine = error.message.split('\n')[1]; | |
90 | |
91 // The isolate error message contains the fully expanded path, not the | |
92 // "package:" URI, so we have to be liberal in what we look for in the | |
93 // error message. | |
94 var missingTransformer = idsToUrls.keys.firstWhere( | |
95 (id) => | |
96 firstErrorLine.startsWith("Uncaught Error: Load Error: Failure g
etting ") && | |
97 firstErrorLine.contains(idsToUrls[id].path), | |
98 orElse: () => throw error); | |
99 var packageUri = idToPackageUri(idsToAssetIds[missingTransformer]); | |
100 | |
101 // If there was an IsolateSpawnException and the import that actually | |
102 // failed was the one we were loading transformers from, throw an | |
103 // application exception with a more user-friendly message. | |
104 fail('Transformer library "$packageUri" not found.', error, stackTrace); | |
105 }); | |
106 }); | |
107 } | |
108 | |
109 TransformerIsolate._(this._port, this._mode, this._idsToUrls); | |
110 | |
111 /// Instantiate the transformers in the [config.id] with | |
112 /// [config.configuration]. | |
113 /// | |
114 /// If there are no transformers defined in the given library, this will | |
115 /// return an empty set. | |
116 Future<Set<Transformer>> create(TransformerConfig config) { | |
117 return call(_port, { | |
118 'library': _idsToUrls[config.id].toString(), | |
119 'mode': _mode.name, | |
120 'configuration': JSON.encode(config.configuration) | |
121 }).then((transformers) { | |
122 transformers = transformers.map( | |
123 (transformer) => deserializeTransformerLike(transformer, config)).toSe
t(); | |
124 log.fine("Transformers from $config: $transformers"); | |
125 return transformers; | |
126 }).catchError((error, stackTrace) { | |
127 throw new TransformerLoadError(error, config.span); | |
128 }); | |
129 } | |
130 } | |
131 | |
132 /// An error thrown when a transformer fails to load. | |
133 class TransformerLoadError extends SourceSpanException implements | |
134 WrappedException { | |
135 final CrossIsolateException innerError; | |
136 Chain get innerChain => innerError.stackTrace; | |
137 | |
138 TransformerLoadError(CrossIsolateException error, SourceSpan span) | |
139 : innerError = error, | |
140 super("Error loading transformer: ${error.message}", span); | |
141 } | |
OLD | NEW |