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 'package:source_maps/span.dart'; | 9 import 'package:source_maps/span.dart'; |
| 10 | 10 |
| 11 import 'asset.dart'; | 11 import 'asset.dart'; |
| 12 import 'asset_id.dart'; | 12 import 'asset_id.dart'; |
| 13 import 'asset_node.dart'; | 13 import 'asset_node.dart'; |
| 14 import 'asset_set.dart'; | 14 import 'asset_set.dart'; |
| 15 import 'declaring_transform.dart'; | |
| 15 import 'errors.dart'; | 16 import 'errors.dart'; |
| 17 import 'lazy_transformer.dart'; | |
| 16 import 'log.dart'; | 18 import 'log.dart'; |
| 17 import 'phase.dart'; | 19 import 'phase.dart'; |
| 18 import 'transform.dart'; | 20 import 'transform.dart'; |
| 19 import 'transformer.dart'; | 21 import 'transformer.dart'; |
| 22 import 'utils.dart'; | |
| 20 | 23 |
| 21 /// Describes a transform on a set of assets and its relationship to the build | 24 /// Describes a transform on a set of assets and its relationship to the build |
| 22 /// dependency graph. | 25 /// dependency graph. |
| 23 /// | 26 /// |
| 24 /// Keeps track of whether it's dirty and needs to be run and which assets it | 27 /// Keeps track of whether it's dirty and needs to be run and which assets it |
| 25 /// depends on. | 28 /// depends on. |
| 26 class TransformNode { | 29 class TransformNode { |
| 27 /// The [Phase] that this transform runs in. | 30 /// The [Phase] that this transform runs in. |
| 28 final Phase phase; | 31 final Phase phase; |
| 29 | 32 |
| 30 /// The [Transformer] to apply to this node's inputs. | 33 /// The [Transformer] to apply to this node's inputs. |
| 31 final Transformer transformer; | 34 final Transformer transformer; |
| 32 | 35 |
| 33 /// The node for the primary asset this transform depends on. | 36 /// The node for the primary asset this transform depends on. |
| 34 final AssetNode primary; | 37 final AssetNode primary; |
| 35 | 38 |
| 36 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | 39 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. |
| 37 StreamSubscription _primarySubscription; | 40 StreamSubscription _primarySubscription; |
| 38 | 41 |
| 39 /// True if an input has been modified since the last time this transform | 42 /// True if an input has been modified since the last time this transform |
| 40 /// began running. | 43 /// began running. |
| 41 bool get isDirty => _isDirty; | 44 bool get isDirty => _isDirty; |
| 42 var _isDirty = true; | 45 var _isDirty = true; |
| 43 | 46 |
| 47 /// Whether [transformer] is lazy and this transform has yet to be forced. | |
| 48 bool _isLazy; | |
| 49 | |
| 44 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | 50 /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
| 45 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | 51 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); |
| 46 | 52 |
| 47 /// The controllers for the asset nodes emitted by this node. | 53 /// The controllers for the asset nodes emitted by this node. |
| 48 var _outputControllers = new Map<AssetId, AssetNodeController>(); | 54 var _outputControllers = new Map<AssetId, AssetNodeController>(); |
| 49 | 55 |
| 50 /// A stream that emits an event whenever this transform becomes dirty and | 56 /// A stream that emits an event whenever this transform becomes dirty and |
| 51 /// needs to be re-run. | 57 /// needs to be re-run. |
| 52 /// | 58 /// |
| 53 /// This may emit events when the transform was already dirty or while | 59 /// This may emit events when the transform was already dirty or while |
| 54 /// processing transforms. Events are emitted synchronously to ensure that the | 60 /// processing transforms. Events are emitted synchronously to ensure that the |
| 55 /// dirty state is thoroughly propagated as soon as any assets are changed. | 61 /// dirty state is thoroughly propagated as soon as any assets are changed. |
| 56 Stream get onDirty => _onDirtyController.stream; | 62 Stream get onDirty => _onDirtyController.stream; |
| 57 final _onDirtyController = new StreamController.broadcast(sync: true); | 63 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 58 | 64 |
| 59 /// A stream that emits an event whenever this transform logs an entry. | 65 /// A stream that emits an event whenever this transform logs an entry. |
| 60 /// | 66 /// |
| 61 /// This is synchronous because error logs can cause the transform to fail, so | 67 /// This is synchronous because error logs can cause the transform to fail, so |
| 62 /// we need to ensure that their processing isn't delayed until after the | 68 /// we need to ensure that their processing isn't delayed until after the |
| 63 /// transform or build has finished. | 69 /// transform or build has finished. |
| 64 Stream<LogEntry> get onLog => _onLogController.stream; | 70 Stream<LogEntry> get onLog => _onLogController.stream; |
| 65 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); | 71 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); |
| 66 | 72 |
| 67 TransformNode(this.phase, this.transformer, this.primary) { | 73 TransformNode(this.phase, Transformer transformer, this.primary) |
| 74 : transformer = transformer, | |
| 75 _isLazy = transformer is LazyTransformer { | |
| 68 _primarySubscription = primary.onStateChange.listen((state) { | 76 _primarySubscription = primary.onStateChange.listen((state) { |
| 69 if (state.isRemoved) { | 77 if (state.isRemoved) { |
| 70 remove(); | 78 remove(); |
| 71 } else { | 79 } else { |
| 72 _dirty(); | 80 _dirty(); |
| 73 } | 81 } |
| 74 }); | 82 }); |
| 75 } | 83 } |
| 76 | 84 |
| 77 /// The [TransformInfo] describing this node. | 85 /// The [TransformInfo] describing this node. |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 91 _onDirtyController.close(); | 99 _onDirtyController.close(); |
| 92 _primarySubscription.cancel(); | 100 _primarySubscription.cancel(); |
| 93 for (var subscription in _inputSubscriptions.values) { | 101 for (var subscription in _inputSubscriptions.values) { |
| 94 subscription.cancel(); | 102 subscription.cancel(); |
| 95 } | 103 } |
| 96 for (var controller in _outputControllers.values) { | 104 for (var controller in _outputControllers.values) { |
| 97 controller.setRemoved(); | 105 controller.setRemoved(); |
| 98 } | 106 } |
| 99 } | 107 } |
| 100 | 108 |
| 109 /// If [transformer] is lazy, ensures that its concrete outputs will be | |
| 110 /// generated. | |
| 111 void force() { | |
| 112 // TODO(nweiz): we might want to have a timeout after which, if the | |
| 113 // transform's outputs have gone unused, we switch it back to lazy mode. | |
| 114 if (!_isLazy) return; | |
| 115 _isLazy = false; | |
| 116 _dirty(); | |
| 117 } | |
| 118 | |
| 101 /// Marks this transform as dirty. | 119 /// Marks this transform as dirty. |
| 102 /// | 120 /// |
| 103 /// This causes all of the transform's outputs to be marked as dirty as well. | 121 /// This causes all of the transform's outputs to be marked as dirty as well. |
| 104 void _dirty() { | 122 void _dirty() { |
| 105 _isDirty = true; | 123 _isDirty = true; |
| 106 for (var controller in _outputControllers.values) { | 124 for (var controller in _outputControllers.values) { |
| 107 controller.setDirty(); | 125 controller.setDirty(); |
| 108 } | 126 } |
| 109 _onDirtyController.add(null); | 127 _onDirtyController.add(null); |
| 110 } | 128 } |
| 111 | 129 |
| 112 /// Applies this transform. | 130 /// Applies this transform. |
| 113 /// | 131 /// |
| 114 /// Returns a set of asset nodes representing the outputs from this transform | 132 /// Returns a set of asset nodes representing the outputs from this transform |
| 115 /// that weren't emitted last time it was run. | 133 /// that weren't emitted last time it was run. |
| 116 Future<Set<AssetNode>> apply() { | 134 Future<Set<AssetNode>> apply() { |
| 117 assert(!_onDirtyController.isClosed); | 135 assert(!_onDirtyController.isClosed); |
| 118 | 136 |
| 119 var newOutputs = new AssetSet(); | |
| 120 var transform = createTransform(this, newOutputs, _log); | |
| 121 | |
| 122 // Clear all the old input subscriptions. If an input is re-used, we'll | 137 // Clear all the old input subscriptions. If an input is re-used, we'll |
| 123 // re-subscribe. | 138 // re-subscribe. |
| 124 for (var subscription in _inputSubscriptions.values) { | 139 for (var subscription in _inputSubscriptions.values) { |
| 125 subscription.cancel(); | 140 subscription.cancel(); |
| 126 } | 141 } |
| 127 _inputSubscriptions.clear(); | 142 _inputSubscriptions.clear(); |
| 128 | 143 |
| 129 _isDirty = false; | 144 _isDirty = false; |
| 130 | 145 |
| 131 return transformer.apply(transform).catchError((error, stack) { | 146 return syncFuture(() { |
| 147 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a | |
| 148 // [LazyTransformer], we can get some mileage out of doing a declarative | |
| 149 // first so we know how to hook up the assets. | |
| 150 if (_isLazy) return _declareLazy(); | |
| 151 return _applyImmediate(); | |
| 152 }).catchError((error, stackTrace) { | |
| 132 // If the transform became dirty while processing, ignore any errors from | 153 // If the transform became dirty while processing, ignore any errors from |
| 133 // it. | 154 // it. |
| 134 if (_isDirty) return; | 155 if (_isDirty) return new Set(); |
| 135 | 156 |
| 136 if (error is! MissingInputException) { | 157 if (error is! MissingInputException) { |
| 137 error = new TransformerException(info, error, stack); | 158 error = new TransformerException(info, error, stackTrace); |
| 138 } | 159 } |
| 139 | 160 |
| 140 // Catch all transformer errors and pipe them to the results stream. | 161 // Catch all transformer errors and pipe them to the results stream. This |
| 141 // This is so a broken transformer doesn't take down the whole graph. | 162 // is so a broken transformer doesn't take down the whole graph. |
| 142 phase.cascade.reportError(error); | 163 phase.cascade.reportError(error); |
| 143 | 164 |
| 144 // Don't allow partial results from a failed transform. | 165 return new Set(); |
| 145 newOutputs.clear(); | |
| 146 }).then((_) { | |
| 147 if (_isDirty) return new Set(); | |
| 148 | |
| 149 return _adjustOutputs(newOutputs); | |
| 150 }); | 166 }); |
| 151 } | 167 } |
| 152 | 168 |
| 153 /// Gets the asset for an input [id]. | 169 /// Gets the asset for an input [id]. |
| 154 /// | 170 /// |
| 155 /// If an input with that ID cannot be found, throws an | 171 /// If an input with that ID cannot be found, throws an |
| 156 /// [AssetNotFoundException]. | 172 /// [AssetNotFoundException]. |
| 157 Future<Asset> getInput(AssetId id) { | 173 Future<Asset> getInput(AssetId id) { |
| 158 return phase.getInput(id).then((node) { | 174 return phase.getInput(id).then((node) { |
| 159 // Throw if the input isn't found. This ensures the transformer's apply | 175 // Throw if the input isn't found. This ensures the transformer's apply |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 170 return asset; | 186 return asset; |
| 171 }).catchError((error) { | 187 }).catchError((error) { |
| 172 if (error is! AssetNotFoundException || error.id != id) throw error; | 188 if (error is! AssetNotFoundException || error.id != id) throw error; |
| 173 // If the node was removed before it could be loaded, treat it as though | 189 // If the node was removed before it could be loaded, treat it as though |
| 174 // it never existed and throw a MissingInputException. | 190 // it never existed and throw a MissingInputException. |
| 175 throw new MissingInputException(info, id); | 191 throw new MissingInputException(info, id); |
| 176 }); | 192 }); |
| 177 }); | 193 }); |
| 178 } | 194 } |
| 179 | 195 |
| 180 /// Adjusts the outputs of the transform to reflect the outputs emitted on its | 196 /// Applies the transform so that it produces concrete (as opposed to lazy) |
| 181 /// most recent run. | 197 /// outputs. |
| 182 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) { | 198 Future<Set<AssetNode>> _applyImmediate() { |
| 183 // Any ids that are for a different package are invalid. | 199 var newOutputs = new AssetSet(); |
| 184 var invalidIds = newOutputs | 200 var transform = new Transform(this, newOutputs, _log); |
| 185 .map((asset) => asset.id) | |
| 186 .where((id) => id.package != phase.cascade.package) | |
| 187 .toSet(); | |
| 188 for (var id in invalidIds) { | |
| 189 newOutputs.removeId(id); | |
| 190 // TODO(nweiz): report this as a warning rather than a failing error. | |
| 191 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
| 192 } | |
| 193 | 201 |
| 194 // Remove outputs that used to exist but don't anymore. | 202 return syncFuture(() => transformer.apply(transform)).then((_) { |
| 195 for (var id in _outputControllers.keys.toList()) { | 203 if (_isDirty) return new Set(); |
| 196 if (newOutputs.containsId(id)) continue; | |
| 197 _outputControllers.remove(id).setRemoved(); | |
| 198 } | |
| 199 | 204 |
| 200 var brandNewOutputs = new Set<AssetNode>(); | 205 // Any ids that are for a different package are invalid. |
| 201 // Store any new outputs or new contents for existing outputs. | 206 var invalidIds = newOutputs |
| 202 for (var asset in newOutputs) { | 207 .map((asset) => asset.id) |
| 203 var controller = _outputControllers[asset.id]; | 208 .where((id) => id.package != phase.cascade.package) |
| 204 if (controller != null) { | 209 .toSet(); |
| 205 controller.setAvailable(asset); | 210 for (var id in invalidIds) { |
| 206 } else { | 211 newOutputs.removeId(id); |
| 207 var controller = new AssetNodeController.available(asset, this); | 212 // TODO(nweiz): report this as a warning rather than a failing error. |
| 208 _outputControllers[asset.id] = controller; | 213 phase.cascade.reportError(new InvalidOutputException(info, id)); |
| 209 brandNewOutputs.add(controller.node); | |
| 210 } | 214 } |
| 211 } | |
| 212 | 215 |
| 213 return brandNewOutputs; | 216 // Remove outputs that used to exist but don't anymore. |
| 217 for (var id in _outputControllers.keys.toList()) { | |
| 218 if (newOutputs.containsId(id)) continue; | |
| 219 _outputControllers.remove(id).setRemoved(); | |
| 220 } | |
| 221 | |
| 222 var brandNewOutputs = new Set<AssetNode>(); | |
| 223 // Store any new outputs or new contents for existing outputs. | |
| 224 for (var asset in newOutputs) { | |
| 225 var controller = _outputControllers[asset.id]; | |
| 226 if (controller != null) { | |
| 227 controller.setAvailable(asset); | |
| 228 } else { | |
| 229 var controller = new AssetNodeController.available(asset, this); | |
| 230 _outputControllers[asset.id] = controller; | |
| 231 brandNewOutputs.add(controller.node); | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 return brandNewOutputs; | |
| 236 }); | |
| 237 } | |
| 238 | |
| 239 /// Applies the transform in declarative mode so that it produces lazy outputs . | |
|
Bob Nystrom
2014/02/07 21:02:28
Long line.
nweiz
2014/02/10 20:14:04
Done.
| |
| 240 Future<Set<AssetNode>> _declareLazy() { | |
| 241 var newIds = new Set(); | |
| 242 var transform = new DeclaringTransform(this, newIds, _log); | |
| 243 | |
| 244 return syncFuture(() { | |
| 245 return (transformer as LazyTransformer).declareOutputs(transform); | |
| 246 }).then((_) { | |
| 247 if (_isDirty) return new Set(); | |
| 248 | |
| 249 var invalidIds = | |
| 250 newIds.where((id) => id.package != phase.cascade.package).toSet(); | |
| 251 for (var id in invalidIds) { | |
| 252 newIds.remove(id); | |
| 253 // TODO(nweiz): report this as a warning rather than a failing error. | |
| 254 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
| 255 } | |
| 256 | |
| 257 // Remove outputs that used to exist but don't anymore. | |
| 258 for (var id in _outputControllers.keys.toList()) { | |
| 259 if (newIds.contains(id)) continue; | |
| 260 _outputControllers.remove(id).setRemoved(); | |
| 261 } | |
| 262 | |
| 263 var brandNewOutputs = new Set<AssetNode>(); | |
| 264 for (var id in newIds) { | |
| 265 var controller = _outputControllers[id]; | |
| 266 if (controller != null) { | |
| 267 controller.setLazy(force); | |
| 268 } else { | |
| 269 var controller = new AssetNodeController.lazy(id, force, this); | |
| 270 _outputControllers[id] = controller; | |
| 271 brandNewOutputs.add(controller.node); | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 return brandNewOutputs; | |
| 276 }); | |
| 214 } | 277 } |
| 215 | 278 |
| 216 void _log(AssetId asset, LogLevel level, String message, Span span) { | 279 void _log(AssetId asset, LogLevel level, String message, Span span) { |
| 217 // If the log isn't already associated with an asset, use the primary. | 280 // If the log isn't already associated with an asset, use the primary. |
| 218 if (asset == null) asset = primary.id; | 281 if (asset == null) asset = primary.id; |
| 219 var entry = new LogEntry(info, asset, level, message, span); | 282 var entry = new LogEntry(info, asset, level, message, span); |
| 220 _onLogController.add(entry); | 283 _onLogController.add(entry); |
| 221 } | 284 } |
| 222 } | 285 } |
| OLD | NEW |