| 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 |