| 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; | 5 library barback.phase; |
| 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'; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 49 final _inputs = new Map<AssetId, AssetNode>(); | 49 final _inputs = new Map<AssetId, AssetNode>(); |
| 50 | 50 |
| 51 /// The transforms currently applicable to assets in [inputs], indexed by | 51 /// The transforms currently applicable to assets in [inputs], indexed by |
| 52 /// the ids of their primary inputs. | 52 /// the ids of their primary inputs. |
| 53 /// | 53 /// |
| 54 /// These are the transforms that have been "wired up": they represent a | 54 /// These are the transforms that have been "wired up": they represent a |
| 55 /// repeatable transformation of a single concrete set of inputs. "dart2js" | 55 /// repeatable transformation of a single concrete set of inputs. "dart2js" |
| 56 /// is a transformer. "dart2js on web/main.dart" is a transform. | 56 /// is a transformer. "dart2js on web/main.dart" is a transform. |
| 57 final _transforms = new Map<AssetId, Set<TransformNode>>(); | 57 final _transforms = new Map<AssetId, Set<TransformNode>>(); |
| 58 | 58 |
| 59 /// Controllers for assets that aren't consumed by transforms in this phase. |
| 60 /// |
| 61 /// These assets are passed to the next phase unmodified. They need |
| 62 /// intervening controllers to ensure that the outputs can be marked dirty |
| 63 /// when determining whether transforms apply, and removed if they do. |
| 64 final _passThroughControllers = new Map<AssetId, AssetNodeController>(); |
| 65 |
| 59 /// Futures that will complete once the transformers that can consume a given | 66 /// Futures that will complete once the transformers that can consume a given |
| 60 /// asset are determined. | 67 /// asset are determined. |
| 61 /// | 68 /// |
| 62 /// Whenever an asset is added or modified, we need to asynchronously | 69 /// Whenever an asset is added or modified, we need to asynchronously |
| 63 /// determine which transformers can use it as their primary input. We can't | 70 /// determine which transformers can use it as their primary input. We can't |
| 64 /// start processing until we know which transformers to run, and this allows | 71 /// start processing until we know which transformers to run, and this allows |
| 65 /// us to wait until we do. | 72 /// us to wait until we do. |
| 66 var _adjustTransformersFutures = new Map<AssetId, Future>(); | 73 var _adjustTransformersFutures = new Map<AssetId, Future>(); |
| 67 | 74 |
| 68 /// New asset nodes that were added while [_adjustTransformers] was still | 75 /// New asset nodes that were added while [_adjustTransformers] was still |
| 69 /// being run on an old version of that asset. | 76 /// being run on an old version of that asset. |
| 70 var _pendingNewInputs = new Map<AssetId, AssetNode>(); | 77 var _pendingNewInputs = new Map<AssetId, AssetNode>(); |
| 71 | 78 |
| 72 /// A map of output ids to the asset node outputs for those ids and the | 79 /// A map of output ids to the asset node outputs for those ids and the |
| 73 /// transforms that produced those asset nodes. | 80 /// transforms that produced those asset nodes. |
| 74 /// | 81 /// |
| 75 /// Usually there's only one node for a given output id. However, it's | 82 /// Usually there's only one node for a given output id. However, it's |
| 76 /// possible for multiple transformers in this phase to output an asset with | 83 /// possible for multiple transformers to output an asset with the same id. In |
| 77 /// the same id. In that case, the chronologically first output emitted is | 84 /// that case, the chronologically first output emitted is passed forward. We |
| 78 /// passed forward. We keep track of the other nodes so that if that output is | 85 /// keep track of the other nodes so that if that output is removed, we know |
| 79 /// removed, we know which asset to replace it with. | 86 /// which asset to replace it with. |
| 80 final _outputs = new Map<AssetId, Queue<AssetNode>>(); | 87 final _outputs = new Map<AssetId, Queue<AssetNode>>(); |
| 81 | 88 |
| 82 /// A stream that emits an event whenever this phase becomes dirty and needs | 89 /// A stream that emits an event whenever this phase becomes dirty and needs |
| 83 /// to be run. | 90 /// to be run. |
| 84 /// | 91 /// |
| 85 /// This may emit events when the phase was already dirty or while processing | 92 /// This may emit events when the phase was already dirty or while processing |
| 86 /// transforms. Events are emitted synchronously to ensure that the dirty | 93 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 87 /// state is thoroughly propagated as soon as any assets are changed. | 94 /// state is thoroughly propagated as soon as any assets are changed. |
| 88 Stream get onDirty => _onDirtyPool.stream; | 95 Stream get onDirty => _onDirtyPool.stream; |
| 89 final _onDirtyPool = new StreamPool.broadcast(); | 96 final _onDirtyPool = new StreamPool.broadcast(); |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 156 // ignore it here but don't start processing the new input. That way | 163 // ignore it here but don't start processing the new input. That way |
| 157 // when [process] is called, the error will be piped through its | 164 // when [process] is called, the error will be piped through its |
| 158 // return value. | 165 // return value. |
| 159 }).catchError((e) { | 166 }).catchError((e) { |
| 160 // If our code above has a programmatic error, ensure it will be piped | 167 // If our code above has a programmatic error, ensure it will be piped |
| 161 // through [process] by putting it into [_adjustTransformersFutures]. | 168 // through [process] by putting it into [_adjustTransformersFutures]. |
| 162 _adjustTransformersFutures[node.id] = new Future.error(e); | 169 _adjustTransformersFutures[node.id] = new Future.error(e); |
| 163 }); | 170 }); |
| 164 } | 171 } |
| 165 | 172 |
| 166 /// Returns the input for this phase with the given [id], but only if that | |
| 167 /// input is known not to be consumed as a transformer's primary input. | |
| 168 /// | |
| 169 /// If the input is unavailable, or if the phase hasn't determined whether or | |
| 170 /// not any transformers will consume it as a primary input, null will be | |
| 171 /// returned instead. This means that the return value is guaranteed to always | |
| 172 /// be [AssetState.AVAILABLE]. | |
| 173 AssetNode getUnconsumedInput(AssetId id) { | |
| 174 if (!_inputs.containsKey(id)) return null; | |
| 175 | |
| 176 // If the asset has transforms, it's not unconsumed. | |
| 177 if (!_transforms[id].isEmpty) return null; | |
| 178 | |
| 179 // If we're working on figuring out if the asset has transforms, we can't | |
| 180 // prove that it's unconsumed. | |
| 181 if (_adjustTransformersFutures.containsKey(id)) return null; | |
| 182 | |
| 183 // The asset should be available. If it were removed, it wouldn't be in | |
| 184 // _inputs, and if it were dirty, it'd be in _adjustTransformersFutures. | |
| 185 assert(_inputs[id].state.isAvailable); | |
| 186 return _inputs[id]; | |
| 187 } | |
| 188 | |
| 189 /// Gets the asset node for an input [id]. | 173 /// Gets the asset node for an input [id]. |
| 190 /// | 174 /// |
| 191 /// If an input with that ID cannot be found, returns null. | 175 /// If an input with that ID cannot be found, returns null. |
| 192 Future<AssetNode> getInput(AssetId id) { | 176 Future<AssetNode> getInput(AssetId id) { |
| 193 return newFuture(() { | 177 return newFuture(() { |
| 194 // TODO(rnystrom): Need to handle passthrough where an asset from a | |
| 195 // previous phase can be found. | |
| 196 if (id.package == cascade.package) return _inputs[id]; | 178 if (id.package == cascade.package) return _inputs[id]; |
| 197 return cascade.graph.getAssetNode(id); | 179 return cascade.graph.getAssetNode(id); |
| 198 }); | 180 }); |
| 199 } | 181 } |
| 200 | 182 |
| 201 /// Asynchronously determines which transformers can consume [node] as a | 183 /// Asynchronously determines which transformers can consume [node] as a |
| 202 /// primary input and creates transforms for them. | 184 /// primary input and creates transforms for them. |
| 203 /// | 185 /// |
| 204 /// This ensures that if [node] is modified or removed during or after the | 186 /// This ensures that if [node] is modified or removed during or after the |
| 205 /// time it takes to adjust its transformers, they're appropriately | 187 /// time it takes to adjust its transformers, they're appropriately |
| 206 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. | 188 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. |
| 207 void _adjustTransformers(AssetNode node) { | 189 void _adjustTransformers(AssetNode node) { |
| 208 // Mark the phase as dirty. This may not actually end up creating any new | 190 // Mark the phase as dirty. This may not actually end up creating any new |
| 209 // transforms, but we want adding or removing a source asset to consistently | 191 // transforms, but we want adding or removing a source asset to consistently |
| 210 // kick off a build, even if that build does nothing. | 192 // kick off a build, even if that build does nothing. |
| 211 _onDirtyController.add(null); | 193 _onDirtyController.add(null); |
| 212 | 194 |
| 195 // If there's a pass-through for this node, mark it dirty while we figure |
| 196 // out whether we need to add any transforms for it. |
| 197 var controller = _passThroughControllers[node.id]; |
| 198 if (controller != null) controller.setDirty(); |
| 199 |
| 213 // Once the input is available, hook up transformers for it. If it changes | 200 // Once the input is available, hook up transformers for it. If it changes |
| 214 // while that's happening, try again. | 201 // while that's happening, try again. |
| 215 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) { | 202 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) { |
| 216 var oldTransformers = _transforms[node.id] | 203 var oldTransformers = _transforms[node.id] |
| 217 .map((transform) => transform.transformer).toSet(); | 204 .map((transform) => transform.transformer).toSet(); |
| 218 | 205 |
| 219 return _removeStaleTransforms(asset) | 206 return _removeStaleTransforms(asset) |
| 220 .then((_) => _addFreshTransforms(node, oldTransformers)); | 207 .then((_) => _addFreshTransforms(node, oldTransformers)); |
| 221 }).then((_) { | 208 }).then((_) { |
| 209 _adjustPassThrough(node); |
| 210 |
| 222 // Now all the transforms are set up correctly and the asset is available | 211 // Now all the transforms are set up correctly and the asset is available |
| 223 // for the time being. Set up handlers for when the asset changes in the | 212 // for the time being. Set up handlers for when the asset changes in the |
| 224 // future. | 213 // future. |
| 225 node.onStateChange.first.then((state) { | 214 node.onStateChange.first.then((state) { |
| 226 if (state.isRemoved) { | 215 if (state.isRemoved) { |
| 227 _onDirtyController.add(null); | 216 _onDirtyController.add(null); |
| 228 _transforms.remove(node.id); | 217 _transforms.remove(node.id); |
| 218 var passThrough = _passThroughControllers.remove(node.id); |
| 219 if (passThrough != null) passThrough.setRemoved(); |
| 229 } else { | 220 } else { |
| 230 _adjustTransformers(node); | 221 _adjustTransformers(node); |
| 231 } | 222 } |
| 232 }).catchError((e) { | 223 }).catchError((e) { |
| 233 _adjustTransformersFutures[node.id] = new Future.error(e); | 224 _adjustTransformersFutures[node.id] = new Future.error(e); |
| 234 }); | 225 }); |
| 235 }).catchError((error) { | 226 }).catchError((error) { |
| 236 if (error is! AssetNotFoundException || error.id != node.id) throw error; | 227 if (error is! AssetNotFoundException || error.id != node.id) throw error; |
| 237 | 228 |
| 238 // If the asset is removed, [tryUntilStable] will throw an | 229 // If the asset is removed, [tryUntilStable] will throw an |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 284 // results. | 275 // results. |
| 285 return transformer.isPrimary(node.asset).then((isPrimary) { | 276 return transformer.isPrimary(node.asset).then((isPrimary) { |
| 286 if (!isPrimary) return; | 277 if (!isPrimary) return; |
| 287 var transform = new TransformNode(this, transformer, node); | 278 var transform = new TransformNode(this, transformer, node); |
| 288 _transforms[node.id].add(transform); | 279 _transforms[node.id].add(transform); |
| 289 _onDirtyPool.add(transform.onDirty); | 280 _onDirtyPool.add(transform.onDirty); |
| 290 }); | 281 }); |
| 291 })); | 282 })); |
| 292 } | 283 } |
| 293 | 284 |
| 285 /// Adjust whether [node] is passed through the phase unmodified, based on |
| 286 /// whether it's consumed by other transforms in this phase. |
| 287 /// |
| 288 /// If [node] was already passed-through, this will update the passed-through |
| 289 /// value. |
| 290 void _adjustPassThrough(AssetNode node) { |
| 291 assert(node.state.isAvailable); |
| 292 |
| 293 if (_transforms[node.id].isEmpty) { |
| 294 var controller = _passThroughControllers[node.id]; |
| 295 if (controller != null) { |
| 296 controller.setAvailable(node.asset); |
| 297 } else { |
| 298 _passThroughControllers[node.id] = |
| 299 new AssetNodeController.available(node.asset, node.transform); |
| 300 } |
| 301 } else { |
| 302 var controller = _passThroughControllers.remove(node.id); |
| 303 if (controller != null) controller.setRemoved(); |
| 304 } |
| 305 } |
| 306 |
| 294 /// Processes this phase. | 307 /// Processes this phase. |
| 295 /// | 308 /// |
| 296 /// Returns a future that completes when processing is done. If there is | 309 /// Returns a future that completes when processing is done. If there is |
| 297 /// nothing to process, returns `null`. | 310 /// nothing to process, returns `null`. |
| 298 Future process() { | 311 Future process() { |
| 299 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); | 312 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); |
| 300 return _waitForInputs().then((_) => _processTransforms()); | 313 return _waitForInputs().then((_) => _processTransforms()); |
| 301 } | 314 } |
| 302 | 315 |
| 303 Future _waitForInputs() { | 316 Future _waitForInputs() { |
| 304 if (_adjustTransformersFutures.isEmpty) return new Future.value(); | 317 if (_adjustTransformersFutures.isEmpty) return new Future.value(); |
| 305 return Future.wait(_adjustTransformersFutures.values) | 318 return Future.wait(_adjustTransformersFutures.values) |
| 306 .then((_) => _waitForInputs()); | 319 .then((_) => _waitForInputs()); |
| 307 } | 320 } |
| 308 | 321 |
| 309 /// Applies all currently wired up and dirty transforms. | 322 /// Applies all currently wired up and dirty transforms. |
| 310 Future _processTransforms() { | 323 Future _processTransforms() { |
| 324 if (_next == null) return; |
| 325 |
| 326 var newPassThroughs = _passThroughControllers.values |
| 327 .map((controller) => controller.node) |
| 328 .where((output) { |
| 329 return !_outputs.containsKey(output.id) || |
| 330 !_outputs[output.id].contains(output); |
| 331 }).toSet(); |
| 332 |
| 311 // Convert this to a list so we can safely modify _transforms while | 333 // Convert this to a list so we can safely modify _transforms while |
| 312 // iterating over it. | 334 // iterating over it. |
| 313 var dirtyTransforms = | 335 var dirtyTransforms = |
| 314 flatten(_transforms.values.map((transforms) => transforms.toList())) | 336 flatten(_transforms.values.map((transforms) => transforms.toList())) |
| 315 .where((transform) => transform.isDirty).toList(); | 337 .where((transform) => transform.isDirty).toList(); |
| 316 if (dirtyTransforms.isEmpty) return null; | |
| 317 | 338 |
| 318 var collisions = new Set<AssetId>(); | 339 if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null; |
| 340 |
| 341 var collisions = _passAssetsThrough(newPassThroughs); |
| 319 return Future.wait(dirtyTransforms.map((transform) { | 342 return Future.wait(dirtyTransforms.map((transform) { |
| 320 return transform.apply().then((outputs) { | 343 return transform.apply().then((outputs) { |
| 321 for (var output in outputs) { | 344 for (var output in outputs) { |
| 322 if (_outputs.containsKey(output.id)) { | 345 if (_outputs.containsKey(output.id)) { |
| 323 _outputs[output.id].add(output); | 346 _outputs[output.id].add(output); |
| 324 collisions.add(output.id); | 347 collisions.add(output.id); |
| 325 } else { | 348 } else { |
| 326 _outputs[output.id] = new Queue<AssetNode>.from([output]); | 349 _outputs[output.id] = new Queue<AssetNode>.from([output]); |
| 327 _next.addInput(output); | 350 _next.addInput(output); |
| 328 } | 351 } |
| (...skipping 10 matching lines...) Expand all Loading... |
| 339 // while another transform was running. | 362 // while another transform was running. |
| 340 if (_outputs[collision].length <= 1) continue; | 363 if (_outputs[collision].length <= 1) continue; |
| 341 cascade.reportError(new AssetCollisionException( | 364 cascade.reportError(new AssetCollisionException( |
| 342 _outputs[collision].where((asset) => asset.transform != null) | 365 _outputs[collision].where((asset) => asset.transform != null) |
| 343 .map((asset) => asset.transform.info), | 366 .map((asset) => asset.transform.info), |
| 344 collision)); | 367 collision)); |
| 345 } | 368 } |
| 346 }); | 369 }); |
| 347 } | 370 } |
| 348 | 371 |
| 372 /// Pass all new assets that aren't consumed by transforms through to the next |
| 373 /// phase. |
| 374 /// |
| 375 /// Returns a set of asset ids that have collisions between new passed-through |
| 376 /// assets and pre-existing transform outputs. |
| 377 Set<AssetId> _passAssetsThrough(Set<AssetId> newPassThroughs) { |
| 378 var collisions = new Set<AssetId>(); |
| 379 for (var output in newPassThroughs) { |
| 380 if (_outputs.containsKey(output.id)) { |
| 381 // There shouldn't be another pass-through asset with the same id. |
| 382 assert(!_outputs[output.id].any((asset) => asset.transform == null)); |
| 383 |
| 384 _outputs[output.id].add(output); |
| 385 collisions.add(output.id); |
| 386 } else { |
| 387 _outputs[output.id] = new Queue<AssetNode>.from([output]); |
| 388 _next.addInput(output); |
| 389 } |
| 390 |
| 391 _handleOutputRemoval(output); |
| 392 } |
| 393 return collisions; |
| 394 } |
| 395 |
| 349 /// Properly resolve collisions when [output] is removed. | 396 /// Properly resolve collisions when [output] is removed. |
| 350 void _handleOutputRemoval(AssetNode output) { | 397 void _handleOutputRemoval(AssetNode output) { |
| 351 output.whenRemoved.then((_) { | 398 output.whenRemoved.then((_) { |
| 352 var assets = _outputs[output.id]; | 399 var assets = _outputs[output.id]; |
| 353 if (assets.length == 1) { | 400 if (assets.length == 1) { |
| 354 assert(assets.single == output); | 401 assert(assets.single == output); |
| 355 _outputs.remove(output.id); | 402 _outputs.remove(output.id); |
| 356 return; | 403 return; |
| 357 } | 404 } |
| 358 | 405 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 375 // Pump the event queue to ensure that the removal of the input triggers | 422 // Pump the event queue to ensure that the removal of the input triggers |
| 376 // a new build to which we can attach the error. | 423 // a new build to which we can attach the error. |
| 377 newFuture(() => cascade.reportError(new AssetCollisionException( | 424 newFuture(() => cascade.reportError(new AssetCollisionException( |
| 378 assets.where((asset) => asset.transform != null) | 425 assets.where((asset) => asset.transform != null) |
| 379 .map((asset) => asset.transform.info), | 426 .map((asset) => asset.transform.info), |
| 380 output.id))); | 427 output.id))); |
| 381 } | 428 } |
| 382 }); | 429 }); |
| 383 } | 430 } |
| 384 } | 431 } |
| OLD | NEW |