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