Index: pkg/barback/lib/src/transform_node.dart |
diff --git a/pkg/barback/lib/src/transform_node.dart b/pkg/barback/lib/src/transform_node.dart |
deleted file mode 100644 |
index 95847a7808737d4fc6a90083df1f540b0cf58d90..0000000000000000000000000000000000000000 |
--- a/pkg/barback/lib/src/transform_node.dart |
+++ /dev/null |
@@ -1,535 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-library barback.transform_node; |
- |
-import 'dart:async'; |
- |
-import 'asset.dart'; |
-import 'asset_id.dart'; |
-import 'asset_node.dart'; |
-import 'declaring_transform.dart'; |
-import 'declaring_transformer.dart'; |
-import 'errors.dart'; |
-import 'lazy_transformer.dart'; |
-import 'log.dart'; |
-import 'node_status.dart'; |
-import 'node_streams.dart'; |
-import 'phase.dart'; |
-import 'transform.dart'; |
-import 'transformer.dart'; |
-import 'utils.dart'; |
- |
-/// Describes a transform on a set of assets and its relationship to the build |
-/// dependency graph. |
-/// |
-/// Keeps track of whether it's dirty and needs to be run and which assets it |
-/// depends on. |
-class TransformNode { |
- /// The [Phase] that this transform runs in. |
- final Phase phase; |
- |
- /// The [Transformer] to apply to this node's inputs. |
- final Transformer transformer; |
- |
- /// The node for the primary asset this transform depends on. |
- final AssetNode primary; |
- |
- /// A string describing the location of [this] in the transformer graph. |
- final String _location; |
- |
- /// The subscription to [primary]'s [AssetNode.onStateChange] stream. |
- StreamSubscription _primarySubscription; |
- |
- /// The subscription to [phase]'s [Phase.onAsset] stream. |
- StreamSubscription<AssetNode> _phaseSubscription; |
- |
- /// How far along [this] is in processing its assets. |
- NodeStatus get status { |
- if (_state == _State.APPLIED || _state == _State.DECLARED) { |
- return NodeStatus.IDLE; |
- } |
- |
- if (_declaring && _state != _State.DECLARING) { |
- return NodeStatus.MATERIALIZING; |
- } else { |
- return NodeStatus.RUNNING; |
- } |
- } |
- |
- /// Whether this is a declaring transform. |
- /// |
- /// This is usually identical to `transformer is DeclaringTransformer`, but if |
- /// a declaring and non-lazy transformer emits an error during |
- /// `declareOutputs` it's treated as though it wasn't declaring. |
- bool get _declaring => transformer is DeclaringTransformer && |
- (_state == _State.DECLARING || _declaredOutputs != null); |
- |
- /// Whether this transform has been forced since it last finished applying. |
- /// |
- /// A transform being forced means it should run until it generates outputs |
- /// and is no longer dirty. This is always true for non-declaring |
- /// transformers, since they always need to eagerly generate outputs. |
- bool _forced; |
- |
- /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
- final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); |
- |
- /// The controllers for the asset nodes emitted by this node. |
- final _outputControllers = new Map<AssetId, AssetNodeController>(); |
- |
- /// The ids of inputs the transformer tried and failed to read last time it |
- /// ran. |
- final _missingInputs = new Set<AssetId>(); |
- |
- /// The controller that's used to pass [primary] through [this] if it's not |
- /// consumed or overwritten. |
- /// |
- /// This needs an intervening controller to ensure that the output can be |
- /// marked dirty when determining whether [this] will consume or overwrite it, |
- /// and be marked removed if it does. [_passThroughController] will be null |
- /// if the asset is not being passed through. |
- AssetNodeController _passThroughController; |
- |
- /// The asset node for this transform. |
- final _streams = new NodeStreams(); |
- Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; |
- Stream<AssetNode> get onAsset => _streams.onAsset; |
- Stream<LogEntry> get onLog => _streams.onLog; |
- |
- /// The current state of [this]. |
- var _state = _State.DECLARING; |
- |
- /// Whether [this] has been marked as removed. |
- bool get _isRemoved => _streams.onAssetController.isClosed; |
- |
- // If [transformer] is declaring but not lazy and [primary] is available, we |
- // can run [apply] even if [force] hasn't been called, since [transformer] |
- // should run eagerly if possible. |
- bool get _canRunDeclaringEagerly => |
- _declaring && transformer is! LazyTransformer && |
- primary.state.isAvailable; |
- |
- /// Whether the most recent run of this transform has declared that it |
- /// consumes the primary input. |
- /// |
- /// Defaults to `false`. This is not meaningful unless [_state] is |
- /// [_State.APPLIED] or [_State.DECLARED]. |
- bool _consumePrimary = false; |
- |
- /// The set of output ids that [transformer] declared it would emit. |
- /// |
- /// This is only non-null if [transformer] is a [DeclaringTransformer] and its |
- /// [declareOutputs] has been run successfully. |
- Set<AssetId> _declaredOutputs; |
- |
- TransformNode(this.phase, Transformer transformer, AssetNode primary, |
- this._location) |
- : transformer = transformer, |
- primary = primary { |
- _forced = transformer is! DeclaringTransformer; |
- if (_forced) primary.force(); |
- |
- _primarySubscription = primary.onStateChange.listen((state) { |
- if (state.isRemoved) { |
- remove(); |
- } else { |
- if (_forced) primary.force(); |
- _dirty(); |
- } |
- }); |
- |
- _phaseSubscription = phase.previous.onAsset.listen((node) { |
- if (!_missingInputs.contains(node.id)) return; |
- if (_forced) node.force(); |
- _dirty(); |
- }); |
- |
- _declareOutputs().then((_) { |
- if (_forced || _canRunDeclaringEagerly) { |
- _apply(); |
- } else { |
- _state = _State.DECLARED; |
- _streams.changeStatus(NodeStatus.IDLE); |
- } |
- }); |
- } |
- |
- /// The [TransformInfo] describing this node. |
- /// |
- /// [TransformInfo] is the publicly-visible representation of a transform |
- /// node. |
- TransformInfo get info => new TransformInfo(transformer, primary.id); |
- |
- /// Marks this transform as removed. |
- /// |
- /// This causes all of the transform's outputs to be marked as removed as |
- /// well. Normally this will be automatically done internally based on events |
- /// from the primary input, but it's possible for a transform to no longer be |
- /// valid even if its primary input still exists. |
- void remove() { |
- _streams.close(); |
- _primarySubscription.cancel(); |
- _phaseSubscription.cancel(); |
- _clearInputSubscriptions(); |
- _clearOutputs(); |
- if (_passThroughController != null) { |
- _passThroughController.setRemoved(); |
- _passThroughController = null; |
- } |
- } |
- |
- /// If [this] is deferred, ensures that its concrete outputs will be |
- /// generated. |
- void force() { |
- if (_forced || _state == _State.APPLIED) return; |
- primary.force(); |
- _forced = true; |
- if (_state == _State.DECLARED) _dirty(); |
- } |
- |
- /// Marks this transform as dirty. |
- /// |
- /// This causes all of the transform's outputs to be marked as dirty as well. |
- void _dirty() { |
- // If we're in the process of running [declareOutputs], we already know that |
- // [apply] needs to be run so there's nothing we need to mark as dirty. |
- if (_state == _State.DECLARING) return; |
- |
- if (!_forced && !_canRunDeclaringEagerly) { |
- // [forced] should only ever be false for a declaring transformer. |
- assert(_declaring); |
- |
- // If we've finished applying, transition to MATERIALIZING, indicating |
- // that we know what outputs [apply] will emit but we're waiting to emit |
- // them concretely until [force] is called. If we're still applying, we'll |
- // transition to MATERIALIZING once we finish. |
- if (_state == _State.APPLIED) _state = _State.DECLARED; |
- for (var controller in _outputControllers.values) { |
- controller.setLazy(force); |
- } |
- _emitDeclaredOutputs(); |
- return; |
- } |
- |
- if (_passThroughController != null) _passThroughController.setDirty(); |
- for (var controller in _outputControllers.values) { |
- controller.setDirty(); |
- } |
- |
- if (_state == _State.APPLIED) { |
- if (_declaredOutputs != null) _emitDeclaredOutputs(); |
- _apply(); |
- } else if (_state == _State.DECLARED) { |
- _apply(); |
- } else { |
- _state = _State.NEEDS_APPLY; |
- } |
- } |
- |
- /// Runs [transform.declareOutputs] and emits the resulting assets as dirty |
- /// assets. |
- Future _declareOutputs() { |
- if (transformer is! DeclaringTransformer) return new Future.value(); |
- |
- var controller = new DeclaringTransformController(this); |
- return syncFuture(() { |
- return (transformer as DeclaringTransformer) |
- .declareOutputs(controller.transform); |
- }).then((_) { |
- if (_isRemoved) return; |
- if (controller.loggedError) return; |
- |
- _consumePrimary = controller.consumePrimary; |
- _declaredOutputs = controller.outputIds; |
- var invalidIds = _declaredOutputs |
- .where((id) => id.package != phase.cascade.package).toSet(); |
- for (var id in invalidIds) { |
- _declaredOutputs.remove(id); |
- // TODO(nweiz): report this as a warning rather than a failing error. |
- phase.cascade.reportError(new InvalidOutputException(info, id)); |
- } |
- |
- if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); |
- _emitDeclaredOutputs(); |
- }).catchError((error, stackTrace) { |
- if (_isRemoved) return; |
- if (transformer is! LazyTransformer) _forced = true; |
- phase.cascade.reportError(_wrapException(error, stackTrace)); |
- }); |
- } |
- |
- /// Emits a dirty asset node for all outputs that were declared by the |
- /// transformer. |
- /// |
- /// This won't emit any outputs for which there already exist output |
- /// controllers. It should only be called for transforms that have declared |
- /// their outputs. |
- void _emitDeclaredOutputs() { |
- assert(_declaredOutputs != null); |
- for (var id in _declaredOutputs) { |
- if (_outputControllers.containsKey(id)) continue; |
- var controller = _forced |
- ? new AssetNodeController(id, this) |
- : new AssetNodeController.lazy(id, force, this); |
- _outputControllers[id] = controller; |
- _streams.onAssetController.add(controller.node); |
- } |
- } |
- |
- /// Applies this transform. |
- void _apply() { |
- assert(!_isRemoved); |
- |
- // Clear input subscriptions here as well as in [_process] because [_apply] |
- // may be restarted independently if only a secondary input changes. |
- _clearInputSubscriptions(); |
- _state = _State.APPLYING; |
- _streams.changeStatus(status); |
- _runApply().then((hadError) { |
- if (_isRemoved) return; |
- |
- if (_state == _State.DECLARED) return; |
- |
- if (_state == _State.NEEDS_APPLY) { |
- _apply(); |
- return; |
- } |
- |
- if (_declaring) _forced = false; |
- |
- assert(_state == _State.APPLYING); |
- if (hadError) { |
- _clearOutputs(); |
- // If the transformer threw an error, we don't want to emit the |
- // pass-through asset in case it will be overwritten by the transformer. |
- // However, if the transformer declared that it wouldn't overwrite or |
- // consume the pass-through asset, we can safely emit it. |
- if (_declaredOutputs != null && !_consumePrimary && |
- !_declaredOutputs.contains(primary.id)) { |
- _emitPassThrough(); |
- } else { |
- _dontEmitPassThrough(); |
- } |
- } |
- |
- _state = _State.APPLIED; |
- _streams.changeStatus(NodeStatus.IDLE); |
- }); |
- } |
- |
- /// Gets the asset for an input [id]. |
- /// |
- /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. |
- Future<Asset> getInput(AssetId id) { |
- return phase.previous.getOutput(id).then((node) { |
- // Throw if the input isn't found. This ensures the transformer's apply |
- // is exited. We'll then catch this and report it through the proper |
- // results stream. |
- if (node == null) { |
- _missingInputs.add(id); |
- throw new AssetNotFoundException(id); |
- } |
- |
- _inputSubscriptions.putIfAbsent(node.id, () { |
- return node.onStateChange.listen((state) => _dirty()); |
- }); |
- |
- return node.asset; |
- }); |
- } |
- |
- /// Run [Transformer.apply] as soon as [primary] is available. |
- /// |
- /// Returns whether or not an error occurred while running the transformer. |
- Future<bool> _runApply() { |
- var transformController = new TransformController(this); |
- _streams.onLogPool.add(transformController.onLog); |
- |
- return primary.whenAvailable((_) { |
- if (_isRemoved) return null; |
- _state = _State.APPLYING; |
- return syncFuture(() => transformer.apply(transformController.transform)); |
- }).then((_) { |
- if (!_forced && !primary.state.isAvailable) { |
- _state = _State.DECLARED; |
- _streams.changeStatus(NodeStatus.IDLE); |
- return false; |
- } |
- |
- if (_isRemoved) return false; |
- if (_state == _State.NEEDS_APPLY) return false; |
- if (_state == _State.DECLARING) return false; |
- if (transformController.loggedError) return true; |
- _handleApplyResults(transformController); |
- return false; |
- }).catchError((error, stackTrace) { |
- // If the transform became dirty while processing, ignore any errors from |
- // it. |
- if (_state == _State.NEEDS_APPLY || _isRemoved) return false; |
- |
- // Catch all transformer errors and pipe them to the results stream. This |
- // is so a broken transformer doesn't take down the whole graph. |
- phase.cascade.reportError(_wrapException(error, stackTrace)); |
- return true; |
- }); |
- } |
- |
- /// Handle the results of running [Transformer.apply]. |
- /// |
- /// [transformController] should be the controller for the [Transform] passed |
- /// to [Transformer.apply]. |
- void _handleApplyResults(TransformController transformController) { |
- _consumePrimary = transformController.consumePrimary; |
- |
- var newOutputs = transformController.outputs; |
- // Any ids that are for a different package are invalid. |
- var invalidIds = newOutputs |
- .map((asset) => asset.id) |
- .where((id) => id.package != phase.cascade.package) |
- .toSet(); |
- for (var id in invalidIds) { |
- newOutputs.removeId(id); |
- // TODO(nweiz): report this as a warning rather than a failing error. |
- phase.cascade.reportError(new InvalidOutputException(info, id)); |
- } |
- |
- // Remove outputs that used to exist but don't anymore. |
- for (var id in _outputControllers.keys.toList()) { |
- if (newOutputs.containsId(id)) continue; |
- _outputControllers.remove(id).setRemoved(); |
- } |
- |
- // Emit or stop emitting the pass-through asset between removing and |
- // adding outputs to ensure there are no collisions. |
- if (!_consumePrimary && !newOutputs.containsId(primary.id)) { |
- _emitPassThrough(); |
- } else { |
- _dontEmitPassThrough(); |
- } |
- |
- // Store any new outputs or new contents for existing outputs. |
- for (var asset in newOutputs) { |
- var controller = _outputControllers[asset.id]; |
- if (controller != null) { |
- controller.setAvailable(asset); |
- } else { |
- var controller = new AssetNodeController.available(asset, this); |
- _outputControllers[asset.id] = controller; |
- _streams.onAssetController.add(controller.node); |
- } |
- } |
- } |
- |
- /// Cancels all subscriptions to secondary input nodes. |
- void _clearInputSubscriptions() { |
- _missingInputs.clear(); |
- for (var subscription in _inputSubscriptions.values) { |
- subscription.cancel(); |
- } |
- _inputSubscriptions.clear(); |
- } |
- |
- /// Removes all output assets. |
- void _clearOutputs() { |
- // Remove all the previously-emitted assets. |
- for (var controller in _outputControllers.values) { |
- controller.setRemoved(); |
- } |
- _outputControllers.clear(); |
- } |
- |
- /// Emit the pass-through asset if it's not being emitted already. |
- void _emitPassThrough() { |
- assert(!_outputControllers.containsKey(primary.id)); |
- |
- if (_consumePrimary) return; |
- if (_passThroughController == null) { |
- _passThroughController = new AssetNodeController.from(primary); |
- _streams.onAssetController.add(_passThroughController.node); |
- } else if (primary.state.isDirty) { |
- _passThroughController.setDirty(); |
- } else if (!_passThroughController.node.state.isAvailable) { |
- _passThroughController.setAvailable(primary.asset); |
- } |
- } |
- |
- /// Stop emitting the pass-through asset if it's being emitted already. |
- void _dontEmitPassThrough() { |
- if (_passThroughController == null) return; |
- _passThroughController.setRemoved(); |
- _passThroughController = null; |
- } |
- |
- BarbackException _wrapException(error, StackTrace stackTrace) { |
- if (error is! AssetNotFoundException) { |
- return new TransformerException(info, error, stackTrace); |
- } else { |
- return new MissingInputException(info, error.id); |
- } |
- } |
- |
- /// Emit a warning about the transformer on [id]. |
- void _warn(String message) { |
- _streams.onLogController.add( |
- new LogEntry(info, primary.id, LogLevel.WARNING, message, null)); |
- } |
- |
- String toString() => |
- "transform node in $_location for $transformer on $primary ($_state, " |
- "$status, ${_forced ? '' : 'un'}forced)"; |
-} |
- |
-/// The enum of states that [TransformNode] can be in. |
-class _State { |
- /// The transform is running [DeclaringTransformer.declareOutputs]. |
- /// |
- /// This is the initial state of the transformer, and it will only occur once |
- /// since [DeclaringTransformer.declareOutputs] is independent of the contents |
- /// of the primary input. Once the method finishes running, this will |
- /// transition to [APPLYING] if the transform is non-lazy and the input is |
- /// available, and [DECLARED] otherwise. |
- /// |
- /// Non-declaring transformers will transition out of this state and into |
- /// [APPLYING] immediately. |
- static final DECLARING = const _State._("declaring outputs"); |
- |
- /// The transform is deferred and has run |
- /// [DeclaringTransformer.declareOutputs] but hasn't yet been forced. |
- /// |
- /// This will transition to [APPLYING] when one of the outputs has been |
- /// forced. |
- static final DECLARED = const _State._("declared"); |
- |
- /// The transform is running [Transformer.apply]. |
- /// |
- /// If an input changes while in this state, it will transition to |
- /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when |
- /// [Transformer.apply] finishes running, it will transition to [APPLIED]. |
- static final APPLYING = const _State._("applying"); |
- |
- /// The transform is running [Transformer.apply], but an input changed after |
- /// it started, so it will need to re-run [Transformer.apply]. |
- /// |
- /// This will transition to [APPLYING] once [Transformer.apply] finishes |
- /// running. |
- static final NEEDS_APPLY = const _State._("needs apply"); |
- |
- /// The transform has finished running [Transformer.apply], whether or not it |
- /// emitted an error. |
- /// |
- /// If the transformer is deferred, the [TransformNode] can also be in this |
- /// state when [Transformer.declareOutputs] has been run but |
- /// [Transformer.apply] has not. |
- /// |
- /// If an input changes, this will transition to [DECLARED] if the transform |
- /// is deferred and [APPLYING] otherwise. |
- static final APPLIED = const _State._("applied"); |
- |
- final String name; |
- |
- const _State._(this.name); |
- |
- String toString() => name; |
-} |