Chromium Code Reviews| 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 necessary | |
| 20 /// transforms on that node. | |
|
Bob Nystrom
2013/08/21 19:47:38
Can you clarify here whether it runs transforms wh
nweiz
2013/08/21 20:33:51
Done.
| |
| 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 /// Theis 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(Iterable<Transformer> transformers) { | |
| 111 var newTransformers = transformers.toSet(); | |
| 112 var oldTransformers = _transformers.toSet(); | |
| 113 for (var removedTransformer in | |
| 114 oldTransformers.difference(newTransformers)) { | |
| 115 _transformers.remove(removedTransformer); | |
| 116 | |
| 117 // If the transformers are being adjusted for [id], it will | |
| 118 // automatically pick up on [removedTransformer] being gone. | |
| 119 if (_adjustTransformersFuture != null) continue; | |
| 120 | |
| 121 _transforms.removeWhere((transform) { | |
| 122 if (transform.transformer != removedTransformer) return false; | |
| 123 transform.remove(); | |
| 124 return true; | |
| 125 }); | |
| 126 } | |
| 127 | |
| 128 if (_transforms.isEmpty && _adjustTransformersFuture == null && | |
| 129 _passThroughController == null) { | |
| 130 _passThroughController = | |
| 131 new AssetNodeController.available(input.asset, input.transform); | |
| 132 _newPassThrough = true; | |
| 133 } | |
| 134 | |
| 135 var brandNewTransformers = newTransformers.difference(oldTransformers); | |
| 136 if (brandNewTransformers.isEmpty) return; | |
| 137 | |
| 138 brandNewTransformers.forEach(_transformers.add); | |
| 139 _adjustTransformers(); | |
| 140 } | |
| 141 | |
| 142 /// Asynchronously determines which transformers can consume [input] as a | |
| 143 /// primary input and creates transforms for them. | |
| 144 /// | |
| 145 /// This ensures that if [input] is modified or removed during or after the | |
| 146 /// time it takes to adjust its transformers, they're appropriately | |
| 147 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. | |
| 148 void _adjustTransformers() { | |
| 149 // Mark the input as dirty. This may not actually end up creating any new | |
| 150 // transforms, but we want adding or removing a source asset to consistently | |
| 151 // kick off a build, even if that build does nothing. | |
| 152 _onDirtyController.add(null); | |
| 153 | |
| 154 // If there's a pass-through for this input, mark it dirty while we figure | |
| 155 // out whether we need to add any transforms for it. | |
| 156 if (_passThroughController != null) _passThroughController.setDirty(); | |
| 157 | |
| 158 // Once the input is available, hook up transformers for it. If it changes | |
| 159 // while that's happening, try again. | |
| 160 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { | |
| 161 var oldTransformers = | |
| 162 _transforms.map((transform) => transform.transformer).toSet(); | |
| 163 | |
| 164 return _removeStaleTransforms(asset, transformers).then((_) => | |
| 165 _addFreshTransforms(transformers, oldTransformers)); | |
| 166 }).then((_) => _adjustPassThrough()).catchError((error) { | |
| 167 if (error is! AssetNotFoundException || error.id != input.id) { | |
| 168 throw error; | |
| 169 } | |
| 170 | |
| 171 // If the asset is removed, [_tryUntilStable] will throw an | |
| 172 // [AssetNotFoundException]. In that case, just remove it. | |
| 173 remove(); | |
| 174 }).whenComplete(() { | |
| 175 _adjustTransformersFuture = null; | |
| 176 }); | |
| 177 | |
| 178 // Don't top-level errors coming from the input processing. Any errors will | |
| 179 // eventually be piped through [process]'s returned Future. | |
| 180 _adjustTransformersFuture.catchError((_) {}); | |
| 181 } | |
| 182 | |
| 183 // Remove any old transforms that used to have [asset] as a primary asset but | |
| 184 // no longer apply to its new contents. | |
| 185 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) { | |
| 186 return Future.wait(_transforms.map((transform) { | |
| 187 return newFuture(() { | |
| 188 if (!transformers.contains(transform.transformer)) return false; | |
| 189 | |
| 190 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | |
| 191 // results. | |
| 192 return transform.transformer.isPrimary(asset); | |
| 193 }).then((isPrimary) { | |
| 194 if (isPrimary) return; | |
| 195 _transforms.remove(transform); | |
| 196 transform.remove(); | |
| 197 }); | |
| 198 })); | |
| 199 } | |
| 200 | |
| 201 // Add new transforms for transformers that consider [input]'s asset to be a | |
| 202 // primary input. | |
| 203 // | |
| 204 // [oldTransformers] is the set of transformers for which there were | |
| 205 // transforms that had [input] as a primary input prior to this. They don't | |
| 206 // need to be checked, since their transforms were removed or preserved in | |
| 207 // [_removeStaleTransforms]. | |
| 208 Future _addFreshTransforms(Set<Transformer> transformers, | |
| 209 Set<Transformer> oldTransformers) { | |
| 210 return Future.wait(transformers.map((transformer) { | |
| 211 if (oldTransformers.contains(transformer)) return new Future.value(); | |
| 212 | |
| 213 // If the asset is unavailable, the results of this [_adjustTransformers] | |
| 214 // run will be discarded, so we can just short-circuit. | |
| 215 if (input.asset == null) return new Future.value(); | |
| 216 | |
| 217 // We can safely access [input.asset] here even though it might have | |
| 218 // changed since (as above) if it has, [_adjustTransformers] will just be | |
| 219 // re-run. | |
| 220 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | |
| 221 // results. | |
| 222 return transformer.isPrimary(input.asset).then((isPrimary) { | |
| 223 if (!isPrimary) return; | |
| 224 var transform = new TransformNode(_phase, transformer, input); | |
| 225 _transforms.add(transform); | |
| 226 _onDirtyPool.add(transform.onDirty); | |
| 227 }); | |
| 228 })); | |
| 229 } | |
| 230 | |
| 231 /// Adjust whether [input] is passed through the phase unmodified, based on | |
| 232 /// whether it's consumed by other transforms in this phase. | |
| 233 /// | |
| 234 /// If [input] was already passed-through, this will update the passed-through | |
| 235 /// value. | |
| 236 void _adjustPassThrough() { | |
| 237 assert(input.state.isAvailable); | |
| 238 | |
| 239 if (_transforms.isEmpty) { | |
| 240 if (_passThroughController != null) { | |
| 241 _passThroughController.setAvailable(input.asset); | |
| 242 } else { | |
| 243 _passThroughController = | |
| 244 new AssetNodeController.available(input.asset, input.transform); | |
| 245 _newPassThrough = true; | |
| 246 } | |
| 247 } else if (_passThroughController != null) { | |
| 248 _passThroughController.setRemoved(); | |
| 249 _passThroughController = null; | |
| 250 _newPassThrough = false; | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this | |
| 255 /// phase's transformers are modified. | |
| 256 Future _tryUntilStable( | |
| 257 Future callback(Asset asset, Set<Transformer> transformers)) { | |
| 258 var oldTransformers; | |
| 259 return input.tryUntilStable((asset) { | |
| 260 oldTransformers = _transformers.toSet(); | |
| 261 return callback(asset, _transformers); | |
| 262 }).then((result) { | |
| 263 if (setEquals(oldTransformers, _transformers)) return result; | |
| 264 return _tryUntilStable(callback); | |
| 265 }); | |
| 266 } | |
| 267 | |
| 268 /// Processes the transforms for this input. | |
| 269 Future<Set<AssetNode>> process() { | |
| 270 if (_adjustTransformersFuture == null) return _processTransforms(); | |
| 271 return _waitForInputs().then((_) => _processTransforms()); | |
| 272 } | |
| 273 | |
| 274 Future _waitForInputs() { | |
| 275 // Return a synchronous future so we can be sure [_adjustTransformers] isn't | |
| 276 // called between now and when the Future completes. | |
| 277 if (_adjustTransformersFuture == null) return new Future.sync(() {}); | |
| 278 return _adjustTransformersFuture.then((_) => _waitForInputs()); | |
| 279 } | |
| 280 | |
| 281 /// Applies all currently wired up and dirty transforms. | |
| 282 Future<Set<AssetNode>> _processTransforms() { | |
| 283 if (input.state.isRemoved) return new Future.value(new Set()); | |
| 284 | |
| 285 if (_passThroughController != null) { | |
| 286 if (!_newPassThrough) return new Future.value(new Set()); | |
| 287 _newPassThrough = false; | |
| 288 return new Future.value( | |
| 289 new Set<AssetNode>.from([_passThroughController.node])); | |
| 290 } | |
| 291 | |
| 292 return Future.wait(_transforms.map((transform) { | |
| 293 if (!transform.isDirty) return new Future.value(new Set()); | |
| 294 return transform.apply(); | |
| 295 })).then((outputs) => unionAll(outputs)); | |
| 296 } | |
| 297 } | |
| OLD | NEW |