| 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.transform_node; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'asset.dart'; | |
| 10 import 'asset_id.dart'; | |
| 11 import 'asset_node.dart'; | |
| 12 import 'declaring_transform.dart'; | |
| 13 import 'declaring_transformer.dart'; | |
| 14 import 'errors.dart'; | |
| 15 import 'lazy_transformer.dart'; | |
| 16 import 'log.dart'; | |
| 17 import 'node_status.dart'; | |
| 18 import 'node_streams.dart'; | |
| 19 import 'phase.dart'; | |
| 20 import 'transform.dart'; | |
| 21 import 'transformer.dart'; | |
| 22 import 'utils.dart'; | |
| 23 | |
| 24 /// Describes a transform on a set of assets and its relationship to the build | |
| 25 /// dependency graph. | |
| 26 /// | |
| 27 /// Keeps track of whether it's dirty and needs to be run and which assets it | |
| 28 /// depends on. | |
| 29 class TransformNode { | |
| 30 /// The [Phase] that this transform runs in. | |
| 31 final Phase phase; | |
| 32 | |
| 33 /// The [Transformer] to apply to this node's inputs. | |
| 34 final Transformer transformer; | |
| 35 | |
| 36 /// The node for the primary asset this transform depends on. | |
| 37 final AssetNode primary; | |
| 38 | |
| 39 /// A string describing the location of [this] in the transformer graph. | |
| 40 final String _location; | |
| 41 | |
| 42 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | |
| 43 StreamSubscription _primarySubscription; | |
| 44 | |
| 45 /// The subscription to [phase]'s [Phase.onAsset] stream. | |
| 46 StreamSubscription<AssetNode> _phaseSubscription; | |
| 47 | |
| 48 /// How far along [this] is in processing its assets. | |
| 49 NodeStatus get status { | |
| 50 if (_state == _State.APPLIED || _state == _State.DECLARED) { | |
| 51 return NodeStatus.IDLE; | |
| 52 } | |
| 53 | |
| 54 if (_declaring && _state != _State.DECLARING) { | |
| 55 return NodeStatus.MATERIALIZING; | |
| 56 } else { | |
| 57 return NodeStatus.RUNNING; | |
| 58 } | |
| 59 } | |
| 60 | |
| 61 /// Whether this is a declaring transform. | |
| 62 /// | |
| 63 /// This is usually identical to `transformer is DeclaringTransformer`, but if | |
| 64 /// a declaring and non-lazy transformer emits an error during | |
| 65 /// `declareOutputs` it's treated as though it wasn't declaring. | |
| 66 bool get _declaring => transformer is DeclaringTransformer && | |
| 67 (_state == _State.DECLARING || _declaredOutputs != null); | |
| 68 | |
| 69 /// Whether this transform has been forced since it last finished applying. | |
| 70 /// | |
| 71 /// A transform being forced means it should run until it generates outputs | |
| 72 /// and is no longer dirty. This is always true for non-declaring | |
| 73 /// transformers, since they always need to eagerly generate outputs. | |
| 74 bool _forced; | |
| 75 | |
| 76 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | |
| 77 final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | |
| 78 | |
| 79 /// The controllers for the asset nodes emitted by this node. | |
| 80 final _outputControllers = new Map<AssetId, AssetNodeController>(); | |
| 81 | |
| 82 /// The ids of inputs the transformer tried and failed to read last time it | |
| 83 /// ran. | |
| 84 final _missingInputs = new Set<AssetId>(); | |
| 85 | |
| 86 /// The controller that's used to pass [primary] through [this] if it's not | |
| 87 /// consumed or overwritten. | |
| 88 /// | |
| 89 /// This needs an intervening controller to ensure that the output can be | |
| 90 /// marked dirty when determining whether [this] will consume or overwrite it, | |
| 91 /// and be marked removed if it does. [_passThroughController] will be null | |
| 92 /// if the asset is not being passed through. | |
| 93 AssetNodeController _passThroughController; | |
| 94 | |
| 95 /// The asset node for this transform. | |
| 96 final _streams = new NodeStreams(); | |
| 97 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | |
| 98 Stream<AssetNode> get onAsset => _streams.onAsset; | |
| 99 Stream<LogEntry> get onLog => _streams.onLog; | |
| 100 | |
| 101 /// The current state of [this]. | |
| 102 var _state = _State.DECLARING; | |
| 103 | |
| 104 /// Whether [this] has been marked as removed. | |
| 105 bool get _isRemoved => _streams.onAssetController.isClosed; | |
| 106 | |
| 107 // If [transformer] is declaring but not lazy and [primary] is available, we | |
| 108 // can run [apply] even if [force] hasn't been called, since [transformer] | |
| 109 // should run eagerly if possible. | |
| 110 bool get _canRunDeclaringEagerly => | |
| 111 _declaring && transformer is! LazyTransformer && | |
| 112 primary.state.isAvailable; | |
| 113 | |
| 114 /// Whether the most recent run of this transform has declared that it | |
| 115 /// consumes the primary input. | |
| 116 /// | |
| 117 /// Defaults to `false`. This is not meaningful unless [_state] is | |
| 118 /// [_State.APPLIED] or [_State.DECLARED]. | |
| 119 bool _consumePrimary = false; | |
| 120 | |
| 121 /// The set of output ids that [transformer] declared it would emit. | |
| 122 /// | |
| 123 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its | |
| 124 /// [declareOutputs] has been run successfully. | |
| 125 Set<AssetId> _declaredOutputs; | |
| 126 | |
| 127 TransformNode(this.phase, Transformer transformer, AssetNode primary, | |
| 128 this._location) | |
| 129 : transformer = transformer, | |
| 130 primary = primary { | |
| 131 _forced = transformer is! DeclaringTransformer; | |
| 132 if (_forced) primary.force(); | |
| 133 | |
| 134 _primarySubscription = primary.onStateChange.listen((state) { | |
| 135 if (state.isRemoved) { | |
| 136 remove(); | |
| 137 } else { | |
| 138 if (_forced) primary.force(); | |
| 139 _dirty(); | |
| 140 } | |
| 141 }); | |
| 142 | |
| 143 _phaseSubscription = phase.previous.onAsset.listen((node) { | |
| 144 if (!_missingInputs.contains(node.id)) return; | |
| 145 if (_forced) node.force(); | |
| 146 _dirty(); | |
| 147 }); | |
| 148 | |
| 149 _declareOutputs().then((_) { | |
| 150 if (_forced || _canRunDeclaringEagerly) { | |
| 151 _apply(); | |
| 152 } else { | |
| 153 _state = _State.DECLARED; | |
| 154 _streams.changeStatus(NodeStatus.IDLE); | |
| 155 } | |
| 156 }); | |
| 157 } | |
| 158 | |
| 159 /// The [TransformInfo] describing this node. | |
| 160 /// | |
| 161 /// [TransformInfo] is the publicly-visible representation of a transform | |
| 162 /// node. | |
| 163 TransformInfo get info => new TransformInfo(transformer, primary.id); | |
| 164 | |
| 165 /// Marks this transform as removed. | |
| 166 /// | |
| 167 /// This causes all of the transform's outputs to be marked as removed as | |
| 168 /// well. Normally this will be automatically done internally based on events | |
| 169 /// from the primary input, but it's possible for a transform to no longer be | |
| 170 /// valid even if its primary input still exists. | |
| 171 void remove() { | |
| 172 _streams.close(); | |
| 173 _primarySubscription.cancel(); | |
| 174 _phaseSubscription.cancel(); | |
| 175 _clearInputSubscriptions(); | |
| 176 _clearOutputs(); | |
| 177 if (_passThroughController != null) { | |
| 178 _passThroughController.setRemoved(); | |
| 179 _passThroughController = null; | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 /// If [this] is deferred, ensures that its concrete outputs will be | |
| 184 /// generated. | |
| 185 void force() { | |
| 186 if (_forced || _state == _State.APPLIED) return; | |
| 187 primary.force(); | |
| 188 _forced = true; | |
| 189 if (_state == _State.DECLARED) _dirty(); | |
| 190 } | |
| 191 | |
| 192 /// Marks this transform as dirty. | |
| 193 /// | |
| 194 /// This causes all of the transform's outputs to be marked as dirty as well. | |
| 195 void _dirty() { | |
| 196 // If we're in the process of running [declareOutputs], we already know that | |
| 197 // [apply] needs to be run so there's nothing we need to mark as dirty. | |
| 198 if (_state == _State.DECLARING) return; | |
| 199 | |
| 200 if (!_forced && !_canRunDeclaringEagerly) { | |
| 201 // [forced] should only ever be false for a declaring transformer. | |
| 202 assert(_declaring); | |
| 203 | |
| 204 // If we've finished applying, transition to MATERIALIZING, indicating | |
| 205 // that we know what outputs [apply] will emit but we're waiting to emit | |
| 206 // them concretely until [force] is called. If we're still applying, we'll | |
| 207 // transition to MATERIALIZING once we finish. | |
| 208 if (_state == _State.APPLIED) _state = _State.DECLARED; | |
| 209 for (var controller in _outputControllers.values) { | |
| 210 controller.setLazy(force); | |
| 211 } | |
| 212 _emitDeclaredOutputs(); | |
| 213 return; | |
| 214 } | |
| 215 | |
| 216 if (_passThroughController != null) _passThroughController.setDirty(); | |
| 217 for (var controller in _outputControllers.values) { | |
| 218 controller.setDirty(); | |
| 219 } | |
| 220 | |
| 221 if (_state == _State.APPLIED) { | |
| 222 if (_declaredOutputs != null) _emitDeclaredOutputs(); | |
| 223 _apply(); | |
| 224 } else if (_state == _State.DECLARED) { | |
| 225 _apply(); | |
| 226 } else { | |
| 227 _state = _State.NEEDS_APPLY; | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty | |
| 232 /// assets. | |
| 233 Future _declareOutputs() { | |
| 234 if (transformer is! DeclaringTransformer) return new Future.value(); | |
| 235 | |
| 236 var controller = new DeclaringTransformController(this); | |
| 237 return syncFuture(() { | |
| 238 return (transformer as DeclaringTransformer) | |
| 239 .declareOutputs(controller.transform); | |
| 240 }).then((_) { | |
| 241 if (_isRemoved) return; | |
| 242 if (controller.loggedError) return; | |
| 243 | |
| 244 _consumePrimary = controller.consumePrimary; | |
| 245 _declaredOutputs = controller.outputIds; | |
| 246 var invalidIds = _declaredOutputs | |
| 247 .where((id) => id.package != phase.cascade.package).toSet(); | |
| 248 for (var id in invalidIds) { | |
| 249 _declaredOutputs.remove(id); | |
| 250 // TODO(nweiz): report this as a warning rather than a failing error. | |
| 251 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
| 252 } | |
| 253 | |
| 254 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); | |
| 255 _emitDeclaredOutputs(); | |
| 256 }).catchError((error, stackTrace) { | |
| 257 if (_isRemoved) return; | |
| 258 if (transformer is! LazyTransformer) _forced = true; | |
| 259 phase.cascade.reportError(_wrapException(error, stackTrace)); | |
| 260 }); | |
| 261 } | |
| 262 | |
| 263 /// Emits a dirty asset node for all outputs that were declared by the | |
| 264 /// transformer. | |
| 265 /// | |
| 266 /// This won't emit any outputs for which there already exist output | |
| 267 /// controllers. It should only be called for transforms that have declared | |
| 268 /// their outputs. | |
| 269 void _emitDeclaredOutputs() { | |
| 270 assert(_declaredOutputs != null); | |
| 271 for (var id in _declaredOutputs) { | |
| 272 if (_outputControllers.containsKey(id)) continue; | |
| 273 var controller = _forced | |
| 274 ? new AssetNodeController(id, this) | |
| 275 : new AssetNodeController.lazy(id, force, this); | |
| 276 _outputControllers[id] = controller; | |
| 277 _streams.onAssetController.add(controller.node); | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 /// Applies this transform. | |
| 282 void _apply() { | |
| 283 assert(!_isRemoved); | |
| 284 | |
| 285 // Clear input subscriptions here as well as in [_process] because [_apply] | |
| 286 // may be restarted independently if only a secondary input changes. | |
| 287 _clearInputSubscriptions(); | |
| 288 _state = _State.APPLYING; | |
| 289 _streams.changeStatus(status); | |
| 290 _runApply().then((hadError) { | |
| 291 if (_isRemoved) return; | |
| 292 | |
| 293 if (_state == _State.DECLARED) return; | |
| 294 | |
| 295 if (_state == _State.NEEDS_APPLY) { | |
| 296 _apply(); | |
| 297 return; | |
| 298 } | |
| 299 | |
| 300 if (_declaring) _forced = false; | |
| 301 | |
| 302 assert(_state == _State.APPLYING); | |
| 303 if (hadError) { | |
| 304 _clearOutputs(); | |
| 305 // If the transformer threw an error, we don't want to emit the | |
| 306 // pass-through asset in case it will be overwritten by the transformer. | |
| 307 // However, if the transformer declared that it wouldn't overwrite or | |
| 308 // consume the pass-through asset, we can safely emit it. | |
| 309 if (_declaredOutputs != null && !_consumePrimary && | |
| 310 !_declaredOutputs.contains(primary.id)) { | |
| 311 _emitPassThrough(); | |
| 312 } else { | |
| 313 _dontEmitPassThrough(); | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 _state = _State.APPLIED; | |
| 318 _streams.changeStatus(NodeStatus.IDLE); | |
| 319 }); | |
| 320 } | |
| 321 | |
| 322 /// Gets the asset for an input [id]. | |
| 323 /// | |
| 324 /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. | |
| 325 Future<Asset> getInput(AssetId id) { | |
| 326 return phase.previous.getOutput(id).then((node) { | |
| 327 // Throw if the input isn't found. This ensures the transformer's apply | |
| 328 // is exited. We'll then catch this and report it through the proper | |
| 329 // results stream. | |
| 330 if (node == null) { | |
| 331 _missingInputs.add(id); | |
| 332 throw new AssetNotFoundException(id); | |
| 333 } | |
| 334 | |
| 335 _inputSubscriptions.putIfAbsent(node.id, () { | |
| 336 return node.onStateChange.listen((state) => _dirty()); | |
| 337 }); | |
| 338 | |
| 339 return node.asset; | |
| 340 }); | |
| 341 } | |
| 342 | |
| 343 /// Run [Transformer.apply] as soon as [primary] is available. | |
| 344 /// | |
| 345 /// Returns whether or not an error occurred while running the transformer. | |
| 346 Future<bool> _runApply() { | |
| 347 var transformController = new TransformController(this); | |
| 348 _streams.onLogPool.add(transformController.onLog); | |
| 349 | |
| 350 return primary.whenAvailable((_) { | |
| 351 if (_isRemoved) return null; | |
| 352 _state = _State.APPLYING; | |
| 353 return syncFuture(() => transformer.apply(transformController.transform)); | |
| 354 }).then((_) { | |
| 355 if (!_forced && !primary.state.isAvailable) { | |
| 356 _state = _State.DECLARED; | |
| 357 _streams.changeStatus(NodeStatus.IDLE); | |
| 358 return false; | |
| 359 } | |
| 360 | |
| 361 if (_isRemoved) return false; | |
| 362 if (_state == _State.NEEDS_APPLY) return false; | |
| 363 if (_state == _State.DECLARING) return false; | |
| 364 if (transformController.loggedError) return true; | |
| 365 _handleApplyResults(transformController); | |
| 366 return false; | |
| 367 }).catchError((error, stackTrace) { | |
| 368 // If the transform became dirty while processing, ignore any errors from | |
| 369 // it. | |
| 370 if (_state == _State.NEEDS_APPLY || _isRemoved) return false; | |
| 371 | |
| 372 // Catch all transformer errors and pipe them to the results stream. This | |
| 373 // is so a broken transformer doesn't take down the whole graph. | |
| 374 phase.cascade.reportError(_wrapException(error, stackTrace)); | |
| 375 return true; | |
| 376 }); | |
| 377 } | |
| 378 | |
| 379 /// Handle the results of running [Transformer.apply]. | |
| 380 /// | |
| 381 /// [transformController] should be the controller for the [Transform] passed | |
| 382 /// to [Transformer.apply]. | |
| 383 void _handleApplyResults(TransformController transformController) { | |
| 384 _consumePrimary = transformController.consumePrimary; | |
| 385 | |
| 386 var newOutputs = transformController.outputs; | |
| 387 // Any ids that are for a different package are invalid. | |
| 388 var invalidIds = newOutputs | |
| 389 .map((asset) => asset.id) | |
| 390 .where((id) => id.package != phase.cascade.package) | |
| 391 .toSet(); | |
| 392 for (var id in invalidIds) { | |
| 393 newOutputs.removeId(id); | |
| 394 // TODO(nweiz): report this as a warning rather than a failing error. | |
| 395 phase.cascade.reportError(new InvalidOutputException(info, id)); | |
| 396 } | |
| 397 | |
| 398 // Remove outputs that used to exist but don't anymore. | |
| 399 for (var id in _outputControllers.keys.toList()) { | |
| 400 if (newOutputs.containsId(id)) continue; | |
| 401 _outputControllers.remove(id).setRemoved(); | |
| 402 } | |
| 403 | |
| 404 // Emit or stop emitting the pass-through asset between removing and | |
| 405 // adding outputs to ensure there are no collisions. | |
| 406 if (!_consumePrimary && !newOutputs.containsId(primary.id)) { | |
| 407 _emitPassThrough(); | |
| 408 } else { | |
| 409 _dontEmitPassThrough(); | |
| 410 } | |
| 411 | |
| 412 // Store any new outputs or new contents for existing outputs. | |
| 413 for (var asset in newOutputs) { | |
| 414 var controller = _outputControllers[asset.id]; | |
| 415 if (controller != null) { | |
| 416 controller.setAvailable(asset); | |
| 417 } else { | |
| 418 var controller = new AssetNodeController.available(asset, this); | |
| 419 _outputControllers[asset.id] = controller; | |
| 420 _streams.onAssetController.add(controller.node); | |
| 421 } | |
| 422 } | |
| 423 } | |
| 424 | |
| 425 /// Cancels all subscriptions to secondary input nodes. | |
| 426 void _clearInputSubscriptions() { | |
| 427 _missingInputs.clear(); | |
| 428 for (var subscription in _inputSubscriptions.values) { | |
| 429 subscription.cancel(); | |
| 430 } | |
| 431 _inputSubscriptions.clear(); | |
| 432 } | |
| 433 | |
| 434 /// Removes all output assets. | |
| 435 void _clearOutputs() { | |
| 436 // Remove all the previously-emitted assets. | |
| 437 for (var controller in _outputControllers.values) { | |
| 438 controller.setRemoved(); | |
| 439 } | |
| 440 _outputControllers.clear(); | |
| 441 } | |
| 442 | |
| 443 /// Emit the pass-through asset if it's not being emitted already. | |
| 444 void _emitPassThrough() { | |
| 445 assert(!_outputControllers.containsKey(primary.id)); | |
| 446 | |
| 447 if (_consumePrimary) return; | |
| 448 if (_passThroughController == null) { | |
| 449 _passThroughController = new AssetNodeController.from(primary); | |
| 450 _streams.onAssetController.add(_passThroughController.node); | |
| 451 } else if (primary.state.isDirty) { | |
| 452 _passThroughController.setDirty(); | |
| 453 } else if (!_passThroughController.node.state.isAvailable) { | |
| 454 _passThroughController.setAvailable(primary.asset); | |
| 455 } | |
| 456 } | |
| 457 | |
| 458 /// Stop emitting the pass-through asset if it's being emitted already. | |
| 459 void _dontEmitPassThrough() { | |
| 460 if (_passThroughController == null) return; | |
| 461 _passThroughController.setRemoved(); | |
| 462 _passThroughController = null; | |
| 463 } | |
| 464 | |
| 465 BarbackException _wrapException(error, StackTrace stackTrace) { | |
| 466 if (error is! AssetNotFoundException) { | |
| 467 return new TransformerException(info, error, stackTrace); | |
| 468 } else { | |
| 469 return new MissingInputException(info, error.id); | |
| 470 } | |
| 471 } | |
| 472 | |
| 473 /// Emit a warning about the transformer on [id]. | |
| 474 void _warn(String message) { | |
| 475 _streams.onLogController.add( | |
| 476 new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); | |
| 477 } | |
| 478 | |
| 479 String toString() => | |
| 480 "transform node in $_location for $transformer on $primary ($_state, " | |
| 481 "$status, ${_forced ? '' : 'un'}forced)"; | |
| 482 } | |
| 483 | |
| 484 /// The enum of states that [TransformNode] can be in. | |
| 485 class _State { | |
| 486 /// The transform is running [DeclaringTransformer.declareOutputs]. | |
| 487 /// | |
| 488 /// This is the initial state of the transformer, and it will only occur once | |
| 489 /// since [DeclaringTransformer.declareOutputs] is independent of the contents | |
| 490 /// of the primary input. Once the method finishes running, this will | |
| 491 /// transition to [APPLYING] if the transform is non-lazy and the input is | |
| 492 /// available, and [DECLARED] otherwise. | |
| 493 /// | |
| 494 /// Non-declaring transformers will transition out of this state and into | |
| 495 /// [APPLYING] immediately. | |
| 496 static final DECLARING = const _State._("declaring outputs"); | |
| 497 | |
| 498 /// The transform is deferred and has run | |
| 499 /// [DeclaringTransformer.declareOutputs] but hasn't yet been forced. | |
| 500 /// | |
| 501 /// This will transition to [APPLYING] when one of the outputs has been | |
| 502 /// forced. | |
| 503 static final DECLARED = const _State._("declared"); | |
| 504 | |
| 505 /// The transform is running [Transformer.apply]. | |
| 506 /// | |
| 507 /// If an input changes while in this state, it will transition to | |
| 508 /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when | |
| 509 /// [Transformer.apply] finishes running, it will transition to [APPLIED]. | |
| 510 static final APPLYING = const _State._("applying"); | |
| 511 | |
| 512 /// The transform is running [Transformer.apply], but an input changed after | |
| 513 /// it started, so it will need to re-run [Transformer.apply]. | |
| 514 /// | |
| 515 /// This will transition to [APPLYING] once [Transformer.apply] finishes | |
| 516 /// running. | |
| 517 static final NEEDS_APPLY = const _State._("needs apply"); | |
| 518 | |
| 519 /// The transform has finished running [Transformer.apply], whether or not it | |
| 520 /// emitted an error. | |
| 521 /// | |
| 522 /// If the transformer is deferred, the [TransformNode] can also be in this | |
| 523 /// state when [Transformer.declareOutputs] has been run but | |
| 524 /// [Transformer.apply] has not. | |
| 525 /// | |
| 526 /// If an input changes, this will transition to [DECLARED] if the transform | |
| 527 /// is deferred and [APPLYING] otherwise. | |
| 528 static final APPLIED = const _State._("applied"); | |
| 529 | |
| 530 final String name; | |
| 531 | |
| 532 const _State._(this.name); | |
| 533 | |
| 534 String toString() => name; | |
| 535 } | |
| OLD | NEW |