| 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.phase_input; | 5 library barback.phase_input; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'asset.dart'; | 10 import 'asset.dart'; |
| 11 import 'asset_forwarder.dart'; | 11 import 'asset_forwarder.dart'; |
| 12 import 'asset_node.dart'; | 12 import 'asset_node.dart'; |
| 13 import 'asset_node_set.dart'; | |
| 14 import 'errors.dart'; | 13 import 'errors.dart'; |
| 15 import 'log.dart'; | 14 import 'log.dart'; |
| 16 import 'phase.dart'; | 15 import 'phase.dart'; |
| 17 import 'stream_pool.dart'; | 16 import 'stream_pool.dart'; |
| 18 import 'transform_node.dart'; | 17 import 'transform_node.dart'; |
| 19 import 'transformer.dart'; | 18 import 'transformer.dart'; |
| 20 import 'utils.dart'; | 19 import 'utils.dart'; |
| 21 | 20 |
| 22 /// A class for watching a single [AssetNode] and running any transforms that | 21 /// A class for watching a single [AssetNode] and running any transforms that |
| 23 /// take that node as a primary input. | 22 /// take that node as a primary input. |
| (...skipping 15 matching lines...) Expand all Loading... |
| 39 final _transforms = new Set<TransformNode>(); | 38 final _transforms = new Set<TransformNode>(); |
| 40 | 39 |
| 41 /// A forwarder for the input [AssetNode] for this phase. | 40 /// A forwarder for the input [AssetNode] for this phase. |
| 42 /// | 41 /// |
| 43 /// This is used to mark the node as removed should the input ever be removed. | 42 /// This is used to mark the node as removed should the input ever be removed. |
| 44 final AssetForwarder _inputForwarder; | 43 final AssetForwarder _inputForwarder; |
| 45 | 44 |
| 46 /// The asset node for this input. | 45 /// The asset node for this input. |
| 47 AssetNode get input => _inputForwarder.node; | 46 AssetNode get input => _inputForwarder.node; |
| 48 | 47 |
| 49 /// The controller that's used for the output node if [input] isn't | 48 /// The controller that's used for the output node if [input] isn't consumed |
| 50 /// overwritten by any transformers. | 49 /// by any transformers. |
| 51 /// | 50 /// |
| 52 /// This needs an intervening controller to ensure that the output can be | 51 /// This needs an intervening controller to ensure that the output can be |
| 53 /// marked dirty when determining whether transforms will overwrite it, and be | 52 /// marked dirty when determining whether transforms apply, and removed if |
| 54 /// marked removed if they do. It's null if the asset is not being passed | 53 /// they do. It's null if the asset is not being passed through. |
| 55 /// through. | |
| 56 AssetNodeController _passThroughController; | 54 AssetNodeController _passThroughController; |
| 57 | 55 |
| 58 /// Whether [_passThroughController] has been newly created since [process] | 56 /// Whether [_passThroughController] has been newly created since [process] |
| 59 /// last completed. | 57 /// last completed. |
| 60 bool _newPassThrough = false; | 58 bool _newPassThrough = false; |
| 61 | 59 |
| 62 final _outputs = new AssetNodeSet(); | |
| 63 | |
| 64 /// A Future that will complete once the transformers that consume [input] are | 60 /// A Future that will complete once the transformers that consume [input] are |
| 65 /// determined. | 61 /// determined. |
| 66 Future _adjustTransformersFuture; | 62 Future _adjustTransformersFuture; |
| 67 | 63 |
| 68 /// A stream that emits an event whenever this input becomes dirty and needs | 64 /// A stream that emits an event whenever this input becomes dirty and needs |
| 69 /// [process] to be called. | 65 /// [process] to be called. |
| 70 /// | 66 /// |
| 71 /// This may emit events when the input was already dirty or while processing | 67 /// This may emit events when the input was already dirty or while processing |
| 72 /// transforms. Events are emitted synchronously to ensure that the dirty | 68 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 73 /// state is thoroughly propagated as soon as any assets are changed. | 69 /// state is thoroughly propagated as soon as any assets are changed. |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 // automatically pick up on [removedTransformer] being gone. | 130 // automatically pick up on [removedTransformer] being gone. |
| 135 if (_adjustTransformersFuture != null) continue; | 131 if (_adjustTransformersFuture != null) continue; |
| 136 | 132 |
| 137 _transforms.removeWhere((transform) { | 133 _transforms.removeWhere((transform) { |
| 138 if (transform.transformer != removedTransformer) return false; | 134 if (transform.transformer != removedTransformer) return false; |
| 139 transform.remove(); | 135 transform.remove(); |
| 140 return true; | 136 return true; |
| 141 }); | 137 }); |
| 142 } | 138 } |
| 143 | 139 |
| 140 if (_transforms.isEmpty && _adjustTransformersFuture == null && |
| 141 _passThroughController == null) { |
| 142 _passThroughController = new AssetNodeController.from(input); |
| 143 _newPassThrough = true; |
| 144 } |
| 145 |
| 144 var brandNewTransformers = newTransformers.difference(oldTransformers); | 146 var brandNewTransformers = newTransformers.difference(oldTransformers); |
| 145 if (brandNewTransformers.isEmpty) return; | 147 if (brandNewTransformers.isEmpty) return; |
| 146 | 148 |
| 147 brandNewTransformers.forEach(_transformers.add); | 149 brandNewTransformers.forEach(_transformers.add); |
| 148 if (_adjustTransformersFuture == null) _adjustTransformers(); | 150 if (_adjustTransformersFuture == null) _adjustTransformers(); |
| 149 } | 151 } |
| 150 | 152 |
| 151 /// Force all [LazyTransformer]s' transforms in this input to begin producing | 153 /// Force all [LazyTransformer]s' transforms in this input to begin producing |
| 152 /// concrete assets. | 154 /// concrete assets. |
| 153 void forceAllTransforms() { | 155 void forceAllTransforms() { |
| 154 for (var transform in _transforms) { | 156 for (var transform in _transforms) { |
| 155 transform.force(); | 157 transform.force(); |
| 156 } | 158 } |
| 157 } | 159 } |
| 158 | 160 |
| 159 /// Asynchronously determines which transformers can consume [input] as a | 161 /// Asynchronously determines which transformers can consume [input] as a |
| 160 /// primary input and creates transforms for them. | 162 /// primary input and creates transforms for them. |
| 161 /// | 163 /// |
| 162 /// This ensures that if [input] is modified or removed during or after the | 164 /// This ensures that if [input] is modified or removed during or after the |
| 163 /// time it takes to adjust its transformers, they're appropriately | 165 /// time it takes to adjust its transformers, they're appropriately |
| 164 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. | 166 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. |
| 165 void _adjustTransformers() { | 167 void _adjustTransformers() { |
| 166 // Mark the input as dirty. This may not actually end up creating any new | 168 // Mark the input as dirty. This may not actually end up creating any new |
| 167 // transforms, but we want adding or removing a source asset to consistently | 169 // transforms, but we want adding or removing a source asset to consistently |
| 168 // kick off a build, even if that build does nothing. | 170 // kick off a build, even if that build does nothing. |
| 169 _onDirtyController.add(null); | 171 _onDirtyController.add(null); |
| 170 | 172 |
| 171 // If there's a pass-through for this input, mark it dirty until we figure | 173 // If there's a pass-through for this input, mark it dirty while we figure |
| 172 // out if a transformer will emit an asset with that id. | 174 // out whether we need to add any transforms for it. |
| 173 if (_passThroughController != null) _passThroughController.setDirty(); | 175 if (_passThroughController != null) _passThroughController.setDirty(); |
| 174 | 176 |
| 175 // Once the input is available, hook up transformers for it. If it changes | 177 // Once the input is available, hook up transformers for it. If it changes |
| 176 // while that's happening, try again. | 178 // while that's happening, try again. |
| 177 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { | 179 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { |
| 178 var oldTransformers = | 180 var oldTransformers = |
| 179 _transforms.map((transform) => transform.transformer).toSet(); | 181 _transforms.map((transform) => transform.transformer).toSet(); |
| 180 | 182 |
| 181 return _removeStaleTransforms(asset, transformers).then((_) => | 183 return _removeStaleTransforms(asset, transformers).then((_) => |
| 182 _addFreshTransforms(transformers, oldTransformers)); | 184 _addFreshTransforms(transformers, oldTransformers)); |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 234 // We can safely access [input.asset] here even though it might have | 236 // We can safely access [input.asset] here even though it might have |
| 235 // changed since (as above) if it has, [_adjustTransformers] will just be | 237 // changed since (as above) if it has, [_adjustTransformers] will just be |
| 236 // re-run. | 238 // re-run. |
| 237 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 239 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
| 238 // results. | 240 // results. |
| 239 return transformer.isPrimary(input.asset).then((isPrimary) { | 241 return transformer.isPrimary(input.asset).then((isPrimary) { |
| 240 if (!isPrimary) return; | 242 if (!isPrimary) return; |
| 241 var transform = new TransformNode( | 243 var transform = new TransformNode( |
| 242 _phase, transformer, input, _location); | 244 _phase, transformer, input, _location); |
| 243 _transforms.add(transform); | 245 _transforms.add(transform); |
| 244 transform.onDirty.listen((_) { | |
| 245 if (_passThroughController != null) _passThroughController.setDirty(); | |
| 246 }); | |
| 247 _onDirtyPool.add(transform.onDirty); | 246 _onDirtyPool.add(transform.onDirty); |
| 248 _onLogPool.add(transform.onLog); | 247 _onLogPool.add(transform.onLog); |
| 249 }); | 248 }); |
| 250 })); | 249 })); |
| 251 } | 250 } |
| 252 | 251 |
| 252 /// Adjust whether [input] is passed through the phase unmodified, based on |
| 253 /// whether it's consumed by other transforms in this phase. |
| 254 /// |
| 255 /// If [input] was already passed-through, this will update the passed-through |
| 256 /// value. |
| 257 void _adjustPassThrough() { |
| 258 assert(input.state.isAvailable); |
| 259 |
| 260 if (_transforms.isEmpty) { |
| 261 if (_passThroughController != null) { |
| 262 _passThroughController.setAvailable(input.asset); |
| 263 } else { |
| 264 _passThroughController = new AssetNodeController.from(input); |
| 265 _newPassThrough = true; |
| 266 } |
| 267 } else if (_passThroughController != null) { |
| 268 _passThroughController.setRemoved(); |
| 269 _passThroughController = null; |
| 270 _newPassThrough = false; |
| 271 } |
| 272 } |
| 273 |
| 253 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this | 274 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this |
| 254 /// phase's transformers are modified. | 275 /// phase's transformers are modified. |
| 255 Future _tryUntilStable( | 276 Future _tryUntilStable( |
| 256 Future callback(Asset asset, Set<Transformer> transformers)) { | 277 Future callback(Asset asset, Set<Transformer> transformers)) { |
| 257 var oldTransformers; | 278 var oldTransformers; |
| 258 return input.tryUntilStable((asset) { | 279 return input.tryUntilStable((asset) { |
| 259 oldTransformers = _transformers.toSet(); | 280 oldTransformers = _transformers.toSet(); |
| 260 return callback(asset, _transformers); | 281 return callback(asset, _transformers); |
| 261 }).then((result) { | 282 }).then((result) { |
| 262 if (setEquals(oldTransformers, _transformers)) return result; | 283 if (setEquals(oldTransformers, _transformers)) return result; |
| 263 return _tryUntilStable(callback); | 284 return _tryUntilStable(callback); |
| 264 }); | 285 }); |
| 265 } | 286 } |
| 266 | 287 |
| 267 /// Processes the transforms for this input. | 288 /// Processes the transforms for this input. |
| 268 /// | 289 /// |
| 269 /// Returns the set of newly-created asset nodes that transforms have emitted | 290 /// Returns the set of newly-created asset nodes that transforms have emitted |
| 270 /// for this input. The assets returned this way are guaranteed not to be | 291 /// for this input. The assets returned this way are guaranteed not to be |
| 271 /// [AssetState.REMOVED]. | 292 /// [AssetState.REMOVED]. |
| 272 Future<Set<AssetNode>> process() { | 293 Future<Set<AssetNode>> process() { |
| 273 return _waitForTransformers(() { | 294 return _waitForTransformers(() => _processTransforms()).then((outputs) { |
| 274 if (input.state.isRemoved) return new Future.value(new Set()); | |
| 275 | |
| 276 return Future.wait(_transforms.map((transform) { | |
| 277 if (!transform.isDirty) return new Future.value(new Set()); | |
| 278 return transform.apply(); | |
| 279 })).then((outputs) => unionAll(outputs)); | |
| 280 }).then((outputs) { | |
| 281 if (input.state.isRemoved) return new Set(); | 295 if (input.state.isRemoved) return new Set(); |
| 282 | |
| 283 for (var output in outputs) { | |
| 284 assert(!output.state.isRemoved); | |
| 285 _outputs.add(output); | |
| 286 output.whenRemoved(_adjustPassThrough); | |
| 287 } | |
| 288 | |
| 289 _adjustPassThrough(); | |
| 290 if (_newPassThrough) { | |
| 291 outputs.add(_passThroughController.node); | |
| 292 _newPassThrough = false; | |
| 293 } | |
| 294 return outputs; | 296 return outputs; |
| 295 }); | 297 }); |
| 296 } | 298 } |
| 297 | 299 |
| 298 /// Runs [callback] once all the transformers are adjusted correctly and the | 300 /// Runs [callback] once all the transformers are adjusted correctly and the |
| 299 /// input is ready to be processed. | 301 /// input is ready to be processed. |
| 300 /// | 302 /// |
| 301 /// If the transformers are already properly adjusted, [callback] is called | 303 /// If the transformers are already properly adjusted, [callback] is called |
| 302 /// synchronously to ensure that [_adjustTransformers] isn't called before the | 304 /// synchronously to ensure that [_adjustTransformers] isn't called before the |
| 303 /// callback. | 305 /// callback. |
| 304 Future _waitForTransformers(callback()) { | 306 Future _waitForTransformers(callback()) { |
| 305 if (_adjustTransformersFuture == null) return syncFuture(callback); | 307 if (_adjustTransformersFuture == null) return syncFuture(callback); |
| 306 return _adjustTransformersFuture.then( | 308 return _adjustTransformersFuture.then( |
| 307 (_) => _waitForTransformers(callback)); | 309 (_) => _waitForTransformers(callback)); |
| 308 } | 310 } |
| 309 | 311 |
| 310 /// Adjust whether [input] is passed through the phase unmodified, based on | 312 /// Applies all currently wired up and dirty transforms. |
| 311 /// whether it's overwritten by other transforms in this phase. | 313 Future<Set<AssetNode>> _processTransforms() { |
| 312 /// | 314 if (input.state.isRemoved) return new Future.value(new Set()); |
| 313 /// If [input] was already passed-through, this will update the passed-through | |
| 314 /// value. | |
| 315 void _adjustPassThrough() { | |
| 316 if (!input.state.isAvailable) return; | |
| 317 | 315 |
| 318 // If there's an output with the same id as the primary input, that | 316 if (_passThroughController != null) { |
| 319 // overwrites the input so it doesn't get passed through. Otherwise, | 317 if (!_newPassThrough) return new Future.value(new Set()); |
| 320 // create a pass-through controller if none exists, or set the existing | 318 _newPassThrough = false; |
| 321 // one available. | 319 return new Future.value( |
| 322 if (_outputs.any((output) => output.id == input.id)) { | 320 new Set<AssetNode>.from([_passThroughController.node])); |
| 323 if (_passThroughController != null) { | |
| 324 _passThroughController.setRemoved(); | |
| 325 _passThroughController = null; | |
| 326 _newPassThrough = false; | |
| 327 } | |
| 328 } else if (_passThroughController == null) { | |
| 329 _passThroughController = new AssetNodeController.from(input); | |
| 330 _newPassThrough = true; | |
| 331 } else if (_passThroughController.node.state.isDirty) { | |
| 332 _passThroughController.setAvailable(input.asset); | |
| 333 } | 321 } |
| 322 |
| 323 return Future.wait(_transforms.map((transform) { |
| 324 if (!transform.isDirty) return new Future.value(new Set()); |
| 325 return transform.apply(); |
| 326 })).then((outputs) => unionAll(outputs)); |
| 334 } | 327 } |
| 335 | 328 |
| 336 String toString() => "phase input in $_location for $input"; | 329 String toString() => "phase input in $_location for $input"; |
| 337 } | 330 } |
| OLD | NEW |