| OLD | NEW |
| (Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library barback.phase_input; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 |
| 10 import 'asset.dart'; |
| 11 import 'asset_forwarder.dart'; |
| 12 import 'asset_node.dart'; |
| 13 import 'errors.dart'; |
| 14 import 'stream_pool.dart'; |
| 15 import 'transform_node.dart'; |
| 16 import 'transformer.dart'; |
| 17 import 'utils.dart'; |
| 18 |
| 19 /// A class for watching a single [AssetNode] and running any transforms that |
| 20 /// take that node as a primary input. |
| 21 class PhaseInput { |
| 22 /// The phase for which this is an input. |
| 23 final Phase _phase; |
| 24 |
| 25 /// The transformers to (potentially) run against [input]. |
| 26 final Set<Transformer> _transformers; |
| 27 |
| 28 /// The transforms currently applicable to [input]. |
| 29 /// |
| 30 /// These are the transforms that have been "wired up": they represent a |
| 31 /// repeatable transformation of a single concrete set of inputs. "dart2js" is |
| 32 /// a transformer. "dart2js on web/main.dart" is a transform. |
| 33 final _transforms = new Set<TransformNode>(); |
| 34 |
| 35 /// A forwarder for the input [AssetNode] for this phase. |
| 36 /// |
| 37 /// This is used to mark the node as removed should the input ever be removed. |
| 38 final AssetForwarder _inputForwarder; |
| 39 |
| 40 /// The asset node for this input. |
| 41 AssetNode get input => _inputForwarder.node; |
| 42 |
| 43 /// The controller that's used for the output node if [input] isn't consumed |
| 44 /// by any transformers. |
| 45 /// |
| 46 /// This needs an intervening controller to ensure that the output can be |
| 47 /// marked dirty when determining whether transforms apply, and removed if |
| 48 /// they do. It's null if the asset is not being passed through. |
| 49 AssetNodeController _passThroughController; |
| 50 |
| 51 /// Whether [_passThroughController] has been newly created since [process] |
| 52 /// last completed. |
| 53 bool _newPassThrough = false; |
| 54 |
| 55 /// A Future that will complete once the transformers that consume [input] are |
| 56 /// determined. |
| 57 Future _adjustTransformersFuture; |
| 58 |
| 59 /// A stream that emits an event whenever this input becomes dirty and needs |
| 60 /// [process] to be called. |
| 61 /// |
| 62 /// This may emit events when the input was already dirty or while processing |
| 63 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 64 /// state is thoroughly propagated as soon as any assets are changed. |
| 65 Stream get onDirty => _onDirtyPool.stream; |
| 66 final _onDirtyPool = new StreamPool.broadcast(); |
| 67 |
| 68 /// A controller whose stream feeds into [_onDirtyPool]. |
| 69 /// |
| 70 /// This is used whenever the input is changed or removed. It's sometimes |
| 71 /// redundant with the events collected from [_transforms], but this stream is |
| 72 /// necessary for removed inputs, and the transform stream is necessary for |
| 73 /// modified secondary inputs. |
| 74 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 75 |
| 76 /// Whether this input is dirty and needs [process] to be called. |
| 77 bool get isDirty => _adjustTransformersFuture != null || |
| 78 _newPassThrough || _transforms.any((transform) => transform.isDirty); |
| 79 |
| 80 PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers) |
| 81 : _transformers = transformers.toSet(), |
| 82 _inputForwarder = new AssetForwarder(input) { |
| 83 _onDirtyPool.add(_onDirtyController.stream); |
| 84 |
| 85 input.onStateChange.listen((state) { |
| 86 if (state.isRemoved) { |
| 87 remove(); |
| 88 } else if (_adjustTransformersFuture == null) { |
| 89 _adjustTransformers(); |
| 90 } |
| 91 }); |
| 92 |
| 93 _adjustTransformers(); |
| 94 } |
| 95 |
| 96 /// Removes this input. |
| 97 /// |
| 98 /// This marks all outputs of the input as removed. |
| 99 void remove() { |
| 100 _onDirtyController.add(null); |
| 101 _onDirtyPool.close(); |
| 102 _inputForwarder.close(); |
| 103 if (_passThroughController != null) { |
| 104 _passThroughController.setRemoved(); |
| 105 _passThroughController = null; |
| 106 } |
| 107 } |
| 108 |
| 109 /// Set this input's transformers to [transformers]. |
| 110 void updateTransformers(Set<Transformer> newTransformers) { |
| 111 var oldTransformers = _transformers.toSet(); |
| 112 for (var removedTransformer in |
| 113 oldTransformers.difference(newTransformers)) { |
| 114 _transformers.remove(removedTransformer); |
| 115 |
| 116 // If the transformers are being adjusted for [id], it will |
| 117 // automatically pick up on [removedTransformer] being gone. |
| 118 if (_adjustTransformersFuture != null) continue; |
| 119 |
| 120 _transforms.removeWhere((transform) { |
| 121 if (transform.transformer != removedTransformer) return false; |
| 122 transform.remove(); |
| 123 return true; |
| 124 }); |
| 125 } |
| 126 |
| 127 if (_transforms.isEmpty && _adjustTransformersFuture == null && |
| 128 _passThroughController == null) { |
| 129 _passThroughController = |
| 130 new AssetNodeController.available(input.asset, input.transform); |
| 131 _newPassThrough = true; |
| 132 } |
| 133 |
| 134 var brandNewTransformers = newTransformers.difference(oldTransformers); |
| 135 if (brandNewTransformers.isEmpty) return; |
| 136 |
| 137 brandNewTransformers.forEach(_transformers.add); |
| 138 _adjustTransformers(); |
| 139 } |
| 140 |
| 141 /// Asynchronously determines which transformers can consume [input] as a |
| 142 /// primary input and creates transforms for them. |
| 143 /// |
| 144 /// This ensures that if [input] is modified or removed during or after the |
| 145 /// time it takes to adjust its transformers, they're appropriately |
| 146 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. |
| 147 void _adjustTransformers() { |
| 148 // Mark the input as dirty. This may not actually end up creating any new |
| 149 // transforms, but we want adding or removing a source asset to consistently |
| 150 // kick off a build, even if that build does nothing. |
| 151 _onDirtyController.add(null); |
| 152 |
| 153 // If there's a pass-through for this input, mark it dirty while we figure |
| 154 // out whether we need to add any transforms for it. |
| 155 if (_passThroughController != null) _passThroughController.setDirty(); |
| 156 |
| 157 // Once the input is available, hook up transformers for it. If it changes |
| 158 // while that's happening, try again. |
| 159 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { |
| 160 var oldTransformers = |
| 161 _transforms.map((transform) => transform.transformer).toSet(); |
| 162 |
| 163 return _removeStaleTransforms(asset, transformers).then((_) => |
| 164 _addFreshTransforms(transformers, oldTransformers)); |
| 165 }).then((_) => _adjustPassThrough()).catchError((error) { |
| 166 if (error is! AssetNotFoundException || error.id != input.id) { |
| 167 throw error; |
| 168 } |
| 169 |
| 170 // If the asset is removed, [_tryUntilStable] will throw an |
| 171 // [AssetNotFoundException]. In that case, just remove it. |
| 172 remove(); |
| 173 }).whenComplete(() { |
| 174 _adjustTransformersFuture = null; |
| 175 }); |
| 176 |
| 177 // Don't top-level errors coming from the input processing. Any errors will |
| 178 // eventually be piped through [process]'s returned Future. |
| 179 _adjustTransformersFuture.catchError((_) {}); |
| 180 } |
| 181 |
| 182 // Remove any old transforms that used to have [asset] as a primary asset but |
| 183 // no longer apply to its new contents. |
| 184 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) { |
| 185 return Future.wait(_transforms.map((transform) { |
| 186 return newFuture(() { |
| 187 if (!transformers.contains(transform.transformer)) return false; |
| 188 |
| 189 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
| 190 // results. |
| 191 return transform.transformer.isPrimary(asset); |
| 192 }).then((isPrimary) { |
| 193 if (isPrimary) return; |
| 194 _transforms.remove(transform); |
| 195 transform.remove(); |
| 196 }); |
| 197 })); |
| 198 } |
| 199 |
| 200 // Add new transforms for transformers that consider [input]'s asset to be a |
| 201 // primary input. |
| 202 // |
| 203 // [oldTransformers] is the set of transformers for which there were |
| 204 // transforms that had [input] as a primary input prior to this. They don't |
| 205 // need to be checked, since their transforms were removed or preserved in |
| 206 // [_removeStaleTransforms]. |
| 207 Future _addFreshTransforms(Set<Transformer> transformers, |
| 208 Set<Transformer> oldTransformers) { |
| 209 return Future.wait(transformers.map((transformer) { |
| 210 if (oldTransformers.contains(transformer)) return new Future.value(); |
| 211 |
| 212 // If the asset is unavailable, the results of this [_adjustTransformers] |
| 213 // run will be discarded, so we can just short-circuit. |
| 214 if (input.asset == null) return new Future.value(); |
| 215 |
| 216 // We can safely access [input.asset] here even though it might have |
| 217 // changed since (as above) if it has, [_adjustTransformers] will just be |
| 218 // re-run. |
| 219 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
| 220 // results. |
| 221 return transformer.isPrimary(input.asset).then((isPrimary) { |
| 222 if (!isPrimary) return; |
| 223 var transform = new TransformNode(_phase, transformer, input); |
| 224 _transforms.add(transform); |
| 225 _onDirtyPool.add(transform.onDirty); |
| 226 }); |
| 227 })); |
| 228 } |
| 229 |
| 230 /// Adjust whether [input] is passed through the phase unmodified, based on |
| 231 /// whether it's consumed by other transforms in this phase. |
| 232 /// |
| 233 /// If [input] was already passed-through, this will update the passed-through |
| 234 /// value. |
| 235 void _adjustPassThrough() { |
| 236 assert(input.state.isAvailable); |
| 237 |
| 238 if (_transforms.isEmpty) { |
| 239 if (_passThroughController != null) { |
| 240 _passThroughController.setAvailable(input.asset); |
| 241 } else { |
| 242 _passThroughController = |
| 243 new AssetNodeController.available(input.asset, input.transform); |
| 244 _newPassThrough = true; |
| 245 } |
| 246 } else if (_passThroughController != null) { |
| 247 _passThroughController.setRemoved(); |
| 248 _passThroughController = null; |
| 249 _newPassThrough = false; |
| 250 } |
| 251 } |
| 252 |
| 253 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this |
| 254 /// phase's transformers are modified. |
| 255 Future _tryUntilStable( |
| 256 Future callback(Asset asset, Set<Transformer> transformers)) { |
| 257 var oldTransformers; |
| 258 return input.tryUntilStable((asset) { |
| 259 oldTransformers = _transformers.toSet(); |
| 260 return callback(asset, _transformers); |
| 261 }).then((result) { |
| 262 if (setEquals(oldTransformers, _transformers)) return result; |
| 263 return _tryUntilStable(callback); |
| 264 }); |
| 265 } |
| 266 |
| 267 /// Processes the transforms for this input. |
| 268 Future<Set<AssetNode>> process() { |
| 269 if (_adjustTransformersFuture == null) return _processTransforms(); |
| 270 return _waitForInputs().then((_) => _processTransforms()); |
| 271 } |
| 272 |
| 273 Future _waitForInputs() { |
| 274 // Return a synchronous future so we can be sure [_adjustTransformers] isn't |
| 275 // called between now and when the Future completes. |
| 276 if (_adjustTransformersFuture == null) return new Future.sync(() {}); |
| 277 return _adjustTransformersFuture.then((_) => _waitForInputs()); |
| 278 } |
| 279 |
| 280 /// Applies all currently wired up and dirty transforms. |
| 281 Future<Set<AssetNode>> _processTransforms() { |
| 282 if (input.state.isRemoved) return new Future.value(new Set()); |
| 283 |
| 284 if (_passThroughController != null) { |
| 285 if (!_newPassThrough) return new Future.value(new Set()); |
| 286 _newPassThrough = false; |
| 287 return new Future.value( |
| 288 new Set<AssetNode>.from([_passThroughController.node])); |
| 289 } |
| 290 |
| 291 return Future.wait(_transforms.map((transform) { |
| 292 if (!transform.isDirty) return new Future.value(new Set()); |
| 293 return transform.apply(); |
| 294 })).then((outputs) => unionAll(outputs)); |
| 295 } |
| 296 } |
| OLD | NEW |