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 barback.transform_node; | 5 library barback.transform_node; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'package:source_maps/span.dart'; | 9 import 'package:source_maps/span.dart'; |
10 | 10 |
11 import 'asset.dart'; | 11 import 'asset.dart'; |
12 import 'asset_id.dart'; | 12 import 'asset_id.dart'; |
13 import 'asset_node.dart'; | 13 import 'asset_node.dart'; |
14 import 'asset_set.dart'; | 14 import 'asset_set.dart'; |
15 import 'declaring_transform.dart'; | |
15 import 'errors.dart'; | 16 import 'errors.dart'; |
17 import 'lazy_transformer.dart'; | |
16 import 'log.dart'; | 18 import 'log.dart'; |
17 import 'phase.dart'; | 19 import 'phase.dart'; |
18 import 'transform.dart'; | 20 import 'transform.dart'; |
19 import 'transformer.dart'; | 21 import 'transformer.dart'; |
22 import 'utils.dart'; | |
20 | 23 |
21 /// Describes a transform on a set of assets and its relationship to the build | 24 /// Describes a transform on a set of assets and its relationship to the build |
22 /// dependency graph. | 25 /// dependency graph. |
23 /// | 26 /// |
24 /// Keeps track of whether it's dirty and needs to be run and which assets it | 27 /// Keeps track of whether it's dirty and needs to be run and which assets it |
25 /// depends on. | 28 /// depends on. |
26 class TransformNode { | 29 class TransformNode { |
27 /// The [Phase] that this transform runs in. | 30 /// The [Phase] that this transform runs in. |
28 final Phase phase; | 31 final Phase phase; |
29 | 32 |
30 /// The [Transformer] to apply to this node's inputs. | 33 /// The [Transformer] to apply to this node's inputs. |
31 final Transformer transformer; | 34 final Transformer transformer; |
32 | 35 |
33 /// The node for the primary asset this transform depends on. | 36 /// The node for the primary asset this transform depends on. |
34 final AssetNode primary; | 37 final AssetNode primary; |
35 | 38 |
36 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | 39 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. |
37 StreamSubscription _primarySubscription; | 40 StreamSubscription _primarySubscription; |
38 | 41 |
39 /// True if an input has been modified since the last time this transform | 42 /// True if an input has been modified since the last time this transform |
40 /// began running. | 43 /// began running. |
41 bool get isDirty => _isDirty; | 44 bool get isDirty => _isDirty; |
42 var _isDirty = true; | 45 var _isDirty = true; |
43 | 46 |
47 /// Whether [transformer] is lazy and this transform has yet to be forced. | |
48 bool _isLazy; | |
49 | |
44 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | 50 /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
45 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | 51 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); |
46 | 52 |
47 /// The controllers for the asset nodes emitted by this node. | 53 /// The controllers for the asset nodes emitted by this node. |
48 var _outputControllers = new Map<AssetId, AssetNodeController>(); | 54 var _outputControllers = new Map<AssetId, AssetNodeController>(); |
49 | 55 |
50 /// A stream that emits an event whenever this transform becomes dirty and | 56 /// A stream that emits an event whenever this transform becomes dirty and |
51 /// needs to be re-run. | 57 /// needs to be re-run. |
52 /// | 58 /// |
53 /// This may emit events when the transform was already dirty or while | 59 /// This may emit events when the transform was already dirty or while |
54 /// processing transforms. Events are emitted synchronously to ensure that the | 60 /// processing transforms. Events are emitted synchronously to ensure that the |
55 /// dirty state is thoroughly propagated as soon as any assets are changed. | 61 /// dirty state is thoroughly propagated as soon as any assets are changed. |
56 Stream get onDirty => _onDirtyController.stream; | 62 Stream get onDirty => _onDirtyController.stream; |
57 final _onDirtyController = new StreamController.broadcast(sync: true); | 63 final _onDirtyController = new StreamController.broadcast(sync: true); |
58 | 64 |
59 /// A stream that emits an event whenever this transform logs an entry. | 65 /// A stream that emits an event whenever this transform logs an entry. |
60 /// | 66 /// |
61 /// This is synchronous because error logs can cause the transform to fail, so | 67 /// This is synchronous because error logs can cause the transform to fail, so |
62 /// we need to ensure that their processing isn't delayed until after the | 68 /// we need to ensure that their processing isn't delayed until after the |
63 /// transform or build has finished. | 69 /// transform or build has finished. |
64 Stream<LogEntry> get onLog => _onLogController.stream; | 70 Stream<LogEntry> get onLog => _onLogController.stream; |
65 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); | 71 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); |
66 | 72 |
67 TransformNode(this.phase, this.transformer, this.primary) { | 73 TransformNode(this.phase, Transformer transformer, this.primary) |
74 : transformer = transformer, | |
75 _isLazy = transformer is LazyTransformer { | |
68 _primarySubscription = primary.onStateChange.listen((state) { | 76 _primarySubscription = primary.onStateChange.listen((state) { |
69 if (state.isRemoved) { | 77 if (state.isRemoved) { |
70 remove(); | 78 remove(); |
71 } else { | 79 } else { |
72 _dirty(); | 80 _dirty(); |
73 } | 81 } |
74 }); | 82 }); |
75 } | 83 } |
76 | 84 |
77 /// The [TransformInfo] describing this node. | 85 /// The [TransformInfo] describing this node. |
(...skipping 13 matching lines...) Expand all Loading... | |
91 _onDirtyController.close(); | 99 _onDirtyController.close(); |
92 _primarySubscription.cancel(); | 100 _primarySubscription.cancel(); |
93 for (var subscription in _inputSubscriptions.values) { | 101 for (var subscription in _inputSubscriptions.values) { |
94 subscription.cancel(); | 102 subscription.cancel(); |
95 } | 103 } |
96 for (var controller in _outputControllers.values) { | 104 for (var controller in _outputControllers.values) { |
97 controller.setRemoved(); | 105 controller.setRemoved(); |
98 } | 106 } |
99 } | 107 } |
100 | 108 |
109 /// If [transformer] is lazy, ensures that its concrete outputs will be | |
110 /// generated. | |
111 void force() { | |
112 // TODO(nweiz): we might want to have a timeout after which, if the | |
113 // transform's outputs have gone unused, we switch it back to lazy mode. | |
114 if (!_isLazy) return; | |
115 _isLazy = false; | |
116 _dirty(); | |
117 } | |
118 | |
101 /// Marks this transform as dirty. | 119 /// Marks this transform as dirty. |
102 /// | 120 /// |
103 /// This causes all of the transform's outputs to be marked as dirty as well. | 121 /// This causes all of the transform's outputs to be marked as dirty as well. |
104 void _dirty() { | 122 void _dirty() { |
105 _isDirty = true; | 123 _isDirty = true; |
106 for (var controller in _outputControllers.values) { | 124 for (var controller in _outputControllers.values) { |
107 controller.setDirty(); | 125 controller.setDirty(); |
108 } | 126 } |
109 _onDirtyController.add(null); | 127 _onDirtyController.add(null); |
110 } | 128 } |
111 | 129 |
112 /// Applies this transform. | 130 /// Applies this transform. |
113 /// | 131 /// |
114 /// Returns a set of asset nodes representing the outputs from this transform | 132 /// Returns a set of asset nodes representing the outputs from this transform |
115 /// that weren't emitted last time it was run. | 133 /// that weren't emitted last time it was run. |
116 Future<Set<AssetNode>> apply() { | 134 Future<Set<AssetNode>> apply() { |
117 assert(!_onDirtyController.isClosed); | 135 assert(!_onDirtyController.isClosed); |
118 | 136 |
119 var newOutputs = new AssetSet(); | |
120 var transform = createTransform(this, newOutputs, _log); | |
121 | |
122 // Clear all the old input subscriptions. If an input is re-used, we'll | 137 // Clear all the old input subscriptions. If an input is re-used, we'll |
123 // re-subscribe. | 138 // re-subscribe. |
124 for (var subscription in _inputSubscriptions.values) { | 139 for (var subscription in _inputSubscriptions.values) { |
125 subscription.cancel(); | 140 subscription.cancel(); |
126 } | 141 } |
127 _inputSubscriptions.clear(); | 142 _inputSubscriptions.clear(); |
128 | 143 |
129 _isDirty = false; | 144 _isDirty = false; |
130 | 145 |
131 return transformer.apply(transform).catchError((error, stack) { | 146 return syncFuture(() { |
147 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a | |
148 // [LazyTransformer], we can get some mileage out of doing a declarative | |
149 // first so we know how to hook up the assets. | |
150 if (_isLazy) return _declareLazy(); | |
151 return _applyImmediate(); | |
152 }).catchError((error, stackTrace) { | |
132 // If the transform became dirty while processing, ignore any errors from | 153 // If the transform became dirty while processing, ignore any errors from |
133 // it. | 154 // it. |
134 if (_isDirty) return; | 155 if (_isDirty) return new Set(); |
135 | 156 |
136 if (error is! MissingInputException) { | 157 if (error is! MissingInputException) { |
137 error = new TransformerException(info, error, stack); | 158 error = new TransformerException(info, error, stackTrace); |
138 } | 159 } |
139 | 160 |
140 // Catch all transformer errors and pipe them to the results stream. | 161 // Catch all transformer errors and pipe them to the results stream. This |
141 // This is so a broken transformer doesn't take down the whole graph. | 162 // is so a broken transformer doesn't take down the whole graph. |
142 phase.cascade.reportError(error); | 163 phase.cascade.reportError(error); |
143 | 164 |
144 // Don't allow partial results from a failed transform. | 165 return new Set(); |
145 newOutputs.clear(); | |
146 }).then((_) { | |
147 if (_isDirty) return new Set(); | |
148 | |
149 return _adjustOutputs(newOutputs); | |
150 }); | 166 }); |
151 } | 167 } |
152 | 168 |
153 /// Gets the asset for an input [id]. | 169 /// Gets the asset for an input [id]. |
154 /// | 170 /// |
155 /// If an input with that ID cannot be found, throws an | 171 /// If an input with that ID cannot be found, throws an |
156 /// [AssetNotFoundException]. | 172 /// [AssetNotFoundException]. |
157 Future<Asset> getInput(AssetId id) { | 173 Future<Asset> getInput(AssetId id) { |
158 return phase.getInput(id).then((node) { | 174 return phase.getInput(id).then((node) { |
159 // Throw if the input isn't found. This ensures the transformer's apply | 175 // Throw if the input isn't found. This ensures the transformer's apply |
(...skipping 10 matching lines...) Expand all Loading... | |
170 return asset; | 186 return asset; |
171 }).catchError((error) { | 187 }).catchError((error) { |
172 if (error is! AssetNotFoundException || error.id != id) throw error; | 188 if (error is! AssetNotFoundException || error.id != id) throw error; |
173 // If the node was removed before it could be loaded, treat it as though | 189 // If the node was removed before it could be loaded, treat it as though |
174 // it never existed and throw a MissingInputException. | 190 // it never existed and throw a MissingInputException. |
175 throw new MissingInputException(info, id); | 191 throw new MissingInputException(info, id); |
176 }); | 192 }); |
177 }); | 193 }); |
178 } | 194 } |
179 | 195 |
180 /// Adjusts the outputs of the transform to reflect the outputs emitted on its | 196 /// Applies the transform so that it produces concrete (as opposed to lazy) |
181 /// most recent run. | 197 /// outputs. |
182 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) { | 198 Future<Set<AssetNode>> _applyImmediate() { |
183 // Any ids that are for a different package are invalid. | 199 var newOutputs = new AssetSet(); |
184 var invalidIds = newOutputs | 200 var transform = new Transform(this, newOutputs, _log); |
185 .map((asset) => asset.id) | |
186 .where((id) => id.package != phase.cascade.package) | |
187 .toSet(); | |
188 for (var id in invalidIds) { | |
189 newOutputs.removeId(id); | |
190 // TODO(nweiz): report this as a warning rather than a failing error. | |
191 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
192 } | |
193 | 201 |
194 // Remove outputs that used to exist but don't anymore. | 202 return syncFuture(() => transformer.apply(transform)).then((_) { |
195 for (var id in _outputControllers.keys.toList()) { | 203 if (_isDirty) return new Set(); |
196 if (newOutputs.containsId(id)) continue; | |
197 _outputControllers.remove(id).setRemoved(); | |
198 } | |
199 | 204 |
200 var brandNewOutputs = new Set<AssetNode>(); | 205 // Any ids that are for a different package are invalid. |
201 // Store any new outputs or new contents for existing outputs. | 206 var invalidIds = newOutputs |
202 for (var asset in newOutputs) { | 207 .map((asset) => asset.id) |
203 var controller = _outputControllers[asset.id]; | 208 .where((id) => id.package != phase.cascade.package) |
204 if (controller != null) { | 209 .toSet(); |
205 controller.setAvailable(asset); | 210 for (var id in invalidIds) { |
206 } else { | 211 newOutputs.removeId(id); |
207 var controller = new AssetNodeController.available(asset, this); | 212 // TODO(nweiz): report this as a warning rather than a failing error. |
208 _outputControllers[asset.id] = controller; | 213 phase.cascade.reportError(new InvalidOutputException(info, id)); |
209 brandNewOutputs.add(controller.node); | |
210 } | 214 } |
211 } | |
212 | 215 |
213 return brandNewOutputs; | 216 // Remove outputs that used to exist but don't anymore. |
217 for (var id in _outputControllers.keys.toList()) { | |
218 if (newOutputs.containsId(id)) continue; | |
219 _outputControllers.remove(id).setRemoved(); | |
220 } | |
221 | |
222 var brandNewOutputs = new Set<AssetNode>(); | |
223 // Store any new outputs or new contents for existing outputs. | |
224 for (var asset in newOutputs) { | |
225 var controller = _outputControllers[asset.id]; | |
226 if (controller != null) { | |
227 controller.setAvailable(asset); | |
228 } else { | |
229 var controller = new AssetNodeController.available(asset, this); | |
230 _outputControllers[asset.id] = controller; | |
231 brandNewOutputs.add(controller.node); | |
232 } | |
233 } | |
234 | |
235 return brandNewOutputs; | |
236 }); | |
237 } | |
238 | |
239 /// Applies the transform in declarative mode so that it produces lazy outputs . | |
Bob Nystrom
2014/02/07 21:02:28
Long line.
nweiz
2014/02/10 20:14:04
Done.
| |
240 Future<Set<AssetNode>> _declareLazy() { | |
241 var newIds = new Set(); | |
242 var transform = new DeclaringTransform(this, newIds, _log); | |
243 | |
244 return syncFuture(() { | |
245 return (transformer as LazyTransformer).declareOutputs(transform); | |
246 }).then((_) { | |
247 if (_isDirty) return new Set(); | |
248 | |
249 var invalidIds = | |
250 newIds.where((id) => id.package != phase.cascade.package).toSet(); | |
251 for (var id in invalidIds) { | |
252 newIds.remove(id); | |
253 // TODO(nweiz): report this as a warning rather than a failing error. | |
254 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
255 } | |
256 | |
257 // Remove outputs that used to exist but don't anymore. | |
258 for (var id in _outputControllers.keys.toList()) { | |
259 if (newIds.contains(id)) continue; | |
260 _outputControllers.remove(id).setRemoved(); | |
261 } | |
262 | |
263 var brandNewOutputs = new Set<AssetNode>(); | |
264 for (var id in newIds) { | |
265 var controller = _outputControllers[id]; | |
266 if (controller != null) { | |
267 controller.setLazy(force); | |
268 } else { | |
269 var controller = new AssetNodeController.lazy(id, force, this); | |
270 _outputControllers[id] = controller; | |
271 brandNewOutputs.add(controller.node); | |
272 } | |
273 } | |
274 | |
275 return brandNewOutputs; | |
276 }); | |
214 } | 277 } |
215 | 278 |
216 void _log(AssetId asset, LogLevel level, String message, Span span) { | 279 void _log(AssetId asset, LogLevel level, String message, Span span) { |
217 // If the log isn't already associated with an asset, use the primary. | 280 // If the log isn't already associated with an asset, use the primary. |
218 if (asset == null) asset = primary.id; | 281 if (asset == null) asset = primary.id; |
219 var entry = new LogEntry(info, asset, level, message, span); | 282 var entry = new LogEntry(info, asset, level, message, span); |
220 _onLogController.add(entry); | 283 _onLogController.add(entry); |
221 } | 284 } |
222 } | 285 } |
OLD | NEW |