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 barback.transform_node; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import 'asset.dart'; | |
10 import 'asset_id.dart'; | |
11 import 'asset_node.dart'; | |
12 import 'declaring_transform.dart'; | |
13 import 'declaring_transformer.dart'; | |
14 import 'errors.dart'; | |
15 import 'lazy_transformer.dart'; | |
16 import 'log.dart'; | |
17 import 'node_status.dart'; | |
18 import 'node_streams.dart'; | |
19 import 'phase.dart'; | |
20 import 'transform.dart'; | |
21 import 'transformer.dart'; | |
22 import 'utils.dart'; | |
23 | |
24 /// Describes a transform on a set of assets and its relationship to the build | |
25 /// dependency graph. | |
26 /// | |
27 /// Keeps track of whether it's dirty and needs to be run and which assets it | |
28 /// depends on. | |
29 class TransformNode { | |
30 /// The [Phase] that this transform runs in. | |
31 final Phase phase; | |
32 | |
33 /// The [Transformer] to apply to this node's inputs. | |
34 final Transformer transformer; | |
35 | |
36 /// The node for the primary asset this transform depends on. | |
37 final AssetNode primary; | |
38 | |
39 /// A string describing the location of [this] in the transformer graph. | |
40 final String _location; | |
41 | |
42 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | |
43 StreamSubscription _primarySubscription; | |
44 | |
45 /// The subscription to [phase]'s [Phase.onAsset] stream. | |
46 StreamSubscription<AssetNode> _phaseSubscription; | |
47 | |
48 /// How far along [this] is in processing its assets. | |
49 NodeStatus get status { | |
50 if (_state == _State.APPLIED || _state == _State.DECLARED) { | |
51 return NodeStatus.IDLE; | |
52 } | |
53 | |
54 if (_declaring && _state != _State.DECLARING) { | |
55 return NodeStatus.MATERIALIZING; | |
56 } else { | |
57 return NodeStatus.RUNNING; | |
58 } | |
59 } | |
60 | |
61 /// Whether this is a declaring transform. | |
62 /// | |
63 /// This is usually identical to `transformer is DeclaringTransformer`, but if | |
64 /// a declaring and non-lazy transformer emits an error during | |
65 /// `declareOutputs` it's treated as though it wasn't declaring. | |
66 bool get _declaring => transformer is DeclaringTransformer && | |
67 (_state == _State.DECLARING || _declaredOutputs != null); | |
68 | |
69 /// Whether this transform has been forced since it last finished applying. | |
70 /// | |
71 /// A transform being forced means it should run until it generates outputs | |
72 /// and is no longer dirty. This is always true for non-declaring | |
73 /// transformers, since they always need to eagerly generate outputs. | |
74 bool _forced; | |
75 | |
76 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | |
77 final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | |
78 | |
79 /// The controllers for the asset nodes emitted by this node. | |
80 final _outputControllers = new Map<AssetId, AssetNodeController>(); | |
81 | |
82 /// The ids of inputs the transformer tried and failed to read last time it | |
83 /// ran. | |
84 final _missingInputs = new Set<AssetId>(); | |
85 | |
86 /// The controller that's used to pass [primary] through [this] if it's not | |
87 /// consumed or overwritten. | |
88 /// | |
89 /// This needs an intervening controller to ensure that the output can be | |
90 /// marked dirty when determining whether [this] will consume or overwrite it, | |
91 /// and be marked removed if it does. [_passThroughController] will be null | |
92 /// if the asset is not being passed through. | |
93 AssetNodeController _passThroughController; | |
94 | |
95 /// The asset node for this transform. | |
96 final _streams = new NodeStreams(); | |
97 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | |
98 Stream<AssetNode> get onAsset => _streams.onAsset; | |
99 Stream<LogEntry> get onLog => _streams.onLog; | |
100 | |
101 /// The current state of [this]. | |
102 var _state = _State.DECLARING; | |
103 | |
104 /// Whether [this] has been marked as removed. | |
105 bool get _isRemoved => _streams.onAssetController.isClosed; | |
106 | |
107 // If [transformer] is declaring but not lazy and [primary] is available, we | |
108 // can run [apply] even if [force] hasn't been called, since [transformer] | |
109 // should run eagerly if possible. | |
110 bool get _canRunDeclaringEagerly => | |
111 _declaring && transformer is! LazyTransformer && | |
112 primary.state.isAvailable; | |
113 | |
114 /// Whether the most recent run of this transform has declared that it | |
115 /// consumes the primary input. | |
116 /// | |
117 /// Defaults to `false`. This is not meaningful unless [_state] is | |
118 /// [_State.APPLIED] or [_State.DECLARED]. | |
119 bool _consumePrimary = false; | |
120 | |
121 /// The set of output ids that [transformer] declared it would emit. | |
122 /// | |
123 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its | |
124 /// [declareOutputs] has been run successfully. | |
125 Set<AssetId> _declaredOutputs; | |
126 | |
127 TransformNode(this.phase, Transformer transformer, AssetNode primary, | |
128 this._location) | |
129 : transformer = transformer, | |
130 primary = primary { | |
131 _forced = transformer is! DeclaringTransformer; | |
132 if (_forced) primary.force(); | |
133 | |
134 _primarySubscription = primary.onStateChange.listen((state) { | |
135 if (state.isRemoved) { | |
136 remove(); | |
137 } else { | |
138 if (_forced) primary.force(); | |
139 _dirty(); | |
140 } | |
141 }); | |
142 | |
143 _phaseSubscription = phase.previous.onAsset.listen((node) { | |
144 if (!_missingInputs.contains(node.id)) return; | |
145 if (_forced) node.force(); | |
146 _dirty(); | |
147 }); | |
148 | |
149 _declareOutputs().then((_) { | |
150 if (_forced || _canRunDeclaringEagerly) { | |
151 _apply(); | |
152 } else { | |
153 _state = _State.DECLARED; | |
154 _streams.changeStatus(NodeStatus.IDLE); | |
155 } | |
156 }); | |
157 } | |
158 | |
159 /// The [TransformInfo] describing this node. | |
160 /// | |
161 /// [TransformInfo] is the publicly-visible representation of a transform | |
162 /// node. | |
163 TransformInfo get info => new TransformInfo(transformer, primary.id); | |
164 | |
165 /// Marks this transform as removed. | |
166 /// | |
167 /// This causes all of the transform's outputs to be marked as removed as | |
168 /// well. Normally this will be automatically done internally based on events | |
169 /// from the primary input, but it's possible for a transform to no longer be | |
170 /// valid even if its primary input still exists. | |
171 void remove() { | |
172 _streams.close(); | |
173 _primarySubscription.cancel(); | |
174 _phaseSubscription.cancel(); | |
175 _clearInputSubscriptions(); | |
176 _clearOutputs(); | |
177 if (_passThroughController != null) { | |
178 _passThroughController.setRemoved(); | |
179 _passThroughController = null; | |
180 } | |
181 } | |
182 | |
183 /// If [this] is deferred, ensures that its concrete outputs will be | |
184 /// generated. | |
185 void force() { | |
186 if (_forced || _state == _State.APPLIED) return; | |
187 primary.force(); | |
188 _forced = true; | |
189 if (_state == _State.DECLARED) _dirty(); | |
190 } | |
191 | |
192 /// Marks this transform as dirty. | |
193 /// | |
194 /// This causes all of the transform's outputs to be marked as dirty as well. | |
195 void _dirty() { | |
196 // If we're in the process of running [declareOutputs], we already know that | |
197 // [apply] needs to be run so there's nothing we need to mark as dirty. | |
198 if (_state == _State.DECLARING) return; | |
199 | |
200 if (!_forced && !_canRunDeclaringEagerly) { | |
201 // [forced] should only ever be false for a declaring transformer. | |
202 assert(_declaring); | |
203 | |
204 // If we've finished applying, transition to MATERIALIZING, indicating | |
205 // that we know what outputs [apply] will emit but we're waiting to emit | |
206 // them concretely until [force] is called. If we're still applying, we'll | |
207 // transition to MATERIALIZING once we finish. | |
208 if (_state == _State.APPLIED) _state = _State.DECLARED; | |
209 for (var controller in _outputControllers.values) { | |
210 controller.setLazy(force); | |
211 } | |
212 _emitDeclaredOutputs(); | |
213 return; | |
214 } | |
215 | |
216 if (_passThroughController != null) _passThroughController.setDirty(); | |
217 for (var controller in _outputControllers.values) { | |
218 controller.setDirty(); | |
219 } | |
220 | |
221 if (_state == _State.APPLIED) { | |
222 if (_declaredOutputs != null) _emitDeclaredOutputs(); | |
223 _apply(); | |
224 } else if (_state == _State.DECLARED) { | |
225 _apply(); | |
226 } else { | |
227 _state = _State.NEEDS_APPLY; | |
228 } | |
229 } | |
230 | |
231 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty | |
232 /// assets. | |
233 Future _declareOutputs() { | |
234 if (transformer is! DeclaringTransformer) return new Future.value(); | |
235 | |
236 var controller = new DeclaringTransformController(this); | |
237 return syncFuture(() { | |
238 return (transformer as DeclaringTransformer) | |
239 .declareOutputs(controller.transform); | |
240 }).then((_) { | |
241 if (_isRemoved) return; | |
242 if (controller.loggedError) return; | |
243 | |
244 _consumePrimary = controller.consumePrimary; | |
245 _declaredOutputs = controller.outputIds; | |
246 var invalidIds = _declaredOutputs | |
247 .where((id) => id.package != phase.cascade.package).toSet(); | |
248 for (var id in invalidIds) { | |
249 _declaredOutputs.remove(id); | |
250 // TODO(nweiz): report this as a warning rather than a failing error. | |
251 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
252 } | |
253 | |
254 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); | |
255 _emitDeclaredOutputs(); | |
256 }).catchError((error, stackTrace) { | |
257 if (_isRemoved) return; | |
258 if (transformer is! LazyTransformer) _forced = true; | |
259 phase.cascade.reportError(_wrapException(error, stackTrace)); | |
260 }); | |
261 } | |
262 | |
263 /// Emits a dirty asset node for all outputs that were declared by the | |
264 /// transformer. | |
265 /// | |
266 /// This won't emit any outputs for which there already exist output | |
267 /// controllers. It should only be called for transforms that have declared | |
268 /// their outputs. | |
269 void _emitDeclaredOutputs() { | |
270 assert(_declaredOutputs != null); | |
271 for (var id in _declaredOutputs) { | |
272 if (_outputControllers.containsKey(id)) continue; | |
273 var controller = _forced | |
274 ? new AssetNodeController(id, this) | |
275 : new AssetNodeController.lazy(id, force, this); | |
276 _outputControllers[id] = controller; | |
277 _streams.onAssetController.add(controller.node); | |
278 } | |
279 } | |
280 | |
281 /// Applies this transform. | |
282 void _apply() { | |
283 assert(!_isRemoved); | |
284 | |
285 // Clear input subscriptions here as well as in [_process] because [_apply] | |
286 // may be restarted independently if only a secondary input changes. | |
287 _clearInputSubscriptions(); | |
288 _state = _State.APPLYING; | |
289 _streams.changeStatus(status); | |
290 _runApply().then((hadError) { | |
291 if (_isRemoved) return; | |
292 | |
293 if (_state == _State.DECLARED) return; | |
294 | |
295 if (_state == _State.NEEDS_APPLY) { | |
296 _apply(); | |
297 return; | |
298 } | |
299 | |
300 if (_declaring) _forced = false; | |
301 | |
302 assert(_state == _State.APPLYING); | |
303 if (hadError) { | |
304 _clearOutputs(); | |
305 // If the transformer threw an error, we don't want to emit the | |
306 // pass-through asset in case it will be overwritten by the transformer. | |
307 // However, if the transformer declared that it wouldn't overwrite or | |
308 // consume the pass-through asset, we can safely emit it. | |
309 if (_declaredOutputs != null && !_consumePrimary && | |
310 !_declaredOutputs.contains(primary.id)) { | |
311 _emitPassThrough(); | |
312 } else { | |
313 _dontEmitPassThrough(); | |
314 } | |
315 } | |
316 | |
317 _state = _State.APPLIED; | |
318 _streams.changeStatus(NodeStatus.IDLE); | |
319 }); | |
320 } | |
321 | |
322 /// Gets the asset for an input [id]. | |
323 /// | |
324 /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. | |
325 Future<Asset> getInput(AssetId id) { | |
326 return phase.previous.getOutput(id).then((node) { | |
327 // Throw if the input isn't found. This ensures the transformer's apply | |
328 // is exited. We'll then catch this and report it through the proper | |
329 // results stream. | |
330 if (node == null) { | |
331 _missingInputs.add(id); | |
332 throw new AssetNotFoundException(id); | |
333 } | |
334 | |
335 _inputSubscriptions.putIfAbsent(node.id, () { | |
336 return node.onStateChange.listen((state) => _dirty()); | |
337 }); | |
338 | |
339 return node.asset; | |
340 }); | |
341 } | |
342 | |
343 /// Run [Transformer.apply] as soon as [primary] is available. | |
344 /// | |
345 /// Returns whether or not an error occurred while running the transformer. | |
346 Future<bool> _runApply() { | |
347 var transformController = new TransformController(this); | |
348 _streams.onLogPool.add(transformController.onLog); | |
349 | |
350 return primary.whenAvailable((_) { | |
351 if (_isRemoved) return null; | |
352 _state = _State.APPLYING; | |
353 return syncFuture(() => transformer.apply(transformController.transform)); | |
354 }).then((_) { | |
355 if (!_forced && !primary.state.isAvailable) { | |
356 _state = _State.DECLARED; | |
357 _streams.changeStatus(NodeStatus.IDLE); | |
358 return false; | |
359 } | |
360 | |
361 if (_isRemoved) return false; | |
362 if (_state == _State.NEEDS_APPLY) return false; | |
363 if (_state == _State.DECLARING) return false; | |
364 if (transformController.loggedError) return true; | |
365 _handleApplyResults(transformController); | |
366 return false; | |
367 }).catchError((error, stackTrace) { | |
368 // If the transform became dirty while processing, ignore any errors from | |
369 // it. | |
370 if (_state == _State.NEEDS_APPLY || _isRemoved) return false; | |
371 | |
372 // Catch all transformer errors and pipe them to the results stream. This | |
373 // is so a broken transformer doesn't take down the whole graph. | |
374 phase.cascade.reportError(_wrapException(error, stackTrace)); | |
375 return true; | |
376 }); | |
377 } | |
378 | |
379 /// Handle the results of running [Transformer.apply]. | |
380 /// | |
381 /// [transformController] should be the controller for the [Transform] passed | |
382 /// to [Transformer.apply]. | |
383 void _handleApplyResults(TransformController transformController) { | |
384 _consumePrimary = transformController.consumePrimary; | |
385 | |
386 var newOutputs = transformController.outputs; | |
387 // Any ids that are for a different package are invalid. | |
388 var invalidIds = newOutputs | |
389 .map((asset) => asset.id) | |
390 .where((id) => id.package != phase.cascade.package) | |
391 .toSet(); | |
392 for (var id in invalidIds) { | |
393 newOutputs.removeId(id); | |
394 // TODO(nweiz): report this as a warning rather than a failing error. | |
395 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
396 } | |
397 | |
398 // Remove outputs that used to exist but don't anymore. | |
399 for (var id in _outputControllers.keys.toList()) { | |
400 if (newOutputs.containsId(id)) continue; | |
401 _outputControllers.remove(id).setRemoved(); | |
402 } | |
403 | |
404 // Emit or stop emitting the pass-through asset between removing and | |
405 // adding outputs to ensure there are no collisions. | |
406 if (!_consumePrimary && !newOutputs.containsId(primary.id)) { | |
407 _emitPassThrough(); | |
408 } else { | |
409 _dontEmitPassThrough(); | |
410 } | |
411 | |
412 // Store any new outputs or new contents for existing outputs. | |
413 for (var asset in newOutputs) { | |
414 var controller = _outputControllers[asset.id]; | |
415 if (controller != null) { | |
416 controller.setAvailable(asset); | |
417 } else { | |
418 var controller = new AssetNodeController.available(asset, this); | |
419 _outputControllers[asset.id] = controller; | |
420 _streams.onAssetController.add(controller.node); | |
421 } | |
422 } | |
423 } | |
424 | |
425 /// Cancels all subscriptions to secondary input nodes. | |
426 void _clearInputSubscriptions() { | |
427 _missingInputs.clear(); | |
428 for (var subscription in _inputSubscriptions.values) { | |
429 subscription.cancel(); | |
430 } | |
431 _inputSubscriptions.clear(); | |
432 } | |
433 | |
434 /// Removes all output assets. | |
435 void _clearOutputs() { | |
436 // Remove all the previously-emitted assets. | |
437 for (var controller in _outputControllers.values) { | |
438 controller.setRemoved(); | |
439 } | |
440 _outputControllers.clear(); | |
441 } | |
442 | |
443 /// Emit the pass-through asset if it's not being emitted already. | |
444 void _emitPassThrough() { | |
445 assert(!_outputControllers.containsKey(primary.id)); | |
446 | |
447 if (_consumePrimary) return; | |
448 if (_passThroughController == null) { | |
449 _passThroughController = new AssetNodeController.from(primary); | |
450 _streams.onAssetController.add(_passThroughController.node); | |
451 } else if (primary.state.isDirty) { | |
452 _passThroughController.setDirty(); | |
453 } else if (!_passThroughController.node.state.isAvailable) { | |
454 _passThroughController.setAvailable(primary.asset); | |
455 } | |
456 } | |
457 | |
458 /// Stop emitting the pass-through asset if it's being emitted already. | |
459 void _dontEmitPassThrough() { | |
460 if (_passThroughController == null) return; | |
461 _passThroughController.setRemoved(); | |
462 _passThroughController = null; | |
463 } | |
464 | |
465 BarbackException _wrapException(error, StackTrace stackTrace) { | |
466 if (error is! AssetNotFoundException) { | |
467 return new TransformerException(info, error, stackTrace); | |
468 } else { | |
469 return new MissingInputException(info, error.id); | |
470 } | |
471 } | |
472 | |
473 /// Emit a warning about the transformer on [id]. | |
474 void _warn(String message) { | |
475 _streams.onLogController.add( | |
476 new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); | |
477 } | |
478 | |
479 String toString() => | |
480 "transform node in $_location for $transformer on $primary ($_state, " | |
481 "$status, ${_forced ? '' : 'un'}forced)"; | |
482 } | |
483 | |
484 /// The enum of states that [TransformNode] can be in. | |
485 class _State { | |
486 /// The transform is running [DeclaringTransformer.declareOutputs]. | |
487 /// | |
488 /// This is the initial state of the transformer, and it will only occur once | |
489 /// since [DeclaringTransformer.declareOutputs] is independent of the contents | |
490 /// of the primary input. Once the method finishes running, this will | |
491 /// transition to [APPLYING] if the transform is non-lazy and the input is | |
492 /// available, and [DECLARED] otherwise. | |
493 /// | |
494 /// Non-declaring transformers will transition out of this state and into | |
495 /// [APPLYING] immediately. | |
496 static final DECLARING = const _State._("declaring outputs"); | |
497 | |
498 /// The transform is deferred and has run | |
499 /// [DeclaringTransformer.declareOutputs] but hasn't yet been forced. | |
500 /// | |
501 /// This will transition to [APPLYING] when one of the outputs has been | |
502 /// forced. | |
503 static final DECLARED = const _State._("declared"); | |
504 | |
505 /// The transform is running [Transformer.apply]. | |
506 /// | |
507 /// If an input changes while in this state, it will transition to | |
508 /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when | |
509 /// [Transformer.apply] finishes running, it will transition to [APPLIED]. | |
510 static final APPLYING = const _State._("applying"); | |
511 | |
512 /// The transform is running [Transformer.apply], but an input changed after | |
513 /// it started, so it will need to re-run [Transformer.apply]. | |
514 /// | |
515 /// This will transition to [APPLYING] once [Transformer.apply] finishes | |
516 /// running. | |
517 static final NEEDS_APPLY = const _State._("needs apply"); | |
518 | |
519 /// The transform has finished running [Transformer.apply], whether or not it | |
520 /// emitted an error. | |
521 /// | |
522 /// If the transformer is deferred, the [TransformNode] can also be in this | |
523 /// state when [Transformer.declareOutputs] has been run but | |
524 /// [Transformer.apply] has not. | |
525 /// | |
526 /// If an input changes, this will transition to [DECLARED] if the transform | |
527 /// is deferred and [APPLYING] otherwise. | |
528 static final APPLIED = const _State._("applied"); | |
529 | |
530 final String name; | |
531 | |
532 const _State._(this.name); | |
533 | |
534 String toString() => name; | |
535 } | |
OLD | NEW |