| 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.asset_cascade; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'asset.dart'; | |
| 10 import 'asset_id.dart'; | |
| 11 import 'asset_node.dart'; | |
| 12 import 'asset_set.dart'; | |
| 13 import 'cancelable_future.dart'; | |
| 14 import 'errors.dart'; | |
| 15 import 'log.dart'; | |
| 16 import 'node_status.dart'; | |
| 17 import 'node_streams.dart'; | |
| 18 import 'package_graph.dart'; | |
| 19 import 'phase.dart'; | |
| 20 import 'transformer.dart'; | |
| 21 | |
| 22 /// The asset cascade for an individual package. | |
| 23 /// | |
| 24 /// This keeps track of which [Transformer]s are applied to which assets, and | |
| 25 /// re-runs those transformers when their dependencies change. The transformed | |
| 26 /// asset nodes are accessible via [getAssetNode]. | |
| 27 /// | |
| 28 /// A cascade consists of one or more [Phases], each of which has one or more | |
| 29 /// [Transformer]s that run in parallel, potentially on the same inputs. The | |
| 30 /// inputs of the first phase are the source assets for this cascade's package. | |
| 31 /// The inputs of each successive phase are the outputs of the previous phase, | |
| 32 /// as well as any assets that haven't yet been transformed. | |
| 33 class AssetCascade { | |
| 34 /// The name of the package whose assets are managed. | |
| 35 final String package; | |
| 36 | |
| 37 /// The [PackageGraph] that tracks all [AssetCascade]s for all dependencies of | |
| 38 /// the current app. | |
| 39 final PackageGraph graph; | |
| 40 | |
| 41 /// The controllers for the [AssetNode]s that provide information about this | |
| 42 /// cascade's package's source assets. | |
| 43 final _sourceControllerMap = new Map<AssetId, AssetNodeController>(); | |
| 44 | |
| 45 /// Futures for source assets that are currently being loaded. | |
| 46 /// | |
| 47 /// These futures are cancelable so that if an asset is updated after a load | |
| 48 /// has been kicked off, the previous load can be ignored in favor of a new | |
| 49 /// one. | |
| 50 final _loadingSources = new Map<AssetId, CancelableFuture<Asset>>(); | |
| 51 | |
| 52 /// The list of phases in this cascade. | |
| 53 /// | |
| 54 /// This will always contain at least one phase, and the first phase will | |
| 55 /// never have any transformers. This ensures that every transformer can | |
| 56 /// request inputs from a previous phase. | |
| 57 final _phases = <Phase>[]; | |
| 58 | |
| 59 /// The subscription to the [Phase.onStatusChange] stream of the last [Phase] | |
| 60 /// in [_phases]. | |
| 61 StreamSubscription _phaseStatusSubscription; | |
| 62 | |
| 63 /// A stream that emits any errors from the cascade or the transformers. | |
| 64 /// | |
| 65 /// This emits errors as they're detected. If an error occurs in one part of | |
| 66 /// the cascade, unrelated parts will continue building. | |
| 67 Stream<BarbackException> get errors => _errorsController.stream; | |
| 68 final _errorsController = | |
| 69 new StreamController<BarbackException>.broadcast(sync: true); | |
| 70 | |
| 71 /// How far along [this] is in processing its assets. | |
| 72 NodeStatus get status { | |
| 73 // Just check the last phase, since it will check all the previous phases | |
| 74 // itself. | |
| 75 return _phases.last.status; | |
| 76 } | |
| 77 | |
| 78 /// The streams exposed by this cascade. | |
| 79 final _streams = new NodeStreams(); | |
| 80 Stream<LogEntry> get onLog => _streams.onLog; | |
| 81 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | |
| 82 | |
| 83 /// Returns all currently-available output assets from this cascade. | |
| 84 AssetSet get availableOutputs => | |
| 85 new AssetSet.from(_phases.last.availableOutputs.map((node) => node.asset)); | |
| 86 | |
| 87 /// Creates a new [AssetCascade]. | |
| 88 /// | |
| 89 /// It loads source assets within [package] using [provider]. | |
| 90 AssetCascade(this.graph, this.package) { | |
| 91 _addPhase(new Phase(this, package)); | |
| 92 } | |
| 93 | |
| 94 /// Gets the asset identified by [id]. | |
| 95 /// | |
| 96 /// If [id] is for a generated or transformed asset, this will wait until it | |
| 97 /// has been created and return it. This means that the returned asset will | |
| 98 /// always be [AssetState.AVAILABLE]. | |
| 99 /// | |
| 100 /// If the asset cannot be found, returns null. | |
| 101 Future<AssetNode> getAssetNode(AssetId id) { | |
| 102 assert(id.package == package); | |
| 103 | |
| 104 var oldLastPhase = _phases.last; | |
| 105 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary | |
| 106 // in some cases. Should optimize: | |
| 107 // * [id] may be generated before the compilation is finished. We should | |
| 108 // be able to quickly check whether there are any more in-place | |
| 109 // transformations that can be run on it. If not, we can return it early. | |
| 110 // * If [id] has never been generated and all active transformers provide | |
| 111 // metadata about the file names of assets it can emit, we can prove that | |
| 112 // none of them can emit [id] and fail early. | |
| 113 return oldLastPhase.getOutput(id).then((node) { | |
| 114 // The last phase may have changed if [updateSources] was called after | |
| 115 // requesting the output. In that case, we want the output from the new | |
| 116 // last phase. | |
| 117 if (_phases.last == oldLastPhase) return node; | |
| 118 return getAssetNode(id); | |
| 119 }); | |
| 120 } | |
| 121 | |
| 122 /// Adds [sources] to the graph's known set of source assets. | |
| 123 /// | |
| 124 /// Begins applying any transforms that can consume any of the sources. If a | |
| 125 /// given source is already known, it is considered modified and all | |
| 126 /// transforms that use it will be re-applied. | |
| 127 void updateSources(Iterable<AssetId> sources) { | |
| 128 for (var id in sources) { | |
| 129 var controller = _sourceControllerMap[id]; | |
| 130 if (controller != null) { | |
| 131 controller.setDirty(); | |
| 132 } else { | |
| 133 _sourceControllerMap[id] = new AssetNodeController(id); | |
| 134 _phases.first.addInput(_sourceControllerMap[id].node); | |
| 135 } | |
| 136 | |
| 137 // If this source was already loading, cancel the old load, since it may | |
| 138 // return out-of-date contents for the asset. | |
| 139 if (_loadingSources.containsKey(id)) _loadingSources[id].cancel(); | |
| 140 | |
| 141 _loadingSources[id] = | |
| 142 new CancelableFuture<Asset>(graph.provider.getAsset(id)); | |
| 143 _loadingSources[id].whenComplete(() { | |
| 144 _loadingSources.remove(id); | |
| 145 }).then((asset) { | |
| 146 var controller = _sourceControllerMap[id].setAvailable(asset); | |
| 147 }).catchError((error, stack) { | |
| 148 reportError(new AssetLoadException(id, error, stack)); | |
| 149 | |
| 150 // TODO(nweiz): propagate error information through asset nodes. | |
| 151 _sourceControllerMap.remove(id).setRemoved(); | |
| 152 }); | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 /// Removes [removed] from the graph's known set of source assets. | |
| 157 void removeSources(Iterable<AssetId> removed) { | |
| 158 removed.forEach((id) { | |
| 159 // If the source was being loaded, cancel that load. | |
| 160 if (_loadingSources.containsKey(id)) _loadingSources.remove(id).cancel(); | |
| 161 | |
| 162 var controller = _sourceControllerMap.remove(id); | |
| 163 // Don't choke if an id is double-removed for some reason. | |
| 164 if (controller != null) controller.setRemoved(); | |
| 165 }); | |
| 166 } | |
| 167 | |
| 168 /// Sets this cascade's transformer phases to [transformers]. | |
| 169 /// | |
| 170 /// Elements of the inner iterable of [transformers] must be either | |
| 171 /// [Transformer]s or [TransformerGroup]s. | |
| 172 void updateTransformers(Iterable<Iterable> transformersIterable) { | |
| 173 var transformers = transformersIterable.toList(); | |
| 174 | |
| 175 // Always preserve a single phase with no transformers at the beginning of | |
| 176 // the cascade so that [TransformNode]s in the first populated phase will | |
| 177 // have something to request assets from. | |
| 178 for (var i = 0; i < transformers.length; i++) { | |
| 179 if (_phases.length > i + 1) { | |
| 180 _phases[i + 1].updateTransformers(transformers[i]); | |
| 181 continue; | |
| 182 } | |
| 183 | |
| 184 var phase = _phases.last.addPhase(); | |
| 185 _addPhase(phase); | |
| 186 phase.updateTransformers(transformers[i]); | |
| 187 } | |
| 188 | |
| 189 for (var i = transformers.length + 1; i < _phases.length; i++) { | |
| 190 _phases[i].remove(); | |
| 191 } | |
| 192 _phases.removeRange(transformers.length + 1, _phases.length); | |
| 193 | |
| 194 _phaseStatusSubscription.cancel(); | |
| 195 _phaseStatusSubscription = _phases.last.onStatusChange | |
| 196 .listen(_streams.changeStatus); | |
| 197 } | |
| 198 | |
| 199 /// Force all [LazyTransformer]s' transforms in this cascade to begin | |
| 200 /// producing concrete assets. | |
| 201 void forceAllTransforms() { | |
| 202 for (var phase in _phases) { | |
| 203 phase.forceAllTransforms(); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 void reportError(BarbackException error) { | |
| 208 _errorsController.add(error); | |
| 209 } | |
| 210 | |
| 211 /// Add [phase] to the end of [_phases] and watch its streams. | |
| 212 void _addPhase(Phase phase) { | |
| 213 _streams.onLogPool.add(phase.onLog); | |
| 214 if (_phaseStatusSubscription != null) _phaseStatusSubscription.cancel(); | |
| 215 _phaseStatusSubscription = | |
| 216 phase.onStatusChange.listen(_streams.changeStatus); | |
| 217 | |
| 218 _phases.add(phase); | |
| 219 } | |
| 220 | |
| 221 String toString() => "cascade for $package"; | |
| 222 } | |
| OLD | NEW |