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