| 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 'asset.dart'; | 9 import 'asset.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 12 import 'declaring_transform.dart'; | 12 import 'declaring_transform.dart'; |
| 13 import 'declaring_transformer.dart'; | 13 import 'declaring_transformer.dart'; |
| 14 import 'errors.dart'; | 14 import 'errors.dart'; |
| 15 import 'lazy_transformer.dart'; | 15 import 'lazy_transformer.dart'; |
| 16 import 'log.dart'; | 16 import 'log.dart'; |
| 17 import 'node_streams.dart'; |
| 17 import 'phase.dart'; | 18 import 'phase.dart'; |
| 18 import 'stream_pool.dart'; | |
| 19 import 'transform.dart'; | 19 import 'transform.dart'; |
| 20 import 'transformer.dart'; | 20 import 'transformer.dart'; |
| 21 import 'utils.dart'; | 21 import 'utils.dart'; |
| 22 | 22 |
| 23 /// Describes a transform on a set of assets and its relationship to the build | 23 /// Describes a transform on a set of assets and its relationship to the build |
| 24 /// dependency graph. | 24 /// dependency graph. |
| 25 /// | 25 /// |
| 26 /// Keeps track of whether it's dirty and needs to be run and which assets it | 26 /// Keeps track of whether it's dirty and needs to be run and which assets it |
| 27 /// depends on. | 27 /// depends on. |
| 28 class TransformNode { | 28 class TransformNode { |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 74 | 74 |
| 75 /// The controller that's used to pass [primary] through [this] if it's not | 75 /// The controller that's used to pass [primary] through [this] if it's not |
| 76 /// consumed or overwritten. | 76 /// consumed or overwritten. |
| 77 /// | 77 /// |
| 78 /// This needs an intervening controller to ensure that the output can be | 78 /// This needs an intervening controller to ensure that the output can be |
| 79 /// marked dirty when determining whether [this] will consume or overwrite it, | 79 /// marked dirty when determining whether [this] will consume or overwrite it, |
| 80 /// and be marked removed if it does. [_passThroughController] will be null | 80 /// and be marked removed if it does. [_passThroughController] will be null |
| 81 /// if the asset is not being passed through. | 81 /// if the asset is not being passed through. |
| 82 AssetNodeController _passThroughController; | 82 AssetNodeController _passThroughController; |
| 83 | 83 |
| 84 /// A stream that emits an event whenever [this] is no longer dirty. | 84 /// The asset node for this transform. |
| 85 /// | 85 final _streams = new NodeStreams(); |
| 86 /// This is synchronous in order to guarantee that it will emit an event as | 86 Stream get onDone => _streams.onDone; |
| 87 /// soon as [isDirty] flips from `true` to `false`. | 87 Stream<AssetNode> get onAsset => _streams.onAsset; |
| 88 Stream get onDone => _onDoneController.stream; | 88 Stream<LogEntry> get onLog => _streams.onLog; |
| 89 final _onDoneController = new StreamController.broadcast(sync: true); | |
| 90 | |
| 91 /// A stream that emits any new assets emitted by [this]. | |
| 92 /// | |
| 93 /// Assets are emitted synchronously to ensure that any changes are thoroughly | |
| 94 /// propagated as soon as they occur. | |
| 95 Stream<AssetNode> get onAsset => _onAssetController.stream; | |
| 96 final _onAssetController = | |
| 97 new StreamController<AssetNode>.broadcast(sync: true); | |
| 98 | |
| 99 /// A stream that emits an event whenever this transform logs an entry. | |
| 100 /// | |
| 101 /// This is synchronous because error logs can cause the transform to fail, so | |
| 102 /// we need to ensure that their processing isn't delayed until after the | |
| 103 /// transform or build has finished. | |
| 104 Stream<LogEntry> get onLog => _onLogPool.stream; | |
| 105 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | |
| 106 | |
| 107 /// A controller for log entries emitted by this node. | |
| 108 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); | |
| 109 | 89 |
| 110 /// The current state of [this]. | 90 /// The current state of [this]. |
| 111 var _state = _State.DECLARING; | 91 var _state = _State.DECLARING; |
| 112 | 92 |
| 113 /// Whether [this] has been marked as removed. | 93 /// Whether [this] has been marked as removed. |
| 114 bool get _isRemoved => _onAssetController.isClosed; | 94 bool get _isRemoved => _streams.onAssetController.isClosed; |
| 115 | 95 |
| 116 /// Whether the most recent run of this transform has declared that it | 96 /// Whether the most recent run of this transform has declared that it |
| 117 /// consumes the primary input. | 97 /// consumes the primary input. |
| 118 /// | 98 /// |
| 119 /// Defaults to `false`. This is not meaningful unless [_state] is | 99 /// Defaults to `false`. This is not meaningful unless [_state] is |
| 120 /// [_State.APPLIED] or [_State.DECLARED]. | 100 /// [_State.APPLIED] or [_State.DECLARED]. |
| 121 bool _consumePrimary = false; | 101 bool _consumePrimary = false; |
| 122 | 102 |
| 123 /// The set of output ids that [transformer] declared it would emit. | 103 /// The set of output ids that [transformer] declared it would emit. |
| 124 /// | 104 /// |
| 125 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its | 105 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its |
| 126 /// [declareOutputs] has been run successfully. | 106 /// [declareOutputs] has been run successfully. |
| 127 Set<AssetId> _declaredOutputs; | 107 Set<AssetId> _declaredOutputs; |
| 128 | 108 |
| 129 TransformNode(this.phase, Transformer transformer, AssetNode primary, | 109 TransformNode(this.phase, Transformer transformer, AssetNode primary, |
| 130 this._location) | 110 this._location) |
| 131 : transformer = transformer, | 111 : transformer = transformer, |
| 132 primary = primary, | 112 primary = primary, |
| 133 deferred = transformer is LazyTransformer || | 113 deferred = transformer is LazyTransformer || |
| 134 (transformer is DeclaringTransformer && primary.deferred) { | 114 (transformer is DeclaringTransformer && primary.deferred) { |
| 135 _forced = !deferred; | 115 _forced = !deferred; |
| 136 | 116 |
| 137 _onLogPool.add(_onLogController.stream); | |
| 138 | |
| 139 _primarySubscription = primary.onStateChange.listen((state) { | 117 _primarySubscription = primary.onStateChange.listen((state) { |
| 140 if (state.isRemoved) { | 118 if (state.isRemoved) { |
| 141 remove(); | 119 remove(); |
| 142 } else { | 120 } else { |
| 143 if (state.isDirty && !deferred) primary.force(); | 121 if (state.isDirty && !deferred) primary.force(); |
| 144 // If this is deferred but applying, that means it must have been | 122 // If this is deferred but applying, that means it must have been |
| 145 // forced, so we should ensure its input remains forced as well. | 123 // forced, so we should ensure its input remains forced as well. |
| 146 if (deferred && _forced && _state == _State.APPLYING) primary.force(); | 124 if (deferred && _forced && _state == _State.APPLYING) primary.force(); |
| 147 _dirty(); | 125 _dirty(); |
| 148 } | 126 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 163 /// node. | 141 /// node. |
| 164 TransformInfo get info => new TransformInfo(transformer, primary.id); | 142 TransformInfo get info => new TransformInfo(transformer, primary.id); |
| 165 | 143 |
| 166 /// Marks this transform as removed. | 144 /// Marks this transform as removed. |
| 167 /// | 145 /// |
| 168 /// This causes all of the transform's outputs to be marked as removed as | 146 /// This causes all of the transform's outputs to be marked as removed as |
| 169 /// well. Normally this will be automatically done internally based on events | 147 /// well. Normally this will be automatically done internally based on events |
| 170 /// from the primary input, but it's possible for a transform to no longer be | 148 /// from the primary input, but it's possible for a transform to no longer be |
| 171 /// valid even if its primary input still exists. | 149 /// valid even if its primary input still exists. |
| 172 void remove() { | 150 void remove() { |
| 173 _onLogController.close(); | 151 _streams.close(); |
| 174 _onAssetController.close(); | |
| 175 _onDoneController.close(); | |
| 176 _primarySubscription.cancel(); | 152 _primarySubscription.cancel(); |
| 177 _phaseSubscription.cancel(); | 153 _phaseSubscription.cancel(); |
| 178 _clearInputSubscriptions(); | 154 _clearInputSubscriptions(); |
| 179 _clearOutputs(); | 155 _clearOutputs(); |
| 180 if (_passThroughController != null) { | 156 if (_passThroughController != null) { |
| 181 _passThroughController.setRemoved(); | 157 _passThroughController.setRemoved(); |
| 182 _passThroughController = null; | 158 _passThroughController = null; |
| 183 } | 159 } |
| 184 } | 160 } |
| 185 | 161 |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 255 }).then((isPrimary) { | 231 }).then((isPrimary) { |
| 256 if (_isRemoved) return null; | 232 if (_isRemoved) return null; |
| 257 if (isPrimary) { | 233 if (isPrimary) { |
| 258 if (!deferred) primary.force(); | 234 if (!deferred) primary.force(); |
| 259 return _declareOutputs().then((_) { | 235 return _declareOutputs().then((_) { |
| 260 if (_isRemoved) return; | 236 if (_isRemoved) return; |
| 261 if (_forced) { | 237 if (_forced) { |
| 262 _apply(); | 238 _apply(); |
| 263 } else { | 239 } else { |
| 264 _state = _State.DECLARED; | 240 _state = _State.DECLARED; |
| 265 _onDoneController.add(null); | 241 _streams.onDoneController.add(null); |
| 266 } | 242 } |
| 267 }); | 243 }); |
| 268 } | 244 } |
| 269 | 245 |
| 270 _emitPassThrough(); | 246 _emitPassThrough(); |
| 271 _state = _State.NOT_PRIMARY; | 247 _state = _State.NOT_PRIMARY; |
| 272 _onDoneController.add(null); | 248 _streams.onDoneController.add(null); |
| 273 }); | 249 }); |
| 274 } | 250 } |
| 275 | 251 |
| 276 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty | 252 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty |
| 277 /// assets. | 253 /// assets. |
| 278 Future _declareOutputs() { | 254 Future _declareOutputs() { |
| 279 if (transformer is! DeclaringTransformer) return new Future.value(); | 255 if (transformer is! DeclaringTransformer) return new Future.value(); |
| 280 | 256 |
| 281 var controller = new DeclaringTransformController(this); | 257 var controller = new DeclaringTransformController(this); |
| 282 return syncFuture(() { | 258 return syncFuture(() { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 296 phase.cascade.reportError(new InvalidOutputException(info, id)); | 272 phase.cascade.reportError(new InvalidOutputException(info, id)); |
| 297 } | 273 } |
| 298 | 274 |
| 299 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); | 275 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); |
| 300 | 276 |
| 301 for (var id in _declaredOutputs) { | 277 for (var id in _declaredOutputs) { |
| 302 var controller = _forced | 278 var controller = _forced |
| 303 ? new AssetNodeController(id, this) | 279 ? new AssetNodeController(id, this) |
| 304 : new AssetNodeController.lazy(id, force, this); | 280 : new AssetNodeController.lazy(id, force, this); |
| 305 _outputControllers[id] = controller; | 281 _outputControllers[id] = controller; |
| 306 _onAssetController.add(controller.node); | 282 _streams.onAssetController.add(controller.node); |
| 307 } | 283 } |
| 308 }).catchError((error, stackTrace) { | 284 }).catchError((error, stackTrace) { |
| 309 if (_isRemoved) return; | 285 if (_isRemoved) return; |
| 310 phase.cascade.reportError(_wrapException(error, stackTrace)); | 286 phase.cascade.reportError(_wrapException(error, stackTrace)); |
| 311 }); | 287 }); |
| 312 } | 288 } |
| 313 | 289 |
| 314 /// Applies this transform. | 290 /// Applies this transform. |
| 315 void _apply() { | 291 void _apply() { |
| 316 assert(!_isRemoved); | 292 assert(!_isRemoved); |
| (...skipping 23 matching lines...) Expand all Loading... |
| 340 // consume the pass-through asset, we can safely emit it. | 316 // consume the pass-through asset, we can safely emit it. |
| 341 if (_declaredOutputs != null && !_consumePrimary && | 317 if (_declaredOutputs != null && !_consumePrimary && |
| 342 !_declaredOutputs.contains(primary.id)) { | 318 !_declaredOutputs.contains(primary.id)) { |
| 343 _emitPassThrough(); | 319 _emitPassThrough(); |
| 344 } else { | 320 } else { |
| 345 _dontEmitPassThrough(); | 321 _dontEmitPassThrough(); |
| 346 } | 322 } |
| 347 } | 323 } |
| 348 | 324 |
| 349 _state = _State.APPLIED; | 325 _state = _State.APPLIED; |
| 350 _onDoneController.add(null); | 326 _streams.onDoneController.add(null); |
| 351 }); | 327 }); |
| 352 } | 328 } |
| 353 | 329 |
| 354 /// Gets the asset for an input [id]. | 330 /// Gets the asset for an input [id]. |
| 355 /// | 331 /// |
| 356 /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. | 332 /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. |
| 357 Future<Asset> getInput(AssetId id) { | 333 Future<Asset> getInput(AssetId id) { |
| 358 return phase.previous.getOutput(id).then((node) { | 334 return phase.previous.getOutput(id).then((node) { |
| 359 // Throw if the input isn't found. This ensures the transformer's apply | 335 // Throw if the input isn't found. This ensures the transformer's apply |
| 360 // is exited. We'll then catch this and report it through the proper | 336 // is exited. We'll then catch this and report it through the proper |
| 361 // results stream. | 337 // results stream. |
| 362 if (node == null) { | 338 if (node == null) { |
| 363 _missingInputs.add(id); | 339 _missingInputs.add(id); |
| 364 throw new AssetNotFoundException(id); | 340 throw new AssetNotFoundException(id); |
| 365 } | 341 } |
| 366 | 342 |
| 367 _inputSubscriptions.putIfAbsent(node.id, () { | 343 _inputSubscriptions.putIfAbsent(node.id, () { |
| 368 return node.onStateChange.listen((state) => _dirty()); | 344 return node.onStateChange.listen((state) => _dirty()); |
| 369 }); | 345 }); |
| 370 | 346 |
| 371 return node.asset; | 347 return node.asset; |
| 372 }); | 348 }); |
| 373 } | 349 } |
| 374 | 350 |
| 375 /// Run [Transformer.apply] as soon as [primary] is available. | 351 /// Run [Transformer.apply] as soon as [primary] is available. |
| 376 /// | 352 /// |
| 377 /// Returns whether or not an error occurred while running the transformer. | 353 /// Returns whether or not an error occurred while running the transformer. |
| 378 Future<bool> _runApply() { | 354 Future<bool> _runApply() { |
| 379 var transformController = new TransformController(this); | 355 var transformController = new TransformController(this); |
| 380 _onLogPool.add(transformController.onLog); | 356 _streams.onLogPool.add(transformController.onLog); |
| 381 | 357 |
| 382 return primary.whenAvailable((_) { | 358 return primary.whenAvailable((_) { |
| 383 if (_isRemoved) return null; | 359 if (_isRemoved) return null; |
| 384 _state = _State.APPLYING; | 360 _state = _State.APPLYING; |
| 385 return syncFuture(() => transformer.apply(transformController.transform)); | 361 return syncFuture(() => transformer.apply(transformController.transform)); |
| 386 }).then((_) { | 362 }).then((_) { |
| 387 if (deferred && !_forced && !primary.state.isAvailable) { | 363 if (deferred && !_forced && !primary.state.isAvailable) { |
| 388 _state = _State.DECLARED; | 364 _state = _State.DECLARED; |
| 389 _onDoneController.add(null); | 365 _streams.onDoneController.add(null); |
| 390 return false; | 366 return false; |
| 391 } | 367 } |
| 392 | 368 |
| 393 if (_isRemoved) return false; | 369 if (_isRemoved) return false; |
| 394 if (_state == _State.NEEDS_APPLY) return false; | 370 if (_state == _State.NEEDS_APPLY) return false; |
| 395 if (_state == _State.DECLARING) return false; | 371 if (_state == _State.DECLARING) return false; |
| 396 if (transformController.loggedError) return true; | 372 if (transformController.loggedError) return true; |
| 397 _handleApplyResults(transformController); | 373 _handleApplyResults(transformController); |
| 398 return false; | 374 return false; |
| 399 }).catchError((error, stackTrace) { | 375 }).catchError((error, stackTrace) { |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 442 } | 418 } |
| 443 | 419 |
| 444 // Store any new outputs or new contents for existing outputs. | 420 // Store any new outputs or new contents for existing outputs. |
| 445 for (var asset in newOutputs) { | 421 for (var asset in newOutputs) { |
| 446 var controller = _outputControllers[asset.id]; | 422 var controller = _outputControllers[asset.id]; |
| 447 if (controller != null) { | 423 if (controller != null) { |
| 448 controller.setAvailable(asset); | 424 controller.setAvailable(asset); |
| 449 } else { | 425 } else { |
| 450 var controller = new AssetNodeController.available(asset, this); | 426 var controller = new AssetNodeController.available(asset, this); |
| 451 _outputControllers[asset.id] = controller; | 427 _outputControllers[asset.id] = controller; |
| 452 _onAssetController.add(controller.node); | 428 _streams.onAssetController.add(controller.node); |
| 453 } | 429 } |
| 454 } | 430 } |
| 455 } | 431 } |
| 456 | 432 |
| 457 /// Cancels all subscriptions to secondary input nodes. | 433 /// Cancels all subscriptions to secondary input nodes. |
| 458 void _clearInputSubscriptions() { | 434 void _clearInputSubscriptions() { |
| 459 _missingInputs.clear(); | 435 _missingInputs.clear(); |
| 460 for (var subscription in _inputSubscriptions.values) { | 436 for (var subscription in _inputSubscriptions.values) { |
| 461 subscription.cancel(); | 437 subscription.cancel(); |
| 462 } | 438 } |
| 463 _inputSubscriptions.clear(); | 439 _inputSubscriptions.clear(); |
| 464 } | 440 } |
| 465 | 441 |
| 466 /// Removes all output assets. | 442 /// Removes all output assets. |
| 467 void _clearOutputs() { | 443 void _clearOutputs() { |
| 468 // Remove all the previously-emitted assets. | 444 // Remove all the previously-emitted assets. |
| 469 for (var controller in _outputControllers.values) { | 445 for (var controller in _outputControllers.values) { |
| 470 controller.setRemoved(); | 446 controller.setRemoved(); |
| 471 } | 447 } |
| 472 _outputControllers.clear(); | 448 _outputControllers.clear(); |
| 473 } | 449 } |
| 474 | 450 |
| 475 /// Emit the pass-through asset if it's not being emitted already. | 451 /// Emit the pass-through asset if it's not being emitted already. |
| 476 void _emitPassThrough() { | 452 void _emitPassThrough() { |
| 477 assert(!_outputControllers.containsKey(primary.id)); | 453 assert(!_outputControllers.containsKey(primary.id)); |
| 478 | 454 |
| 479 if (_consumePrimary) return; | 455 if (_consumePrimary) return; |
| 480 if (_passThroughController == null) { | 456 if (_passThroughController == null) { |
| 481 _passThroughController = new AssetNodeController.from(primary); | 457 _passThroughController = new AssetNodeController.from(primary); |
| 482 _onAssetController.add(_passThroughController.node); | 458 _streams.onAssetController.add(_passThroughController.node); |
| 483 } else if (primary.state.isDirty) { | 459 } else if (primary.state.isDirty) { |
| 484 _passThroughController.setDirty(); | 460 _passThroughController.setDirty(); |
| 485 } else if (!_passThroughController.node.state.isAvailable) { | 461 } else if (!_passThroughController.node.state.isAvailable) { |
| 486 _passThroughController.setAvailable(primary.asset); | 462 _passThroughController.setAvailable(primary.asset); |
| 487 } | 463 } |
| 488 } | 464 } |
| 489 | 465 |
| 490 /// Stop emitting the pass-through asset if it's being emitted already. | 466 /// Stop emitting the pass-through asset if it's being emitted already. |
| 491 void _dontEmitPassThrough() { | 467 void _dontEmitPassThrough() { |
| 492 if (_passThroughController == null) return; | 468 if (_passThroughController == null) return; |
| 493 _passThroughController.setRemoved(); | 469 _passThroughController.setRemoved(); |
| 494 _passThroughController = null; | 470 _passThroughController = null; |
| 495 } | 471 } |
| 496 | 472 |
| 497 BarbackException _wrapException(error, StackTrace stackTrace) { | 473 BarbackException _wrapException(error, StackTrace stackTrace) { |
| 498 if (error is! AssetNotFoundException) { | 474 if (error is! AssetNotFoundException) { |
| 499 return new TransformerException(info, error, stackTrace); | 475 return new TransformerException(info, error, stackTrace); |
| 500 } else { | 476 } else { |
| 501 return new MissingInputException(info, error.id); | 477 return new MissingInputException(info, error.id); |
| 502 } | 478 } |
| 503 } | 479 } |
| 504 | 480 |
| 505 /// Emit a warning about the transformer on [id]. | 481 /// Emit a warning about the transformer on [id]. |
| 506 void _warn(String message) { | 482 void _warn(String message) { |
| 507 _onLogController.add( | 483 _streams.onLogController.add( |
| 508 new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); | 484 new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); |
| 509 } | 485 } |
| 510 | 486 |
| 511 String toString() => | 487 String toString() => |
| 512 "transform node in $_location for $transformer on $primary"; | 488 "transform node in $_location for $transformer on $primary"; |
| 513 } | 489 } |
| 514 | 490 |
| 515 /// The enum of states that [TransformNode] can be in. | 491 /// The enum of states that [TransformNode] can be in. |
| 516 class _State { | 492 class _State { |
| 517 /// The transform is running [Transformer.isPrimary] followed by | 493 /// The transform is running [Transformer.isPrimary] followed by |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 562 /// | 538 /// |
| 563 /// This will never transition to another state. | 539 /// This will never transition to another state. |
| 564 static final NOT_PRIMARY = const _State._("not primary"); | 540 static final NOT_PRIMARY = const _State._("not primary"); |
| 565 | 541 |
| 566 final String name; | 542 final String name; |
| 567 | 543 |
| 568 const _State._(this.name); | 544 const _State._(this.name); |
| 569 | 545 |
| 570 String toString() => name; | 546 String toString() => name; |
| 571 } | 547 } |
| OLD | NEW |