| 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 'asset.dart'; | 9 import 'asset.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 12 import 'asset_set.dart'; | 12 import 'asset_set.dart'; |
| 13 import 'errors.dart'; | 13 import 'errors.dart'; |
| 14 import 'phase.dart'; | 14 import 'phase.dart'; |
| 15 import 'transform.dart'; | 15 import 'transform.dart'; |
| 16 import 'transformer.dart'; | 16 import 'transformer.dart'; |
| 17 import 'utils.dart'; |
| 17 | 18 |
| 18 /// Describes a transform on a set of assets and its relationship to the build | 19 /// Describes a transform on a set of assets and its relationship to the build |
| 19 /// dependency graph. | 20 /// dependency graph. |
| 20 /// | 21 /// |
| 21 /// Keeps track of whether it's dirty and needs to be run and which assets it | 22 /// Keeps track of whether it's dirty and needs to be run and which assets it |
| 22 /// depends on. | 23 /// depends on. |
| 23 class TransformNode { | 24 class TransformNode { |
| 24 /// The [Phase] that this transform runs in. | 25 /// The [Phase] that this transform runs in. |
| 25 final Phase phase; | 26 final Phase phase; |
| 26 | 27 |
| 27 /// The [Transformer] to apply to this node's inputs. | 28 /// The [Transformer] to apply to this node's inputs. |
| 28 final Transformer transformer; | 29 final Transformer transformer; |
| 29 | 30 |
| 30 /// The node for the primary asset this transform depends on. | 31 /// The node for the primary asset this transform depends on. |
| 31 final AssetNode primary; | 32 final AssetNode primary; |
| 32 | 33 |
| 33 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | 34 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. |
| 34 StreamSubscription _primarySubscription; | 35 StreamSubscription _primarySubscription; |
| 35 | 36 |
| 36 /// True if an input has been modified since the last time this transform | 37 /// True if an input has been modified since the last time this transform |
| 37 /// began running. | 38 /// began running. |
| 38 bool get isDirty => _isDirty; | 39 bool get isDirty => _isDirty; |
| 39 var _isDirty = true; | 40 var _isDirty = true; |
| 40 | 41 |
| 41 /// The inputs read by this transform the last time it was run. | |
| 42 /// | |
| 43 /// Used to tell if an input was added or removed in a later run. | |
| 44 var _inputs = new Set<AssetNode>(); | |
| 45 | |
| 46 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | 42 /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
| 47 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | 43 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); |
| 48 | 44 |
| 49 /// The controllers for the asset nodes emitted by this node. | 45 /// The controllers for the asset nodes emitted by this node. |
| 50 var _outputControllers = new Map<AssetId, AssetNodeController>(); | 46 var _outputControllers = new Map<AssetId, AssetNodeController>(); |
| 51 | 47 |
| 52 TransformNode(this.phase, this.transformer, this.primary) { | 48 TransformNode(this.phase, this.transformer, this.primary) { |
| 53 _primarySubscription = primary.onStateChange.listen((state) { | 49 _primarySubscription = primary.onStateChange.listen((state) { |
| 54 if (state.isRemoved) { | 50 if (state.isRemoved) { |
| 55 remove(); | 51 remove(); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 84 for (var controller in _outputControllers.values) { | 80 for (var controller in _outputControllers.values) { |
| 85 controller.setDirty(); | 81 controller.setDirty(); |
| 86 } | 82 } |
| 87 } | 83 } |
| 88 | 84 |
| 89 /// Applies this transform. | 85 /// Applies this transform. |
| 90 /// | 86 /// |
| 91 /// Returns a set of asset nodes representing the outputs from this transform | 87 /// Returns a set of asset nodes representing the outputs from this transform |
| 92 /// that weren't emitted last time it was run. | 88 /// that weren't emitted last time it was run. |
| 93 Future<Set<AssetNode>> apply() { | 89 Future<Set<AssetNode>> apply() { |
| 94 var newInputs = new Set<AssetNode>(); | |
| 95 var newOutputs = new AssetSet(); | 90 var newOutputs = new AssetSet(); |
| 96 var transform = createTransform(this, newInputs, newOutputs); | 91 var transform = createTransform(this, newOutputs); |
| 92 |
| 93 // Clear all the old input subscriptions. If an input is re-used, we'll |
| 94 // re-subscribe. |
| 95 for (var subscription in _inputSubscriptions.values) { |
| 96 subscription.cancel(); |
| 97 } |
| 98 _inputSubscriptions.clear(); |
| 99 |
| 97 _isDirty = false; | 100 _isDirty = false; |
| 98 return transformer.apply(transform).catchError((error) { | 101 return transformer.apply(transform).catchError((error) { |
| 99 // If the transform became dirty while processing, ignore any errors from | 102 // If the transform became dirty while processing, ignore any errors from |
| 100 // it. | 103 // it. |
| 101 if (_isDirty) return; | 104 if (_isDirty) return; |
| 102 | 105 |
| 103 // Catch all transformer errors and pipe them to the results stream. | 106 // Catch all transformer errors and pipe them to the results stream. |
| 104 // This is so a broken transformer doesn't take down the whole graph. | 107 // This is so a broken transformer doesn't take down the whole graph. |
| 105 phase.cascade.reportError(error); | 108 phase.cascade.reportError(error); |
| 106 | 109 |
| 107 // Don't allow partial results from a failed transform. | 110 // Don't allow partial results from a failed transform. |
| 108 newOutputs.clear(); | 111 newOutputs.clear(); |
| 109 }).then((_) { | 112 }).then((_) { |
| 110 if (_isDirty) return []; | 113 if (_isDirty) return []; |
| 111 | 114 |
| 112 _adjustInputs(newInputs); | |
| 113 return _adjustOutputs(newOutputs); | 115 return _adjustOutputs(newOutputs); |
| 114 }); | 116 }); |
| 115 } | 117 } |
| 116 | 118 |
| 117 /// Adjusts the inputs of the transform to reflect the inputs consumed on its | 119 Future<Asset> getInput(AssetId id) { |
| 118 /// most recent run. | 120 return newFuture(() { |
| 119 void _adjustInputs(Set<AssetNode> newInputs) { | 121 var node = phase.inputs[id]; |
| 120 // Stop watching any inputs that were removed. | 122 // TODO(rnystrom): Need to handle passthrough where an asset from a |
| 121 for (var oldInput in _inputs.difference(newInputs)) { | 123 // previous phase can be found. |
| 122 _inputSubscriptions.remove(oldInput.id).cancel(); | |
| 123 } | |
| 124 | 124 |
| 125 // Watch any new inputs so this transform will be re-processed when an | 125 // Throw if the input isn't found. This ensures the transformer's apply |
| 126 // input is modified. | 126 // is exited. We'll then catch this and report it through the proper |
| 127 for (var newInput in newInputs.difference(_inputs)) { | 127 // results stream. |
| 128 if (newInput.id == primary.id) continue; | 128 if (node == null) throw new MissingInputException(id); |
| 129 // TODO(nweiz): support the case where a new secondary input changes | |
| 130 // after it's been loaded by the transform but before the transform has | |
| 131 // finished running. | |
| 132 _inputSubscriptions[newInput.id] = newInput.onStateChange | |
| 133 .listen((_) => _dirty()); | |
| 134 } | |
| 135 | 129 |
| 136 _inputs = newInputs; | 130 // If the asset node is found, wait until its contents are actually |
| 131 // available before we return them. |
| 132 return node.whenAvailable.then((asset) { |
| 133 _inputSubscriptions.putIfAbsent(node.id, |
| 134 () => node.onStateChange.listen((_) => _dirty())); |
| 135 |
| 136 return asset; |
| 137 }).catchError((error) { |
| 138 if (error is! AssetNotFoundException || error.id != id) throw error; |
| 139 // If the node was removed before it could be loaded, treat it as though |
| 140 // it never existed and throw a MissingInputException. |
| 141 throw new MissingInputException(id); |
| 142 }); |
| 143 }); |
| 137 } | 144 } |
| 138 | 145 |
| 139 /// Adjusts the outputs of the transform to reflect the outputs emitted on its | 146 /// Adjusts the outputs of the transform to reflect the outputs emitted on its |
| 140 /// most recent run. | 147 /// most recent run. |
| 141 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) { | 148 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) { |
| 142 // Any ids that are for a different package are invalid. | 149 // Any ids that are for a different package are invalid. |
| 143 var invalidIds = newOutputs | 150 var invalidIds = newOutputs |
| 144 .map((asset) => asset.id) | 151 .map((asset) => asset.id) |
| 145 .where((id) => id.package != phase.cascade.package) | 152 .where((id) => id.package != phase.cascade.package) |
| 146 .toSet(); | 153 .toSet(); |
| (...skipping 19 matching lines...) Expand all Loading... |
| 166 } else { | 173 } else { |
| 167 var controller = new AssetNodeController.available(asset); | 174 var controller = new AssetNodeController.available(asset); |
| 168 _outputControllers[asset.id] = controller; | 175 _outputControllers[asset.id] = controller; |
| 169 brandNewOutputs.add(controller.node); | 176 brandNewOutputs.add(controller.node); |
| 170 } | 177 } |
| 171 } | 178 } |
| 172 | 179 |
| 173 return brandNewOutputs; | 180 return brandNewOutputs; |
| 174 } | 181 } |
| 175 } | 182 } |
| OLD | NEW |