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 |